From fb6a5eb6b8a5393fb787ec2418e17484e22d8353 Mon Sep 17 00:00:00 2001
From: OPmasterLEO <98689894+OPmasterLEO@users.noreply.github.com>
Date: Wed, 7 Jan 2026 15:45:03 +0100
Subject: [PATCH 1/3] Add platform detection and scheduler abstraction for
Folia support
---
README.md | 14 +
api/pom.xml | 6 +
.../java/de/rapha149/signgui/SignGUI.java | 9 +-
.../de/rapha149/signgui/SignGUIBuilder.java | 5 +-
.../signgui/util/PlatformDetector.java | 103 +++++++
.../rapha149/signgui/util/PlatformType.java | 26 ++
.../signgui/util/SchedulerAdapter.java | 81 ++++++
.../scheduler/BukkitSchedulerAdapter.java | 62 +++++
.../util/scheduler/FoliaSchedulerAdapter.java | 261 ++++++++++++++++++
.../util/scheduler/SchedulerFactory.java | 38 +++
pom.xml | 4 +
11 files changed, 605 insertions(+), 4 deletions(-)
create mode 100644 api/src/main/java/de/rapha149/signgui/util/PlatformDetector.java
create mode 100644 api/src/main/java/de/rapha149/signgui/util/PlatformType.java
create mode 100644 api/src/main/java/de/rapha149/signgui/util/SchedulerAdapter.java
create mode 100644 api/src/main/java/de/rapha149/signgui/util/scheduler/BukkitSchedulerAdapter.java
create mode 100644 api/src/main/java/de/rapha149/signgui/util/scheduler/FoliaSchedulerAdapter.java
create mode 100644 api/src/main/java/de/rapha149/signgui/util/scheduler/SchedulerFactory.java
diff --git a/README.md b/README.md
index 1fae7a7..f128a63 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, CanvasMC, and Archlight
+ .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 for Folia/CanvasMC/Archlight:** Always use `callHandlerSynchronously(plugin)` to ensure thread-safe operation on all platforms. 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..31b13ad 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 on the player's region thread.
+ * 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..e80c230
--- /dev/null
+++ b/api/src/main/java/de/rapha149/signgui/util/scheduler/FoliaSchedulerAdapter.java
@@ -0,0 +1,261 @@
+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 (Exception e) {
+ System.err.println("Warning: Folia classes not found, but FoliaSchedulerAdapter was attempted to be used. " +
+ "This should not happen. Falling back to Bukkit scheduler.");
+ }
+
+ 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
-
+
+
+ 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/pom.xml b/pom.xml
index ecbf170..4cf25ce 100644
--- a/pom.xml
+++ b/pom.xml
@@ -71,7 +71,7 @@
org.apache.maven.plugins
maven-compiler-plugin
- 3.8.1
+ 3.14.1
1.8
1.8
From 9a28246a27351938aa74985192643da32d36a86b Mon Sep 17 00:00:00 2001
From: OPmasterLEO <98689894+OPmasterLEO@users.noreply.github.com>
Date: Wed, 7 Jan 2026 17:08:54 +0100
Subject: [PATCH 3/3] Improve Folia scheduler error handling and docs
---
README.md | 4 +-
.../de/rapha149/signgui/SignGUIBuilder.java | 2 +-
.../util/scheduler/FoliaSchedulerAdapter.java | 39 ++++++++++++-------
3 files changed, 28 insertions(+), 17 deletions(-)
diff --git a/README.md b/README.md
index f128a63..d958dc2 100644
--- a/README.md
+++ b/README.md
@@ -129,7 +129,7 @@ try {
})
// RECOMMENDED: Call handler synchronously for thread safety
- // REQUIRED for Folia, CanvasMC, and Archlight
+ // REQUIRED for Folia; RECOMMENDED for CanvasMC, Archlight, and other platforms
.callHandlerSynchronously(this) // "this" = your JavaPlugin instance
// build the SignGUI
@@ -147,7 +147,7 @@ try {
You don't have to call all methods. Only `setHandler` is mandatory.
-**Important for Folia/CanvasMC/Archlight:** Always use `callHandlerSynchronously(plugin)` to ensure thread-safe operation on all platforms. On Folia, this ensures tasks run on the correct region thread. On other platforms, it ensures tasks run on the main thread.
+**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/src/main/java/de/rapha149/signgui/SignGUIBuilder.java b/api/src/main/java/de/rapha149/signgui/SignGUIBuilder.java
index 31b13ad..6efdf85 100644
--- a/api/src/main/java/de/rapha149/signgui/SignGUIBuilder.java
+++ b/api/src/main/java/de/rapha149/signgui/SignGUIBuilder.java
@@ -170,7 +170,7 @@ public SignGUIBuilder setHandler(SignGUIFinishHandler handler) {
/**
* 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 on the player's region thread.
+ * 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.
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
index e80c230..7d1909b 100644
--- a/api/src/main/java/de/rapha149/signgui/util/scheduler/FoliaSchedulerAdapter.java
+++ b/api/src/main/java/de/rapha149/signgui/util/scheduler/FoliaSchedulerAdapter.java
@@ -80,9 +80,19 @@ public class FoliaSchedulerAdapter implements SchedulerAdapter {
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: Folia classes not found, but FoliaSchedulerAdapter was attempted to be used. " +
- "This should not happen. Falling back to Bukkit scheduler.");
+ System.err.println("Warning: Unexpected error while initializing FoliaSchedulerAdapter. Falling back to Bukkit scheduler.");
+ e.printStackTrace(System.err);
}
FOLIA_AVAILABLE = available;
@@ -154,7 +164,11 @@ public void runTask(JavaPlugin plugin, Location location, Runnable task) {
try {
boolean isOwned = (boolean) IS_OWNED_BY_CURRENT_REGION_METHOD.invoke(null, location);
if (isOwned) {
- task.run();
+ try {
+ task.run();
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
return;
}
@@ -191,14 +205,12 @@ public void runTaskLater(JavaPlugin plugin, Runnable task, long delayTicks) {
try {
Object globalScheduler = GET_GLOBAL_REGION_SCHEDULER_METHOD.invoke(Bukkit.getServer());
- Object asyncScheduler = GET_ASYNC_SCHEDULER_METHOD.invoke(Bukkit.getServer());
- long delayMillis = delayTicks * 50L;
- Method asyncRunDelayed = ASYNC_SCHEDULER_CLASS.getMethod("runDelayed",
- org.bukkit.plugin.Plugin.class, java.util.function.Consumer.class, long.class, TimeUnit.class);
- asyncRunDelayed.invoke(asyncScheduler, plugin,
- (java.util.function.Consumer) scheduledTask -> runTask(plugin, task),
- delayMillis, TimeUnit.MILLISECONDS);
+ 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);
@@ -244,10 +256,9 @@ public boolean isOnMainThread() {
if (!FOLIA_AVAILABLE) {
return fallbackScheduler().isOnMainThread();
}
-
- // On Folia, there is no single main thread - we're always on a valid region thread
- // if we're executing game logic
- return true;
+ // 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();
}
/**