diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBlockCommandSender.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBlockCommandSender.java index 12840c4dd7..ccd162ff8a 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBlockCommandSender.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBlockCommandSender.java @@ -156,11 +156,9 @@ public boolean isActive() { updateActive(); } else { // we should update it eventually - Bukkit.getScheduler().callSyncMethod(plugin, - () -> { - updateActive(); - return null; - }); + plugin.getScheduler().executeAtRegion( + BukkitAdapter.adapt(sender.getBlock().getLocation()), + this::updateActive); } return active; } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitEntity.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitEntity.java index e719902862..a71d8bab62 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitEntity.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitEntity.java @@ -125,4 +125,20 @@ public T getFacet(Class cls) { return null; } } + + @Override + public void executeAtEntity(Runnable runnable) { + org.bukkit.entity.Entity entity = entityRef.get(); + if (entity != null) { + entity.getScheduler().run(WorldEditPlugin.getInstance(), task -> runnable.run(), null); + } + } + + @Override + public void runAtEntityDelayed(Runnable runnable, long delay) { + org.bukkit.entity.Entity entity = entityRef.get(); + if (entity != null) { + entity.getScheduler().runDelayed(WorldEditPlugin.getInstance(), task -> runnable.run(), null, delay); + } + } } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java index 1a9b9c8ff2..dfed974e8d 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java @@ -120,7 +120,9 @@ public void reload() { @Override public int schedule(long delay, long period, Runnable task) { - return Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, task, delay, period); + plugin.getScheduler().runAsyncRate(task, delay, period); + return 0; +// return Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, task, delay, period); } @Override diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java index b338b2498f..ed93b46045 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java @@ -30,6 +30,7 @@ import com.sk89q.worldedit.bukkit.adapter.AdapterLoadException; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.bukkit.adapter.BukkitImplLoader; +import com.sk89q.worldedit.bukkit.scheduler.BukkitSchedulerAdapters; import com.sk89q.worldedit.event.platform.CommandEvent; import com.sk89q.worldedit.event.platform.CommandSuggestionEvent; import com.sk89q.worldedit.event.platform.PlatformReadyEvent; @@ -40,6 +41,7 @@ import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extension.platform.Platform; +import com.sk89q.worldedit.extension.platform.scheduler.SchedulerAdapter; import com.sk89q.worldedit.extent.inventory.BlockBag; import com.sk89q.worldedit.internal.anvil.ChunkDeleter; import com.sk89q.worldedit.internal.command.CommandUtil; @@ -119,6 +121,7 @@ public class WorldEditPlugin extends JavaPlugin implements TabCompleter { private final SimpleLifecycled adapter = SimpleLifecycled.invalid(); + private final SchedulerAdapter scheduler = BukkitSchedulerAdapters.create(this); private BukkitServerInterface platform; private BukkitConfiguration config; @@ -321,7 +324,7 @@ public void onDisable() { if (config != null) { config.unload(); } - this.getServer().getScheduler().cancelTasks(this); + scheduler.cancelTasks(); } /** @@ -517,6 +520,10 @@ BukkitImplAdapter getBukkitImplAdapter() { return adapter.value().orElse(null); } + SchedulerAdapter getScheduler() { + return scheduler; + } + private class WorldInitListener implements Listener { private boolean loaded = false; diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/scheduler/BukkitSchedulerAdapters.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/scheduler/BukkitSchedulerAdapters.java new file mode 100644 index 0000000000..8fc28659cb --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/scheduler/BukkitSchedulerAdapters.java @@ -0,0 +1,49 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * 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.sk89q.worldedit.bukkit.scheduler; + +import com.sk89q.worldedit.bukkit.scheduler.adapters.BukkitSchedulerAdapter; +import com.sk89q.worldedit.bukkit.scheduler.adapters.FoliaSchedulerAdapter; +import com.sk89q.worldedit.extension.platform.scheduler.SchedulerAdapter; +import org.bukkit.plugin.Plugin; + +public final class BukkitSchedulerAdapters { + private final static boolean FOLIA_SUPPORT = foliaSupport(); + + private BukkitSchedulerAdapters() { + // Call only through a method + } + + public static SchedulerAdapter create(Plugin plugin) { + if (FOLIA_SUPPORT) { + return new FoliaSchedulerAdapter(plugin); + } + return new BukkitSchedulerAdapter(plugin); + } + + private static boolean foliaSupport() { + try { + Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } +} diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/scheduler/adapters/BukkitSchedulerAdapter.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/scheduler/adapters/BukkitSchedulerAdapter.java new file mode 100644 index 0000000000..e76bec07df --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/scheduler/adapters/BukkitSchedulerAdapter.java @@ -0,0 +1,62 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * 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.sk89q.worldedit.bukkit.scheduler.adapters; + +import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.extension.platform.scheduler.SchedulerAdapter; +import com.sk89q.worldedit.util.Location; +import org.bukkit.plugin.Plugin; + +public class BukkitSchedulerAdapter implements SchedulerAdapter { + + private final Plugin plugin; + @SuppressWarnings("deprecation") + private final org.bukkit.scheduler.BukkitScheduler scheduler; + + public BukkitSchedulerAdapter(final Plugin plugin) { + this.plugin = plugin; + this.scheduler = plugin.getServer().getScheduler(); + } + + @Override + public void runAsyncRate(Runnable runnable, long delay, long period) { + scheduler.runTaskTimerAsynchronously(plugin, runnable, delay, period); + } + + @Override + public void executeAtEntity(Entity entity, Runnable runnable) { + scheduler.runTask(plugin, runnable); + } + + @Override + public void runAtEntityDelayed(final Entity entity, final Runnable runnable, final long delay) { + scheduler.runTaskLater(plugin, runnable, delay); + } + + @Override + public void executeAtRegion(Location location, Runnable runnable) { + scheduler.runTask(plugin, runnable); + } + + @Override + public void cancelTasks() { + scheduler.cancelTasks(plugin); + } +} \ No newline at end of file diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/scheduler/adapters/FoliaSchedulerAdapter.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/scheduler/adapters/FoliaSchedulerAdapter.java new file mode 100644 index 0000000000..0acc76c931 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/scheduler/adapters/FoliaSchedulerAdapter.java @@ -0,0 +1,57 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * 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.sk89q.worldedit.bukkit.scheduler.adapters; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.extension.platform.scheduler.SchedulerAdapter; +import com.sk89q.worldedit.util.Location; +import io.papermc.paper.threadedregions.scheduler.AsyncScheduler; +import io.papermc.paper.threadedregions.scheduler.RegionScheduler; +import org.bukkit.plugin.Plugin; + +import java.util.concurrent.TimeUnit; + +public class FoliaSchedulerAdapter implements SchedulerAdapter { + private final Plugin plugin; + private final AsyncScheduler asyncScheduler; + private final RegionScheduler regionScheduler; + + public FoliaSchedulerAdapter(final Plugin plugin) { + this.plugin = plugin; + this.asyncScheduler = plugin.getServer().getAsyncScheduler(); + this.regionScheduler = plugin.getServer().getRegionScheduler(); + } + + @Override + public void runAsyncRate(final Runnable runnable, final long delay, final long period) { + asyncScheduler.runAtFixedRate(plugin, task -> runnable.run(), delay * 50, period * 50, TimeUnit.MILLISECONDS); + } + + @Override + public void executeAtRegion(final Location location, final Runnable runnable) { + regionScheduler.execute(plugin, BukkitAdapter.adapt(location), runnable); + } + + @Override + public void cancelTasks() { + asyncScheduler.cancelTasks(plugin); + } + +} diff --git a/worldedit-bukkit/src/main/resources/plugin.yml b/worldedit-bukkit/src/main/resources/plugin.yml index aa19044bb0..d1e505931b 100644 --- a/worldedit-bukkit/src/main/resources/plugin.yml +++ b/worldedit-bukkit/src/main/resources/plugin.yml @@ -5,4 +5,6 @@ load: STARTUP api-version: 1.13 softdepend: [Vault] author: EngineHub -website: https://enginehub.org/worldedit \ No newline at end of file +website: https://enginehub.org/worldedit + +folia-supported: true diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/entity/Entity.java b/worldedit-core/src/main/java/com/sk89q/worldedit/entity/Entity.java index ffa5dfd0fa..2f54ebc033 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/entity/Entity.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/entity/Entity.java @@ -54,4 +54,25 @@ public interface Entity extends Faceted, Locatable { */ boolean remove(); + /** + * Schedules a task. + * + * @see com.sk89q.worldedit.extension.platform.scheduler.SchedulerAdapter + * @param runnable The task to execute. + */ + default void executeAtEntity(Runnable runnable) { + throw new UnsupportedOperationException("Not implemented in this platform"); + } + + /** + * Schedules a task with the given delay. + * + * @see com.sk89q.worldedit.extension.platform.scheduler.SchedulerAdapter + * @param runnable The task to execute. + * @param delay The time delay to pass before the task should be executed, in ticks. + */ + default void runAtEntityDelayed(Runnable runnable, long delay) { + throw new UnsupportedOperationException("Not implemented in this platform"); + } + } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/scheduler/SchedulerAdapter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/scheduler/SchedulerAdapter.java new file mode 100644 index 0000000000..e02f899352 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/scheduler/SchedulerAdapter.java @@ -0,0 +1,86 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * 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.sk89q.worldedit.extension.platform.scheduler; + +import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.util.Location; + +public interface SchedulerAdapter { + + /** + * Schedules the specified task to be executed asynchronously after the delay has passed, + * and then periodically executed with the specified period. + * + * @param runnable The task to execute. + * @param delay The time delay to pass before the task should be executed. + * @param period The time between task executions after the first execution of the task. + */ + void runAsyncRate(Runnable runnable, long delay, long period); + + /** + * Schedules a task. If the task failed to schedule because the scheduler is retired (entity removed), + * then returns {@code false}. Otherwise, either the run callback will be invoked after the specified delay, + * or the retired callback will be invoked if the scheduler is retired. + * Note that the retired callback is invoked in critical code, so it should not attempt to remove the entity, + * remove other entities, load chunks, load worlds, modify ticket levels, etc. + * + *

+ * It is guaranteed that the task and retired callback are invoked on the region which owns the entity. + *

+ * + * @param entity The entity relative to which the scheduler is obtained. + * @param runnable The task to execute. + */ + default void executeAtEntity(Entity entity, Runnable runnable) { + entity.executeAtEntity(runnable); + } + + /** + * Schedules a task with the given delay. If the task failed to schedule because the scheduler is retired (entity removed), + * then returns {@code false}. Otherwise, either the run callback will be invoked after the specified delay, + * or the retired callback will be invoked if the scheduler is retired. + * Note that the retired callback is invoked in critical code, so it should not attempt to remove the entity, + * remove other entities, load chunks, load worlds, modify ticket levels, etc. + * + *

+ * It is guaranteed that the task and retired callback are invoked on the region which owns the entity. + *

+ * + * @param entity The entity relative to which the scheduler is obtained. + * @param runnable The task to execute. + * @param delay The time delay to pass before the task should be executed, in ticks. + */ + default void runAtEntityDelayed(Entity entity, Runnable runnable, long delay) { + entity.runAtEntityDelayed(runnable, delay); + } + + /** + * Schedules a task to be executed on the region which owns the location. + * + * @param location The location at which the region executing should own. + * @param runnable The task to execute. + */ + void executeAtRegion(Location location, Runnable runnable); + + /** + * Attempts to cancel all tasks scheduled by the plugin. + */ + void cancelTasks(); +} \ No newline at end of file