package emu.grasscutter.server.scheduler; import java.util.concurrent.ConcurrentHashMap; /** * A class to manage all time-based tasks scheduled on the server. * This handles both synchronous and asynchronous tasks. * * Developers note: A server tick is ONE REAL-TIME SECOND. */ public final class ServerTaskScheduler { /* A map to contain all running tasks. */ private final ConcurrentHashMap tasks = new ConcurrentHashMap<>(); /* A map to contain all async tasks. */ private final ConcurrentHashMap asyncTasks = new ConcurrentHashMap<>(); /* The ID assigned to the next runnable. */ private int nextTaskId = 0; /** * Ran every server tick. * Attempts to run all scheduled tasks. * This method is synchronous and will block until all tasks are complete. */ public void runTasks() { // Skip if there are no tasks. if(this.tasks.size() == 0) return; // Run all tasks. for(ServerTask task : this.tasks.values()) { // Check if the task should run. if (task.shouldRun()) { // Run the task. task.run(); } // Check if the task should be canceled. if (task.shouldCancel()) { // Cancel the task. this.cancelTask(task.getTaskId()); } } // Run all async tasks. for(AsyncServerTask task : this.asyncTasks.values()) { if(!task.hasStarted()) { // Create a thread for the task. Thread thread = new Thread(task); // Start the thread. thread.start(); } else if(task.isFinished()) { // Cancel the task. this.asyncTasks.remove(task.getTaskId()); // Run the task's callback. task.complete(); } } } /** * Gets a task from the scheduler. * @param taskId The ID of the task to get. * @return The task, or null if it does not exist. */ public ServerTask getTask(int taskId) { return this.tasks.get(taskId); } /** * Gets an async task from the scheduler. * @param taskId The ID of the task to get. * @return The task, or null if it does not exist. */ public AsyncServerTask getAsyncTask(int taskId) { return this.asyncTasks.get(taskId); } /** * Removes a task from the scheduler. * @param taskId The ID of the task to remove. */ public void cancelTask(int taskId) { this.tasks.remove(taskId); } /** * Schedules a task to be run on a separate thread. * The task runs on the next server tick. * @param runnable The runnable to run. * @return The ID of the task. */ public int scheduleAsyncTask(Runnable runnable) { // Get the next task ID. var taskId = this.nextTaskId++; // Create a new task. this.asyncTasks.put(taskId, new AsyncServerTask(runnable, taskId)); // Return the task ID. return taskId; } /** * Schedules a task to be run on the next server tick. * @param runnable The runnable to run. * @return The ID of the task. */ public int scheduleTask(Runnable runnable) { return this.scheduleDelayedRepeatingTask(runnable, -1, -1); } /** * Schedules a task to be run after the amount of ticks has passed. * @param runnable The runnable to run. * @param delay The amount of ticks to wait before running. * @return The ID of the task. */ public int scheduleDelayedTask(Runnable runnable, int delay) { return this.scheduleDelayedRepeatingTask(runnable, -1, delay); } /** * Schedules a task to be run every amount of ticks. * @param runnable The runnable to run. * @param period The amount of ticks to wait before running again. * @return The ID of the task. */ public int scheduleRepeatingTask(Runnable runnable, int period) { return this.scheduleDelayedRepeatingTask(runnable, period, 0); } /** * Schedules a task to be run after the amount of ticks has passed. * @param runnable The runnable to run. * @param period The amount of ticks to wait before running again. * @param delay The amount of ticks to wait before running the first time. * @return The ID of the task. */ public int scheduleDelayedRepeatingTask(Runnable runnable, int period, int delay) { // Get the next task ID. var taskId = this.nextTaskId++; // Create a new task. this.tasks.put(taskId, new ServerTask(runnable, taskId, period, delay)); // Return the task ID. return taskId; } }