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