diff --git a/1_17_R1/pom.xml b/1_17_R1/pom.xml index 665a245..954521a 100644 --- a/1_17_R1/pom.xml +++ b/1_17_R1/pom.xml @@ -35,7 +35,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 16 16 diff --git a/1_18_R1/pom.xml b/1_18_R1/pom.xml index 114afbe..aaf2a97 100644 --- a/1_18_R1/pom.xml +++ b/1_18_R1/pom.xml @@ -35,7 +35,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 17 17 diff --git a/1_18_R2/pom.xml b/1_18_R2/pom.xml index b307410..1deaefe 100644 --- a/1_18_R2/pom.xml +++ b/1_18_R2/pom.xml @@ -35,7 +35,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 17 17 diff --git a/1_19_R1/pom.xml b/1_19_R1/pom.xml index 09fdbfe..8617a6a 100644 --- a/1_19_R1/pom.xml +++ b/1_19_R1/pom.xml @@ -35,7 +35,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 17 17 diff --git a/1_19_R2/pom.xml b/1_19_R2/pom.xml index 6af12bc..d0595f4 100644 --- a/1_19_R2/pom.xml +++ b/1_19_R2/pom.xml @@ -35,7 +35,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 17 17 diff --git a/1_19_R3/pom.xml b/1_19_R3/pom.xml index dde640b..5023cc5 100644 --- a/1_19_R3/pom.xml +++ b/1_19_R3/pom.xml @@ -35,7 +35,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 17 17 diff --git a/1_20_R1/pom.xml b/1_20_R1/pom.xml index 5b5a823..dbaf75b 100644 --- a/1_20_R1/pom.xml +++ b/1_20_R1/pom.xml @@ -35,7 +35,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 17 17 diff --git a/1_20_R2/pom.xml b/1_20_R2/pom.xml index 050a37f..06181d0 100644 --- a/1_20_R2/pom.xml +++ b/1_20_R2/pom.xml @@ -35,7 +35,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 17 17 diff --git a/1_20_R3/pom.xml b/1_20_R3/pom.xml index 83209f5..822307f 100644 --- a/1_20_R3/pom.xml +++ b/1_20_R3/pom.xml @@ -35,7 +35,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 17 17 diff --git a/1_20_R4/pom.xml b/1_20_R4/pom.xml index ea7ba53..710e10b 100644 --- a/1_20_R4/pom.xml +++ b/1_20_R4/pom.xml @@ -35,7 +35,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 21 21 diff --git a/1_21_R1/pom.xml b/1_21_R1/pom.xml index a29ec80..9220004 100644 --- a/1_21_R1/pom.xml +++ b/1_21_R1/pom.xml @@ -17,8 +17,8 @@ - org.spigotmc - spigot + io.papermc.paper + paper-api 1.21.1-R0.1-SNAPSHOT provided @@ -29,13 +29,18 @@ provided - + + + papermc + https://repo.papermc.io/repository/maven-public/ + + org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 21 21 diff --git a/1_21_R2/pom.xml b/1_21_R2/pom.xml index bd44edb..cacda40 100644 --- a/1_21_R2/pom.xml +++ b/1_21_R2/pom.xml @@ -35,7 +35,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 21 21 diff --git a/1_21_R3/pom.xml b/1_21_R3/pom.xml index f8acfdf..f27a50f 100644 --- a/1_21_R3/pom.xml +++ b/1_21_R3/pom.xml @@ -35,7 +35,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 21 21 diff --git a/1_21_R4/pom.xml b/1_21_R4/pom.xml index b07307a..71a9475 100644 --- a/1_21_R4/pom.xml +++ b/1_21_R4/pom.xml @@ -35,7 +35,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 21 21 diff --git a/1_21_R5/pom.xml b/1_21_R5/pom.xml index 90df437..85c2ffd 100644 --- a/1_21_R5/pom.xml +++ b/1_21_R5/pom.xml @@ -35,7 +35,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 21 21 diff --git a/Mojang1_20_R4/pom.xml b/Mojang1_20_R4/pom.xml index f046623..8f68b64 100644 --- a/Mojang1_20_R4/pom.xml +++ b/Mojang1_20_R4/pom.xml @@ -52,8 +52,8 @@ ca.bkaw paper-nms-maven-plugin - 1.4.7 + 1.4.10 - + \ No newline at end of file diff --git a/Mojang1_21_R1/pom.xml b/Mojang1_21_R1/pom.xml index c52f7d2..fbde605 100644 --- a/Mojang1_21_R1/pom.xml +++ b/Mojang1_21_R1/pom.xml @@ -43,7 +43,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 21 21 @@ -52,7 +52,7 @@ ca.bkaw paper-nms-maven-plugin - 1.4.7 + 1.4.10 diff --git a/Mojang1_21_R2/pom.xml b/Mojang1_21_R2/pom.xml index 4668dda..4a8c04e 100644 --- a/Mojang1_21_R2/pom.xml +++ b/Mojang1_21_R2/pom.xml @@ -43,7 +43,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 21 21 @@ -52,7 +52,7 @@ ca.bkaw paper-nms-maven-plugin - 1.4.7 + 1.4.10 diff --git a/Mojang1_21_R3/pom.xml b/Mojang1_21_R3/pom.xml index 3586b6d..d8077c2 100644 --- a/Mojang1_21_R3/pom.xml +++ b/Mojang1_21_R3/pom.xml @@ -43,7 +43,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 21 21 @@ -52,7 +52,7 @@ ca.bkaw paper-nms-maven-plugin - 1.4.7 + 1.4.10 diff --git a/Mojang1_21_R4/pom.xml b/Mojang1_21_R4/pom.xml index c6d3509..e7e6157 100644 --- a/Mojang1_21_R4/pom.xml +++ b/Mojang1_21_R4/pom.xml @@ -43,7 +43,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 21 21 @@ -52,7 +52,7 @@ ca.bkaw paper-nms-maven-plugin - 1.4.7 + 1.4.10 diff --git a/Mojang1_21_R5/pom.xml b/Mojang1_21_R5/pom.xml index d201d02..4d7c54f 100644 --- a/Mojang1_21_R5/pom.xml +++ b/Mojang1_21_R5/pom.xml @@ -43,7 +43,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 21 21 @@ -52,7 +52,7 @@ ca.bkaw paper-nms-maven-plugin - 1.4.7 + 1.4.10 diff --git a/README.md b/README.md index 1fae7a7..d958dc2 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,14 @@ An api to get input text via a sign in Minecraft. The api supports the Minecraft versions from `1.8` to `1.21`. Also supports adventure text and mojang-mapped Paper plugins (1.20.5+). +## ✨ Full Platform Support +- ✅ **Bukkit / Spigot / Paper** - Full support +- ✅ **Folia** - Complete regionized multithreading support +- ✅ **CanvasMC** - Full support +- ✅ **Archlight** - Full support (Forge+Bukkit hybrid) + +SignGUI automatically detects your server platform and uses the appropriate scheduler for thread-safe operation! + ## Integration Maven dependency: @@ -120,6 +128,10 @@ try { return Collections.emptyList(); }) + // RECOMMENDED: Call handler synchronously for thread safety + // REQUIRED for Folia; RECOMMENDED for CanvasMC, Archlight, and other platforms + .callHandlerSynchronously(this) // "this" = your JavaPlugin instance + // build the SignGUI .build(); @@ -135,6 +147,8 @@ try { You don't have to call all methods. Only `setHandler` is mandatory. +**Important:** `callHandlerSynchronously(plugin)` is **REQUIRED for Folia** and **RECOMMENDED for all other platforms for thread safety**. On Folia, this ensures tasks run on the correct region thread. On other platforms, it ensures tasks run on the main thread. + By default, the handler is called by an asynchronous thread. You can change that behaviour by calling the method `callHandlerSynchronously` of the builder. An explanation for the different methods can be found on the [Javadoc](https://javadoc.io/doc/de.rapha149.signgui/signgui). diff --git a/api/pom.xml b/api/pom.xml index 243b55f..d3f16c2 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -49,6 +49,12 @@ 1.8-R0.1-SNAPSHOT provided + + dev.folia + folia-api + 1.21.11-R0.1-SNAPSHOT + provided + de.rapha149.signgui signgui-wrapper diff --git a/api/src/main/java/de/rapha149/signgui/SignGUI.java b/api/src/main/java/de/rapha149/signgui/SignGUI.java index 1f37daa..51c9696 100644 --- a/api/src/main/java/de/rapha149/signgui/SignGUI.java +++ b/api/src/main/java/de/rapha149/signgui/SignGUI.java @@ -3,6 +3,7 @@ import de.rapha149.signgui.SignGUIAction.SignGUIActionInfo; import de.rapha149.signgui.exception.SignGUIException; import de.rapha149.signgui.exception.SignGUIVersionException; +import de.rapha149.signgui.util.scheduler.SchedulerFactory; import de.rapha149.signgui.version.VersionMatcher; import org.apache.commons.lang.Validate; import org.bukkit.Bukkit; @@ -107,10 +108,12 @@ public void open(Player player) throws SignGUIException { action.execute(this, signEditor, player); }; - if (callHandlerSynchronously) - Bukkit.getScheduler().runTask(plugin, runnable); - else + if (callHandlerSynchronously) { + // Use platform-aware scheduler that works on Folia, CanvasMC, Archlight, and Bukkit + SchedulerFactory.getScheduler().runTask(plugin, player, runnable); + } else { runnable.run(); + } }); } catch (Exception e) { throw new SignGUIException("Failed to open sign gui", e); diff --git a/api/src/main/java/de/rapha149/signgui/SignGUIBuilder.java b/api/src/main/java/de/rapha149/signgui/SignGUIBuilder.java index 33babcc..6efdf85 100644 --- a/api/src/main/java/de/rapha149/signgui/SignGUIBuilder.java +++ b/api/src/main/java/de/rapha149/signgui/SignGUIBuilder.java @@ -168,7 +168,10 @@ 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 by using the appropriate scheduler. + * This is required for Folia, CanvasMC, and Archlight support and ensures thread-safe execution. + * On Folia, tasks are scheduled via the player's entity scheduler (on the thread owning the player's region). + * On Bukkit/Spigot/Paper, tasks are scheduled on the main thread. * * @param plugin Your {@link org.bukkit.plugin.java.JavaPlugin} instance. * @return The {@link SignGUIBuilder} instance diff --git a/api/src/main/java/de/rapha149/signgui/util/PlatformDetector.java b/api/src/main/java/de/rapha149/signgui/util/PlatformDetector.java new file mode 100644 index 0000000..9b6b1af --- /dev/null +++ b/api/src/main/java/de/rapha149/signgui/util/PlatformDetector.java @@ -0,0 +1,103 @@ +package de.rapha149.signgui.util; + +import org.bukkit.Bukkit; + +/** + * Utility class for detecting the server platform. + */ +public class PlatformDetector { + + private static PlatformType detectedPlatform; + private static boolean hasFoliaClasses; + private static boolean hasRegionScheduler; + + static { + detectPlatform(); + } + + /** + * Detects the server platform. + */ + private static void detectPlatform() { + try { + Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); + hasFoliaClasses = true; + } catch (ClassNotFoundException ignored) { + hasFoliaClasses = false; + } + + try { + Class.forName("io.papermc.paper.threadedregions.scheduler.RegionScheduler"); + hasRegionScheduler = true; + } catch (ClassNotFoundException ignored) { + hasRegionScheduler = false; + } + + String serverVersion = Bukkit.getVersion(); + String serverName = Bukkit.getName(); + + if (hasFoliaClasses && hasRegionScheduler) { + detectedPlatform = PlatformType.FOLIA; + } else if (serverVersion.contains("Canvas") || serverName.contains("Canvas")) { + detectedPlatform = PlatformType.CANVAS; + } else if (serverVersion.contains("Archlight") || serverName.contains("Archlight")) { + detectedPlatform = PlatformType.ARCHLIGHT; + } else { + detectedPlatform = PlatformType.BUKKIT; + } + } + + /** + * Gets the detected platform type. + * + * @return The detected platform type + */ + public static PlatformType getPlatformType() { + return detectedPlatform; + } + + /** + * Checks if the server is running on Folia. + * + * @return true if running on Folia, false otherwise + */ + public static boolean isFolia() { + return detectedPlatform == PlatformType.FOLIA; + } + + /** + * Checks if the server is running on CanvasMC. + * + * @return true if running on CanvasMC, false otherwise + */ + public static boolean isCanvas() { + return detectedPlatform == PlatformType.CANVAS; + } + + /** + * Checks if the server is running on Archlight. + * + * @return true if running on Archlight, false otherwise + */ + public static boolean isArchlight() { + return detectedPlatform == PlatformType.ARCHLIGHT; + } + + /** + * Checks if the server supports region-based scheduling (Folia). + * + * @return true if region scheduling is supported, false otherwise + */ + public static boolean hasRegionScheduler() { + return hasRegionScheduler; + } + + /** + * Checks if the server has Folia classes available. + * + * @return true if Folia classes are available, false otherwise + */ + public static boolean hasFoliaClasses() { + return hasFoliaClasses; + } +} diff --git a/api/src/main/java/de/rapha149/signgui/util/PlatformType.java b/api/src/main/java/de/rapha149/signgui/util/PlatformType.java new file mode 100644 index 0000000..0f394f5 --- /dev/null +++ b/api/src/main/java/de/rapha149/signgui/util/PlatformType.java @@ -0,0 +1,26 @@ +package de.rapha149.signgui.util; + +/** + * Enum representing different server platforms. + */ +public enum PlatformType { + /** + * Folia - Paper's regionized multithreaded server + */ + FOLIA, + + /** + * CanvasMC - Fork of Paper + */ + CANVAS, + + /** + * Archlight - Forge+Bukkit hybrid server + */ + ARCHLIGHT, + + /** + * Standard Bukkit/Spigot/Paper server + */ + BUKKIT +} diff --git a/api/src/main/java/de/rapha149/signgui/util/SchedulerAdapter.java b/api/src/main/java/de/rapha149/signgui/util/SchedulerAdapter.java new file mode 100644 index 0000000..9c78c23 --- /dev/null +++ b/api/src/main/java/de/rapha149/signgui/util/SchedulerAdapter.java @@ -0,0 +1,81 @@ +package de.rapha149.signgui.util; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.plugin.java.JavaPlugin; + +/** + * Adapter interface for scheduling tasks across different server platforms. + */ +public interface SchedulerAdapter { + + /** + * Runs a task synchronously on the main thread or appropriate region thread. + * + * @param plugin The plugin instance + * @param task The task to run + */ + void runTask(JavaPlugin plugin, Runnable task); + + /** + * Runs a task synchronously on the main thread or appropriate region thread. + * + * @param plugin The plugin instance + * @param entity The entity to run the task for (used for Folia region scheduling) + * @param task The task to run + */ + void runTask(JavaPlugin plugin, Entity entity, Runnable task); + + /** + * Runs a task synchronously at a specific location. + * + * @param plugin The plugin instance + * @param location The location to run the task at (used for Folia region scheduling) + * @param task The task to run + */ + void runTask(JavaPlugin plugin, Location location, Runnable task); + + /** + * Runs a task asynchronously. + * + * @param plugin The plugin instance + * @param task The task to run + */ + void runTaskAsynchronously(JavaPlugin plugin, Runnable task); + + /** + * Schedules a delayed task synchronously. + * + * @param plugin The plugin instance + * @param task The task to run + * @param delayTicks The delay in ticks + */ + void runTaskLater(JavaPlugin plugin, Runnable task, long delayTicks); + + /** + * Schedules a delayed task synchronously for an entity. + * + * @param plugin The plugin instance + * @param entity The entity to run the task for (used for Folia region scheduling) + * @param task The task to run + * @param delayTicks The delay in ticks + */ + void runTaskLater(JavaPlugin plugin, Entity entity, Runnable task, long delayTicks); + + /** + * Schedules a delayed task synchronously at a location. + * + * @param plugin The plugin instance + * @param location The location to run the task at (used for Folia region scheduling) + * @param task The task to run + * @param delayTicks The delay in ticks + */ + void runTaskLater(JavaPlugin plugin, Location location, Runnable task, long delayTicks); + + /** + * Checks if the current thread is the main server thread or appropriate region thread. + * + * @return true if on the main/region thread, false otherwise + */ + boolean isOnMainThread(); +} diff --git a/api/src/main/java/de/rapha149/signgui/util/scheduler/BukkitSchedulerAdapter.java b/api/src/main/java/de/rapha149/signgui/util/scheduler/BukkitSchedulerAdapter.java new file mode 100644 index 0000000..95e9741 --- /dev/null +++ b/api/src/main/java/de/rapha149/signgui/util/scheduler/BukkitSchedulerAdapter.java @@ -0,0 +1,62 @@ +package de.rapha149.signgui.util.scheduler; + +import de.rapha149.signgui.util.SchedulerAdapter; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.plugin.java.JavaPlugin; + +/** + * Standard Bukkit/Spigot/Paper scheduler implementation. + * Also used for CanvasMC and Archlight which are compatible with Bukkit API. + */ +public class BukkitSchedulerAdapter implements SchedulerAdapter { + + @Override + public void runTask(JavaPlugin plugin, Runnable task) { + if (Bukkit.isPrimaryThread()) { + task.run(); + } else { + Bukkit.getScheduler().runTask(plugin, task); + } + } + + @Override + public void runTask(JavaPlugin plugin, Entity entity, Runnable task) { + runTask(plugin, task); + } + + @Override + public void runTask(JavaPlugin plugin, Location location, Runnable task) { + runTask(plugin, task); + } + + @Override + public void runTaskAsynchronously(JavaPlugin plugin, Runnable task) { + if (Bukkit.isPrimaryThread()) { + Bukkit.getScheduler().runTaskAsynchronously(plugin, task); + } else { + task.run(); + } + } + + @Override + public void runTaskLater(JavaPlugin plugin, Runnable task, long delayTicks) { + Bukkit.getScheduler().runTaskLater(plugin, task, delayTicks); + } + + @Override + public void runTaskLater(JavaPlugin plugin, Entity entity, Runnable task, long delayTicks) { + runTaskLater(plugin, task, delayTicks); + } + + @Override + public void runTaskLater(JavaPlugin plugin, Location location, Runnable task, long delayTicks) { + runTaskLater(plugin, task, delayTicks); + } + + @Override + public boolean isOnMainThread() { + return Bukkit.isPrimaryThread(); + } +} diff --git a/api/src/main/java/de/rapha149/signgui/util/scheduler/FoliaSchedulerAdapter.java b/api/src/main/java/de/rapha149/signgui/util/scheduler/FoliaSchedulerAdapter.java new file mode 100644 index 0000000..7d1909b --- /dev/null +++ b/api/src/main/java/de/rapha149/signgui/util/scheduler/FoliaSchedulerAdapter.java @@ -0,0 +1,272 @@ +package de.rapha149.signgui.util.scheduler; + +import de.rapha149.signgui.util.SchedulerAdapter; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.plugin.java.JavaPlugin; + +import java.lang.reflect.Method; +import java.util.concurrent.TimeUnit; + +/** + * Folia-compatible scheduler implementation using region-based scheduling. + * This implementation uses reflection to access Folia's API without requiring it at compile time. + */ +public class FoliaSchedulerAdapter implements SchedulerAdapter { + + private static final Class REGION_SCHEDULER_CLASS; + private static final Class GLOBAL_REGION_SCHEDULER_CLASS; + private static final Class ENTITY_SCHEDULER_CLASS; + private static final Class ASYNC_SCHEDULER_CLASS; + + private static final Method GET_REGION_SCHEDULER_METHOD; + private static final Method GET_GLOBAL_REGION_SCHEDULER_METHOD; + private static final Method GET_ASYNC_SCHEDULER_METHOD; + private static final Method REGION_EXECUTE_METHOD; + private static final Method REGION_RUN_DELAYED_METHOD; + private static final Method GLOBAL_RUN_METHOD; + private static final Method ASYNC_RUN_NOW_METHOD; + private static final Method ENTITY_GET_SCHEDULER_METHOD; + private static final Method ENTITY_EXECUTE_METHOD; + private static final Method ENTITY_RUN_DELAYED_METHOD; + private static final Method IS_OWNED_BY_CURRENT_REGION_METHOD; + + private static final boolean FOLIA_AVAILABLE; + + static { + boolean available = false; + Class regionSchedulerClass = null; + Class globalRegionSchedulerClass = null; + Class entitySchedulerClass = null; + Class asyncSchedulerClass = null; + Method getRegionScheduler = null; + Method getGlobalRegionScheduler = null; + Method getAsyncScheduler = null; + Method regionExecute = null; + Method regionRunDelayed = null; + Method globalRun = null; + Method asyncRunNow = null; + Method entityGetScheduler = null; + Method entityExecute = null; + Method entityRunDelayed = null; + Method isOwnedByCurrentRegion = null; + + try { + regionSchedulerClass = Class.forName("io.papermc.paper.threadedregions.scheduler.RegionScheduler"); + globalRegionSchedulerClass = Class.forName("io.papermc.paper.threadedregions.scheduler.GlobalRegionScheduler"); + entitySchedulerClass = Class.forName("io.papermc.paper.threadedregions.scheduler.EntityScheduler"); + asyncSchedulerClass = Class.forName("io.papermc.paper.threadedregions.scheduler.AsyncScheduler"); + + Class serverClass = Bukkit.getServer().getClass(); + getRegionScheduler = serverClass.getMethod("getRegionScheduler"); + getGlobalRegionScheduler = serverClass.getMethod("getGlobalRegionScheduler"); + getAsyncScheduler = serverClass.getMethod("getAsyncScheduler"); + regionExecute = regionSchedulerClass.getMethod("execute", org.bukkit.plugin.Plugin.class, Location.class, Runnable.class); + regionRunDelayed = regionSchedulerClass.getMethod("runDelayed", org.bukkit.plugin.Plugin.class, Location.class, + java.util.function.Consumer.class, long.class); + + globalRun = globalRegionSchedulerClass.getMethod("run", org.bukkit.plugin.Plugin.class, java.util.function.Consumer.class); + + asyncRunNow = asyncSchedulerClass.getMethod("runNow", org.bukkit.plugin.Plugin.class, java.util.function.Consumer.class); + + entityGetScheduler = Entity.class.getMethod("getScheduler"); + entityExecute = entitySchedulerClass.getMethod("execute", org.bukkit.plugin.Plugin.class, Runnable.class, + Runnable.class, long.class); + entityRunDelayed = entitySchedulerClass.getMethod("runDelayed", org.bukkit.plugin.Plugin.class, + java.util.function.Consumer.class, Runnable.class, long.class); + + Class regionizedServerClass = Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); + isOwnedByCurrentRegion = regionizedServerClass.getMethod("isOwnedByCurrentRegion", Location.class); + + available = true; + } catch (ClassNotFoundException e) { + System.err.println("Warning: Required Folia/Paper scheduler class not found while initializing FoliaSchedulerAdapter: " + + e.getMessage()); + System.err.println("Falling back to Bukkit scheduler. This usually indicates a Folia/Paper version incompatibility or that Folia is not present."); + e.printStackTrace(System.err); + } catch (NoSuchMethodException e) { + System.err.println("Warning: Required Folia/Paper scheduler method not found while initializing FoliaSchedulerAdapter: " + + e.getMessage()); + System.err.println("Falling back to Bukkit scheduler. This usually indicates a Folia/Paper or Bukkit API version incompatibility."); + e.printStackTrace(System.err); + } catch (Exception e) { + System.err.println("Warning: Unexpected error while initializing FoliaSchedulerAdapter. Falling back to Bukkit scheduler."); + e.printStackTrace(System.err); + } + + FOLIA_AVAILABLE = available; + REGION_SCHEDULER_CLASS = regionSchedulerClass; + GLOBAL_REGION_SCHEDULER_CLASS = globalRegionSchedulerClass; + ENTITY_SCHEDULER_CLASS = entitySchedulerClass; + ASYNC_SCHEDULER_CLASS = asyncSchedulerClass; + GET_REGION_SCHEDULER_METHOD = getRegionScheduler; + GET_GLOBAL_REGION_SCHEDULER_METHOD = getGlobalRegionScheduler; + GET_ASYNC_SCHEDULER_METHOD = getAsyncScheduler; + REGION_EXECUTE_METHOD = regionExecute; + REGION_RUN_DELAYED_METHOD = regionRunDelayed; + GLOBAL_RUN_METHOD = globalRun; + ASYNC_RUN_NOW_METHOD = asyncRunNow; + ENTITY_GET_SCHEDULER_METHOD = entityGetScheduler; + ENTITY_EXECUTE_METHOD = entityExecute; + ENTITY_RUN_DELAYED_METHOD = entityRunDelayed; + IS_OWNED_BY_CURRENT_REGION_METHOD = isOwnedByCurrentRegion; + } + + /** + * Checks if Folia is available. + * + * @return true if Folia is available, false otherwise + */ + public static boolean isFoliaAvailable() { + return FOLIA_AVAILABLE; + } + + @Override + public void runTask(JavaPlugin plugin, Runnable task) { + if (!FOLIA_AVAILABLE) { + fallbackScheduler().runTask(plugin, task); + return; + } + + try { + Object globalScheduler = GET_GLOBAL_REGION_SCHEDULER_METHOD.invoke(Bukkit.getServer()); + GLOBAL_RUN_METHOD.invoke(globalScheduler, plugin, (java.util.function.Consumer) scheduledTask -> task.run()); + } catch (Exception e) { + e.printStackTrace(); + fallbackScheduler().runTask(plugin, task); + } + } + + @Override + public void runTask(JavaPlugin plugin, Entity entity, Runnable task) { + if (!FOLIA_AVAILABLE) { + fallbackScheduler().runTask(plugin, entity, task); + return; + } + + try { + Object entityScheduler = ENTITY_GET_SCHEDULER_METHOD.invoke(entity); + ENTITY_EXECUTE_METHOD.invoke(entityScheduler, plugin, task, null, 1L); + } catch (Exception e) { + e.printStackTrace(); + fallbackScheduler().runTask(plugin, entity, task); + } + } + + @Override + public void runTask(JavaPlugin plugin, Location location, Runnable task) { + if (!FOLIA_AVAILABLE) { + fallbackScheduler().runTask(plugin, location, task); + return; + } + + try { + boolean isOwned = (boolean) IS_OWNED_BY_CURRENT_REGION_METHOD.invoke(null, location); + if (isOwned) { + try { + task.run(); + } catch (Exception ex) { + ex.printStackTrace(); + } + return; + } + + Object regionScheduler = GET_REGION_SCHEDULER_METHOD.invoke(Bukkit.getServer()); + REGION_EXECUTE_METHOD.invoke(regionScheduler, plugin, location, task); + } catch (Exception e) { + e.printStackTrace(); + fallbackScheduler().runTask(plugin, location, task); + } + } + + @Override + public void runTaskAsynchronously(JavaPlugin plugin, Runnable task) { + if (!FOLIA_AVAILABLE) { + fallbackScheduler().runTaskAsynchronously(plugin, task); + return; + } + + try { + Object asyncScheduler = GET_ASYNC_SCHEDULER_METHOD.invoke(Bukkit.getServer()); + ASYNC_RUN_NOW_METHOD.invoke(asyncScheduler, plugin, (java.util.function.Consumer) scheduledTask -> task.run()); + } catch (Exception e) { + e.printStackTrace(); + fallbackScheduler().runTaskAsynchronously(plugin, task); + } + } + + @Override + public void runTaskLater(JavaPlugin plugin, Runnable task, long delayTicks) { + if (!FOLIA_AVAILABLE) { + fallbackScheduler().runTaskLater(plugin, task, delayTicks); + return; + } + + try { + Object globalScheduler = GET_GLOBAL_REGION_SCHEDULER_METHOD.invoke(Bukkit.getServer()); + + Method globalRunDelayed = globalScheduler.getClass().getMethod("runDelayed", + org.bukkit.plugin.Plugin.class, java.util.function.Consumer.class, long.class); + globalRunDelayed.invoke(globalScheduler, plugin, + (java.util.function.Consumer) scheduledTask -> task.run(), + delayTicks); + } catch (Exception e) { + e.printStackTrace(); + fallbackScheduler().runTaskLater(plugin, task, delayTicks); + } + } + + @Override + public void runTaskLater(JavaPlugin plugin, Entity entity, Runnable task, long delayTicks) { + if (!FOLIA_AVAILABLE) { + fallbackScheduler().runTaskLater(plugin, entity, task, delayTicks); + return; + } + + try { + Object entityScheduler = ENTITY_GET_SCHEDULER_METHOD.invoke(entity); + ENTITY_RUN_DELAYED_METHOD.invoke(entityScheduler, plugin, + (java.util.function.Consumer) scheduledTask -> task.run(), null, delayTicks); + } catch (Exception e) { + e.printStackTrace(); + fallbackScheduler().runTaskLater(plugin, entity, task, delayTicks); + } + } + + @Override + public void runTaskLater(JavaPlugin plugin, Location location, Runnable task, long delayTicks) { + if (!FOLIA_AVAILABLE) { + fallbackScheduler().runTaskLater(plugin, location, task, delayTicks); + return; + } + + try { + Object regionScheduler = GET_REGION_SCHEDULER_METHOD.invoke(Bukkit.getServer()); + REGION_RUN_DELAYED_METHOD.invoke(regionScheduler, plugin, location, + (java.util.function.Consumer) scheduledTask -> task.run(), delayTicks); + } catch (Exception e) { + e.printStackTrace(); + fallbackScheduler().runTaskLater(plugin, location, task, delayTicks); + } + } + + @Override + public boolean isOnMainThread() { + if (!FOLIA_AVAILABLE) { + return fallbackScheduler().isOnMainThread(); + } + // On Folia, there is no single main thread; use Bukkit's primary thread check, + // which returns true on region/global region threads and false on async threads. + return Bukkit.isPrimaryThread(); + } + + /** + * Gets the fallback scheduler adapter. + * + * @return The fallback scheduler adapter + */ + private SchedulerAdapter fallbackScheduler() { + return new BukkitSchedulerAdapter(); + } +} diff --git a/api/src/main/java/de/rapha149/signgui/util/scheduler/SchedulerFactory.java b/api/src/main/java/de/rapha149/signgui/util/scheduler/SchedulerFactory.java new file mode 100644 index 0000000..cfd6b92 --- /dev/null +++ b/api/src/main/java/de/rapha149/signgui/util/scheduler/SchedulerFactory.java @@ -0,0 +1,38 @@ +package de.rapha149.signgui.util.scheduler; + +import de.rapha149.signgui.util.PlatformDetector; +import de.rapha149.signgui.util.SchedulerAdapter; + +/** + * Factory class for creating appropriate scheduler adapters based on the server platform. + */ +public class SchedulerFactory { + + private static SchedulerAdapter cachedAdapter; + + /** + * Gets the appropriate scheduler adapter for the current platform. + * + * @return The scheduler adapter + */ + public static SchedulerAdapter getScheduler() { + if (cachedAdapter != null) { + return cachedAdapter; + } + + if (PlatformDetector.isFolia()) { + cachedAdapter = new FoliaSchedulerAdapter(); + } else { + cachedAdapter = new BukkitSchedulerAdapter(); + } + + return cachedAdapter; + } + + /** + * Resets the cached scheduler adapter. Useful for testing or reloading. + */ + public static void reset() { + cachedAdapter = null; + } +} diff --git a/pom.xml b/pom.xml index 12a1556..4cf25ce 100644 --- a/pom.xml +++ b/pom.xml @@ -59,6 +59,10 @@ nms-repo https://repo.codemc.org/repository/nms/ + + papermc + https://repo.papermc.io/repository/maven-public/ + @@ -67,7 +71,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 1.8 1.8