ScriptsLab
WikiDownloadsSourcesSupport
ScriptsLab
DocumentationDownloadsGitHubDiscord

© 2026 ScriptsLab

Back to src/main/java/com/scriptslab/core/script
J

ScriptScheduler.java

Java · 131 lines · 4.3 KB

src/main/java/com/scriptslab/core/script/ScriptScheduler.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
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());
        }
    }
}