diff --git a/src/main/java/com/drtshock/playervaults/Metrics.java b/src/main/java/com/drtshock/playervaults/Metrics.java index caf9f92..4f64943 100644 --- a/src/main/java/com/drtshock/playervaults/Metrics.java +++ b/src/main/java/com/drtshock/playervaults/Metrics.java @@ -188,9 +188,10 @@ public void run() { timer.cancel(); return; } - // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler + // Nevertheless we want our code to run on the server thread, so we have to use the scheduler // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;) - Bukkit.getScheduler().runTask(plugin, () -> submitData()); + // Scheduler routes to Folia's global region scheduler when running under Folia. + com.drtshock.playervaults.util.Scheduler.run(() -> submitData()); } }, 1000 * 60 * 5, 1000 * 60 * 30); // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start diff --git a/src/main/java/com/drtshock/playervaults/PlayerVaults.java b/src/main/java/com/drtshock/playervaults/PlayerVaults.java index cace052..745a13b 100644 --- a/src/main/java/com/drtshock/playervaults/PlayerVaults.java +++ b/src/main/java/com/drtshock/playervaults/PlayerVaults.java @@ -35,6 +35,7 @@ import com.drtshock.playervaults.tasks.Cleanup; import com.drtshock.playervaults.util.ComponentDispatcher; import com.drtshock.playervaults.util.Permission; +import com.drtshock.playervaults.util.Scheduler; import com.drtshock.playervaults.vaultmanagement.EconomyOperations; import com.drtshock.playervaults.vaultmanagement.VaultManager; import com.drtshock.playervaults.vaultmanagement.VaultViewInfo; @@ -59,7 +60,6 @@ import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scheduler.BukkitRunnable; import dev.kitteh.cardboardbox.CardboardBox; import sun.misc.Unsafe; @@ -186,17 +186,14 @@ public void onEnable() { debug("setup economy", time); if (getConf().getPurge().isEnabled()) { - getServer().getScheduler().runTaskAsynchronously(this, new Cleanup(getConf().getPurge().getDaysSinceLastEdit())); + Scheduler.runAsync(new Cleanup(getConf().getPurge().getDaysSinceLastEdit())); } - new BukkitRunnable() { - @Override - public void run() { - if (saveQueued) { - saveSignsFile(); - } + Scheduler.runTimer(task -> { + if (saveQueued) { + saveSignsFile(); } - }.runTaskTimer(this, 20, 20); + }, 20, 20); this.metrics = new Metrics(this, 6905); Plugin vault = getServer().getPluginManager().getPlugin("Vault"); @@ -296,42 +293,39 @@ public void run() { this.updateCheck = new Gson().toJson(update); if (!HelpMeCommand.likesCats) return; - new BukkitRunnable() { - @Override - public void run() { - try { - URL url = new URL("https://update.plugin.party/check"); - HttpURLConnection con = (HttpURLConnection) url.openConnection(); - con.setRequestMethod("POST"); - con.setDoOutput(true); - con.setRequestProperty("Content-Type", "application/json"); - con.setRequestProperty("Accept", "application/json"); - try (OutputStream out = con.getOutputStream()) { - out.write(PlayerVaults.this.updateCheck.getBytes(StandardCharsets.UTF_8)); - } - String reply = new BufferedReader(new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8)).lines().collect(Collectors.joining("\n")); - Response response = new Gson().fromJson(reply, Response.class); - if (response.isSuccess()) { - if (response.isUpdateAvailable()) { - PlayerVaults.this.updateResponse = response; - if (response.isUrgent()) { - PlayerVaults.this.getServer().getOnlinePlayers().forEach(PlayerVaults.this::updateNotification); - } - PlayerVaults.this.getLogger().warning("Update available: " + response.getLatestVersion() + (response.getMessage() == null ? "" : (" - " + response.getMessage()))); + Scheduler.runTimerAsync(task -> { + try { + URL url = new URL("https://update.plugin.party/check"); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("POST"); + con.setDoOutput(true); + con.setRequestProperty("Content-Type", "application/json"); + con.setRequestProperty("Accept", "application/json"); + try (OutputStream out = con.getOutputStream()) { + out.write(PlayerVaults.this.updateCheck.getBytes(StandardCharsets.UTF_8)); + } + String reply = new BufferedReader(new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8)).lines().collect(Collectors.joining("\n")); + Response response = new Gson().fromJson(reply, Response.class); + if (response.isSuccess()) { + if (response.isUpdateAvailable()) { + PlayerVaults.this.updateResponse = response; + if (response.isUrgent()) { + PlayerVaults.this.getServer().getOnlinePlayers().forEach(PlayerVaults.this::updateNotification); } + PlayerVaults.this.getLogger().warning("Update available: " + response.getLatestVersion() + (response.getMessage() == null ? "" : (" - " + response.getMessage()))); + } + } else { + if (response.getMessage().equals("INVALID")) { + task.cancel(); + } else if (response.getMessage().equals("TOO_FAST")) { + // Nothing for now } else { - if (response.getMessage().equals("INVALID")) { - this.cancel(); - } else if (response.getMessage().equals("TOO_FAST")) { - // Nothing for now - } else { - PlayerVaults.this.getLogger().warning("Failed to check for updates: " + response.getMessage()); - } + PlayerVaults.this.getLogger().warning("Failed to check for updates: " + response.getMessage()); } - } catch (Exception ignored) { } + } catch (Exception ignored) { } - }.runTaskTimerAsynchronously(this, 1, 20 /* ticks */ * 60 /* seconds in a minute */ * 60 /* minutes in an hour*/); + }, 1, 20 /* ticks */ * 60 /* seconds in a minute */ * 60 /* minutes in an hour*/); } private void metricsLine(String name, Callable callable) { @@ -764,4 +758,4 @@ public T addException(T t) { } return t; } -} +} \ No newline at end of file diff --git a/src/main/java/com/drtshock/playervaults/commands/ConvertCommand.java b/src/main/java/com/drtshock/playervaults/commands/ConvertCommand.java index b6c88f9..4556b18 100644 --- a/src/main/java/com/drtshock/playervaults/commands/ConvertCommand.java +++ b/src/main/java/com/drtshock/playervaults/commands/ConvertCommand.java @@ -21,6 +21,7 @@ import com.drtshock.playervaults.PlayerVaults; import com.drtshock.playervaults.converters.*; import com.drtshock.playervaults.util.Permission; +import com.drtshock.playervaults.util.Scheduler; import com.drtshock.playervaults.vaultmanagement.VaultOperations; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; @@ -72,7 +73,7 @@ public boolean onCommand(final CommandSender sender, Command command, String lab } else { // Fork into background this.plugin.getTL().convertBackground().title().send(sender); - PlayerVaults.getInstance().getServer().getScheduler().runTaskLaterAsynchronously(PlayerVaults.getInstance(), () -> { + Scheduler.runLaterAsync(() -> { int converted = 0; VaultOperations.setLocked(true); for (Converter converter : applicableConverters) { diff --git a/src/main/java/com/drtshock/playervaults/commands/HelpMeCommand.java b/src/main/java/com/drtshock/playervaults/commands/HelpMeCommand.java index 9e0f2fc..21a08eb 100644 --- a/src/main/java/com/drtshock/playervaults/commands/HelpMeCommand.java +++ b/src/main/java/com/drtshock/playervaults/commands/HelpMeCommand.java @@ -21,6 +21,7 @@ import com.drtshock.playervaults.PlayerVaults; import com.drtshock.playervaults.util.ComponentDispatcher; import com.drtshock.playervaults.util.Permission; +import com.drtshock.playervaults.util.Scheduler; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.minimessage.MiniMessage; @@ -30,7 +31,6 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import org.bukkit.scheduler.BukkitRunnable; import org.kitteh.pastegg.PasteBuilder; import org.kitteh.pastegg.PasteContent; import org.kitteh.pastegg.PasteFile; @@ -77,7 +77,7 @@ public boolean onCommand(final CommandSender sender, Command command, String lab mainInfo.append(" ").append(plugin.getDescription().getAuthors()).append('\n'); } - new BukkitRunnable() { + Scheduler.runAsync(new Runnable() { private final PasteBuilder builder = new PasteBuilder().name("PlayerVaultsX Debug") .visibility(Visibility.UNLISTED) .expires(ZonedDateTime.now(ZoneOffset.UTC).plusDays(3)); @@ -106,31 +106,23 @@ public void run() { } add("config.conf", getFile(dataPath.resolve("config.conf"))); PasteBuilder.PasteResult result = builder.build(); - new BukkitRunnable() { - @Override - public void run() { - if (result.getPaste().isPresent()) { - String delKey = result.getPaste().get().getDeletionKey().orElse("No deletion key"); - String url = "https://paste.gg/anonymous/" + result.getPaste().get().getId(); - ComponentDispatcher.send(sender, Component.text("URL generated: ").append(Component.text().clickEvent(ClickEvent.openUrl(url)).content(url))); - ComponentDispatcher.send(sender, MiniMessage.miniMessage().deserialize((sender instanceof Player ? "" : "") + "Deletion key: " + delKey)); - } else { - ComponentDispatcher.send(sender, MiniMessage.miniMessage().deserialize("Failed to generate output. See console for details.")); - PlayerVaults.getInstance().getLogger().warning("Received: " + result.getMessage()); - } + Scheduler.run(() -> { + if (result.getPaste().isPresent()) { + String delKey = result.getPaste().get().getDeletionKey().orElse("No deletion key"); + String url = "https://paste.gg/anonymous/" + result.getPaste().get().getId(); + ComponentDispatcher.send(sender, Component.text("URL generated: ").append(Component.text().clickEvent(ClickEvent.openUrl(url)).content(url))); + ComponentDispatcher.send(sender, MiniMessage.miniMessage().deserialize((sender instanceof Player ? "" : "") + "Deletion key: " + delKey)); + } else { + ComponentDispatcher.send(sender, MiniMessage.miniMessage().deserialize("Failed to generate output. See console for details.")); + PlayerVaults.getInstance().getLogger().warning("Received: " + result.getMessage()); } - }.runTask(PlayerVaults.getInstance()); + }); } catch (Exception e) { PlayerVaults.getInstance().getLogger().log(Level.SEVERE, "Failed to execute debug command", e); - new BukkitRunnable() { - @Override - public void run() { - ComponentDispatcher.send(sender, MiniMessage.miniMessage().deserialize("Failed to generate output. See console for details.")); - } - }.runTask(PlayerVaults.getInstance()); + Scheduler.run(() -> ComponentDispatcher.send(sender, MiniMessage.miniMessage().deserialize("Failed to generate output. See console for details."))); } } - }.runTaskAsynchronously(PlayerVaults.getInstance()); + }); } return true; } diff --git a/src/main/java/com/drtshock/playervaults/listeners/VaultPreloadListener.java b/src/main/java/com/drtshock/playervaults/listeners/VaultPreloadListener.java index 14467a0..caa19c5 100644 --- a/src/main/java/com/drtshock/playervaults/listeners/VaultPreloadListener.java +++ b/src/main/java/com/drtshock/playervaults/listeners/VaultPreloadListener.java @@ -19,13 +19,13 @@ package com.drtshock.playervaults.listeners; import com.drtshock.playervaults.PlayerVaults; +import com.drtshock.playervaults.util.Scheduler; import com.drtshock.playervaults.vaultmanagement.VaultManager; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.scheduler.BukkitRunnable; import java.util.UUID; @@ -37,12 +37,7 @@ public class VaultPreloadListener implements Listener { public void onPlayerJoin(PlayerJoinEvent event) { PlayerVaults.getInstance().updateNotification(event.getPlayer()); final UUID uuid = event.getPlayer().getUniqueId(); - new BukkitRunnable() { - @Override - public void run() { - vm.cachePlayerVaultFile(uuid.toString()); - } - }.runTaskAsynchronously(PlayerVaults.getInstance()); + Scheduler.runAsync(() -> vm.cachePlayerVaultFile(uuid.toString())); } @EventHandler(priority = EventPriority.MONITOR) diff --git a/src/main/java/com/drtshock/playervaults/util/Scheduler.java b/src/main/java/com/drtshock/playervaults/util/Scheduler.java new file mode 100644 index 0000000..7142703 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/util/Scheduler.java @@ -0,0 +1,265 @@ +/* + * PlayerVaultsX + * Copyright (C) 2013 Trent Hensler + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.drtshock.playervaults.util; + +import com.drtshock.playervaults.PlayerVaults; +import org.bukkit.Bukkit; +import org.bukkit.entity.Entity; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; + +import java.lang.reflect.Method; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * Scheduling abstraction that works on both regular Bukkit/Spigot/Paper servers and on + * Folia. + *

