From 52313078386a3e84e53f30148807568e8c5a64c7 Mon Sep 17 00:00:00 2001 From: yLeoft Date: Thu, 7 Aug 2025 19:39:38 -0300 Subject: [PATCH 1/9] Add folia support --- pom.xml | 13 +- .../InventoryRollbackPlus.java | 18 +- .../inventoryrollback/ImportSubCmd.java | 5 +- .../inventoryrollback/RestoreSubCmd.java | 5 +- .../folia/FoliaRunnable.java | 22 ++ .../folia/SchedulerUtils.java | 261 ++++++++++++++++++ .../inventoryrollback/InventoryRollback.java | 3 +- .../inventoryrollback/commands/Commands.java | 9 +- .../inventoryrollback/data/PlayerData.java | 11 +- .../gui/menu/EnderChestBackupMenu.java | 7 +- .../gui/menu/MainInventoryBackupMenu.java | 82 +++--- .../inventory/SaveInventory.java | 3 +- .../inventoryrollback/listeners/ClickGUI.java | 208 +++++++------- .../listeners/EventLogs.java | 3 +- src/main/resources/plugin.yml | 1 + 15 files changed, 470 insertions(+), 181 deletions(-) create mode 100644 src/main/java/com/nuclyon/technicallycoded/inventoryrollback/folia/FoliaRunnable.java create mode 100644 src/main/java/com/nuclyon/technicallycoded/inventoryrollback/folia/SchedulerUtils.java diff --git a/pom.xml b/pom.xml index e38fc71..0bc26da 100644 --- a/pom.xml +++ b/pom.xml @@ -64,13 +64,6 @@ ${project.basedir}/lib/spigot-1.19.3.jar true --> - - org.spigotmc - spigot - 1.20.5-R0.1-SNAPSHOT - true - provided - org.bstats @@ -85,6 +78,12 @@ 1.0.6 compile + + dev.folia + folia-api + 1.21.4-R0.1-SNAPSHOT + provided + org.jetbrains annotations diff --git a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/InventoryRollbackPlus.java b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/InventoryRollbackPlus.java index 7ca7e06..7f22d50 100644 --- a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/InventoryRollbackPlus.java +++ b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/InventoryRollbackPlus.java @@ -2,6 +2,7 @@ import com.nuclyon.technicallycoded.inventoryrollback.UpdateChecker.UpdateResult; import com.nuclyon.technicallycoded.inventoryrollback.commands.Commands; +import com.nuclyon.technicallycoded.inventoryrollback.folia.SchedulerUtils; import com.nuclyon.technicallycoded.inventoryrollback.util.TimeZoneUtil; import com.nuclyon.technicallycoded.inventoryrollback.util.test.SelfTestSerialization; import com.tcoded.lightlibs.bukkitversion.BukkitVersion; @@ -30,6 +31,7 @@ public class InventoryRollbackPlus extends InventoryRollback { private static InventoryRollbackPlus instancePlus; + public static boolean usingFolia = false; private TimeZoneUtil timeZoneUtil = null; @@ -42,6 +44,16 @@ public static InventoryRollbackPlus getInstance() { return instancePlus; } + @Override + public void onLoad() { + try { + Class.forName("io.papermc.paper.threadedregions.scheduler.RegionScheduler"); + usingFolia = true; + } catch (ClassNotFoundException e) { + usingFolia = false; + } + } + @Override public void onEnable() { instancePlus = this; @@ -89,7 +101,7 @@ public void onEnable() { getServer().getPluginManager().registerEvents(new ClickGUI(), this); getServer().getPluginManager().registerEvents(new EventLogs(), this); // Run after all plugin enable - getServer().getScheduler().runTask(this, EventLogs::patchLowestHandlers); + SchedulerUtils.runTask(null, EventLogs::patchLowestHandlers); // PaperLib if (!PaperLib.isPaper()) { @@ -124,7 +136,7 @@ public void onDisable() { HandlerList.unregisterAll(this); // Cancel tasks - this.getServer().getScheduler().cancelTasks(this); + if(!usingFolia) this.getServer().getScheduler().cancelTasks(this); // Clear instance references instancePlus = null; @@ -149,7 +161,7 @@ public boolean isCompatibleCb(String cbVersion) { } public void checkUpdate() { - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), () -> { + SchedulerUtils.runTaskAsynchronously(() -> { InventoryRollbackPlus.getInstance().getConsoleSender().sendMessage(MessageData.getPluginPrefix() + "Checking for updates..."); final UpdateResult result = new UpdateChecker(getInstance(), 85811).getResult(); diff --git a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/inventoryrollback/ImportSubCmd.java b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/inventoryrollback/ImportSubCmd.java index af926d9..aac06d8 100644 --- a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/inventoryrollback/ImportSubCmd.java +++ b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/inventoryrollback/ImportSubCmd.java @@ -2,6 +2,7 @@ import com.nuclyon.technicallycoded.inventoryrollback.InventoryRollbackPlus; import com.nuclyon.technicallycoded.inventoryrollback.commands.IRPCommand; +import com.nuclyon.technicallycoded.inventoryrollback.folia.SchedulerUtils; import com.nuclyon.technicallycoded.inventoryrollback.util.LegacyBackupConversionUtil; import me.danjono.inventoryrollback.config.MessageData; import org.bukkit.Bukkit; @@ -32,7 +33,7 @@ public void onCommand(CommandSender sender, Command cmd, String label, String[] suggestConfirm.set(true); // Reset suggestion availability after 10 seconds - this.main.getServer().getScheduler().runTaskLaterAsynchronously(this.main, () -> { + SchedulerUtils.runTaskLaterAsynchronously(null, () -> { suggestConfirm.set(false); }, 10 * 20); @@ -40,7 +41,7 @@ public void onCommand(CommandSender sender, Command cmd, String label, String[] } // Execute import - Bukkit.getScheduler().runTaskAsynchronously(main, LegacyBackupConversionUtil::convertOldBackupData); + SchedulerUtils.runTaskAsynchronously(LegacyBackupConversionUtil::convertOldBackupData); // Reset suggestion to not visible suggestConfirm.set(false); diff --git a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/inventoryrollback/RestoreSubCmd.java b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/inventoryrollback/RestoreSubCmd.java index d5e7e23..cde277d 100644 --- a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/inventoryrollback/RestoreSubCmd.java +++ b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/inventoryrollback/RestoreSubCmd.java @@ -2,6 +2,7 @@ import com.nuclyon.technicallycoded.inventoryrollback.InventoryRollbackPlus; import com.nuclyon.technicallycoded.inventoryrollback.commands.IRPCommand; +import com.nuclyon.technicallycoded.inventoryrollback.folia.SchedulerUtils; import me.danjono.inventoryrollback.InventoryRollback; import me.danjono.inventoryrollback.config.ConfigData; import me.danjono.inventoryrollback.config.MessageData; @@ -90,14 +91,14 @@ private void openMainMenu(Player staff) { MainMenu menu = new MainMenu(staff, 1); staff.openInventory(menu.getInventory()); - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), menu::getMainMenu); + SchedulerUtils.runTaskAsynchronously(menu::getMainMenu); } private void openPlayerMenu(Player staff, OfflinePlayer offlinePlayer) { PlayerMenu menu = new PlayerMenu(staff, offlinePlayer); staff.openInventory(menu.getInventory()); - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), menu::getPlayerMenu); + SchedulerUtils.runTaskAsynchronously(menu::getPlayerMenu); } } diff --git a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/folia/FoliaRunnable.java b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/folia/FoliaRunnable.java new file mode 100644 index 0000000..b467021 --- /dev/null +++ b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/folia/FoliaRunnable.java @@ -0,0 +1,22 @@ +package com.nuclyon.technicallycoded.inventoryrollback.folia; + +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; +import org.bukkit.scheduler.BukkitRunnable; + +public class FoliaRunnable extends BukkitRunnable { + + private ScheduledTask foliaTask; + + @Override + public synchronized void cancel() throws IllegalStateException { + if(foliaTask != null) foliaTask.cancel(); + } + + @Override + public void run() { + } + + public void setScheduledTask(ScheduledTask task) { + this.foliaTask = task; + } +} diff --git a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/folia/SchedulerUtils.java b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/folia/SchedulerUtils.java new file mode 100644 index 0000000..56932da --- /dev/null +++ b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/folia/SchedulerUtils.java @@ -0,0 +1,261 @@ +package com.nuclyon.technicallycoded.inventoryrollback.folia; + +import com.nuclyon.technicallycoded.inventoryrollback.InventoryRollbackPlus; +import io.papermc.paper.threadedregions.scheduler.GlobalRegionScheduler; +import io.papermc.paper.threadedregions.scheduler.RegionScheduler; +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Method; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; + +import static com.nuclyon.technicallycoded.inventoryrollback.InventoryRollbackPlus.usingFolia; + +/** + * Utility class for scheduling tasks in a Paper/Folia server. + */ +public abstract class SchedulerUtils { + + /** + * Schedules a task to run later on the main server thread. + * @param loc The location where the task should run, or null for the main thread. + * @param task The task to run. + * @param delay The delay in ticks before the task runs. + */ + public static void runTaskLater(@Nullable Location loc, @NotNull Runnable task, long delay) { + JavaPlugin plugin = InventoryRollbackPlus.getInstance(); + if (usingFolia) { + try { + if (loc != null) { + Method getRegionScheduler = plugin.getServer().getClass().getMethod("getRegionScheduler"); + RegionScheduler regionScheduler = (RegionScheduler) getRegionScheduler.invoke(plugin.getServer()); + regionScheduler.runDelayed( + plugin, + loc, + (ScheduledTask scheduledTask) -> task.run(), + delay + ); + } else { + Method getGlobalScheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler"); + GlobalRegionScheduler globalScheduler = (GlobalRegionScheduler) getGlobalScheduler.invoke(plugin.getServer()); + globalScheduler.runDelayed( + plugin, + (ScheduledTask scheduledTask) -> task.run(), + delay + ); + } + return; + } catch (Exception e) { + e.printStackTrace(); + } + } + plugin.getServer().getScheduler().runTaskLater(plugin, task, delay); + } + + /** + * Schedules a task to run later asynchronously on the main server thread. + * @param loc The location where the task should run, or null for the main thread. + * @param task The task to run. + * @param delay The delay in ticks before the task runs. + */ + public static void runTaskLaterAsynchronously(@Nullable Location loc, @NotNull Runnable task, long delay) { + JavaPlugin plugin = InventoryRollbackPlus.getInstance(); + if (usingFolia) { + try { + if (loc != null) { + Method getRegionScheduler = plugin.getServer().getClass().getMethod("getRegionScheduler"); + RegionScheduler regionScheduler = (RegionScheduler) getRegionScheduler.invoke(plugin.getServer()); + regionScheduler.runDelayed( + plugin, + loc, + (ScheduledTask scheduledTask) -> task.run(), + delay + ); + } else { + Method getGlobalScheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler"); + GlobalRegionScheduler globalScheduler = (GlobalRegionScheduler) getGlobalScheduler.invoke(plugin.getServer()); + globalScheduler.runDelayed( + plugin, + (ScheduledTask scheduledTask) -> task.run(), + delay + ); + } + return; + } catch (Exception e) { + e.printStackTrace(); + } + } + plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, task, delay); + } + + /** + * Schedules a task to run repeatedly on the main server thread. + * @param loc The location where the task should run, or null for the main thread. + * @param runnable The BukkitRunnable to run. + * @param delay The delay in ticks before the task runs. + * @param period The period in ticks between subsequent runs of the task. + */ + public static void runTaskTimer(@Nullable Location loc, @NotNull FoliaRunnable runnable, long delay, long period) { + JavaPlugin plugin = InventoryRollbackPlus.getInstance(); + if (usingFolia) { + try { + ScheduledTask task; + if (loc != null) { + Method getRegionScheduler = plugin.getServer().getClass().getMethod("getRegionScheduler"); + RegionScheduler regionScheduler = (RegionScheduler) getRegionScheduler.invoke(plugin.getServer()); + task = regionScheduler.runAtFixedRate( + plugin, + loc, + (ScheduledTask t) -> runnable.run(), + delay, + period + ); + } else { + Method getGlobalScheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler"); + GlobalRegionScheduler globalScheduler = (GlobalRegionScheduler) getGlobalScheduler.invoke(plugin.getServer()); + task = globalScheduler.runAtFixedRate( + plugin, + (ScheduledTask t) -> runnable.run(), + delay, + period + ); + } + runnable.setScheduledTask(task); + } catch (Exception e) { + e.printStackTrace(); + } + return; + } + runnable.runTaskTimer(plugin, delay, period); + } + + /** + * Schedules a task to run repeatedly on the main server thread asynchronously. + * @param runnable The FoliaRunnable to run. + * @param delay The delay in ticks before the task runs. + * @param period The period in ticks between subsequent runs of the task. + */ + public static void runTaskTimerAsynchronously(@NotNull FoliaRunnable runnable, long delay, long period) { + JavaPlugin plugin = InventoryRollbackPlus.getInstance(); + if (usingFolia) { + try { + Method getGlobalScheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler"); + GlobalRegionScheduler globalScheduler = (GlobalRegionScheduler) getGlobalScheduler.invoke(plugin.getServer()); + class AsyncRepeatingTask { + private ScheduledTask task; + void start(long initialDelay) { + task = globalScheduler.runDelayed(plugin, (ScheduledTask t) -> { + runnable.run(); + start(period); + }, initialDelay); + runnable.setScheduledTask(task); + } + } + new AsyncRepeatingTask().start(delay); + } catch (Exception e) { + e.printStackTrace(); + } + return; + } + runnable.runTaskTimerAsynchronously(plugin, delay, period); + } + + /** + * Runs a task asynchronously on the main server thread. + * @param task The task to run. + */ + public static void runTaskAsynchronously(@NotNull Runnable task) { + JavaPlugin plugin = InventoryRollbackPlus.getInstance(); + if (usingFolia) { + try { + Method getGlobalScheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler"); + GlobalRegionScheduler globalScheduler = (GlobalRegionScheduler) getGlobalScheduler.invoke(plugin.getServer()); + globalScheduler.execute(plugin, task); + return; + } catch (Exception e) { + e.printStackTrace(); + } + } + plugin.getServer().getScheduler().runTaskAsynchronously(plugin, task); + } + + /** + * Runs a task on the main server thread at a specific location or globally. + * @param loc The location where the task should run, or null for the main thread. + * @param task The task to run. + */ + public static void runTask(@Nullable Location loc, @NotNull Runnable task) { + JavaPlugin plugin = InventoryRollbackPlus.getInstance(); + if (usingFolia) { + try { + if (loc != null) { + Method getRegionScheduler = plugin.getServer().getClass().getMethod("getRegionScheduler"); + RegionScheduler regionScheduler = (RegionScheduler) getRegionScheduler.invoke(plugin.getServer()); + regionScheduler.execute(plugin, loc, task); + } else { + Method getGlobalScheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler"); + GlobalRegionScheduler globalScheduler = (GlobalRegionScheduler) getGlobalScheduler.invoke(plugin.getServer()); + globalScheduler.execute(plugin, task); + } + return; + } catch (Exception e) { + e.printStackTrace(); + } + } + plugin.getServer().getScheduler().runTask(plugin, task); + } + + public static CompletableFuture callSyncMethod(@Nullable Location loc, @NotNull Callable task) { + JavaPlugin plugin = InventoryRollbackPlus.getInstance(); + if (usingFolia) { + CompletableFuture future = new CompletableFuture<>(); + try { + if (loc != null) { + Method getRegionScheduler = plugin.getServer().getClass().getMethod("getRegionScheduler"); + RegionScheduler regionScheduler = (RegionScheduler) getRegionScheduler.invoke(plugin.getServer()); + regionScheduler.execute(plugin, loc, () -> { + try { + future.complete(task.call()); + } catch (Exception e) { + future.completeExceptionally(e); + } + }); + } else { + Method getGlobalScheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler"); + GlobalRegionScheduler globalScheduler = (GlobalRegionScheduler) getGlobalScheduler.invoke(plugin.getServer()); + globalScheduler.execute(plugin, () -> { + try { + future.complete(task.call()); + } catch (Exception e) { + future.completeExceptionally(e); + } + }); + } + } catch (Exception e) { + future.completeExceptionally(e); + } + return future; + } + CompletableFuture cf = new CompletableFuture<>(); + try { + Future bukkitFuture = plugin.getServer().getScheduler().callSyncMethod(plugin, task); + plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> { + try { + T result = bukkitFuture.get(); + cf.complete(result); + } catch (Throwable ex) { + cf.completeExceptionally(ex); + } + }); + } catch (Throwable e) { + cf.completeExceptionally(e); + } + return cf; + } +} diff --git a/src/main/java/me/danjono/inventoryrollback/InventoryRollback.java b/src/main/java/me/danjono/inventoryrollback/InventoryRollback.java index e4e98b7..63b5caf 100644 --- a/src/main/java/me/danjono/inventoryrollback/InventoryRollback.java +++ b/src/main/java/me/danjono/inventoryrollback/InventoryRollback.java @@ -1,6 +1,7 @@ package me.danjono.inventoryrollback; import com.nuclyon.technicallycoded.inventoryrollback.InventoryRollbackPlus; +import com.nuclyon.technicallycoded.inventoryrollback.folia.SchedulerUtils; import me.danjono.inventoryrollback.UpdateChecker.UpdateResult; import me.danjono.inventoryrollback.commands.Commands; import me.danjono.inventoryrollback.config.ConfigData; @@ -165,7 +166,7 @@ public void bStats() { } public void checkUpdate() { - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), () -> { + SchedulerUtils.runTaskAsynchronously(() -> { logger.log(Level.INFO, MessageData.getPluginPrefix() + "Checking for updates..."); final UpdateResult result = new UpdateChecker(getInstance(), 85811).getResult(); diff --git a/src/main/java/me/danjono/inventoryrollback/commands/Commands.java b/src/main/java/me/danjono/inventoryrollback/commands/Commands.java index 591ce15..e6368cb 100644 --- a/src/main/java/me/danjono/inventoryrollback/commands/Commands.java +++ b/src/main/java/me/danjono/inventoryrollback/commands/Commands.java @@ -1,5 +1,6 @@ package me.danjono.inventoryrollback.commands; +import com.nuclyon.technicallycoded.inventoryrollback.folia.SchedulerUtils; import me.danjono.inventoryrollback.InventoryRollback; import me.danjono.inventoryrollback.config.ConfigData; import me.danjono.inventoryrollback.config.MessageData; @@ -114,14 +115,14 @@ private void openMainMenu(Player staff) { MainMenu menu = new MainMenu(staff, 1); staff.openInventory(menu.getInventory()); - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), menu::getMainMenu); + SchedulerUtils.runTaskAsynchronously(menu::getMainMenu); } private void openPlayerMenu(Player staff, OfflinePlayer offlinePlayer) { PlayerMenu menu = new PlayerMenu(staff, offlinePlayer); staff.openInventory(menu.getInventory()); - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), menu::getPlayerMenu); + SchedulerUtils.runTaskAsynchronously(menu::getPlayerMenu); } private void forceBackupCommand(CommandSender sender, String[] args) { @@ -220,13 +221,13 @@ private void versionCommand(CommandSender sender) { private void convertMySQL(CommandSender sender) { if (sender instanceof ConsoleCommandSender && sender.isOp()) { - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), MySQL::convertYAMLToMySQL); + SchedulerUtils.runTaskAsynchronously(MySQL::convertYAMLToMySQL); } } private void convertYAML(CommandSender sender) { if (sender instanceof ConsoleCommandSender && sender.isOp()) { - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), YAML::convertOldBackupData); + SchedulerUtils.runTaskAsynchronously(YAML::convertOldBackupData); } } diff --git a/src/main/java/me/danjono/inventoryrollback/data/PlayerData.java b/src/main/java/me/danjono/inventoryrollback/data/PlayerData.java index dd717b7..54bcb6c 100644 --- a/src/main/java/me/danjono/inventoryrollback/data/PlayerData.java +++ b/src/main/java/me/danjono/inventoryrollback/data/PlayerData.java @@ -9,6 +9,8 @@ import java.util.concurrent.CompletableFuture; import com.nuclyon.technicallycoded.inventoryrollback.InventoryRollbackPlus; +import com.nuclyon.technicallycoded.inventoryrollback.folia.FoliaRunnable; +import com.nuclyon.technicallycoded.inventoryrollback.folia.SchedulerUtils; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.inventory.ItemStack; @@ -16,7 +18,6 @@ import me.danjono.inventoryrollback.InventoryRollback; import me.danjono.inventoryrollback.config.ConfigData; import me.danjono.inventoryrollback.config.ConfigData.SaveType; -import org.bukkit.scheduler.BukkitRunnable; public class PlayerData { @@ -134,7 +135,7 @@ public CompletableFuture purgeExcessSaves(boolean shouldSaveAsync) { }; InventoryRollbackPlus instance = InventoryRollbackPlus.getInstance(); - if (saveAsync) instance.getServer().getScheduler().runTaskAsynchronously(instance, purgeTask); + if (saveAsync) SchedulerUtils.runTaskAsynchronously(purgeTask); else purgeTask.run(); return future; @@ -263,7 +264,7 @@ public void getRollbackMenuData() { public CompletableFuture getAllBackupData() { CompletableFuture future = new CompletableFuture<>(); if (ConfigData.getSaveType() == SaveType.MYSQL) { - new BukkitRunnable() { + SchedulerUtils.runTaskAsynchronously(new FoliaRunnable() { @Override public void run() { try { @@ -273,7 +274,7 @@ public void run() { } future.complete(null); } - }.runTaskAsynchronously(InventoryRollbackPlus.getInstance()); + }); } return future; } @@ -437,7 +438,7 @@ public void saveData(boolean shouldSaveAsync) { } }; - if (saveAsync) Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(),saveDataTask); + if (saveAsync) SchedulerUtils.runTaskAsynchronously(saveDataTask); else saveDataTask.run(); } diff --git a/src/main/java/me/danjono/inventoryrollback/gui/menu/EnderChestBackupMenu.java b/src/main/java/me/danjono/inventoryrollback/gui/menu/EnderChestBackupMenu.java index 7b6f9bb..abf3149 100644 --- a/src/main/java/me/danjono/inventoryrollback/gui/menu/EnderChestBackupMenu.java +++ b/src/main/java/me/danjono/inventoryrollback/gui/menu/EnderChestBackupMenu.java @@ -1,6 +1,8 @@ package me.danjono.inventoryrollback.gui.menu; import com.nuclyon.technicallycoded.inventoryrollback.InventoryRollbackPlus; +import com.nuclyon.technicallycoded.inventoryrollback.folia.FoliaRunnable; +import com.nuclyon.technicallycoded.inventoryrollback.folia.SchedulerUtils; import me.danjono.inventoryrollback.config.ConfigData; import me.danjono.inventoryrollback.config.MessageData; import me.danjono.inventoryrollback.data.LogType; @@ -11,7 +13,6 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; -import org.bukkit.scheduler.BukkitRunnable; import java.util.ArrayList; import java.util.List; @@ -85,7 +86,7 @@ public void showEnderChestItems() { try { // Add items, 5 per tick - new BukkitRunnable() { + SchedulerUtils.runTaskTimer(null, new FoliaRunnable() { int invPosition = 0; int itemPos = (pageNumber - 1) * 27; @@ -111,7 +112,7 @@ public void run() { itemPos++; } } - }.runTaskTimer(InventoryRollbackPlus.getInstance(), 0, 1); + }, 1, 1); } catch (NullPointerException e) { staff.sendMessage(MessageData.getPluginPrefix() + MessageData.getErrorInventory()); return; diff --git a/src/main/java/me/danjono/inventoryrollback/gui/menu/MainInventoryBackupMenu.java b/src/main/java/me/danjono/inventoryrollback/gui/menu/MainInventoryBackupMenu.java index ab75e59..592f008 100644 --- a/src/main/java/me/danjono/inventoryrollback/gui/menu/MainInventoryBackupMenu.java +++ b/src/main/java/me/danjono/inventoryrollback/gui/menu/MainInventoryBackupMenu.java @@ -1,6 +1,8 @@ package me.danjono.inventoryrollback.gui.menu; import com.nuclyon.technicallycoded.inventoryrollback.InventoryRollbackPlus; +import com.nuclyon.technicallycoded.inventoryrollback.folia.FoliaRunnable; +import com.nuclyon.technicallycoded.inventoryrollback.folia.SchedulerUtils; import me.danjono.inventoryrollback.config.ConfigData; import me.danjono.inventoryrollback.config.MessageData; import me.danjono.inventoryrollback.data.LogType; @@ -11,11 +13,11 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; -import org.bukkit.scheduler.BukkitRunnable; import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; public class MainInventoryBackupMenu { @@ -78,12 +80,12 @@ public void showBackupItems() { assert !Bukkit.isPrimaryThread(); int item = 0; - int position = 0; + AtomicInteger position = new AtomicInteger(); //If the backup file is invalid it will return null, we want to catch it here try { // Add items, 5 per tick - new BukkitRunnable() { + SchedulerUtils.runTaskTimer(null, new FoliaRunnable() { int invPosition = 0; int itemPos = 0; @@ -108,7 +110,7 @@ public void run() { itemPos++; } } - }.runTaskTimer(main, 0, 1); + }, 1, 1); } catch (Exception ex) { ex.printStackTrace(); staff.sendMessage(MessageData.getPluginPrefix() + MessageData.getErrorInventory()); @@ -116,47 +118,43 @@ public void run() { } item = 36; - position = 44; + position.set(44); //Add armour if (armour != null && armour.length > 0) { - try { - for (int i = 0; i < armour.length; i++) { - // Place item safely - final int finalPos = position; - final int finalItem = i; - Future placeItemFuture = main.getServer().getScheduler().callSyncMethod(main, - () -> { - inventory.setItem(finalPos, armour[finalItem]); - return null; - }); - placeItemFuture.get(); - position--; - } - } catch (ExecutionException | InterruptedException ex) { - ex.printStackTrace(); - } - } else { - try { - for (int i = 36; i < mainInvLen; i++) { - if (mainInventory[item] != null) { - // Place item safely - final int finalPos = position; - final int finalItem = item; - Future placeItemFuture = main.getServer().getScheduler().callSyncMethod(main, - () -> { - inventory.setItem(finalPos, mainInventory[finalItem]); - return null; - }); - placeItemFuture.get(); - position--; - } - item++; - } - } catch (ExecutionException | InterruptedException ex) { - ex.printStackTrace(); - } - } + for (int i = 0; i < armour.length; i++) { + // Place item safely + final int finalPos = position.get(); + final int finalItem = i; + SchedulerUtils.callSyncMethod(null, () -> { + inventory.setItem(finalPos, armour[finalItem]); + return null; + }).whenComplete((res, ex) -> { + if (ex != null) ex.printStackTrace(); + else position.getAndDecrement(); + }); + + } + } else { + for (int i = 36; i < mainInvLen; i++) { + if (mainInventory[item] != null) { + // Place item safely + final int finalPos = position.get(); + final int finalItem = item; + SchedulerUtils.callSyncMethod(null, () -> { + inventory.setItem(finalPos, mainInventory[finalItem]); + return null; + }).whenComplete((res, ex) -> { + if (ex != null) { + ex.printStackTrace(); + } else { + position.getAndDecrement(); + } + }); + } + item++; + } + } // Add restore all player inventory button if (ConfigData.isRestoreToPlayerButton()) diff --git a/src/main/java/me/danjono/inventoryrollback/inventory/SaveInventory.java b/src/main/java/me/danjono/inventoryrollback/inventory/SaveInventory.java index 431fc6d..3b625e4 100644 --- a/src/main/java/me/danjono/inventoryrollback/inventory/SaveInventory.java +++ b/src/main/java/me/danjono/inventoryrollback/inventory/SaveInventory.java @@ -1,6 +1,7 @@ package me.danjono.inventoryrollback.inventory; import com.nuclyon.technicallycoded.inventoryrollback.InventoryRollbackPlus; +import com.nuclyon.technicallycoded.inventoryrollback.folia.SchedulerUtils; import com.nuclyon.technicallycoded.inventoryrollback.util.UserLogRateLimiter; import com.nuclyon.technicallycoded.inventoryrollback.util.serialization.ItemStackSerialization; import com.tcoded.lightlibs.bukkitversion.BukkitVersion; @@ -97,7 +98,7 @@ public void save(PlayerDataSnapshot snapshot, boolean async) { purgeTask.thenRun(() -> data.saveData(saveAsync)); }; - if (saveAsync) main.getServer().getScheduler().runTaskAsynchronously(main, saveTask); + if (saveAsync) SchedulerUtils.runTaskAsynchronously(saveTask); else saveTask.run(); } diff --git a/src/main/java/me/danjono/inventoryrollback/listeners/ClickGUI.java b/src/main/java/me/danjono/inventoryrollback/listeners/ClickGUI.java index 2480efc..bdf91af 100644 --- a/src/main/java/me/danjono/inventoryrollback/listeners/ClickGUI.java +++ b/src/main/java/me/danjono/inventoryrollback/listeners/ClickGUI.java @@ -2,9 +2,10 @@ import com.nuclyon.technicallycoded.inventoryrollback.InventoryRollbackPlus; import com.nuclyon.technicallycoded.inventoryrollback.customdata.CustomDataItemEditor; +import com.nuclyon.technicallycoded.inventoryrollback.folia.FoliaRunnable; +import com.nuclyon.technicallycoded.inventoryrollback.folia.SchedulerUtils; import com.tcoded.lightlibs.bukkitversion.BukkitVersion; import io.papermc.lib.PaperLib; -import me.danjono.inventoryrollback.InventoryRollback; import me.danjono.inventoryrollback.config.ConfigData; import me.danjono.inventoryrollback.config.MessageData; import me.danjono.inventoryrollback.config.SoundData; @@ -22,7 +23,6 @@ import org.bukkit.event.inventory.InventoryDragEvent; import org.bukkit.inventory.InventoryView; import org.bukkit.inventory.ItemStack; -import org.bukkit.scheduler.BukkitRunnable; import java.util.UUID; import java.util.concurrent.ExecutionException; @@ -134,7 +134,7 @@ private void mainMenu(InventoryClickEvent e, Player staff, ItemStack icon) { MainMenu menu = new MainMenu(staff, page); staff.openInventory(menu.getInventory()); - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), menu::getMainMenu); + SchedulerUtils.runTaskAsynchronously(menu::getMainMenu); } //Clicked a player head else { @@ -142,7 +142,7 @@ private void mainMenu(InventoryClickEvent e, Player staff, ItemStack icon) { PlayerMenu menu = new PlayerMenu(staff, offlinePlayer); staff.openInventory(menu.getInventory()); - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), menu::getPlayerMenu); + SchedulerUtils.runTaskAsynchronously(menu::getPlayerMenu); } } else { if (e.getRawSlot() >= e.getInventory().getSize() && !e.isShiftClick()) { @@ -168,13 +168,13 @@ private void playerMenu(InventoryClickEvent e, Player staff, ItemStack icon) { MainMenu menu = new MainMenu(staff, 1); staff.openInventory(menu.getInventory()); - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), menu::getMainMenu); + SchedulerUtils.runTaskAsynchronously(menu::getMainMenu); } else { LogType logType = LogType.valueOf(nbt.getString("logType")); RollbackListMenu menu = new RollbackListMenu(staff, offlinePlayer, logType, 1); staff.openInventory(menu.getInventory()); - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), menu::showBackups); + SchedulerUtils.runTaskAsynchronously(menu::showBackups); } } else { @@ -200,7 +200,7 @@ private void rollbackMenu(InventoryClickEvent e, Player staff, ItemStack icon) { String location = nbt.getString("location"); // Run all data retrieval operations async to avoid tick lag - new BukkitRunnable() { + SchedulerUtils.runTaskAsynchronously(new FoliaRunnable() { @Override public void run() { // Init from MySQL or, if YAML, init & load config file @@ -218,20 +218,21 @@ public void run() { // Create inventory MainInventoryBackupMenu menu = new MainInventoryBackupMenu(staff, data, location); - // Display inventory to player - Future inventoryViewFuture = - main.getServer().getScheduler().callSyncMethod(main, - () -> staff.openInventory(menu.getInventory())); - //If the backup file is invalid it will return null, we want to catch it here - try { - inventoryViewFuture.get(); - // Start placing items in the inventory async + SchedulerUtils.callSyncMethod( + e.getWhoClicked().getLocation(), + () -> staff.openInventory(menu.getInventory()) + ).whenComplete((view, ex) -> { + if (ex != null) { + ex.printStackTrace(); + return; + } + if (view == null) { + return; + } menu.showBackupItems(); - } catch (NullPointerException | ExecutionException | InterruptedException ex) { - ex.printStackTrace(); - } + }); } - }.runTaskAsynchronously(main); + }); } //Player has selected a page icon @@ -244,13 +245,13 @@ else if (icon.getType().equals(Buttons.getPageSelectorIcon())) { PlayerMenu menu = new PlayerMenu(staff, player); staff.openInventory(menu.getInventory()); - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), menu::getPlayerMenu); + SchedulerUtils.runTaskAsynchronously(menu::getPlayerMenu); } else { LogType logType = LogType.valueOf(nbt.getString("logType")); RollbackListMenu menu = new RollbackListMenu(staff, player, logType, page); staff.openInventory(menu.getInventory()); - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), menu::showBackups); + SchedulerUtils.runTaskAsynchronously(menu::showBackups); } } } else { @@ -278,7 +279,7 @@ private void mainBackupMenu(InventoryClickEvent e, Player staff, ItemStack icon) RollbackListMenu menu = new RollbackListMenu(staff, offlinePlayer, logType, 1); staff.openInventory(menu.getInventory()); - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), menu::showBackups); + SchedulerUtils.runTaskAsynchronously(menu::showBackups); } //Clicked icon to overwrite player inventory with backup data @@ -292,7 +293,7 @@ else if (icon.getType().equals(Buttons.getRestoreAllInventoryIcon())) { if (offlinePlayer.isOnline()) { Player player = (Player) offlinePlayer; - new BukkitRunnable() { + SchedulerUtils.runTaskAsynchronously(new FoliaRunnable() { @Override public void run() { // Init from MySQL or, if YAML, init & load config file @@ -311,35 +312,36 @@ public void run() { ItemStack[] armour = data.getArmour(); // Place inventory items sync (compressed code) - Future futureSetInv = main.getServer().getScheduler().callSyncMethod(main, - () -> { player.getInventory().setContents(inventory); return null; }); - try { futureSetInv.get(); } - catch (ExecutionException | InterruptedException ex) { ex.printStackTrace(); } - - // If 1.8, place armor contents separately - if (main.getVersion().lessOrEqThan(BukkitVersion.v1_8_R3)) { - // Place items sync (compressed code) - Future futureSetArmor = main.getServer().getScheduler().callSyncMethod(main, - () -> { player.getInventory().setArmorContents(armour); return null; }); - try { futureSetArmor.get(); } - catch (ExecutionException | InterruptedException ex) { ex.printStackTrace(); } - } - - // Play sound effect is enabled - if (SoundData.isInventoryRestoreEnabled()) { - // Play sound sync (compressed code) - Future futurePlaySound = main.getServer().getScheduler().callSyncMethod(main, - () -> { player.playSound(player.getLocation(), SoundData.getInventoryRestored(), 1, 1); return null; }); - try { futurePlaySound.get(); } - catch (ExecutionException | InterruptedException ex) { ex.printStackTrace(); } - } - - // Send player & staff feedback - player.sendMessage(MessageData.getPluginPrefix() + MessageData.getMainInventoryRestoredPlayer(staff.getName())); - if (!staff.getUniqueId().equals(player.getUniqueId())) - staff.sendMessage(MessageData.getPluginPrefix() + MessageData.getMainInventoryRestored(offlinePlayer.getName())); + SchedulerUtils.callSyncMethod(e.getWhoClicked().getLocation(), () -> { + player.getInventory().setContents(inventory); + return null; + }).whenComplete((res, ex) -> { + if (ex != null) ex.printStackTrace(); + else { + if (main.getVersion().lessOrEqThan(BukkitVersion.v1_8_R3)) { + SchedulerUtils.callSyncMethod(e.getWhoClicked().getLocation(), () -> { + player.getInventory().setArmorContents(armour); + return null; + }).whenComplete((r, e) -> { + if (e != null) e.printStackTrace(); + }); + } + if (SoundData.isInventoryRestoreEnabled()) { + SchedulerUtils.callSyncMethod(e.getWhoClicked().getLocation(), () -> { + player.playSound(player.getLocation(), SoundData.getInventoryRestored(), 1, 1); + return null; + }).whenComplete((r, e) -> { + if (e != null) e.printStackTrace(); + }); + } + + player.sendMessage(MessageData.getPluginPrefix() + MessageData.getMainInventoryRestoredPlayer(staff.getName())); + if (!staff.getUniqueId().equals(player.getUniqueId())) + staff.sendMessage(MessageData.getPluginPrefix() + MessageData.getMainInventoryRestored(offlinePlayer.getName())); + } + }); } - }.runTaskAsynchronously(main); + }); } else { staff.sendMessage(MessageData.getPluginPrefix() + MessageData.getMainInventoryNotOnline(offlinePlayer.getName())); @@ -370,7 +372,7 @@ else if (icon.getType().equals(Buttons.getTeleportLocationIcon())) { .add(0.5, 0.5, 0.5); // Teleport player on a slight delay to block the teleport icon glitching out into the player inventory - Bukkit.getScheduler().runTaskLater(InventoryRollback.getInstance(), () -> { + SchedulerUtils.runTaskLater(e.getWhoClicked().getLocation(), () -> { e.getWhoClicked().closeInventory(); PaperLib.teleportAsync(staff,loc).thenAccept((result) -> { if (SoundData.isTeleportEnabled()) @@ -384,7 +386,7 @@ else if (icon.getType().equals(Buttons.getTeleportLocationIcon())) { // Clicked icon to restore backup players ender chest else if (icon.getType().equals(Buttons.getEnderChestIcon())) { - new BukkitRunnable() { + SchedulerUtils.runTaskAsynchronously(new FoliaRunnable() { @Override public void run() { // Init from MySQL or, if YAML, init & load config file @@ -403,21 +405,17 @@ public void run() { EnderChestBackupMenu menu = new EnderChestBackupMenu(staff, data, 1); // Open inventory sync (compressed code) - Future futureOpenInv = main.getServer().getScheduler().callSyncMethod(main, - () -> { - staff.openInventory(menu.getInventory()); - return null; - }); - try { - futureOpenInv.get(); - } catch (ExecutionException | InterruptedException ex) { - ex.printStackTrace(); - } - - // Place items async - menu.showEnderChestItems(); + SchedulerUtils.callSyncMethod(e.getWhoClicked().getLocation(), () -> { + staff.openInventory(menu.getInventory()); + return null; + }).whenComplete((res, ex) -> { + if (ex != null) ex.printStackTrace(); + else { + menu.showEnderChestItems(); + } + }); } - }.runTaskAsynchronously(this.main); + }); } // Clicked icon to restore backup players health @@ -545,7 +543,7 @@ private void enderChestBackupMenu(InventoryClickEvent e, Player staff, ItemStack if (page == 0) { // Run all data retrieval operations async to avoid tick lag - new BukkitRunnable() { + SchedulerUtils.runTaskAsynchronously(new FoliaRunnable() { @Override public void run() { // Init from MySQL or, if YAML, init & load config file @@ -567,22 +565,24 @@ public void run() { MainInventoryBackupMenu menu = new MainInventoryBackupMenu(staff, data, location); // Display inventory to player - Future inventoryViewFuture = main.getServer().getScheduler().callSyncMethod(main, - () -> staff.openInventory(menu.getInventory())); - //If the backup file is invalid it will return null, we want to catch it here - try { - inventoryViewFuture.get(); - // Start placing items in the inventory async - menu.showBackupItems(); - } catch (NullPointerException | ExecutionException | InterruptedException ex) { - ex.printStackTrace(); - } + SchedulerUtils.callSyncMethod(e.getWhoClicked().getLocation(), () -> staff.openInventory(menu.getInventory())) + .whenComplete((view, ex) -> { + if (ex != null) { + ex.printStackTrace(); + return; + } + if (view == null) { + // backup inválido, tratar aqui se quiser + return; + } + menu.showBackupItems(); + }); } - }.runTaskAsynchronously(main); + }); } else { - new BukkitRunnable() { + SchedulerUtils.runTaskAsynchronously(new FoliaRunnable() { @Override public void run() { // Init from MySQL or, if YAML, init & load config file @@ -601,21 +601,17 @@ public void run() { EnderChestBackupMenu menu = new EnderChestBackupMenu(staff, data, page); // Open inventory sync (compressed code) - Future futureOpenInv = main.getServer().getScheduler().callSyncMethod(main, - () -> { - staff.openInventory(menu.getInventory()); - return null; - }); - try { - futureOpenInv.get(); - } catch (ExecutionException | InterruptedException ex) { - ex.printStackTrace(); - } - - // Place items async - menu.showEnderChestItems(); + SchedulerUtils.callSyncMethod(e.getWhoClicked().getLocation(), () -> { + staff.openInventory(menu.getInventory()); + return null; + }).whenComplete((res, ex) -> { + if (ex != null) ex.printStackTrace(); + else { + menu.showEnderChestItems(); + } + }); } - }.runTaskAsynchronously(this.main); + }); } } @@ -631,7 +627,7 @@ else if (icon.getType().equals(Buttons.getRestoreAllInventoryIcon())) { Player player = (Player) offlinePlayer; // Run all data retrieval operations async to avoid tick lag - new BukkitRunnable() { + SchedulerUtils.runTaskAsynchronously(new FoliaRunnable() { @Override public void run() { // Init from MySQL or, if YAML, init & load config file @@ -647,22 +643,14 @@ public void run() { } // Display inventory to player - Future inventoryReplaceFuture = main.getServer().getScheduler().callSyncMethod(main, - () -> { - ItemStack[] enderChest = data.getEnderChest(); - if (enderChest == null) enderChest = new ItemStack[0]; - player.getEnderChest().setContents(enderChest); - return null; - }); - - //If the backup file is invalid it will return null, we want to catch it here - try { - inventoryReplaceFuture.get(); - } catch (NullPointerException | ExecutionException | InterruptedException ex) { - ex.printStackTrace(); - } + SchedulerUtils.callSyncMethod(e.getWhoClicked().getLocation(), () -> { + player.getEnderChest().setContents(data.getEnderChest() != null ? data.getEnderChest() : new ItemStack[0]); + return null; + }).whenComplete((res, ex) -> { + if (ex != null) ex.printStackTrace(); + }); } - }.runTaskAsynchronously(main); + }); if (SoundData.isInventoryRestoreEnabled()) player.playSound(player.getLocation(), SoundData.getInventoryRestored(), 1, 1); diff --git a/src/main/java/me/danjono/inventoryrollback/listeners/EventLogs.java b/src/main/java/me/danjono/inventoryrollback/listeners/EventLogs.java index 170ddc2..d6cfab7 100644 --- a/src/main/java/me/danjono/inventoryrollback/listeners/EventLogs.java +++ b/src/main/java/me/danjono/inventoryrollback/listeners/EventLogs.java @@ -1,6 +1,7 @@ package me.danjono.inventoryrollback.listeners; import com.nuclyon.technicallycoded.inventoryrollback.InventoryRollbackPlus; +import com.nuclyon.technicallycoded.inventoryrollback.folia.SchedulerUtils; import com.tcoded.lightlibs.bukkitversion.BukkitVersion; import me.danjono.inventoryrollback.config.ConfigData; import me.danjono.inventoryrollback.data.LogType; @@ -93,7 +94,7 @@ private void playerQuit(PlayerQuitEvent e) { // Run the cleanup 1 tick later in case the rate limiter should need to provide debug data. // If the cleanup would run and the event is being spammed, this cleanup would delete the rate limiter's data // before it has a chance to act. - main.getServer().getScheduler().runTaskLater(main, () -> { + SchedulerUtils.runTaskLater(e.getPlayer().getLocation(), () -> { // Double check that the player is offline if (main.getServer().getPlayer(uuid) != null) return; // Cleanup the player's data diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 9d56c25..03b5306 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -5,6 +5,7 @@ description: ${project.description} author: ${project.organization.name} authors: [${project.organization.name}, danjono] api-version: 1.13 +folia-supported: true loadbefore: - DeluxeCombat From 65e21fa2a4edea0cc83f7631283c79d78268aacc Mon Sep 17 00:00:00 2001 From: yLeoft Date: Wed, 10 Dec 2025 03:03:16 -0300 Subject: [PATCH 2/9] Change HashMaps to ConcurrentHashMaps --- .../inventoryrollback/commands/Commands.java | 4 ++-- .../inventoryrollback/util/TimeZoneUtil.java | 6 +++--- .../inventoryrollback/inventory/SaveInventory.java | 4 ++-- .../reflections/LegacyNBTWrapper.java | 10 +++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/Commands.java b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/Commands.java index 47d02d4..d4bc726 100644 --- a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/Commands.java +++ b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/Commands.java @@ -4,8 +4,8 @@ import com.nuclyon.technicallycoded.inventoryrollback.commands.inventoryrollback.*; import me.danjono.inventoryrollback.config.MessageData; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; @@ -20,7 +20,7 @@ public class Commands implements CommandExecutor, TabCompleter { private String[] backupOptions = new String[] {"all", "player"}; private String[] importOptions = new String[] {"confirm"}; - private HashMap subCommands = new HashMap<>(); + private ConcurrentHashMap subCommands = new ConcurrentHashMap<>(); public Commands(InventoryRollbackPlus mainIn) { this.main = mainIn; diff --git a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/util/TimeZoneUtil.java b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/util/TimeZoneUtil.java index b4fc373..3be2104 100644 --- a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/util/TimeZoneUtil.java +++ b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/util/TimeZoneUtil.java @@ -4,7 +4,7 @@ import java.time.Instant; import java.util.Date; -import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; public class TimeZoneUtil { @@ -125,8 +125,8 @@ public String getTimeZoneFullName(String shortCode) { // INIT DATA public void loadDefaultData() { - HashMap offsetsMapBuilder = new HashMap<>(); - HashMap shortCodeNamesBuilder = new HashMap<>(); + ConcurrentHashMap offsetsMapBuilder = new ConcurrentHashMap<>(); + ConcurrentHashMap shortCodeNamesBuilder = new ConcurrentHashMap<>(); offsetsMapBuilder.put("ACDT", "GMT+10:30"); offsetsMapBuilder.put("ACST", "GMT+09:30"); diff --git a/src/main/java/me/danjono/inventoryrollback/inventory/SaveInventory.java b/src/main/java/me/danjono/inventoryrollback/inventory/SaveInventory.java index 3b625e4..06ed422 100644 --- a/src/main/java/me/danjono/inventoryrollback/inventory/SaveInventory.java +++ b/src/main/java/me/danjono/inventoryrollback/inventory/SaveInventory.java @@ -17,13 +17,13 @@ import org.jetbrains.annotations.Nullable; import java.util.Arrays; -import java.util.HashMap; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; public class SaveInventory { - private static final HashMap rateLimiters = new HashMap<>(); + private static final ConcurrentHashMap rateLimiters = new ConcurrentHashMap<>(); private final InventoryRollbackPlus main; diff --git a/src/main/java/me/danjono/inventoryrollback/reflections/LegacyNBTWrapper.java b/src/main/java/me/danjono/inventoryrollback/reflections/LegacyNBTWrapper.java index 78cb39a..85e22e4 100644 --- a/src/main/java/me/danjono/inventoryrollback/reflections/LegacyNBTWrapper.java +++ b/src/main/java/me/danjono/inventoryrollback/reflections/LegacyNBTWrapper.java @@ -9,7 +9,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; public class LegacyNBTWrapper implements CustomDataItemEditor { @@ -17,8 +17,8 @@ public class LegacyNBTWrapper implements CustomDataItemEditor { private ItemStack item; private final NMSHandler nmsHandler; - private static HashMap, String> getTagElementMethodNames; - private static HashMap, String> setTagElementMethodNames; + private static ConcurrentHashMap, String> getTagElementMethodNames; + private static ConcurrentHashMap, String> setTagElementMethodNames; // 1.8 - 1.20.4 private static String getTagMethodName; @@ -104,8 +104,8 @@ else if (nmsVersion.greaterOrEqThan(BukkitVersion.v1_18_R2)) { } private void resolveNbtTagCompoundReflectionNames(BukkitVersion nmsVersion) { - getTagElementMethodNames = new HashMap<>(); - setTagElementMethodNames = new HashMap<>(); + getTagElementMethodNames = new ConcurrentHashMap<>(); + setTagElementMethodNames = new ConcurrentHashMap<>(); if (nmsVersion.greaterOrEqThan(BukkitVersion.v1_18_R1)) { getTagElementMethodNames.put(Integer.class, "h"); From dca3481226b03b50390db48cd10b8f87c26ab955 Mon Sep 17 00:00:00 2001 From: yLeoft Date: Thu, 7 Aug 2025 19:39:38 -0300 Subject: [PATCH 3/9] Sync fork --- pom.xml | 13 +- .../InventoryRollbackPlus.java | 18 +- .../inventoryrollback/ImportSubCmd.java | 5 +- .../inventoryrollback/RestoreSubCmd.java | 5 +- .../folia/FoliaRunnable.java | 22 ++ .../folia/SchedulerUtils.java | 261 ++++++++++++++++++ .../inventoryrollback/InventoryRollback.java | 3 +- .../inventoryrollback/commands/Commands.java | 9 +- .../inventoryrollback/data/PlayerData.java | 11 +- .../gui/menu/EnderChestBackupMenu.java | 7 +- .../gui/menu/MainInventoryBackupMenu.java | 75 +++-- .../inventory/SaveInventory.java | 3 +- .../inventoryrollback/listeners/ClickGUI.java | 214 +++++++------- .../listeners/EventLogs.java | 3 +- src/main/resources/plugin.yml | 1 + 15 files changed, 468 insertions(+), 182 deletions(-) create mode 100644 src/main/java/com/nuclyon/technicallycoded/inventoryrollback/folia/FoliaRunnable.java create mode 100644 src/main/java/com/nuclyon/technicallycoded/inventoryrollback/folia/SchedulerUtils.java diff --git a/pom.xml b/pom.xml index 908cd3e..89a43b0 100644 --- a/pom.xml +++ b/pom.xml @@ -64,13 +64,6 @@ ${project.basedir}/lib/spigot-1.19.3.jar true --> - - org.spigotmc - spigot - 1.20.5-R0.1-SNAPSHOT - true - provided - org.bstats @@ -85,6 +78,12 @@ 1.0.6 compile + + dev.folia + folia-api + 1.21.4-R0.1-SNAPSHOT + provided + org.jetbrains annotations diff --git a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/InventoryRollbackPlus.java b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/InventoryRollbackPlus.java index 7ca7e06..7f22d50 100644 --- a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/InventoryRollbackPlus.java +++ b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/InventoryRollbackPlus.java @@ -2,6 +2,7 @@ import com.nuclyon.technicallycoded.inventoryrollback.UpdateChecker.UpdateResult; import com.nuclyon.technicallycoded.inventoryrollback.commands.Commands; +import com.nuclyon.technicallycoded.inventoryrollback.folia.SchedulerUtils; import com.nuclyon.technicallycoded.inventoryrollback.util.TimeZoneUtil; import com.nuclyon.technicallycoded.inventoryrollback.util.test.SelfTestSerialization; import com.tcoded.lightlibs.bukkitversion.BukkitVersion; @@ -30,6 +31,7 @@ public class InventoryRollbackPlus extends InventoryRollback { private static InventoryRollbackPlus instancePlus; + public static boolean usingFolia = false; private TimeZoneUtil timeZoneUtil = null; @@ -42,6 +44,16 @@ public static InventoryRollbackPlus getInstance() { return instancePlus; } + @Override + public void onLoad() { + try { + Class.forName("io.papermc.paper.threadedregions.scheduler.RegionScheduler"); + usingFolia = true; + } catch (ClassNotFoundException e) { + usingFolia = false; + } + } + @Override public void onEnable() { instancePlus = this; @@ -89,7 +101,7 @@ public void onEnable() { getServer().getPluginManager().registerEvents(new ClickGUI(), this); getServer().getPluginManager().registerEvents(new EventLogs(), this); // Run after all plugin enable - getServer().getScheduler().runTask(this, EventLogs::patchLowestHandlers); + SchedulerUtils.runTask(null, EventLogs::patchLowestHandlers); // PaperLib if (!PaperLib.isPaper()) { @@ -124,7 +136,7 @@ public void onDisable() { HandlerList.unregisterAll(this); // Cancel tasks - this.getServer().getScheduler().cancelTasks(this); + if(!usingFolia) this.getServer().getScheduler().cancelTasks(this); // Clear instance references instancePlus = null; @@ -149,7 +161,7 @@ public boolean isCompatibleCb(String cbVersion) { } public void checkUpdate() { - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), () -> { + SchedulerUtils.runTaskAsynchronously(() -> { InventoryRollbackPlus.getInstance().getConsoleSender().sendMessage(MessageData.getPluginPrefix() + "Checking for updates..."); final UpdateResult result = new UpdateChecker(getInstance(), 85811).getResult(); diff --git a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/inventoryrollback/ImportSubCmd.java b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/inventoryrollback/ImportSubCmd.java index af926d9..aac06d8 100644 --- a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/inventoryrollback/ImportSubCmd.java +++ b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/inventoryrollback/ImportSubCmd.java @@ -2,6 +2,7 @@ import com.nuclyon.technicallycoded.inventoryrollback.InventoryRollbackPlus; import com.nuclyon.technicallycoded.inventoryrollback.commands.IRPCommand; +import com.nuclyon.technicallycoded.inventoryrollback.folia.SchedulerUtils; import com.nuclyon.technicallycoded.inventoryrollback.util.LegacyBackupConversionUtil; import me.danjono.inventoryrollback.config.MessageData; import org.bukkit.Bukkit; @@ -32,7 +33,7 @@ public void onCommand(CommandSender sender, Command cmd, String label, String[] suggestConfirm.set(true); // Reset suggestion availability after 10 seconds - this.main.getServer().getScheduler().runTaskLaterAsynchronously(this.main, () -> { + SchedulerUtils.runTaskLaterAsynchronously(null, () -> { suggestConfirm.set(false); }, 10 * 20); @@ -40,7 +41,7 @@ public void onCommand(CommandSender sender, Command cmd, String label, String[] } // Execute import - Bukkit.getScheduler().runTaskAsynchronously(main, LegacyBackupConversionUtil::convertOldBackupData); + SchedulerUtils.runTaskAsynchronously(LegacyBackupConversionUtil::convertOldBackupData); // Reset suggestion to not visible suggestConfirm.set(false); diff --git a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/inventoryrollback/RestoreSubCmd.java b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/inventoryrollback/RestoreSubCmd.java index d5e7e23..cde277d 100644 --- a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/inventoryrollback/RestoreSubCmd.java +++ b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/inventoryrollback/RestoreSubCmd.java @@ -2,6 +2,7 @@ import com.nuclyon.technicallycoded.inventoryrollback.InventoryRollbackPlus; import com.nuclyon.technicallycoded.inventoryrollback.commands.IRPCommand; +import com.nuclyon.technicallycoded.inventoryrollback.folia.SchedulerUtils; import me.danjono.inventoryrollback.InventoryRollback; import me.danjono.inventoryrollback.config.ConfigData; import me.danjono.inventoryrollback.config.MessageData; @@ -90,14 +91,14 @@ private void openMainMenu(Player staff) { MainMenu menu = new MainMenu(staff, 1); staff.openInventory(menu.getInventory()); - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), menu::getMainMenu); + SchedulerUtils.runTaskAsynchronously(menu::getMainMenu); } private void openPlayerMenu(Player staff, OfflinePlayer offlinePlayer) { PlayerMenu menu = new PlayerMenu(staff, offlinePlayer); staff.openInventory(menu.getInventory()); - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), menu::getPlayerMenu); + SchedulerUtils.runTaskAsynchronously(menu::getPlayerMenu); } } diff --git a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/folia/FoliaRunnable.java b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/folia/FoliaRunnable.java new file mode 100644 index 0000000..b467021 --- /dev/null +++ b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/folia/FoliaRunnable.java @@ -0,0 +1,22 @@ +package com.nuclyon.technicallycoded.inventoryrollback.folia; + +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; +import org.bukkit.scheduler.BukkitRunnable; + +public class FoliaRunnable extends BukkitRunnable { + + private ScheduledTask foliaTask; + + @Override + public synchronized void cancel() throws IllegalStateException { + if(foliaTask != null) foliaTask.cancel(); + } + + @Override + public void run() { + } + + public void setScheduledTask(ScheduledTask task) { + this.foliaTask = task; + } +} diff --git a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/folia/SchedulerUtils.java b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/folia/SchedulerUtils.java new file mode 100644 index 0000000..56932da --- /dev/null +++ b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/folia/SchedulerUtils.java @@ -0,0 +1,261 @@ +package com.nuclyon.technicallycoded.inventoryrollback.folia; + +import com.nuclyon.technicallycoded.inventoryrollback.InventoryRollbackPlus; +import io.papermc.paper.threadedregions.scheduler.GlobalRegionScheduler; +import io.papermc.paper.threadedregions.scheduler.RegionScheduler; +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Method; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; + +import static com.nuclyon.technicallycoded.inventoryrollback.InventoryRollbackPlus.usingFolia; + +/** + * Utility class for scheduling tasks in a Paper/Folia server. + */ +public abstract class SchedulerUtils { + + /** + * Schedules a task to run later on the main server thread. + * @param loc The location where the task should run, or null for the main thread. + * @param task The task to run. + * @param delay The delay in ticks before the task runs. + */ + public static void runTaskLater(@Nullable Location loc, @NotNull Runnable task, long delay) { + JavaPlugin plugin = InventoryRollbackPlus.getInstance(); + if (usingFolia) { + try { + if (loc != null) { + Method getRegionScheduler = plugin.getServer().getClass().getMethod("getRegionScheduler"); + RegionScheduler regionScheduler = (RegionScheduler) getRegionScheduler.invoke(plugin.getServer()); + regionScheduler.runDelayed( + plugin, + loc, + (ScheduledTask scheduledTask) -> task.run(), + delay + ); + } else { + Method getGlobalScheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler"); + GlobalRegionScheduler globalScheduler = (GlobalRegionScheduler) getGlobalScheduler.invoke(plugin.getServer()); + globalScheduler.runDelayed( + plugin, + (ScheduledTask scheduledTask) -> task.run(), + delay + ); + } + return; + } catch (Exception e) { + e.printStackTrace(); + } + } + plugin.getServer().getScheduler().runTaskLater(plugin, task, delay); + } + + /** + * Schedules a task to run later asynchronously on the main server thread. + * @param loc The location where the task should run, or null for the main thread. + * @param task The task to run. + * @param delay The delay in ticks before the task runs. + */ + public static void runTaskLaterAsynchronously(@Nullable Location loc, @NotNull Runnable task, long delay) { + JavaPlugin plugin = InventoryRollbackPlus.getInstance(); + if (usingFolia) { + try { + if (loc != null) { + Method getRegionScheduler = plugin.getServer().getClass().getMethod("getRegionScheduler"); + RegionScheduler regionScheduler = (RegionScheduler) getRegionScheduler.invoke(plugin.getServer()); + regionScheduler.runDelayed( + plugin, + loc, + (ScheduledTask scheduledTask) -> task.run(), + delay + ); + } else { + Method getGlobalScheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler"); + GlobalRegionScheduler globalScheduler = (GlobalRegionScheduler) getGlobalScheduler.invoke(plugin.getServer()); + globalScheduler.runDelayed( + plugin, + (ScheduledTask scheduledTask) -> task.run(), + delay + ); + } + return; + } catch (Exception e) { + e.printStackTrace(); + } + } + plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, task, delay); + } + + /** + * Schedules a task to run repeatedly on the main server thread. + * @param loc The location where the task should run, or null for the main thread. + * @param runnable The BukkitRunnable to run. + * @param delay The delay in ticks before the task runs. + * @param period The period in ticks between subsequent runs of the task. + */ + public static void runTaskTimer(@Nullable Location loc, @NotNull FoliaRunnable runnable, long delay, long period) { + JavaPlugin plugin = InventoryRollbackPlus.getInstance(); + if (usingFolia) { + try { + ScheduledTask task; + if (loc != null) { + Method getRegionScheduler = plugin.getServer().getClass().getMethod("getRegionScheduler"); + RegionScheduler regionScheduler = (RegionScheduler) getRegionScheduler.invoke(plugin.getServer()); + task = regionScheduler.runAtFixedRate( + plugin, + loc, + (ScheduledTask t) -> runnable.run(), + delay, + period + ); + } else { + Method getGlobalScheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler"); + GlobalRegionScheduler globalScheduler = (GlobalRegionScheduler) getGlobalScheduler.invoke(plugin.getServer()); + task = globalScheduler.runAtFixedRate( + plugin, + (ScheduledTask t) -> runnable.run(), + delay, + period + ); + } + runnable.setScheduledTask(task); + } catch (Exception e) { + e.printStackTrace(); + } + return; + } + runnable.runTaskTimer(plugin, delay, period); + } + + /** + * Schedules a task to run repeatedly on the main server thread asynchronously. + * @param runnable The FoliaRunnable to run. + * @param delay The delay in ticks before the task runs. + * @param period The period in ticks between subsequent runs of the task. + */ + public static void runTaskTimerAsynchronously(@NotNull FoliaRunnable runnable, long delay, long period) { + JavaPlugin plugin = InventoryRollbackPlus.getInstance(); + if (usingFolia) { + try { + Method getGlobalScheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler"); + GlobalRegionScheduler globalScheduler = (GlobalRegionScheduler) getGlobalScheduler.invoke(plugin.getServer()); + class AsyncRepeatingTask { + private ScheduledTask task; + void start(long initialDelay) { + task = globalScheduler.runDelayed(plugin, (ScheduledTask t) -> { + runnable.run(); + start(period); + }, initialDelay); + runnable.setScheduledTask(task); + } + } + new AsyncRepeatingTask().start(delay); + } catch (Exception e) { + e.printStackTrace(); + } + return; + } + runnable.runTaskTimerAsynchronously(plugin, delay, period); + } + + /** + * Runs a task asynchronously on the main server thread. + * @param task The task to run. + */ + public static void runTaskAsynchronously(@NotNull Runnable task) { + JavaPlugin plugin = InventoryRollbackPlus.getInstance(); + if (usingFolia) { + try { + Method getGlobalScheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler"); + GlobalRegionScheduler globalScheduler = (GlobalRegionScheduler) getGlobalScheduler.invoke(plugin.getServer()); + globalScheduler.execute(plugin, task); + return; + } catch (Exception e) { + e.printStackTrace(); + } + } + plugin.getServer().getScheduler().runTaskAsynchronously(plugin, task); + } + + /** + * Runs a task on the main server thread at a specific location or globally. + * @param loc The location where the task should run, or null for the main thread. + * @param task The task to run. + */ + public static void runTask(@Nullable Location loc, @NotNull Runnable task) { + JavaPlugin plugin = InventoryRollbackPlus.getInstance(); + if (usingFolia) { + try { + if (loc != null) { + Method getRegionScheduler = plugin.getServer().getClass().getMethod("getRegionScheduler"); + RegionScheduler regionScheduler = (RegionScheduler) getRegionScheduler.invoke(plugin.getServer()); + regionScheduler.execute(plugin, loc, task); + } else { + Method getGlobalScheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler"); + GlobalRegionScheduler globalScheduler = (GlobalRegionScheduler) getGlobalScheduler.invoke(plugin.getServer()); + globalScheduler.execute(plugin, task); + } + return; + } catch (Exception e) { + e.printStackTrace(); + } + } + plugin.getServer().getScheduler().runTask(plugin, task); + } + + public static CompletableFuture callSyncMethod(@Nullable Location loc, @NotNull Callable task) { + JavaPlugin plugin = InventoryRollbackPlus.getInstance(); + if (usingFolia) { + CompletableFuture future = new CompletableFuture<>(); + try { + if (loc != null) { + Method getRegionScheduler = plugin.getServer().getClass().getMethod("getRegionScheduler"); + RegionScheduler regionScheduler = (RegionScheduler) getRegionScheduler.invoke(plugin.getServer()); + regionScheduler.execute(plugin, loc, () -> { + try { + future.complete(task.call()); + } catch (Exception e) { + future.completeExceptionally(e); + } + }); + } else { + Method getGlobalScheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler"); + GlobalRegionScheduler globalScheduler = (GlobalRegionScheduler) getGlobalScheduler.invoke(plugin.getServer()); + globalScheduler.execute(plugin, () -> { + try { + future.complete(task.call()); + } catch (Exception e) { + future.completeExceptionally(e); + } + }); + } + } catch (Exception e) { + future.completeExceptionally(e); + } + return future; + } + CompletableFuture cf = new CompletableFuture<>(); + try { + Future bukkitFuture = plugin.getServer().getScheduler().callSyncMethod(plugin, task); + plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> { + try { + T result = bukkitFuture.get(); + cf.complete(result); + } catch (Throwable ex) { + cf.completeExceptionally(ex); + } + }); + } catch (Throwable e) { + cf.completeExceptionally(e); + } + return cf; + } +} diff --git a/src/main/java/me/danjono/inventoryrollback/InventoryRollback.java b/src/main/java/me/danjono/inventoryrollback/InventoryRollback.java index e4e98b7..63b5caf 100644 --- a/src/main/java/me/danjono/inventoryrollback/InventoryRollback.java +++ b/src/main/java/me/danjono/inventoryrollback/InventoryRollback.java @@ -1,6 +1,7 @@ package me.danjono.inventoryrollback; import com.nuclyon.technicallycoded.inventoryrollback.InventoryRollbackPlus; +import com.nuclyon.technicallycoded.inventoryrollback.folia.SchedulerUtils; import me.danjono.inventoryrollback.UpdateChecker.UpdateResult; import me.danjono.inventoryrollback.commands.Commands; import me.danjono.inventoryrollback.config.ConfigData; @@ -165,7 +166,7 @@ public void bStats() { } public void checkUpdate() { - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), () -> { + SchedulerUtils.runTaskAsynchronously(() -> { logger.log(Level.INFO, MessageData.getPluginPrefix() + "Checking for updates..."); final UpdateResult result = new UpdateChecker(getInstance(), 85811).getResult(); diff --git a/src/main/java/me/danjono/inventoryrollback/commands/Commands.java b/src/main/java/me/danjono/inventoryrollback/commands/Commands.java index 591ce15..e6368cb 100644 --- a/src/main/java/me/danjono/inventoryrollback/commands/Commands.java +++ b/src/main/java/me/danjono/inventoryrollback/commands/Commands.java @@ -1,5 +1,6 @@ package me.danjono.inventoryrollback.commands; +import com.nuclyon.technicallycoded.inventoryrollback.folia.SchedulerUtils; import me.danjono.inventoryrollback.InventoryRollback; import me.danjono.inventoryrollback.config.ConfigData; import me.danjono.inventoryrollback.config.MessageData; @@ -114,14 +115,14 @@ private void openMainMenu(Player staff) { MainMenu menu = new MainMenu(staff, 1); staff.openInventory(menu.getInventory()); - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), menu::getMainMenu); + SchedulerUtils.runTaskAsynchronously(menu::getMainMenu); } private void openPlayerMenu(Player staff, OfflinePlayer offlinePlayer) { PlayerMenu menu = new PlayerMenu(staff, offlinePlayer); staff.openInventory(menu.getInventory()); - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), menu::getPlayerMenu); + SchedulerUtils.runTaskAsynchronously(menu::getPlayerMenu); } private void forceBackupCommand(CommandSender sender, String[] args) { @@ -220,13 +221,13 @@ private void versionCommand(CommandSender sender) { private void convertMySQL(CommandSender sender) { if (sender instanceof ConsoleCommandSender && sender.isOp()) { - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), MySQL::convertYAMLToMySQL); + SchedulerUtils.runTaskAsynchronously(MySQL::convertYAMLToMySQL); } } private void convertYAML(CommandSender sender) { if (sender instanceof ConsoleCommandSender && sender.isOp()) { - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), YAML::convertOldBackupData); + SchedulerUtils.runTaskAsynchronously(YAML::convertOldBackupData); } } diff --git a/src/main/java/me/danjono/inventoryrollback/data/PlayerData.java b/src/main/java/me/danjono/inventoryrollback/data/PlayerData.java index dd717b7..54bcb6c 100644 --- a/src/main/java/me/danjono/inventoryrollback/data/PlayerData.java +++ b/src/main/java/me/danjono/inventoryrollback/data/PlayerData.java @@ -9,6 +9,8 @@ import java.util.concurrent.CompletableFuture; import com.nuclyon.technicallycoded.inventoryrollback.InventoryRollbackPlus; +import com.nuclyon.technicallycoded.inventoryrollback.folia.FoliaRunnable; +import com.nuclyon.technicallycoded.inventoryrollback.folia.SchedulerUtils; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.inventory.ItemStack; @@ -16,7 +18,6 @@ import me.danjono.inventoryrollback.InventoryRollback; import me.danjono.inventoryrollback.config.ConfigData; import me.danjono.inventoryrollback.config.ConfigData.SaveType; -import org.bukkit.scheduler.BukkitRunnable; public class PlayerData { @@ -134,7 +135,7 @@ public CompletableFuture purgeExcessSaves(boolean shouldSaveAsync) { }; InventoryRollbackPlus instance = InventoryRollbackPlus.getInstance(); - if (saveAsync) instance.getServer().getScheduler().runTaskAsynchronously(instance, purgeTask); + if (saveAsync) SchedulerUtils.runTaskAsynchronously(purgeTask); else purgeTask.run(); return future; @@ -263,7 +264,7 @@ public void getRollbackMenuData() { public CompletableFuture getAllBackupData() { CompletableFuture future = new CompletableFuture<>(); if (ConfigData.getSaveType() == SaveType.MYSQL) { - new BukkitRunnable() { + SchedulerUtils.runTaskAsynchronously(new FoliaRunnable() { @Override public void run() { try { @@ -273,7 +274,7 @@ public void run() { } future.complete(null); } - }.runTaskAsynchronously(InventoryRollbackPlus.getInstance()); + }); } return future; } @@ -437,7 +438,7 @@ public void saveData(boolean shouldSaveAsync) { } }; - if (saveAsync) Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(),saveDataTask); + if (saveAsync) SchedulerUtils.runTaskAsynchronously(saveDataTask); else saveDataTask.run(); } diff --git a/src/main/java/me/danjono/inventoryrollback/gui/menu/EnderChestBackupMenu.java b/src/main/java/me/danjono/inventoryrollback/gui/menu/EnderChestBackupMenu.java index 7b6f9bb..abf3149 100644 --- a/src/main/java/me/danjono/inventoryrollback/gui/menu/EnderChestBackupMenu.java +++ b/src/main/java/me/danjono/inventoryrollback/gui/menu/EnderChestBackupMenu.java @@ -1,6 +1,8 @@ package me.danjono.inventoryrollback.gui.menu; import com.nuclyon.technicallycoded.inventoryrollback.InventoryRollbackPlus; +import com.nuclyon.technicallycoded.inventoryrollback.folia.FoliaRunnable; +import com.nuclyon.technicallycoded.inventoryrollback.folia.SchedulerUtils; import me.danjono.inventoryrollback.config.ConfigData; import me.danjono.inventoryrollback.config.MessageData; import me.danjono.inventoryrollback.data.LogType; @@ -11,7 +13,6 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; -import org.bukkit.scheduler.BukkitRunnable; import java.util.ArrayList; import java.util.List; @@ -85,7 +86,7 @@ public void showEnderChestItems() { try { // Add items, 5 per tick - new BukkitRunnable() { + SchedulerUtils.runTaskTimer(null, new FoliaRunnable() { int invPosition = 0; int itemPos = (pageNumber - 1) * 27; @@ -111,7 +112,7 @@ public void run() { itemPos++; } } - }.runTaskTimer(InventoryRollbackPlus.getInstance(), 0, 1); + }, 1, 1); } catch (NullPointerException e) { staff.sendMessage(MessageData.getPluginPrefix() + MessageData.getErrorInventory()); return; diff --git a/src/main/java/me/danjono/inventoryrollback/gui/menu/MainInventoryBackupMenu.java b/src/main/java/me/danjono/inventoryrollback/gui/menu/MainInventoryBackupMenu.java index 033b774..06f5c38 100644 --- a/src/main/java/me/danjono/inventoryrollback/gui/menu/MainInventoryBackupMenu.java +++ b/src/main/java/me/danjono/inventoryrollback/gui/menu/MainInventoryBackupMenu.java @@ -1,6 +1,8 @@ package me.danjono.inventoryrollback.gui.menu; import com.nuclyon.technicallycoded.inventoryrollback.InventoryRollbackPlus; +import com.nuclyon.technicallycoded.inventoryrollback.folia.FoliaRunnable; +import com.nuclyon.technicallycoded.inventoryrollback.folia.SchedulerUtils; import com.tcoded.lightlibs.bukkitversion.MCVersion; import me.danjono.inventoryrollback.config.ConfigData; import me.danjono.inventoryrollback.config.MessageData; @@ -12,11 +14,11 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; -import org.bukkit.scheduler.BukkitRunnable; import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; public class MainInventoryBackupMenu { @@ -105,12 +107,12 @@ public void showBackupItems() { assert !Bukkit.isPrimaryThread(); int item = 0; - int position = 0; + AtomicInteger position = new AtomicInteger(); //If the backup file is invalid it will return null, we want to catch it here try { // Add items, 5 per tick - new BukkitRunnable() { + SchedulerUtils.runTaskTimer(null, new FoliaRunnable() { boolean processedHotbar; int menuPos = 27; @@ -143,7 +145,7 @@ public void run() { backupPos++; } } - }.runTaskTimer(main, 0, 1); + }, 1, 1); } catch (Exception ex) { ex.printStackTrace(); staff.sendMessage(MessageData.getPluginPrefix() + MessageData.getErrorInventory()); @@ -151,45 +153,40 @@ public void run() { } item = 36; - position = 44; + position.set(44); //Add armor if (armor != null && armor.length > 0) { - try { - for (int i = 0; i < armor.length; i++) { - // Place item safely - final int finalPos = position; - final int finalItem = i; - Future placeItemFuture = main.getServer().getScheduler().callSyncMethod(main, - () -> { - inventory.setItem(finalPos, armor[finalItem]); - return null; - }); - placeItemFuture.get(); - position--; - } - } catch (ExecutionException | InterruptedException ex) { - ex.printStackTrace(); - } + for (int i = 0; i < armor.length; i++) { + // Place item safely + final int finalPos = position.get(); + final int finalItem = i; + SchedulerUtils.callSyncMethod(null, () -> { + inventory.setItem(finalPos, armor[finalItem]); + return null; + }).whenComplete((res, ex) -> { + if (ex != null) ex.printStackTrace(); + else position.getAndDecrement(); + }); + } } else { - try { - for (; item < mainInvLen; item++) { - if (mainInventory[item] != null) { - // Place item safely - final int finalPos = position; - final int finalItem = item; - Future placeItemFuture = main.getServer().getScheduler().callSyncMethod(main, - () -> { - inventory.setItem(finalPos, mainInventory[finalItem]); - return null; - }); - placeItemFuture.get(); - position--; - } - } - } catch (ExecutionException | InterruptedException ex) { - ex.printStackTrace(); - } + for (; item < mainInvLen; item++) { + if (mainInventory[item] != null) { + // Place item safely + final int finalPos = position.get(); + final int finalItem = item; + SchedulerUtils.callSyncMethod(null, () -> { + inventory.setItem(finalPos, mainInventory[finalItem]); + return null; + }).whenComplete((res, ex) -> { + if (ex != null) { + ex.printStackTrace(); + } else { + position.getAndDecrement(); + } + }); + } + } } } diff --git a/src/main/java/me/danjono/inventoryrollback/inventory/SaveInventory.java b/src/main/java/me/danjono/inventoryrollback/inventory/SaveInventory.java index 4da41e2..d597d72 100644 --- a/src/main/java/me/danjono/inventoryrollback/inventory/SaveInventory.java +++ b/src/main/java/me/danjono/inventoryrollback/inventory/SaveInventory.java @@ -1,6 +1,7 @@ package me.danjono.inventoryrollback.inventory; import com.nuclyon.technicallycoded.inventoryrollback.InventoryRollbackPlus; +import com.nuclyon.technicallycoded.inventoryrollback.folia.SchedulerUtils; import com.nuclyon.technicallycoded.inventoryrollback.util.UserLogRateLimiter; import com.nuclyon.technicallycoded.inventoryrollback.util.serialization.ItemStackSerialization; import com.tcoded.lightlibs.bukkitversion.BukkitVersion; @@ -98,7 +99,7 @@ public void save(PlayerDataSnapshot snapshot, boolean async) { purgeTask.thenRun(() -> data.saveData(saveAsync)); }; - if (saveAsync) main.getServer().getScheduler().runTaskAsynchronously(main, saveTask); + if (saveAsync) SchedulerUtils.runTaskAsynchronously(saveTask); else saveTask.run(); } diff --git a/src/main/java/me/danjono/inventoryrollback/listeners/ClickGUI.java b/src/main/java/me/danjono/inventoryrollback/listeners/ClickGUI.java index fc3c714..93ffed5 100644 --- a/src/main/java/me/danjono/inventoryrollback/listeners/ClickGUI.java +++ b/src/main/java/me/danjono/inventoryrollback/listeners/ClickGUI.java @@ -2,10 +2,11 @@ import com.nuclyon.technicallycoded.inventoryrollback.InventoryRollbackPlus; import com.nuclyon.technicallycoded.inventoryrollback.customdata.CustomDataItemEditor; +import com.nuclyon.technicallycoded.inventoryrollback.folia.FoliaRunnable; +import com.nuclyon.technicallycoded.inventoryrollback.folia.SchedulerUtils; import com.tcoded.lightlibs.bukkitversion.BukkitVersion; import com.tcoded.lightlibs.bukkitversion.MCVersion; import io.papermc.lib.PaperLib; -import me.danjono.inventoryrollback.InventoryRollback; import me.danjono.inventoryrollback.config.ConfigData; import me.danjono.inventoryrollback.config.MessageData; import me.danjono.inventoryrollback.config.SoundData; @@ -22,16 +23,13 @@ import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryDragEvent; -import org.bukkit.inventory.InventoryView; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.BlockStateMeta; import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.scheduler.BukkitRunnable; import java.util.Arrays; import java.util.UUID; import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; public class ClickGUI implements Listener { @@ -139,7 +137,7 @@ private void mainMenu(InventoryClickEvent e, Player staff, ItemStack icon) { MainMenu menu = new MainMenu(staff, page); staff.openInventory(menu.getInventory()); - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), menu::getMainMenu); + SchedulerUtils.runTaskAsynchronously(menu::getMainMenu); } //Clicked a player head else { @@ -147,7 +145,7 @@ private void mainMenu(InventoryClickEvent e, Player staff, ItemStack icon) { PlayerMenu menu = new PlayerMenu(staff, offlinePlayer); staff.openInventory(menu.getInventory()); - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), menu::getPlayerMenu); + SchedulerUtils.runTaskAsynchronously(menu::getPlayerMenu); } } else { if (e.getRawSlot() >= e.getInventory().getSize() && !e.isShiftClick()) { @@ -173,13 +171,13 @@ private void playerMenu(InventoryClickEvent e, Player staff, ItemStack icon) { MainMenu menu = new MainMenu(staff, 1); staff.openInventory(menu.getInventory()); - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), menu::getMainMenu); + SchedulerUtils.runTaskAsynchronously(menu::getMainMenu); } else { LogType logType = LogType.valueOf(nbt.getString("logType")); RollbackListMenu menu = new RollbackListMenu(staff, offlinePlayer, logType, 1); staff.openInventory(menu.getInventory()); - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), menu::showBackups); + SchedulerUtils.runTaskAsynchronously(menu::showBackups); } } else { @@ -205,7 +203,7 @@ private void rollbackMenu(InventoryClickEvent e, Player staff, ItemStack icon) { String location = nbt.getString("location"); // Run all data retrieval operations async to avoid tick lag - new BukkitRunnable() { + SchedulerUtils.runTaskAsynchronously(new FoliaRunnable() { @Override public void run() { // Init from MySQL or, if YAML, init & load config file @@ -223,20 +221,21 @@ public void run() { // Create inventory MainInventoryBackupMenu menu = new MainInventoryBackupMenu(staff, data, location); - // Display inventory to player - Future inventoryViewFuture = - main.getServer().getScheduler().callSyncMethod(main, - () -> staff.openInventory(menu.getInventory())); - //If the backup file is invalid it will return null, we want to catch it here - try { - inventoryViewFuture.get(); - // Start placing items in the inventory async + SchedulerUtils.callSyncMethod( + e.getWhoClicked().getLocation(), + () -> staff.openInventory(menu.getInventory()) + ).whenComplete((view, ex) -> { + if (ex != null) { + ex.printStackTrace(); + return; + } + if (view == null) { + return; + } menu.showBackupItems(); - } catch (NullPointerException | ExecutionException | InterruptedException ex) { - ex.printStackTrace(); - } + }); } - }.runTaskAsynchronously(main); + }); } //Player has selected a page icon @@ -249,13 +248,13 @@ else if (icon.getType().equals(Buttons.getPageSelectorIcon())) { PlayerMenu menu = new PlayerMenu(staff, player); staff.openInventory(menu.getInventory()); - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), menu::getPlayerMenu); + SchedulerUtils.runTaskAsynchronously(menu::getPlayerMenu); } else { LogType logType = LogType.valueOf(nbt.getString("logType")); RollbackListMenu menu = new RollbackListMenu(staff, player, logType, page); staff.openInventory(menu.getInventory()); - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), menu::showBackups); + SchedulerUtils.runTaskAsynchronously(menu::showBackups); } } } else { @@ -283,7 +282,7 @@ private void mainBackupMenu(InventoryClickEvent e, Player staff, ItemStack icon) RollbackListMenu menu = new RollbackListMenu(staff, offlinePlayer, logType, 1); staff.openInventory(menu.getInventory()); - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), menu::showBackups); + SchedulerUtils.runTaskAsynchronously(menu::showBackups); } //Click on page selector button to go back to rollback menu @@ -294,7 +293,7 @@ else if (e.getRawSlot() == MainInventoryBackupMenu.GIVE_SHULKERS_BUTTON_SLOT) { return; } - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), () -> { + SchedulerUtils.runTaskAsynchronously(() -> { // Unsupported on older versions if (main.getVersion().lessThan(MCVersion.v1_11.toBukkitVersion())) { return; @@ -361,7 +360,7 @@ else if (e.getRawSlot() == MainInventoryBackupMenu.GIVE_SHULKERS_BUTTON_SLOT) { } } - Bukkit.getScheduler().runTask(main, t -> { + SchedulerUtils.runTask(staff.getLocation(), () -> { staff.getInventory().addItem(firstShulker, secondShulker); staff.closeInventory(); }); @@ -379,7 +378,7 @@ else if (icon.getType().equals(Buttons.getRestoreAllInventoryIcon())) { if (offlinePlayer.isOnline()) { Player player = (Player) offlinePlayer; - new BukkitRunnable() { + SchedulerUtils.runTaskAsynchronously(new FoliaRunnable() { @Override public void run() { // Init from MySQL or, if YAML, init & load config file @@ -398,35 +397,36 @@ public void run() { ItemStack[] armour = data.getArmour(); // Place inventory items sync (compressed code) - Future futureSetInv = main.getServer().getScheduler().callSyncMethod(main, - () -> { player.getInventory().setContents(inventory); return null; }); - try { futureSetInv.get(); } - catch (ExecutionException | InterruptedException ex) { ex.printStackTrace(); } - - // If 1.8, place armor contents separately - if (main.getVersion().lessOrEqThan(BukkitVersion.v1_8_R3)) { - // Place items sync (compressed code) - Future futureSetArmor = main.getServer().getScheduler().callSyncMethod(main, - () -> { player.getInventory().setArmorContents(armour); return null; }); - try { futureSetArmor.get(); } - catch (ExecutionException | InterruptedException ex) { ex.printStackTrace(); } - } - - // Play sound effect is enabled - if (SoundData.isInventoryRestoreEnabled()) { - // Play sound sync (compressed code) - Future futurePlaySound = main.getServer().getScheduler().callSyncMethod(main, - () -> { player.playSound(player.getLocation(), SoundData.getInventoryRestored(), 1, 1); return null; }); - try { futurePlaySound.get(); } - catch (ExecutionException | InterruptedException ex) { ex.printStackTrace(); } - } - - // Send player & staff feedback - player.sendMessage(MessageData.getPluginPrefix() + MessageData.getMainInventoryRestoredPlayer(staff.getName())); - if (!staff.getUniqueId().equals(player.getUniqueId())) - staff.sendMessage(MessageData.getPluginPrefix() + MessageData.getMainInventoryRestored(offlinePlayer.getName())); + SchedulerUtils.callSyncMethod(e.getWhoClicked().getLocation(), () -> { + player.getInventory().setContents(inventory); + return null; + }).whenComplete((res, ex) -> { + if (ex != null) ex.printStackTrace(); + else { + if (main.getVersion().lessOrEqThan(BukkitVersion.v1_8_R3)) { + SchedulerUtils.callSyncMethod(e.getWhoClicked().getLocation(), () -> { + player.getInventory().setArmorContents(armour); + return null; + }).whenComplete((r, e) -> { + if (e != null) e.printStackTrace(); + }); + } + if (SoundData.isInventoryRestoreEnabled()) { + SchedulerUtils.callSyncMethod(e.getWhoClicked().getLocation(), () -> { + player.playSound(player.getLocation(), SoundData.getInventoryRestored(), 1, 1); + return null; + }).whenComplete((r, e) -> { + if (e != null) e.printStackTrace(); + }); + } + + player.sendMessage(MessageData.getPluginPrefix() + MessageData.getMainInventoryRestoredPlayer(staff.getName())); + if (!staff.getUniqueId().equals(player.getUniqueId())) + staff.sendMessage(MessageData.getPluginPrefix() + MessageData.getMainInventoryRestored(offlinePlayer.getName())); + } + }); } - }.runTaskAsynchronously(main); + }); } else { staff.sendMessage(MessageData.getPluginPrefix() + MessageData.getMainInventoryNotOnline(offlinePlayer.getName())); @@ -457,7 +457,7 @@ else if (icon.getType().equals(Buttons.getTeleportLocationIcon())) { .add(0.5, 0.5, 0.5); // Teleport player on a slight delay to block the teleport icon glitching out into the player inventory - Bukkit.getScheduler().runTaskLater(InventoryRollback.getInstance(), () -> { + SchedulerUtils.runTaskLater(e.getWhoClicked().getLocation(), () -> { e.getWhoClicked().closeInventory(); PaperLib.teleportAsync(staff,loc).thenAccept((result) -> { if (SoundData.isTeleportEnabled()) @@ -471,7 +471,7 @@ else if (icon.getType().equals(Buttons.getTeleportLocationIcon())) { // Clicked icon to restore backup players ender chest else if (icon.getType().equals(Buttons.getEnderChestIcon())) { - new BukkitRunnable() { + SchedulerUtils.runTaskAsynchronously(new FoliaRunnable() { @Override public void run() { // Init from MySQL or, if YAML, init & load config file @@ -490,21 +490,17 @@ public void run() { EnderChestBackupMenu menu = new EnderChestBackupMenu(staff, data, 1); // Open inventory sync (compressed code) - Future futureOpenInv = main.getServer().getScheduler().callSyncMethod(main, - () -> { - staff.openInventory(menu.getInventory()); - return null; - }); - try { - futureOpenInv.get(); - } catch (ExecutionException | InterruptedException ex) { - ex.printStackTrace(); - } - - // Place items async - menu.showEnderChestItems(); + SchedulerUtils.callSyncMethod(e.getWhoClicked().getLocation(), () -> { + staff.openInventory(menu.getInventory()); + return null; + }).whenComplete((res, ex) -> { + if (ex != null) ex.printStackTrace(); + else { + menu.showEnderChestItems(); + } + }); } - }.runTaskAsynchronously(this.main); + }); } // Clicked icon to restore backup players health @@ -632,7 +628,7 @@ private void enderChestBackupMenu(InventoryClickEvent e, Player staff, ItemStack if (page == 0) { // Run all data retrieval operations async to avoid tick lag - new BukkitRunnable() { + SchedulerUtils.runTaskAsynchronously(new FoliaRunnable() { @Override public void run() { // Init from MySQL or, if YAML, init & load config file @@ -654,22 +650,24 @@ public void run() { MainInventoryBackupMenu menu = new MainInventoryBackupMenu(staff, data, location); // Display inventory to player - Future inventoryViewFuture = main.getServer().getScheduler().callSyncMethod(main, - () -> staff.openInventory(menu.getInventory())); - //If the backup file is invalid it will return null, we want to catch it here - try { - inventoryViewFuture.get(); - // Start placing items in the inventory async - menu.showBackupItems(); - } catch (NullPointerException | ExecutionException | InterruptedException ex) { - ex.printStackTrace(); - } + SchedulerUtils.callSyncMethod(e.getWhoClicked().getLocation(), () -> staff.openInventory(menu.getInventory())) + .whenComplete((view, ex) -> { + if (ex != null) { + ex.printStackTrace(); + return; + } + if (view == null) { + // backup inválido, tratar aqui se quiser + return; + } + menu.showBackupItems(); + }); } - }.runTaskAsynchronously(main); + }); } else { - new BukkitRunnable() { + SchedulerUtils.runTaskAsynchronously(new FoliaRunnable() { @Override public void run() { // Init from MySQL or, if YAML, init & load config file @@ -688,21 +686,17 @@ public void run() { EnderChestBackupMenu menu = new EnderChestBackupMenu(staff, data, page); // Open inventory sync (compressed code) - Future futureOpenInv = main.getServer().getScheduler().callSyncMethod(main, - () -> { - staff.openInventory(menu.getInventory()); - return null; - }); - try { - futureOpenInv.get(); - } catch (ExecutionException | InterruptedException ex) { - ex.printStackTrace(); - } - - // Place items async - menu.showEnderChestItems(); + SchedulerUtils.callSyncMethod(e.getWhoClicked().getLocation(), () -> { + staff.openInventory(menu.getInventory()); + return null; + }).whenComplete((res, ex) -> { + if (ex != null) ex.printStackTrace(); + else { + menu.showEnderChestItems(); + } + }); } - }.runTaskAsynchronously(this.main); + }); } } @@ -718,7 +712,7 @@ else if (icon.getType().equals(Buttons.getRestoreAllInventoryIcon())) { Player player = (Player) offlinePlayer; // Run all data retrieval operations async to avoid tick lag - new BukkitRunnable() { + SchedulerUtils.runTaskAsynchronously(new FoliaRunnable() { @Override public void run() { // Init from MySQL or, if YAML, init & load config file @@ -734,22 +728,14 @@ public void run() { } // Display inventory to player - Future inventoryReplaceFuture = main.getServer().getScheduler().callSyncMethod(main, - () -> { - ItemStack[] enderChest = data.getEnderChest(); - if (enderChest == null) enderChest = new ItemStack[0]; - player.getEnderChest().setContents(enderChest); - return null; - }); - - //If the backup file is invalid it will return null, we want to catch it here - try { - inventoryReplaceFuture.get(); - } catch (NullPointerException | ExecutionException | InterruptedException ex) { - ex.printStackTrace(); - } + SchedulerUtils.callSyncMethod(e.getWhoClicked().getLocation(), () -> { + player.getEnderChest().setContents(data.getEnderChest() != null ? data.getEnderChest() : new ItemStack[0]); + return null; + }).whenComplete((res, ex) -> { + if (ex != null) ex.printStackTrace(); + }); } - }.runTaskAsynchronously(main); + }); if (SoundData.isInventoryRestoreEnabled()) player.playSound(player.getLocation(), SoundData.getInventoryRestored(), 1, 1); diff --git a/src/main/java/me/danjono/inventoryrollback/listeners/EventLogs.java b/src/main/java/me/danjono/inventoryrollback/listeners/EventLogs.java index 170ddc2..d6cfab7 100644 --- a/src/main/java/me/danjono/inventoryrollback/listeners/EventLogs.java +++ b/src/main/java/me/danjono/inventoryrollback/listeners/EventLogs.java @@ -1,6 +1,7 @@ package me.danjono.inventoryrollback.listeners; import com.nuclyon.technicallycoded.inventoryrollback.InventoryRollbackPlus; +import com.nuclyon.technicallycoded.inventoryrollback.folia.SchedulerUtils; import com.tcoded.lightlibs.bukkitversion.BukkitVersion; import me.danjono.inventoryrollback.config.ConfigData; import me.danjono.inventoryrollback.data.LogType; @@ -93,7 +94,7 @@ private void playerQuit(PlayerQuitEvent e) { // Run the cleanup 1 tick later in case the rate limiter should need to provide debug data. // If the cleanup would run and the event is being spammed, this cleanup would delete the rate limiter's data // before it has a chance to act. - main.getServer().getScheduler().runTaskLater(main, () -> { + SchedulerUtils.runTaskLater(e.getPlayer().getLocation(), () -> { // Double check that the player is offline if (main.getServer().getPlayer(uuid) != null) return; // Cleanup the player's data diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 9d56c25..03b5306 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -5,6 +5,7 @@ description: ${project.description} author: ${project.organization.name} authors: [${project.organization.name}, danjono] api-version: 1.13 +folia-supported: true loadbefore: - DeluxeCombat From ea0cf24a71570b225d15fab5ba4558e4c4695775 Mon Sep 17 00:00:00 2001 From: yLeoft Date: Wed, 10 Dec 2025 03:03:16 -0300 Subject: [PATCH 4/9] Change HashMaps to ConcurrentHashMaps --- .../inventoryrollback/commands/Commands.java | 4 ++-- .../inventoryrollback/util/TimeZoneUtil.java | 6 +++--- .../inventoryrollback/inventory/SaveInventory.java | 4 ++-- .../reflections/LegacyNBTWrapper.java | 10 +++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/Commands.java b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/Commands.java index 47d02d4..d4bc726 100644 --- a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/Commands.java +++ b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/Commands.java @@ -4,8 +4,8 @@ import com.nuclyon.technicallycoded.inventoryrollback.commands.inventoryrollback.*; import me.danjono.inventoryrollback.config.MessageData; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; @@ -20,7 +20,7 @@ public class Commands implements CommandExecutor, TabCompleter { private String[] backupOptions = new String[] {"all", "player"}; private String[] importOptions = new String[] {"confirm"}; - private HashMap subCommands = new HashMap<>(); + private ConcurrentHashMap subCommands = new ConcurrentHashMap<>(); public Commands(InventoryRollbackPlus mainIn) { this.main = mainIn; diff --git a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/util/TimeZoneUtil.java b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/util/TimeZoneUtil.java index b4fc373..3be2104 100644 --- a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/util/TimeZoneUtil.java +++ b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/util/TimeZoneUtil.java @@ -4,7 +4,7 @@ import java.time.Instant; import java.util.Date; -import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; public class TimeZoneUtil { @@ -125,8 +125,8 @@ public String getTimeZoneFullName(String shortCode) { // INIT DATA public void loadDefaultData() { - HashMap offsetsMapBuilder = new HashMap<>(); - HashMap shortCodeNamesBuilder = new HashMap<>(); + ConcurrentHashMap offsetsMapBuilder = new ConcurrentHashMap<>(); + ConcurrentHashMap shortCodeNamesBuilder = new ConcurrentHashMap<>(); offsetsMapBuilder.put("ACDT", "GMT+10:30"); offsetsMapBuilder.put("ACST", "GMT+09:30"); diff --git a/src/main/java/me/danjono/inventoryrollback/inventory/SaveInventory.java b/src/main/java/me/danjono/inventoryrollback/inventory/SaveInventory.java index d597d72..136bdbf 100644 --- a/src/main/java/me/danjono/inventoryrollback/inventory/SaveInventory.java +++ b/src/main/java/me/danjono/inventoryrollback/inventory/SaveInventory.java @@ -18,13 +18,13 @@ import org.jetbrains.annotations.Nullable; import java.util.Arrays; -import java.util.HashMap; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; public class SaveInventory { - private static final HashMap rateLimiters = new HashMap<>(); + private static final ConcurrentHashMap rateLimiters = new ConcurrentHashMap<>(); private final InventoryRollbackPlus main; diff --git a/src/main/java/me/danjono/inventoryrollback/reflections/LegacyNBTWrapper.java b/src/main/java/me/danjono/inventoryrollback/reflections/LegacyNBTWrapper.java index 78cb39a..85e22e4 100644 --- a/src/main/java/me/danjono/inventoryrollback/reflections/LegacyNBTWrapper.java +++ b/src/main/java/me/danjono/inventoryrollback/reflections/LegacyNBTWrapper.java @@ -9,7 +9,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; public class LegacyNBTWrapper implements CustomDataItemEditor { @@ -17,8 +17,8 @@ public class LegacyNBTWrapper implements CustomDataItemEditor { private ItemStack item; private final NMSHandler nmsHandler; - private static HashMap, String> getTagElementMethodNames; - private static HashMap, String> setTagElementMethodNames; + private static ConcurrentHashMap, String> getTagElementMethodNames; + private static ConcurrentHashMap, String> setTagElementMethodNames; // 1.8 - 1.20.4 private static String getTagMethodName; @@ -104,8 +104,8 @@ else if (nmsVersion.greaterOrEqThan(BukkitVersion.v1_18_R2)) { } private void resolveNbtTagCompoundReflectionNames(BukkitVersion nmsVersion) { - getTagElementMethodNames = new HashMap<>(); - setTagElementMethodNames = new HashMap<>(); + getTagElementMethodNames = new ConcurrentHashMap<>(); + setTagElementMethodNames = new ConcurrentHashMap<>(); if (nmsVersion.greaterOrEqThan(BukkitVersion.v1_18_R1)) { getTagElementMethodNames.put(Integer.class, "h"); From 8cffd2c4de5f3fa7d49e5144bfb800af282afe95 Mon Sep 17 00:00:00 2001 From: yLeoft Date: Sun, 25 Jan 2026 20:21:46 -0300 Subject: [PATCH 5/9] Remove unused imports --- .../inventoryrollback/gui/menu/MainInventoryBackupMenu.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/me/danjono/inventoryrollback/gui/menu/MainInventoryBackupMenu.java b/src/main/java/me/danjono/inventoryrollback/gui/menu/MainInventoryBackupMenu.java index 06f5c38..5998c08 100644 --- a/src/main/java/me/danjono/inventoryrollback/gui/menu/MainInventoryBackupMenu.java +++ b/src/main/java/me/danjono/inventoryrollback/gui/menu/MainInventoryBackupMenu.java @@ -16,8 +16,6 @@ import org.bukkit.inventory.ItemStack; import java.util.UUID; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; public class MainInventoryBackupMenu { From b6a75c66b6c4724e2a267a5065fa053ed11df9af Mon Sep 17 00:00:00 2001 From: yLeoft Date: Sun, 25 Jan 2026 21:56:47 -0300 Subject: [PATCH 6/9] Changes for the fork --- pom.xml | 2 +- .../commands/inventoryrollback/VersionSubCmd.java | 6 +++--- src/main/resources/plugin.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 89a43b0..eadca24 100644 --- a/pom.xml +++ b/pom.xml @@ -112,7 +112,7 @@ - ${project.name}-${project.version} + ${project.name}Folia-${project.version} ${project.basedir}/src/main/resources diff --git a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/inventoryrollback/VersionSubCmd.java b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/inventoryrollback/VersionSubCmd.java index 084394b..8d7c324 100644 --- a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/inventoryrollback/VersionSubCmd.java +++ b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/commands/inventoryrollback/VersionSubCmd.java @@ -23,7 +23,7 @@ public void onCommand(CommandSender sender, Command cmd, String label, String[] .append(ChatColor.WHITE) .append("Plugin:").append("\n") .append(ChatColor.GRAY) - .append(" Running InventoryRollbackPlus"); + .append(" Running InventoryRollbackPlusFolia"); // Can see version? if (hasVersionPerm) strb.append(" v").append(InventoryRollback.getPluginVersion()); strb.append("\n"); @@ -37,10 +37,10 @@ public void onCommand(CommandSender sender, Command cmd, String label, String[] .append("Authors:").append("\n") .append(ChatColor.GRAY) .append(" - Maintained/updated by: TechnicallyCoded").append("\n") - .append(" - Original author: danjono").append("\n") + .append(" - Original author: danjono").append("\n").append(" - Folia support: yLeoft") .append("\n") .append(ChatColor.WHITE).append("Update link:").append("\n") - .append(ChatColor.BLUE).append(ChatColor.ITALIC).append(" https://www.spigotmc.org/resources/inventoryrollback-plus.85811/"); + .append(ChatColor.BLUE).append(ChatColor.ITALIC).append(" https://github.com/yL3oft/Inventory-Rollback-Plus-Folia/releases"); // Send diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 03b5306..04041fe 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -3,7 +3,7 @@ main: ${project.groupId}.InventoryRollbackPlus version: ${project.version} description: ${project.description} author: ${project.organization.name} -authors: [${project.organization.name}, danjono] +authors: [${project.organization.name}, danjono, yLeoft] api-version: 1.13 folia-supported: true From 5f67a801c6ab1aa29096c34a117ed08686578b5a Mon Sep 17 00:00:00 2001 From: Mitality <96748385+Mitality@users.noreply.github.com> Date: Mon, 23 Mar 2026 12:41:44 +0100 Subject: [PATCH 7/9] Fix MainInventoryBackupMenu not showing armor contents --- .../gui/menu/MainInventoryBackupMenu.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/java/me/danjono/inventoryrollback/gui/menu/MainInventoryBackupMenu.java b/src/main/java/me/danjono/inventoryrollback/gui/menu/MainInventoryBackupMenu.java index 5998c08..41ec208 100644 --- a/src/main/java/me/danjono/inventoryrollback/gui/menu/MainInventoryBackupMenu.java +++ b/src/main/java/me/danjono/inventoryrollback/gui/menu/MainInventoryBackupMenu.java @@ -157,32 +157,29 @@ public void run() { if (armor != null && armor.length > 0) { for (int i = 0; i < armor.length; i++) { // Place item safely - final int finalPos = position.get(); + final int finalPos = position.getAndDecrement(); final int finalItem = i; SchedulerUtils.callSyncMethod(null, () -> { inventory.setItem(finalPos, armor[finalItem]); return null; }).whenComplete((res, ex) -> { if (ex != null) ex.printStackTrace(); - else position.getAndDecrement(); }); } } else { for (; item < mainInvLen; item++) { if (mainInventory[item] != null) { // Place item safely - final int finalPos = position.get(); + final int finalPos = position.getAndDecrement(); final int finalItem = item; SchedulerUtils.callSyncMethod(null, () -> { inventory.setItem(finalPos, mainInventory[finalItem]); return null; }).whenComplete((res, ex) -> { - if (ex != null) { - ex.printStackTrace(); - } else { - position.getAndDecrement(); - } + if (ex != null) ex.printStackTrace(); }); + } else { + position.getAndDecrement(); } } } From f013be11a4e98a8ebf95c3ac2af35f6443677eb0 Mon Sep 17 00:00:00 2001 From: yLeoft Date: Sat, 9 May 2026 16:27:27 -0300 Subject: [PATCH 8/9] Make folia compatible --- .../java/me/danjono/inventoryrollback/listeners/ClickGUI.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/me/danjono/inventoryrollback/listeners/ClickGUI.java b/src/main/java/me/danjono/inventoryrollback/listeners/ClickGUI.java index d376baf..404ef1c 100644 --- a/src/main/java/me/danjono/inventoryrollback/listeners/ClickGUI.java +++ b/src/main/java/me/danjono/inventoryrollback/listeners/ClickGUI.java @@ -714,7 +714,7 @@ else if (e.getRawSlot() == EnderChestBackupMenu.GIVE_SHULKERS_BUTTON_SLOT) { return; } - Bukkit.getScheduler().runTaskAsynchronously(InventoryRollback.getInstance(), () -> { + SchedulerUtils.runTaskAsynchronously(() -> { // Unsupported on older versions if (main.getVersion().lessThan(MCVersion.v1_11.toBukkitVersion())) { return; @@ -770,7 +770,7 @@ else if (e.getRawSlot() == EnderChestBackupMenu.GIVE_SHULKERS_BUTTON_SLOT) { } }; - Bukkit.getScheduler().runTask(main, t -> { + SchedulerUtils.runTask(null, () -> { staff.getInventory().addItem(shulkers.toArray(new ItemStack[0])); staff.closeInventory(); }); From f6124b7124615bc924ebbb34bf7bbf55895526da Mon Sep 17 00:00:00 2001 From: Codah <87586393+CodahWasTaken@users.noreply.github.com> Date: Sat, 13 Jun 2026 16:50:50 -0300 Subject: [PATCH 9/9] Fix global region deadlock by routing async tasks to the async scheduler On Folia the "async" helpers in SchedulerUtils were dispatching to the global/region tick schedulers instead of a real async pool - runTaskAsynchronously -> getGlobalRegionScheduler().execute() - runTaskLaterAsynchronously -> region/global runDelayed() - runTaskTimerAsynchronously -> global runDelayed() loop This is harmless until a caller blocks.. ClickGUI restore flow calls PlayerData.getAllBackupData().get() inside runTaskAsynchronously and getAllBackupData() itself schedules the completing work via the same helper. Both halves land on the single global tick thread, the outer task blocks the thread waiting on a future that can only be completed by a task queued behind it on that same thread = permanent self deadlock. With MySQL storage this freezes the global region on every restore GUI interaction (FoliaWatchdogThread: "Global region has not responded") recoverable only by force restart. Route all three async helpers to getAsyncScheduler(). The async scheduler is time based, so tick delays are converted to milliseconds and clamped to a positive minimum where the API requires it. The non-Folia paths are unchanged. --- .../folia/SchedulerUtils.java | 61 ++++++++++--------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/folia/SchedulerUtils.java b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/folia/SchedulerUtils.java index 56932da..2eac2ff 100644 --- a/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/folia/SchedulerUtils.java +++ b/src/main/java/com/nuclyon/technicallycoded/inventoryrollback/folia/SchedulerUtils.java @@ -1,6 +1,7 @@ package com.nuclyon.technicallycoded.inventoryrollback.folia; import com.nuclyon.technicallycoded.inventoryrollback.InventoryRollbackPlus; +import io.papermc.paper.threadedregions.scheduler.AsyncScheduler; import io.papermc.paper.threadedregions.scheduler.GlobalRegionScheduler; import io.papermc.paper.threadedregions.scheduler.RegionScheduler; import io.papermc.paper.threadedregions.scheduler.ScheduledTask; @@ -14,6 +15,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import static com.nuclyon.technicallycoded.inventoryrollback.InventoryRollbackPlus.usingFolia; @@ -68,22 +70,18 @@ public static void runTaskLaterAsynchronously(@Nullable Location loc, @NotNull R JavaPlugin plugin = InventoryRollbackPlus.getInstance(); if (usingFolia) { try { - if (loc != null) { - Method getRegionScheduler = plugin.getServer().getClass().getMethod("getRegionScheduler"); - RegionScheduler regionScheduler = (RegionScheduler) getRegionScheduler.invoke(plugin.getServer()); - regionScheduler.runDelayed( - plugin, - loc, - (ScheduledTask scheduledTask) -> task.run(), - delay - ); + // Use the async scheduler not a region/global tick thread! + // the async scheduler is time based so convert ticks to milliseconds. + Method getAsyncScheduler = plugin.getServer().getClass().getMethod("getAsyncScheduler"); + AsyncScheduler asyncScheduler = (AsyncScheduler) getAsyncScheduler.invoke(plugin.getServer()); + if (delay <= 0) { + asyncScheduler.runNow(plugin, (ScheduledTask scheduledTask) -> task.run()); } else { - Method getGlobalScheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler"); - GlobalRegionScheduler globalScheduler = (GlobalRegionScheduler) getGlobalScheduler.invoke(plugin.getServer()); - globalScheduler.runDelayed( + asyncScheduler.runDelayed( plugin, (ScheduledTask scheduledTask) -> task.run(), - delay + delay * 50L, + TimeUnit.MILLISECONDS ); } return; @@ -145,19 +143,20 @@ public static void runTaskTimerAsynchronously(@NotNull FoliaRunnable runnable, l JavaPlugin plugin = InventoryRollbackPlus.getInstance(); if (usingFolia) { try { - Method getGlobalScheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler"); - GlobalRegionScheduler globalScheduler = (GlobalRegionScheduler) getGlobalScheduler.invoke(plugin.getServer()); - class AsyncRepeatingTask { - private ScheduledTask task; - void start(long initialDelay) { - task = globalScheduler.runDelayed(plugin, (ScheduledTask t) -> { - runnable.run(); - start(period); - }, initialDelay); - runnable.setScheduledTask(task); - } - } - new AsyncRepeatingTask().start(delay); + // True asyncronous repeating task via the async scheduler (time based) + // requires strictly positive initial delay/period, so convert ticks to ms and clamp to a minimum of one tick + Method getAsyncScheduler = plugin.getServer().getClass().getMethod("getAsyncScheduler"); + AsyncScheduler asyncScheduler = (AsyncScheduler) getAsyncScheduler.invoke(plugin.getServer()); + long initialDelayMs = Math.max(1L, delay) * 50L; + long periodMs = Math.max(1L, period) * 50L; + ScheduledTask task = asyncScheduler.runAtFixedRate( + plugin, + (ScheduledTask t) -> runnable.run(), + initialDelayMs, + periodMs, + TimeUnit.MILLISECONDS + ); + runnable.setScheduledTask(task); } catch (Exception e) { e.printStackTrace(); } @@ -174,9 +173,13 @@ public static void runTaskAsynchronously(@NotNull Runnable task) { JavaPlugin plugin = InventoryRollbackPlus.getInstance(); if (usingFolia) { try { - Method getGlobalScheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler"); - GlobalRegionScheduler globalScheduler = (GlobalRegionScheduler) getGlobalScheduler.invoke(plugin.getServer()); - globalScheduler.execute(plugin, task); + // Must use the async scheduler NOT the global region scheduler + // running here lets callers safely block (eg CompletableFuture.get()) without + // freezing the global region. Using the global scheduler caused a self deadlock + // whenever a task here blocked on a future completed by another such "async" task + Method getAsyncScheduler = plugin.getServer().getClass().getMethod("getAsyncScheduler"); + AsyncScheduler asyncScheduler = (AsyncScheduler) getAsyncScheduler.invoke(plugin.getServer()); + asyncScheduler.runNow(plugin, (ScheduledTask t) -> task.run()); return; } catch (Exception e) { e.printStackTrace();