Back
ScriptScheduler.java
javasrc/main/java/com/scriptslab/core/script/ScriptScheduler.java
package com.scriptslab.core.script;
import com.scriptslab.ScriptsLabPlugin;
import org.graalvm.polyglot.Value;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
/**
* Thread-safe scheduler for JavaScript scripts.
* Executes JS callbacks on the main server thread via BukkitScheduler.
*
* Usage in JS:
* var taskId = Scheduler.runLater(function() { ... }, 20);
* var taskId = Scheduler.runTimer(function() { ... }, 0, 20);
* Scheduler.cancel(taskId);
*/
public final class ScriptScheduler {
private final ScriptsLabPlugin plugin;
private final Logger logger;
private final Map<Integer, org.bukkit.scheduler.BukkitTask> activeTasks;
private final AtomicInteger taskIdCounter;
public ScriptScheduler(ScriptsLabPlugin plugin) {
this.plugin = plugin;
this.logger = Logger.getLogger("ScriptScheduler");
this.activeTasks = new ConcurrentHashMap<>();
this.taskIdCounter = new AtomicInteger(0);
}
/**
* Runs a JS function after a delay on the main thread.
*
* @param callback JS function (Value or executable)
* @param delayTicks delay in ticks (20 ticks = 1 second)
* @return task ID (use to cancel)
*/
public int runLater(Object callback, long delayTicks) {
int id = taskIdCounter.incrementAndGet();
org.bukkit.scheduler.BukkitTask task = plugin.getServer().getScheduler()
.runTaskLater(plugin, () -> {
activeTasks.remove(id);
invokeCallback(callback, "runLater#" + id);
}, delayTicks);
activeTasks.put(id, task);
return id;
}
/**
* Runs a JS function repeatedly on the main thread.
*
* @param callback JS function
* @param delayTicks initial delay in ticks
* @param periodTicks period in ticks
* @return task ID (use to cancel)
*/
public int runTimer(Object callback, long delayTicks, long periodTicks) {
int id = taskIdCounter.incrementAndGet();
org.bukkit.scheduler.BukkitTask task = plugin.getServer().getScheduler()
.runTaskTimer(plugin, () -> invokeCallback(callback, "runTimer#" + id),
delayTicks, periodTicks);
activeTasks.put(id, task);
return id;
}
/**
* Runs a JS function asynchronously (NOT on main thread).
* WARNING: Cannot call Bukkit API from async context!
*
* @param callback JS function
* @return task ID
*/
public int runAsync(Object callback) {
int id = taskIdCounter.incrementAndGet();
org.bukkit.scheduler.BukkitTask task = plugin.getServer().getScheduler()
.runTaskAsynchronously(plugin, () -> {
activeTasks.remove(id);
invokeCallback(callback, "runAsync#" + id);
});
activeTasks.put(id, task);
return id;
}
/**
* Cancels a scheduled task.
*
* @param taskId task ID returned by runLater/runTimer/runAsync
*/
public void cancel(int taskId) {
org.bukkit.scheduler.BukkitTask task = activeTasks.remove(taskId);
if (task != null) {
task.cancel();
}
}
/**
* Cancels all active script tasks.
*/
public void cancelAll() {
activeTasks.values().forEach(org.bukkit.scheduler.BukkitTask::cancel);
activeTasks.clear();
}
/**
* Returns number of active tasks.
*/
public int getActiveCount() {
return activeTasks.size();
}
private void invokeCallback(Object callback, String context) {
try {
if (callback instanceof Value v) {
if (v.canExecute()) {
v.executeVoid();
} else {
logger.warning("[ScriptScheduler] Callback is not executable in " + context);
}
} else {
logger.warning("[ScriptScheduler] Unknown callback type in " + context
+ ": " + (callback == null ? "null" : callback.getClass().getName()));
}
} catch (Exception e) {
logger.warning("[ScriptScheduler] Error in scheduled callback " + context + ": " + e.getMessage());
}
}
}