diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/BukkitTaskManager.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/BukkitTaskManager.java index 11b8565a67..01b1fe7612 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/BukkitTaskManager.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/BukkitTaskManager.java @@ -2,13 +2,20 @@ import com.fastasyncworldedit.core.util.TaskManager; import org.bukkit.Bukkit; +import org.bukkit.Server; import org.bukkit.plugin.Plugin; import javax.annotation.Nonnull; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; public class BukkitTaskManager extends TaskManager { private final Plugin plugin; + private final AtomicInteger foliaTaskCounter = new AtomicInteger(); + private final Map foliaTaskCancels = new ConcurrentHashMap<>(); public BukkitTaskManager(final Plugin plugin) { this.plugin = plugin; @@ -16,7 +23,11 @@ public BukkitTaskManager(final Plugin plugin) { @Override public int repeat(@Nonnull final Runnable runnable, final int interval) { - return this.plugin.getServer().getScheduler().scheduleSyncRepeatingTask(this.plugin, runnable, interval, interval); + try { + return this.plugin.getServer().getScheduler().scheduleSyncRepeatingTask(this.plugin, runnable, interval, interval); + } catch (UnsupportedOperationException ignored) { + return scheduleFoliaRepeatingTask(runnable, interval); + } } @Override @@ -31,12 +42,20 @@ public void async(@Nonnull final Runnable runnable) { @Override public void task(@Nonnull final Runnable runnable) { - this.plugin.getServer().getScheduler().runTask(this.plugin, runnable).getTaskId(); + try { + this.plugin.getServer().getScheduler().runTask(this.plugin, runnable).getTaskId(); + } catch (UnsupportedOperationException ignored) { + executeFoliaNow(runnable); + } } @Override public void later(@Nonnull final Runnable runnable, final int delay) { - this.plugin.getServer().getScheduler().runTaskLater(this.plugin, runnable, delay).getTaskId(); + try { + this.plugin.getServer().getScheduler().runTaskLater(this.plugin, runnable, delay).getTaskId(); + } catch (UnsupportedOperationException ignored) { + scheduleFoliaDelayedTask(runnable, delay); + } } @Override @@ -47,8 +66,85 @@ public void laterAsync(@Nonnull final Runnable runnable, final int delay) { @Override public void cancel(final int task) { if (task != -1) { - Bukkit.getScheduler().cancelTask(task); + Runnable foliaCancel = foliaTaskCancels.remove(task); + if (foliaCancel != null) { + foliaCancel.run(); + } else { + Bukkit.getScheduler().cancelTask(task); + } + } + } + + private void executeFoliaNow(final Runnable runnable) { + try { + Object globalRegionScheduler = getGlobalRegionScheduler(); + Method execute = globalRegionScheduler.getClass().getMethod("execute", Plugin.class, Runnable.class); + execute.invoke(globalRegionScheduler, this.plugin, runnable); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Unable to schedule sync task on Folia", e); + } + } + + private void scheduleFoliaDelayedTask(final Runnable runnable, final long delay) { + try { + Object globalRegionScheduler = getGlobalRegionScheduler(); + Method runDelayed = globalRegionScheduler.getClass() + .getMethod("runDelayed", Plugin.class, java.util.function.Consumer.class, long.class); + Object scheduledTask = runDelayed.invoke(globalRegionScheduler, this.plugin, new FoliaTaskConsumer(runnable), delay); + storeFoliaTaskCancel(scheduledTask); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Unable to schedule delayed sync task on Folia", e); + } + } + + private int scheduleFoliaRepeatingTask(final Runnable runnable, final long interval) { + try { + Object globalRegionScheduler = getGlobalRegionScheduler(); + Method runAtFixedRate = globalRegionScheduler.getClass() + .getMethod("runAtFixedRate", Plugin.class, java.util.function.Consumer.class, long.class, long.class); + Object scheduledTask = runAtFixedRate.invoke( + globalRegionScheduler, + this.plugin, + new FoliaTaskConsumer(runnable), + interval, + interval + ); + return storeFoliaTaskCancel(scheduledTask); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Unable to schedule repeating sync task on Folia", e); + } + } + + private int storeFoliaTaskCancel(final Object scheduledTask) { + try { + Method cancel = scheduledTask.getClass().getMethod("cancel"); + int taskId = foliaTaskCounter.decrementAndGet(); + foliaTaskCancels.put(taskId, () -> { + try { + cancel.invoke(scheduledTask); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Unable to cancel Folia task", e); + } + }); + return taskId; + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Unable to wire Folia task cancellation", e); } } + private Object getGlobalRegionScheduler() throws ReflectiveOperationException { + Server server = this.plugin.getServer(); + Method method = server.getClass().getMethod("getGlobalRegionScheduler"); + return method.invoke(server); + } + + private record FoliaTaskConsumer(Runnable runnable) implements java.util.function.Consumer { + + @Override + public void accept(final Object ignored) { + runnable.run(); + } + + } + } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java index d0708887b0..8cddf73827 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java @@ -457,7 +457,11 @@ public void onDisable() { if (config != null) { config.unload(); } - this.getServer().getScheduler().cancelTasks(this); + try { + this.getServer().getScheduler().cancelTasks(this); + } catch (UnsupportedOperationException ignored) { + // Folia does not support legacy scheduler cancellation for all task types. + } } /**