From 94e773416ff51a9420e170f5de3aec4f35624e62 Mon Sep 17 00:00:00 2001 From: TheLimeGlass Date: Sat, 5 Jul 2025 17:09:39 -0600 Subject: [PATCH 1/8] Creates an executor for syncing tasks to the script loader threads --- .../java/ch/njol/skript/ScriptLoader.java | 49 ++++++++++++ src/main/java/ch/njol/skript/util/Task.java | 75 +++++++++++++++++-- 2 files changed, 116 insertions(+), 8 deletions(-) diff --git a/src/main/java/ch/njol/skript/ScriptLoader.java b/src/main/java/ch/njol/skript/ScriptLoader.java index eb64a4085d0..135404aeaaa 100644 --- a/src/main/java/ch/njol/skript/ScriptLoader.java +++ b/src/main/java/ch/njol/skript/ScriptLoader.java @@ -28,6 +28,7 @@ import org.bukkit.Bukkit; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnknownNullability; import org.skriptlang.skript.lang.script.Script; import org.skriptlang.skript.lang.script.ScriptWarning; import org.skriptlang.skript.lang.structure.Structure; @@ -253,6 +254,12 @@ static void updateDisabledScripts(Path path) { */ private static int asyncLoaderSize; + /** + * The executor used for async loading. + * Gets applied in the {@link #setAsyncLoaderSize(int)} method. + */ + private static Executor executor; + /** * Checks if scripts are loaded in separate thread. If true, * following behavior should be expected: @@ -279,6 +286,21 @@ public static boolean isParallel() { return asyncLoaderSize > 1; } + /** + * Returns the executor used for async loading. + * + * The thread count will be based on the value of {@link #asyncLoaderSize}. + *

+ * You may also use class {@link ch.njol.skript.util.Task} and the appropriate constructor + * to run tasks on the script loader executor. + * + * @return the executor used for async loading. Can be null if called before Skript loads config.sk + */ + @UnknownNullability + public static Executor getExecutor() { + return executor; + } + /** * Sets the amount of async loaders, by updating * {@link #asyncLoaderSize} and {@link #loaderThreads}. @@ -311,6 +333,33 @@ public static void setAsyncLoaderSize(int size) throws IllegalStateException { if (loaderThreads.size() != size) throw new IllegalStateException(); + + if (asyncLoaderSize <= 0) { + executor = Bukkit.getScheduler().getMainThreadExecutor(Skript.getInstance()); + } else { + executor = new ForkJoinPool( + asyncLoaderSize > 0 ? asyncLoaderSize : Runtime.getRuntime().availableProcessors(), + ForkJoinPool.defaultForkJoinWorkerThreadFactory, + null, + false + ) { + @Override + public ForkJoinWorkerThreadFactory getFactory() { + return pool -> { + ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool); + // Set the thread group to asyncLoaderThreadGroup if possible + try { + java.lang.reflect.Field groupField = Thread.class.getDeclaredField("group"); + groupField.setAccessible(true); + groupField.set(worker, asyncLoaderThreadGroup); + } catch (Exception e) { + // If we can't set the thread group, just ignore and use the default + } + return worker; + }; + } + }; + } } /** diff --git a/src/main/java/ch/njol/skript/util/Task.java b/src/main/java/ch/njol/skript/util/Task.java index 068b1cc758d..5b790ac891d 100644 --- a/src/main/java/ch/njol/skript/util/Task.java +++ b/src/main/java/ch/njol/skript/util/Task.java @@ -3,12 +3,16 @@ import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.Nullable; +import ch.njol.skript.ScriptLoader; import ch.njol.skript.Skript; import ch.njol.util.Closeable; @@ -18,28 +22,76 @@ @SuppressWarnings("removal") public abstract class Task implements Runnable, Closeable { - private final Plugin plugin; private final boolean async; + private final Plugin plugin; + + private boolean useScriptLoaderExecutor; private long period = -1; - private int taskID = -1; - public Task(final Plugin plugin, final long delay, final long period) { + /** + * Creates a new task that will run after the given delay and then repeat every period ticks. + *

+ * @param plugin The plugin that owns this task. + * @param delay Delay in ticks before the task is run for the first time. + * @param period Period in ticks between subsequent executions of the task. + */ + public Task(Plugin plugin, long delay, long period) { this(plugin, delay, period, false); } - public Task(final Plugin plugin, final long delay, final long period, final boolean async) { + /** + * Creates a new task that will run after the given delay and then repeat every period ticks. + *

+ * @param plugin The plugin that owns this task. + * @param delay Delay in ticks before the task is run for the first time. + * @param period Period in ticks between subsequent executions of the task. + * @param async Whether to run the task asynchronously + */ + public Task(Plugin plugin, long delay, long period, boolean async) { this.plugin = plugin; this.period = period; this.async = async; schedule(delay); } - public Task(final Plugin plugin, final long delay) { - this(plugin, delay, false); + /** + * Creates a new task that will run after the given delay and then repeat every period ticks. + *

+ * @param plugin The plugin that owns this task. + * @param useScriptLoaderExecutor Whether to use the script loader executor. Setting is based on the config.sk user setting. + * @param delay Delay in ticks before the task is run for the first time. + * @param period Period in ticks between subsequent executions of the task. + */ + public Task(Plugin plugin, long delay) { + this(plugin, false, delay, false); + } + + /** + * Creates a new task that will run after the given delay and then repeat every period ticks. + *

+ * @param plugin The plugin that owns this task. + * @param useScriptLoaderExecutor Whether to use the script loader executor. Setting is based on the config.sk user setting. + * @param delay Delay in ticks before the task is run for the first time. + * @param async Whether to run the task asynchronously + */ + public Task(Plugin plugin, long delay, boolean async) { + this(plugin, delay, -1, async); } - public Task(final Plugin plugin, final long delay, final boolean async) { + /** + * Creates a new task that will run after the given delay and then repeat every period ticks. + *

+ * @param plugin The plugin that owns this task. + * @param useScriptLoaderExecutor Whether to use the script loader executor. Setting is based on the config.sk user setting. + * @param delay Delay in ticks before the task is run for the first time. + */ + public Task(Plugin plugin, boolean useScriptLoaderExecutor, long delay) { + this(plugin, useScriptLoaderExecutor, delay, false); + } + + private Task(Plugin plugin, boolean useScriptLoaderExecutor, long delay, boolean async) { + this.useScriptLoaderExecutor = useScriptLoaderExecutor; this.plugin = plugin; this.async = async; schedule(delay); @@ -54,7 +106,14 @@ private void schedule(final long delay) { assert !isAlive(); if (!Skript.getInstance().isEnabled()) return; - + if (useScriptLoaderExecutor) { + Executor executor = ScriptLoader.getExecutor(); + if (delay > 0) { + taskID = Bukkit.getScheduler().runTaskLater(plugin, () -> executor.execute(this), delay); + } else { + executor.execute(this); + } + } else { if (period == -1) { if (async) { taskID = Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, this, delay).getTaskId(); From f404398c9594db7d0971f4ce465524679210e08e Mon Sep 17 00:00:00 2001 From: TheLimeGlass Date: Sat, 5 Jul 2025 17:24:44 -0600 Subject: [PATCH 2/8] Creates an executor for syncing tasks to the script loader threads --- src/main/java/ch/njol/skript/util/Task.java | 47 ++++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/src/main/java/ch/njol/skript/util/Task.java b/src/main/java/ch/njol/skript/util/Task.java index 5b790ac891d..b76721310a6 100644 --- a/src/main/java/ch/njol/skript/util/Task.java +++ b/src/main/java/ch/njol/skript/util/Task.java @@ -4,10 +4,7 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.Nullable; @@ -41,7 +38,7 @@ public Task(Plugin plugin, long delay, long period) { } /** - * Creates a new task that will run after the given delay and then repeat every period ticks. + * Creates a new task that will run after the given delay and then repeat every period ticks optionally asyncronously. *

* @param plugin The plugin that owns this task. * @param delay Delay in ticks before the task is run for the first time. @@ -56,22 +53,29 @@ public Task(Plugin plugin, long delay, long period, boolean async) { } /** - * Creates a new task that will run after the given delay and then repeat every period ticks. + * Creates a new task that will run after the given delay. *

* @param plugin The plugin that owns this task. - * @param useScriptLoaderExecutor Whether to use the script loader executor. Setting is based on the config.sk user setting. * @param delay Delay in ticks before the task is run for the first time. - * @param period Period in ticks between subsequent executions of the task. */ public Task(Plugin plugin, long delay) { this(plugin, false, delay, false); } /** - * Creates a new task that will run after the given delay and then repeat every period ticks. + * Creates a new task that will run optionally on the script loader executor. *

* @param plugin The plugin that owns this task. * @param useScriptLoaderExecutor Whether to use the script loader executor. Setting is based on the config.sk user setting. + */ + public Task(Plugin plugin, boolean useScriptLoaderExecutor) { + this(plugin, useScriptLoaderExecutor, 0, false); + } + + /** + * Creates a new task that will run after the given delay and optionally asynchronously. + *

+ * @param plugin The plugin that owns this task. * @param delay Delay in ticks before the task is run for the first time. * @param async Whether to run the task asynchronously */ @@ -80,7 +84,7 @@ public Task(Plugin plugin, long delay, boolean async) { } /** - * Creates a new task that will run after the given delay and then repeat every period ticks. + * Creates a new task that will run optionally on the script loader executor and after a delay. *

* @param plugin The plugin that owns this task. * @param useScriptLoaderExecutor Whether to use the script loader executor. Setting is based on the config.sk user setting. @@ -109,25 +113,26 @@ private void schedule(final long delay) { if (useScriptLoaderExecutor) { Executor executor = ScriptLoader.getExecutor(); if (delay > 0) { - taskID = Bukkit.getScheduler().runTaskLater(plugin, () -> executor.execute(this), delay); + taskID = Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> executor.execute(this), delay); } else { executor.execute(this); } } else { - if (period == -1) { - if (async) { - taskID = Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, this, delay).getTaskId(); - } else { - taskID = Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, this, delay); - } - } else { - if (async) { - taskID = Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, this, delay, period).getTaskId(); + if (period == -1) { + if (async) { + taskID = Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, this, delay).getTaskId(); + } else { + taskID = Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, this, delay); + } } else { - taskID = Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, this, delay, period); + if (async) { + taskID = Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, this, delay, period).getTaskId(); + } else { + taskID = Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, this, delay, period); + } } + assert taskID != -1; } - assert taskID != -1; } /** From 7ce5118feb58d7d497ea943013ffc5d046af071f Mon Sep 17 00:00:00 2001 From: TheLimeGlass Date: Sat, 5 Jul 2025 17:26:05 -0600 Subject: [PATCH 3/8] Creates an executor for syncing tasks to the script loader threads --- src/main/java/ch/njol/skript/util/Task.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/ch/njol/skript/util/Task.java b/src/main/java/ch/njol/skript/util/Task.java index b76721310a6..0240ed52d7a 100644 --- a/src/main/java/ch/njol/skript/util/Task.java +++ b/src/main/java/ch/njol/skript/util/Task.java @@ -94,6 +94,7 @@ public Task(Plugin plugin, boolean useScriptLoaderExecutor, long delay) { this(plugin, useScriptLoaderExecutor, delay, false); } + // Private because async and useScriptLoaderExecutor contradict each other, as the script loader executor may be asynchronous. private Task(Plugin plugin, boolean useScriptLoaderExecutor, long delay, boolean async) { this.useScriptLoaderExecutor = useScriptLoaderExecutor; this.plugin = plugin; From 13619955cbb644eb992609bee785918354161f11 Mon Sep 17 00:00:00 2001 From: TheLimeGlass Date: Sat, 5 Jul 2025 17:27:05 -0600 Subject: [PATCH 4/8] Creates an executor for syncing tasks to the script loader threads --- src/main/java/ch/njol/skript/ScriptLoader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/ScriptLoader.java b/src/main/java/ch/njol/skript/ScriptLoader.java index 135404aeaaa..e0103c6074d 100644 --- a/src/main/java/ch/njol/skript/ScriptLoader.java +++ b/src/main/java/ch/njol/skript/ScriptLoader.java @@ -287,7 +287,7 @@ public static boolean isParallel() { } /** - * Returns the executor used for async loading. + * Returns the executor used for submitting tasks based on the user config.sk settings. * * The thread count will be based on the value of {@link #asyncLoaderSize}. *

From a8b8a33050985f5bbefc4026f9ecebf1818ca98f Mon Sep 17 00:00:00 2001 From: TheLimeGlass Date: Sat, 5 Jul 2025 17:27:25 -0600 Subject: [PATCH 5/8] Creates an executor for syncing tasks to the script loader threads --- src/main/java/ch/njol/skript/ScriptLoader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/ScriptLoader.java b/src/main/java/ch/njol/skript/ScriptLoader.java index e0103c6074d..7f97fa71637 100644 --- a/src/main/java/ch/njol/skript/ScriptLoader.java +++ b/src/main/java/ch/njol/skript/ScriptLoader.java @@ -294,7 +294,7 @@ public static boolean isParallel() { * You may also use class {@link ch.njol.skript.util.Task} and the appropriate constructor * to run tasks on the script loader executor. * - * @return the executor used for async loading. Can be null if called before Skript loads config.sk + * @return the executor used for submitting tasks. Can be null if called before Skript loads config.sk */ @UnknownNullability public static Executor getExecutor() { From 083ab30d72f0c1cc6847c57b2bda45eaaf05ddb2 Mon Sep 17 00:00:00 2001 From: TheLimeGlass Date: Sat, 5 Jul 2025 17:34:05 -0600 Subject: [PATCH 6/8] Creates an executor for syncing tasks to the script loader threads --- src/main/java/ch/njol/skript/ScriptLoader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/ScriptLoader.java b/src/main/java/ch/njol/skript/ScriptLoader.java index 7f97fa71637..d0c5ce21ab4 100644 --- a/src/main/java/ch/njol/skript/ScriptLoader.java +++ b/src/main/java/ch/njol/skript/ScriptLoader.java @@ -341,7 +341,7 @@ public static void setAsyncLoaderSize(int size) throws IllegalStateException { asyncLoaderSize > 0 ? asyncLoaderSize : Runtime.getRuntime().availableProcessors(), ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, - false + isAsync() ) { @Override public ForkJoinWorkerThreadFactory getFactory() { From 6274b7bdf5287aaf8fe91631703afb78cadfb7a3 Mon Sep 17 00:00:00 2001 From: TheLimeGlass Date: Sat, 5 Jul 2025 17:42:37 -0600 Subject: [PATCH 7/8] Remove the reflection --- .../java/ch/njol/skript/ScriptLoader.java | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/src/main/java/ch/njol/skript/ScriptLoader.java b/src/main/java/ch/njol/skript/ScriptLoader.java index d0c5ce21ab4..4e7138179c8 100644 --- a/src/main/java/ch/njol/skript/ScriptLoader.java +++ b/src/main/java/ch/njol/skript/ScriptLoader.java @@ -42,6 +42,7 @@ import java.nio.file.Path; import java.util.*; import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -337,28 +338,16 @@ public static void setAsyncLoaderSize(int size) throws IllegalStateException { if (asyncLoaderSize <= 0) { executor = Bukkit.getScheduler().getMainThreadExecutor(Skript.getInstance()); } else { - executor = new ForkJoinPool( - asyncLoaderSize > 0 ? asyncLoaderSize : Runtime.getRuntime().availableProcessors(), - ForkJoinPool.defaultForkJoinWorkerThreadFactory, - null, - isAsync() - ) { + executor = Executors.newFixedThreadPool(asyncLoaderSize, new ThreadFactory() { + private final AtomicInteger threadId = new AtomicInteger(0); + @Override - public ForkJoinWorkerThreadFactory getFactory() { - return pool -> { - ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool); - // Set the thread group to asyncLoaderThreadGroup if possible - try { - java.lang.reflect.Field groupField = Thread.class.getDeclaredField("group"); - groupField.setAccessible(true); - groupField.set(worker, asyncLoaderThreadGroup); - } catch (Exception e) { - // If we can't set the thread group, just ignore and use the default - } - return worker; - }; + public Thread newThread(Runnable runnable) { + Thread thread = new Thread(asyncLoaderThreadGroup, runnable, "Skript async loaders thread " + threadId.incrementAndGet()); + thread.setDaemon(true); + return thread; } - }; + }); } } From 7e07b42f816c1a95dc09bb9fbab04b3ecdd891c9 Mon Sep 17 00:00:00 2001 From: TheLimeGlass Date: Sat, 5 Jul 2025 17:44:37 -0600 Subject: [PATCH 8/8] Remove redundant check --- .../java/ch/njol/skript/ScriptLoader.java | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/main/java/ch/njol/skript/ScriptLoader.java b/src/main/java/ch/njol/skript/ScriptLoader.java index 4e7138179c8..333d907e306 100644 --- a/src/main/java/ch/njol/skript/ScriptLoader.java +++ b/src/main/java/ch/njol/skript/ScriptLoader.java @@ -335,20 +335,16 @@ public static void setAsyncLoaderSize(int size) throws IllegalStateException { if (loaderThreads.size() != size) throw new IllegalStateException(); - if (asyncLoaderSize <= 0) { - executor = Bukkit.getScheduler().getMainThreadExecutor(Skript.getInstance()); - } else { - executor = Executors.newFixedThreadPool(asyncLoaderSize, new ThreadFactory() { - private final AtomicInteger threadId = new AtomicInteger(0); - - @Override - public Thread newThread(Runnable runnable) { - Thread thread = new Thread(asyncLoaderThreadGroup, runnable, "Skript async loaders thread " + threadId.incrementAndGet()); - thread.setDaemon(true); - return thread; - } - }); - } + executor = Executors.newFixedThreadPool(asyncLoaderSize, new ThreadFactory() { + private final AtomicInteger threadId = new AtomicInteger(0); + + @Override + public Thread newThread(Runnable runnable) { + Thread thread = new Thread(asyncLoaderThreadGroup, runnable, "Skript async loaders thread " + threadId.incrementAndGet()); + thread.setDaemon(true); + return thread; + } + }); } /**