Skip to content

Executor System

Lars edited this page Oct 18, 2024 · 1 revision

Overview

The Executor class in this plugin framework provides a powerful utility for managing asynchronous tasks, scheduling tasks, and executing tasks at fixed intervals within a Bukkit/Spigot server environment. It leverages Bukkit’s built-in task scheduler and Java’s Future for managing task results. This system makes it easier to handle tasks that need to be performed outside the main server thread without blocking the game.

Note: Initialization of the Executor is automatically handled within the PluginEntry class, so there’s no need to manually initialize or manage the Executor.

Table of Contents


Key Features

The Executor class provides several important functionalities for handling tasks asynchronously or with specific schedules:

  • Submitting tasks: Run tasks in the background or off the main server thread.
  • Scheduling tasks: Schedule tasks to run after a specific delay or at fixed intervals.
  • Task management: Track and manage all running tasks, providing control over their lifecycle.
  • Shutdown support: Cleanly shutdown all pending tasks when the plugin is disabled or no longer needs to execute tasks.

Submitting Tasks

Submitting a Callable Task

The submit() method allows you to submit a Callable task for asynchronous execution. This method returns a Future<T> object, which represents the result of the task and can be retrieved later.

val future: Future<Int> = executor.submit(Callable {
    // Perform some long-running operation
    42
})

The result of the task (in this case, 42) can be obtained using future.get(), which blocks until the task is completed.

Submitting a Runnable Task

You can also submit a simple Runnable task using the execute() method. This method runs the task asynchronously, but without expecting any return value.

executor.execute(Runnable {
    // Perform some task asynchronously
    println("Task executed!")
})

This is useful when you don’t need a result from the task but want to run it in the background.


Task Scheduling

The Executor also provides methods to schedule tasks to run after a delay or at regular intervals.

Delayed Task

The schedule() method allows you to schedule a Runnable task to run after a specified delay (in ticks). This is particularly useful for tasks that need to run at a future point in time.

executor.schedule(Runnable {
    println("This task runs after a delay!")
}, 100L) // Delay in ticks (100 ticks = 5 seconds)

Fixed-Rate Repeated Task

The scheduleAtFixedRate() method lets you run a task repeatedly at fixed intervals. You can specify an initial delay before the first execution and the interval (in ticks) between subsequent executions.

executor.scheduleAtFixedRate(Runnable {
    println("This task runs every 10 seconds!")
}, 100L, 200L) // Initial delay = 100 ticks, period = 200 ticks (10 seconds)

Handling Task Results

Timeouts

If you want to ensure a task completes within a certain amount of time, you can use the submitWithTimeout() method. This method will attempt to execute the Callable task and return its result within the specified timeout period. If the task takes too long, it is canceled and a TimeoutException is thrown.

try {
    val result: Int? = executor.submitWithTimeout(Callable {
        // Some long-running task
        42
    }, 5, TimeUnit.SECONDS)
    println("Task completed: $result")
} catch (e: TimeoutException) {
    println("Task timed out!")
}

Multiple Tasks

The invokeAll() method allows you to submit multiple Callable tasks at once. This method blocks until all tasks are complete, and it returns a list of Future<T> objects representing the result of each task.

val tasks = listOf(
    Callable { "Task 1" },
    Callable { "Task 2" },
    Callable { "Task 3" }
)
val futures = executor.invokeAll(tasks)
futures.forEach { future ->
    println("Result: ${future.get()}")
}

This is useful when you need to run several tasks in parallel and wait for all of them to complete.


Shutdown

Orderly Shutdown

The shutdown() method allows you to initiate an orderly shutdown of the Executor. This will cancel any pending tasks and prevent new tasks from being submitted. It is important to call shutdown() when your plugin is disabled to ensure that no lingering tasks continue running after the plugin is unloaded.

executor.shutdown()

This method ensures all running tasks are canceled and cleans up resources to avoid memory leaks or unexpected behavior.

Shutdown Handling

The checkShutdown() method is called internally by the Executor to ensure that no new tasks are submitted after the shutdown() method has been invoked. If a task is attempted after shutdown, a RejectedExecutionException is thrown.

private fun checkShutdown() {
    if (isShutdown.get()) {
        throw RejectedExecutionException("Executor has been shut down")
    }
}

This ensures that once the Executor is shut down, no further tasks can be added, maintaining a clean state.