diff --git a/api/pom.xml b/api/pom.xml
index 243b55f..4cbd359 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -55,6 +55,12 @@
${project.parent.version}
compile
+
+ de.rapha149.signgui
+ signgui-scheduler
+ ${project.parent.version}
+ compile
+
de.rapha149.signgui
signgui-1_8_R1
diff --git a/api/src/main/java/de/rapha149/signgui/SignGUI.java b/api/src/main/java/de/rapha149/signgui/SignGUI.java
index 1f37daa..0d48a35 100644
--- a/api/src/main/java/de/rapha149/signgui/SignGUI.java
+++ b/api/src/main/java/de/rapha149/signgui/SignGUI.java
@@ -3,9 +3,9 @@
import de.rapha149.signgui.SignGUIAction.SignGUIActionInfo;
import de.rapha149.signgui.exception.SignGUIException;
import de.rapha149.signgui.exception.SignGUIVersionException;
+import de.rapha149.signgui.scheduler.SchedulerAdapterFactory;
import de.rapha149.signgui.version.VersionMatcher;
import org.apache.commons.lang.Validate;
-import org.bukkit.Bukkit;
import org.bukkit.DyeColor;
import org.bukkit.Location;
import org.bukkit.Material;
@@ -108,7 +108,7 @@ public void open(Player player) throws SignGUIException {
};
if (callHandlerSynchronously)
- Bukkit.getScheduler().runTask(plugin, runnable);
+ SchedulerAdapterFactory.getScheduler().runAtEntity(plugin, player, runnable, null);
else
runnable.run();
});
diff --git a/api/src/main/java/de/rapha149/signgui/SignGUIAction.java b/api/src/main/java/de/rapha149/signgui/SignGUIAction.java
index 1617547..81bbff0 100644
--- a/api/src/main/java/de/rapha149/signgui/SignGUIAction.java
+++ b/api/src/main/java/de/rapha149/signgui/SignGUIAction.java
@@ -1,5 +1,6 @@
package de.rapha149.signgui;
+import de.rapha149.signgui.scheduler.SchedulerAdapterFactory;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@@ -122,7 +123,7 @@ public SignGUIActionInfo getInfo() {
@Override
public void execute(SignGUI gui, SignEditor signEditor, Player player) {
- Bukkit.getScheduler().runTask(plugin, () -> player.openInventory(inventory));
+ SchedulerAdapterFactory.getScheduler().runAtEntity(plugin, player, () -> player.openInventory(inventory), null);
}
};
}
@@ -175,7 +176,7 @@ public SignGUIActionInfo getInfo() {
@Override
public void execute(SignGUI gui, SignEditor signEditor, Player player) {
- Bukkit.getScheduler().runTask(plugin, runnable);
+ SchedulerAdapterFactory.getScheduler().runAtEntity(plugin, player, runnable, null);
}
};
}
diff --git a/api/src/main/java/de/rapha149/signgui/SignGUIBuilder.java b/api/src/main/java/de/rapha149/signgui/SignGUIBuilder.java
index 33babcc..fd85f79 100644
--- a/api/src/main/java/de/rapha149/signgui/SignGUIBuilder.java
+++ b/api/src/main/java/de/rapha149/signgui/SignGUIBuilder.java
@@ -6,7 +6,6 @@
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Player;
-import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.Arrays;
@@ -168,7 +167,9 @@ public SignGUIBuilder setHandler(SignGUIFinishHandler handler) {
}
/**
- * If called the handler will be called synchronously by calling the method {@link org.bukkit.scheduler.BukkitScheduler#runTask(Plugin, Runnable)}
+ * If called the handler will be called synchronously on the player's thread.
+ * On Bukkit/Paper servers this runs on the main thread.
+ * On Folia servers this runs on the entity's region thread.
*
* @param plugin Your {@link org.bukkit.plugin.java.JavaPlugin} instance.
* @return The {@link SignGUIBuilder} instance
diff --git a/pom.xml b/pom.xml
index 12a1556..76c49f7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -12,6 +12,7 @@
api
wrapper
+ scheduler
1_8_R1
1_8_R2
1_8_R3
diff --git a/scheduler/pom.xml b/scheduler/pom.xml
new file mode 100644
index 0000000..4026bd3
--- /dev/null
+++ b/scheduler/pom.xml
@@ -0,0 +1,47 @@
+
+
+ 4.0.0
+
+
+ signgui-parent
+ de.rapha149.signgui
+ 2.5.4
+
+
+ signgui-scheduler
+
+
+ true
+
+
+
+
+ papermc
+ https://repo.papermc.io/repository/maven-public/
+
+
+
+
+
+ dev.folia
+ folia-api
+ 1.20.1-R0.1-SNAPSHOT
+ provided
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 8
+ 8
+
+
+
+
+
diff --git a/scheduler/src/main/java/de/rapha149/signgui/scheduler/BukkitSchedulerAdapter.java b/scheduler/src/main/java/de/rapha149/signgui/scheduler/BukkitSchedulerAdapter.java
new file mode 100644
index 0000000..a425823
--- /dev/null
+++ b/scheduler/src/main/java/de/rapha149/signgui/scheduler/BukkitSchedulerAdapter.java
@@ -0,0 +1,66 @@
+package de.rapha149.signgui.scheduler;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.entity.Entity;
+import org.bukkit.plugin.Plugin;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Scheduler adapter implementation for Bukkit/Paper/Spigot servers.
+ * All tasks run on the main server thread except for async tasks.
+ */
+public class BukkitSchedulerAdapter implements SchedulerAdapter {
+
+ @Override
+ public void runNextTick(Plugin plugin, Runnable runnable) {
+ Bukkit.getScheduler().runTask(plugin, runnable);
+ }
+
+ @Override
+ public void runNextTick(Plugin plugin, Runnable runnable, long delayTicks) {
+ Bukkit.getScheduler().runTaskLater(plugin, runnable, delayTicks);
+ }
+
+ @Override
+ public void runAsync(Plugin plugin, Runnable runnable) {
+ Bukkit.getScheduler().runTaskAsynchronously(plugin, runnable);
+ }
+
+ @Override
+ public void runAsync(Plugin plugin, Runnable runnable, long delayTicks) {
+ Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, runnable, delayTicks);
+ }
+
+ @Override
+ public void runAtEntity(Plugin plugin, Entity entity, Runnable runnable, @Nullable Runnable retired) {
+ Bukkit.getScheduler().runTask(plugin, () -> {
+ if (entity.isValid()) {
+ runnable.run();
+ } else if (retired != null) {
+ retired.run();
+ }
+ });
+ }
+
+ @Override
+ public void runAtEntity(Plugin plugin, Entity entity, Runnable runnable, @Nullable Runnable retired, long delayTicks) {
+ Bukkit.getScheduler().runTaskLater(plugin, () -> {
+ if (entity.isValid()) {
+ runnable.run();
+ } else if (retired != null) {
+ retired.run();
+ }
+ }, delayTicks);
+ }
+
+ @Override
+ public void runAtLocation(Plugin plugin, Location location, Runnable runnable) {
+ Bukkit.getScheduler().runTask(plugin, runnable);
+ }
+
+ @Override
+ public void runAtLocation(Plugin plugin, Location location, Runnable runnable, long delayTicks) {
+ Bukkit.getScheduler().runTaskLater(plugin, runnable, delayTicks);
+ }
+}
diff --git a/scheduler/src/main/java/de/rapha149/signgui/scheduler/FoliaSchedulerAdapter.java b/scheduler/src/main/java/de/rapha149/signgui/scheduler/FoliaSchedulerAdapter.java
new file mode 100644
index 0000000..1bb0950
--- /dev/null
+++ b/scheduler/src/main/java/de/rapha149/signgui/scheduler/FoliaSchedulerAdapter.java
@@ -0,0 +1,53 @@
+package de.rapha149.signgui.scheduler;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.entity.Entity;
+import org.bukkit.plugin.Plugin;
+
+/**
+ * Scheduler adapter implementation for Folia servers.
+ * Tasks are scheduled on the appropriate region threads.
+ */
+public class FoliaSchedulerAdapter implements SchedulerAdapter {
+
+ @Override
+ public void runNextTick(Plugin plugin, Runnable runnable) {
+ Bukkit.getGlobalRegionScheduler().run(plugin, task -> runnable.run());
+ }
+
+ @Override
+ public void runNextTick(Plugin plugin, Runnable runnable, long delayTicks) {
+ Bukkit.getGlobalRegionScheduler().runDelayed(plugin, task -> runnable.run(), delayTicks);
+ }
+
+ @Override
+ public void runAsync(Plugin plugin, Runnable runnable) {
+ Bukkit.getAsyncScheduler().runNow(plugin, task -> runnable.run());
+ }
+
+ @Override
+ public void runAsync(Plugin plugin, Runnable runnable, long delayTicks) {
+ Bukkit.getAsyncScheduler().runDelayed(plugin, task -> runnable.run(), delayTicks * 50, java.util.concurrent.TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void runAtEntity(Plugin plugin, Entity entity, Runnable runnable, Runnable retired) {
+ entity.getScheduler().run(plugin, task -> runnable.run(), retired);
+ }
+
+ @Override
+ public void runAtEntity(Plugin plugin, Entity entity, Runnable runnable, Runnable retired, long delayTicks) {
+ entity.getScheduler().runDelayed(plugin, task -> runnable.run(), retired, delayTicks);
+ }
+
+ @Override
+ public void runAtLocation(Plugin plugin, Location location, Runnable runnable) {
+ Bukkit.getRegionScheduler().run(plugin, location, task -> runnable.run());
+ }
+
+ @Override
+ public void runAtLocation(Plugin plugin, Location location, Runnable runnable, long delayTicks) {
+ Bukkit.getRegionScheduler().runDelayed(plugin, location, task -> runnable.run(), delayTicks);
+ }
+}
diff --git a/scheduler/src/main/java/de/rapha149/signgui/scheduler/SchedulerAdapter.java b/scheduler/src/main/java/de/rapha149/signgui/scheduler/SchedulerAdapter.java
new file mode 100644
index 0000000..2e80c9b
--- /dev/null
+++ b/scheduler/src/main/java/de/rapha149/signgui/scheduler/SchedulerAdapter.java
@@ -0,0 +1,95 @@
+package de.rapha149.signgui.scheduler;
+
+import org.bukkit.Location;
+import org.bukkit.entity.Entity;
+import org.bukkit.plugin.Plugin;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Adapter interface for scheduling tasks across different server implementations.
+ * Supports both Bukkit/Paper and Folia schedulers.
+ */
+public interface SchedulerAdapter {
+
+ /**
+ * Runs a task on the next server tick (main thread for Bukkit, global region for Folia).
+ *
+ * @param plugin the plugin owning this task
+ * @param runnable the task to execute
+ */
+ void runNextTick(Plugin plugin, Runnable runnable);
+
+ /**
+ * Runs a task after a delay (main thread for Bukkit, global region for Folia).
+ *
+ * @param plugin the plugin owning this task
+ * @param runnable the task to execute
+ * @param delayTicks the delay in ticks before execution
+ */
+ void runNextTick(Plugin plugin, Runnable runnable, long delayTicks);
+
+ /**
+ * Runs a task asynchronously.
+ *
+ * @param plugin the plugin owning this task
+ * @param runnable the task to execute
+ */
+ void runAsync(Plugin plugin, Runnable runnable);
+
+ /**
+ * Runs a task asynchronously after a delay.
+ *
+ * @param plugin the plugin owning this task
+ * @param runnable the task to execute
+ * @param delayTicks the delay in ticks before execution
+ */
+ void runAsync(Plugin plugin, Runnable runnable, long delayTicks);
+
+ /**
+ * Runs a task on the thread that owns the given entity.
+ * On Bukkit, this runs on the main thread.
+ * On Folia, this runs on the entity's region thread.
+ *
+ * @param plugin the plugin owning this task
+ * @param entity the entity to run the task for
+ * @param runnable the task to execute
+ * @param retired called if the entity is removed before the task executes (may be null)
+ */
+ void runAtEntity(Plugin plugin, Entity entity, Runnable runnable, @Nullable Runnable retired);
+
+ /**
+ * Runs a task on the thread that owns the given entity after a delay.
+ * On Bukkit, this runs on the main thread.
+ * On Folia, this runs on the entity's region thread.
+ *
+ * @param plugin the plugin owning this task
+ * @param entity the entity to run the task for
+ * @param runnable the task to execute
+ * @param retired called if the entity is removed before the task executes (may be null)
+ * @param delayTicks the delay in ticks before execution
+ */
+ void runAtEntity(Plugin plugin, Entity entity, Runnable runnable, @Nullable Runnable retired, long delayTicks);
+
+ /**
+ * Runs a task on the thread that owns the given location.
+ * On Bukkit, this runs on the main thread.
+ * On Folia, this runs on the region thread that owns the location.
+ *
+ * @param plugin the plugin owning this task
+ * @param location the location to run the task at
+ * @param runnable the task to execute
+ */
+ void runAtLocation(Plugin plugin, Location location, Runnable runnable);
+
+ /**
+ * Runs a task on the thread that owns the given location after a delay.
+ * On Bukkit, this runs on the main thread.
+ * On Folia, this runs on the region thread that owns the location.
+ *
+ * @param plugin the plugin owning this task
+ * @param location the location to run the task at
+ * @param runnable the task to execute
+ * @param delayTicks the delay in ticks before execution
+ */
+ void runAtLocation(Plugin plugin, Location location, Runnable runnable, long delayTicks);
+}
diff --git a/scheduler/src/main/java/de/rapha149/signgui/scheduler/SchedulerAdapterFactory.java b/scheduler/src/main/java/de/rapha149/signgui/scheduler/SchedulerAdapterFactory.java
new file mode 100644
index 0000000..8a6cb80
--- /dev/null
+++ b/scheduler/src/main/java/de/rapha149/signgui/scheduler/SchedulerAdapterFactory.java
@@ -0,0 +1,57 @@
+package de.rapha149.signgui.scheduler;
+
+/**
+ * Factory class for creating the appropriate scheduler adapter based on server type.
+ * Automatically detects whether the server is running Folia or Bukkit/Paper.
+ */
+public final class SchedulerAdapterFactory {
+
+ private static final boolean IS_FOLIA;
+ private static volatile SchedulerAdapter instance;
+
+ static {
+ IS_FOLIA = detectFolia();
+ }
+
+ private SchedulerAdapterFactory() {
+ // Utility class
+ }
+
+ /**
+ * Detects if the server is running Folia by checking for Folia-specific classes.
+ */
+ private static boolean detectFolia() {
+ try {
+ Class.forName("io.papermc.paper.threadedregions.RegionizedServer");
+ return true;
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Returns whether the server is running Folia.
+ *
+ * @return true if running on Folia, false otherwise
+ */
+ public static boolean isFolia() {
+ return IS_FOLIA;
+ }
+
+ /**
+ * Gets the singleton scheduler adapter instance.
+ * Creates the appropriate adapter based on the detected server type.
+ *
+ * @return the scheduler adapter instance
+ */
+ public static SchedulerAdapter getScheduler() {
+ if (instance == null) {
+ synchronized (SchedulerAdapterFactory.class) {
+ if (instance == null) {
+ instance = IS_FOLIA ? new FoliaSchedulerAdapter() : new BukkitSchedulerAdapter();
+ }
+ }
+ }
+ return instance;
+ }
+}