+ * Folia doesn't support {@link org.bukkit.scheduler.BukkitScheduler}, so this + * reflectively dispatches to Folia's schedulers ({@code GlobalRegionScheduler}, + * {@code AsyncScheduler} and the per-entity {@code EntityScheduler}). it uses reflection + * because those classes aren't present in Spigot. When the server isn't running folia, + * it uses the regular Bukkit scheduler. + *

+ * Delays and periods are done in ticks (for the normal Bukkit API) + * for the async scheduler ticks are converted to milliseconds (1:50). + */ +public final class Scheduler { + + /** + * A handle to a scheduled task. Wraps either a Bukkit {@link BukkitTask} or a Folia + * {@code ScheduledTask} so callers can cancel repeating work normally. + */ + public interface Task { + void cancel(); + } + + private static final long MS_PER_TICK = 50L; + + private static final boolean FOLIA; + + private static Object globalRegionScheduler; + private static Object asyncScheduler; + private static Method entityGetScheduler; + private static Method globalRun; + private static Method globalRunDelayed; + private static Method globalRunAtFixedRate; + private static Method asyncRunNow; + private static Method asyncRunDelayed; + private static Method asyncRunAtFixedRate; + private static Method entityRun; + private static Method entityRunDelayed; + private static Method scheduledTaskCancel; + + static { + boolean folia; + try { + Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); + folia = true; + } catch (ClassNotFoundException e) { + folia = false; + } + FOLIA = folia; + if (FOLIA) { + try { + initFolia(); + } catch (Throwable t) { + throw new IllegalStateException("Failed to initialise Folia scheduler bridge", t); + } + } + } + + private Scheduler() { + } + + private static void initFolia() throws Exception { + Class scheduledTask = Class.forName("io.papermc.paper.threadedregions.scheduler.ScheduledTask"); + scheduledTaskCancel = scheduledTask.getMethod("cancel"); + + Class globalCls = Class.forName("io.papermc.paper.threadedregions.scheduler.GlobalRegionScheduler"); + Class asyncCls = Class.forName("io.papermc.paper.threadedregions.scheduler.AsyncScheduler"); + Class entityCls = Class.forName("io.papermc.paper.threadedregions.scheduler.EntityScheduler"); + + globalRegionScheduler = Bukkit.class.getMethod("getGlobalRegionScheduler").invoke(null); + asyncScheduler = Bukkit.class.getMethod("getAsyncScheduler").invoke(null); + entityGetScheduler = Entity.class.getMethod("getScheduler"); + + globalRun = globalCls.getMethod("run", Plugin.class, Consumer.class); + globalRunDelayed = globalCls.getMethod("runDelayed", Plugin.class, Consumer.class, long.class); + globalRunAtFixedRate = globalCls.getMethod("runAtFixedRate", Plugin.class, Consumer.class, long.class, long.class); + + asyncRunNow = asyncCls.getMethod("runNow", Plugin.class, Consumer.class); + asyncRunDelayed = asyncCls.getMethod("runDelayed", Plugin.class, Consumer.class, long.class, TimeUnit.class); + asyncRunAtFixedRate = asyncCls.getMethod("runAtFixedRate", Plugin.class, Consumer.class, long.class, long.class, TimeUnit.class); + + entityRun = entityCls.getMethod("run", Plugin.class, Consumer.class, Runnable.class); + entityRunDelayed = entityCls.getMethod("runDelayed", Plugin.class, Consumer.class, Runnable.class, long.class); + } + + /** + * @return {@code true} when running on a Folia server. + */ + public static boolean isFolia() { + return FOLIA; + } + + private static Plugin plugin() { + return PlayerVaults.getInstance(); + } + + /** + * Run a task on the next tick of the global region (Folia) or the main thread (Bukkit). + */ + public static Task run(Runnable runnable) { + if (FOLIA) { + return invokeFolia(globalRegionScheduler, globalRun, foliaConsumer(runnable)); + } + return wrap(Bukkit.getScheduler().runTask(plugin(), runnable)); + } + + /** + * Run a task after the given delay in ticks, on the global region / main thread. + */ + public static Task runLater(Runnable runnable, long delayTicks) { + if (FOLIA) { + return invokeFolia(globalRegionScheduler, globalRunDelayed, foliaConsumer(runnable), Math.max(1L, delayTicks)); + } + return wrap(Bukkit.getScheduler().runTaskLater(plugin(), runnable, delayTicks)); + } + + /** + * Run a repeating task on the global region / main thread. The task body receives its own + * {@link Task} handle so it can cancel itself. + */ + public static Task runTimer(Consumer runnable, long delayTicks, long periodTicks) { + if (FOLIA) { + return invokeFolia(globalRegionScheduler, globalRunAtFixedRate, + foliaSelfCancelConsumer(runnable), Math.max(1L, delayTicks), Math.max(1L, periodTicks)); + } + return bukkitTimer(runnable, delayTicks, periodTicks, false); + } + + /** + * Run a task off the main thread as soon as possible. + */ + public static Task runAsync(Runnable runnable) { + if (FOLIA) { + return invokeFolia(asyncScheduler, asyncRunNow, foliaConsumer(runnable)); + } + return wrap(Bukkit.getScheduler().runTaskAsynchronously(plugin(), runnable)); + } + + /** + * Run a task off the main thread after a delay. + */ + public static Task runLaterAsync(Runnable runnable, long delayTicks) { + if (FOLIA) { + return invokeFolia(asyncScheduler, asyncRunDelayed, + foliaConsumer(runnable), Math.max(1L, delayTicks) * MS_PER_TICK, TimeUnit.MILLISECONDS); + } + return wrap(Bukkit.getScheduler().runTaskLaterAsynchronously(plugin(), runnable, delayTicks)); + } + + /** + * Run a repeating task off the main thread. The task body receives its own {@link Task} handle + * so it can cancel itself. + */ + public static Task runTimerAsync(Consumer runnable, long delayTicks, long periodTicks) { + if (FOLIA) { + return invokeFolia(asyncScheduler, asyncRunAtFixedRate, foliaSelfCancelConsumer(runnable), + Math.max(1L, delayTicks) * MS_PER_TICK, Math.max(1L, periodTicks) * MS_PER_TICK, TimeUnit.MILLISECONDS); + } + return bukkitTimer(runnable, delayTicks, periodTicks, true); + } + + /** + * Run a task tied to an entity's region. On Folia the task is scheduled through the + * entity scheduler (and skipped via {@code retired} if the entity is removed) + * on Bukkit it runs on the main thread. + * + * @param entity the entity to run the task at + * @param runnable what to do + * @param retired run if the entity has been removed (nullable) + */ + public static Task runForEntity(Entity entity, Runnable runnable, Runnable retired) { + if (FOLIA) { + try { + Object entityScheduler = entityGetScheduler.invoke(entity); + Object task = entityRun.invoke(entityScheduler, plugin(), foliaConsumer(runnable), retired); + return task == null ? null : foliaTask(task); + } catch (Exception e) { + throw new IllegalStateException("Failed to schedule Folia entity task", e); + } + } + return wrap(Bukkit.getScheduler().runTask(plugin(), runnable)); + } + + private static Task bukkitTimer(Consumer body, long delay, long period, boolean async) { + BukkitRunnable runnable = new BukkitRunnable() { + @Override + public void run() { + body.accept(this::cancel); + } + }; + BukkitTask task = async + ? runnable.runTaskTimerAsynchronously(plugin(), delay, period) + : runnable.runTaskTimer(plugin(), delay, period); + return wrap(task); + } + + private static Task wrap(BukkitTask task) { + return task::cancel; + } + + private static Consumer foliaConsumer(Runnable runnable) { + return ignored -> runnable.run(); + } + + private static Consumer foliaSelfCancelConsumer(Consumer body) { + return scheduledTask -> body.accept(foliaTask(scheduledTask)); + } + + private static Task foliaTask(Object scheduledTask) { + return () -> { + try { + scheduledTaskCancel.invoke(scheduledTask); + } catch (Exception e) { + throw new IllegalStateException("Failed to cancel Folia task", e); + } + }; + } + + private static Task invokeFolia(Object scheduler, Method method, Object... args) { + try { + Object task = method.invoke(scheduler, prepend(plugin(), args)); + return task == null ? null : foliaTask(task); + } catch (Exception e) { + throw new IllegalStateException("Failed to schedule Folia task", e); + } + } + + private static Object[] prepend(Object first, Object[] rest) { + Object[] result = new Object[rest.length + 1]; + result[0] = first; + System.arraycopy(rest, 0, result, 1, rest.length); + return result; + } +} \ No newline at end of file diff --git a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultManager.java b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultManager.java index 742ac8c..64b6ff5 100644 --- a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultManager.java +++ b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultManager.java @@ -27,7 +27,7 @@ import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; -import org.bukkit.scheduler.BukkitRunnable; +import com.drtshock.playervaults.util.Scheduler; import java.io.File; import java.io.IOException; @@ -258,27 +258,24 @@ public void deleteAllVaults(String holder) { * @param number The vault number. */ public void deleteVault(CommandSender sender, final String holder, final int number) { - new BukkitRunnable() { - @Override - public void run() { - File file = new File(directory, holder + ".yml"); - if (!file.exists()) { - return; - } + Scheduler.runAsync(() -> { + File file = new File(directory, holder + ".yml"); + if (!file.exists()) { + return; + } - YamlConfiguration playerFile = YamlConfiguration.loadConfiguration(file); - if (file.exists()) { - playerFile.set(String.format(VAULTKEY, number), null); - if (cachedVaultFiles.containsKey(holder)) { - cachedVaultFiles.put(holder, playerFile); - } - try { - playerFile.save(file); - } catch (IOException ignored) { - } + YamlConfiguration playerFile = YamlConfiguration.loadConfiguration(file); + if (file.exists()) { + playerFile.set(String.format(VAULTKEY, number), null); + if (cachedVaultFiles.containsKey(holder)) { + cachedVaultFiles.put(holder, playerFile); + } + try { + playerFile.save(file); + } catch (IOException ignored) { } } - }.runTaskAsynchronously(PlayerVaults.getInstance()); + }); OfflinePlayer player = Bukkit.getPlayer(holder); if (player != null) { @@ -374,4 +371,4 @@ public void saveFileSync(final String holder, final YamlConfiguration yaml) { } PlayerVaults.debug("Saved vault for " + holder); } -} +} \ No newline at end of file diff --git a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java index 9134795..317d25a 100644 --- a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java +++ b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java @@ -20,6 +20,7 @@ import com.drtshock.playervaults.PlayerVaults; import com.drtshock.playervaults.util.Permission; +import com.drtshock.playervaults.util.Scheduler; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; @@ -387,11 +388,11 @@ public static int countVaults(Player player) { } PlayerCount newCount = new PlayerCount(vaultCount, Instant.now()); countCache.put(uuid, newCount); - PlayerVaults.getInstance().getServer().getScheduler().runTaskLater(PlayerVaults.getInstance(), () -> { + Scheduler.runLaterAsync(() -> { if (countCache.get(uuid) == newCount) { countCache.remove(uuid); // Do a lil cleanup to avoid the world's smallest memory leak } }, 20 * secondsToLive + 1); return vaultCount; } -} +} \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index a2fb0e9..39eed43 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -6,6 +6,7 @@ version: ${project.version} main: com.drtshock.playervaults.PlayerVaults softdepend: [Vault, Multiverse-Inventories, PlaceholderAPI] api-version: 1.21.11 +folia-supported: true commands: pv: