Skip to content

Creates an executor for respecting the user script loader threads configuration #8011

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: dev/feature
Choose a base branch
from
34 changes: 34 additions & 0 deletions src/main/java/ch/njol/skript/ScriptLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -41,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;
Expand Down Expand Up @@ -253,6 +255,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:
Expand All @@ -279,6 +287,21 @@ public static boolean isParallel() {
return asyncLoaderSize > 1;
}

/**
* 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}.
* <p>
* 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 submitting tasks. 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}.
Expand Down Expand Up @@ -311,6 +334,17 @@ public static void setAsyncLoaderSize(int size) throws IllegalStateException {

if (loaderThreads.size() != size)
throw new IllegalStateException();

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;
}
});
}

/**
Expand Down
99 changes: 82 additions & 17 deletions src/main/java/ch/njol/skript/util/Task.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;

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;

Expand All @@ -18,28 +19,84 @@
@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.
* <p>
* @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 optionally asyncronously.
* <p>
* @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.
* <p>
* @param plugin The plugin that owns this task.
* @param delay Delay in ticks before the task is run for the first time.
*/
public Task(Plugin plugin, long delay) {
this(plugin, false, delay, false);
}

public Task(final Plugin plugin, final long delay, final boolean async) {
/**
* Creates a new task that will run optionally on the script loader executor.
* <p>
* @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.
* <p>
* @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
*/
public Task(Plugin plugin, long delay, boolean async) {
this(plugin, delay, -1, async);
}

/**
* Creates a new task that will run optionally on the script loader executor and after a delay.
* <p>
* @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 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;
this.async = async;
schedule(delay);
Expand All @@ -54,21 +111,29 @@ private void schedule(final long delay) {
assert !isAlive();
if (!Skript.getInstance().isEnabled())
return;

if (period == -1) {
if (async) {
taskID = Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, this, delay).getTaskId();
if (useScriptLoaderExecutor) {
Executor executor = ScriptLoader.getExecutor();
if (delay > 0) {
taskID = Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> executor.execute(this), delay);
} else {
taskID = Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, this, delay);
executor.execute(this);
}
} 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;
}

/**
Expand Down