diff --git a/install.bat b/install.bat new file mode 100644 index 0000000..080cb72 --- /dev/null +++ b/install.bat @@ -0,0 +1 @@ +./gradlew publishToMavenLocal \ No newline at end of file diff --git a/patches/api/0002-Region-scheduler-API.patch b/patches/api/0002-Region-scheduler-API.patch new file mode 100644 index 0000000..6999314 --- /dev/null +++ b/patches/api/0002-Region-scheduler-API.patch @@ -0,0 +1,162 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 4 Mar 2023 12:48:43 -0800 +Subject: [PATCH] Region scheduler API + +Add both a location based scheduler and an entity based scheduler + +diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/EntityScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/EntityScheduler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..edf4fe717c72aa147e5c0dee758bc7a133a19aa3 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/EntityScheduler.java +@@ -0,0 +1,49 @@ ++package io.papermc.paper.threadedregions.scheduler; ++ ++import org.bukkit.entity.Entity; ++import org.bukkit.plugin.Plugin; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++import java.util.function.Consumer; ++ ++/** ++ * An entity can move between worlds with an arbitrary tick delay, be temporarily removed ++ * for players (i.e end credits), be partially removed from world state (i.e inactive but not removed), ++ * teleport between ticking regions, teleport between worlds, and even be removed entirely from the server. ++ * The uncertainty of an entity's state can make it difficult to schedule tasks without worrying about undefined ++ * behaviors resulting from any of the states listed previously. ++ * ++ *

++ * This class is designed to eliminate those states by providing an interface to run tasks only when an entity ++ * is contained in a world, on the owning thread for the region, and by providing the current Entity object. ++ * The scheduler also allows a task to provide a callback, the "retired" callback, that will be invoked ++ * if the entity is removed before a task that was scheduled could be executed. The scheduler is also ++ * completely thread-safe, allowing tasks to be scheduled from any thread context. The scheduler also indicates ++ * properly whether a task was scheduled successfully (i.e scheduler not retired), thus the code scheduling any task ++ * knows whether the given callbacks will be invoked eventually or not - which may be critical for off-thread ++ * contexts. ++ *

++ */ ++public interface EntityScheduler { ++ ++ /** ++ * 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 run and retired callback are invoked on the region which owns the entity. ++ *

++ * @param run The callback to run after the specified delay, may not be null. ++ * @param retired Retire callback to run if the entity is retired before the run callback can be invoked, may be null. ++ * @param delay The delay in ticks before the run callback is invoked. Any value less-than 1 is treated as 1. ++ * @return {@code true} if the task was scheduled, which means that either the run function or the retired function ++ * will be invoked (but never both), or {@code false} indicating neither the run nor retired function will be invoked ++ * since the scheduler has been retired. ++ */ ++ public boolean execute(@NotNull final Plugin plugin, @NotNull final Consumer run, ++ @Nullable final Consumer retired, final long delay); ++ ++} +diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/RegionisedScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/RegionisedScheduler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..210a3dce74959efd7ac0ca9a92a2ad8815844246 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/RegionisedScheduler.java +@@ -0,0 +1,26 @@ ++package io.papermc.paper.threadedregions.scheduler; ++ ++import org.bukkit.Location; ++import org.bukkit.entity.Entity; ++import org.bukkit.plugin.Plugin; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * The region task scheduler can be used to schedule tasks by location to be executed on the region which owns the location. ++ *

++ * Note: It is entirely inappropriate to use the region scheduler to schedule tasks for entities. ++ * If you wish to schedule tasks to perform actions on entities, you should be using {@link Entity#getScheduler()} ++ * as the entity scheduler will "follow" an entity if it is teleported, whereas the region task scheduler ++ * will not. ++ *

++ */ ++public interface RegionisedScheduler { ++ ++ /** ++ * Schedules a task to be executed on the region which owns the location. ++ * @param plugin The plugin that owns the task ++ * @param location The location at which the region executing should own ++ * @param run The task to execute ++ */ ++ public void execute(@NotNull final Plugin plugin, @NotNull final Location location, @NotNull final Runnable run); ++} +diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java +index ac9b690fcccb60b587e5345f12f1383afd0a73a1..3b8a6d7336b0cb20594de9c6b56f215f1370ce89 100644 +--- a/src/main/java/org/bukkit/Bukkit.java ++++ b/src/main/java/org/bukkit/Bukkit.java +@@ -2459,6 +2459,22 @@ public final class Bukkit { + return server.getPotionBrewer(); + } + // Paper end ++ // Folia start - region threading API ++ /** ++ * Returns the region task scheduler. The region task scheduler can be used to schedule ++ * tasks by location to be executed on the region which owns the location. ++ *

++ * Note: It is entirely inappropriate to use the region scheduler to schedule tasks for entities. ++ * If you wish to schedule tasks to perform actions on entities, you should be using {@link Entity#getScheduler()} ++ * as the entity scheduler will "follow" an entity if it is teleported, whereas the region task scheduler ++ * will not. ++ *

++ * @return the region task scheduler ++ */ ++ public static @NotNull io.papermc.paper.threadedregions.scheduler.RegionisedScheduler getRegionScheduler() { ++ return server.getRegionScheduler(); ++ } ++ // Folia end - region threading API + + @NotNull + public static Server.Spigot spigot() { +diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java +index 2204336d8800311b65e894739ab1b27273e7c6f2..135467092eec33a21b171df39267d15bad26003e 100644 +--- a/src/main/java/org/bukkit/Server.java ++++ b/src/main/java/org/bukkit/Server.java +@@ -2139,4 +2139,18 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi + */ + @NotNull org.bukkit.potion.PotionBrewer getPotionBrewer(); + // Paper end ++ // Folia start - region threading API ++ /** ++ * Returns the region task scheduler. The region task scheduler can be used to schedule ++ * tasks by location to be executed on the region which owns the location. ++ *

++ * Note: It is entirely inappropriate to use the region scheduler to schedule tasks for entities. ++ * If you wish to schedule tasks to perform actions on entities, you should be using {@link Entity#getScheduler()} ++ * as the entity scheduler will "follow" an entity if it is teleported, whereas the region task scheduler ++ * will not. ++ *

++ * @return the region task scheduler ++ */ ++ @NotNull io.papermc.paper.threadedregions.scheduler.RegionisedScheduler getRegionScheduler(); ++ // Folia end - region threading API + } +diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java +index cdbc7329cf5f67d66e31eb31e83b9e7997040f72..90451ed12b2c95bb372ac2e3cbb57b8b83cc6a82 100644 +--- a/src/main/java/org/bukkit/entity/Entity.java ++++ b/src/main/java/org/bukkit/entity/Entity.java +@@ -970,4 +970,13 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent + */ + boolean wouldCollideUsing(@NotNull BoundingBox boundingBox); + // Paper End - Collision API ++ // Folia start - region threading API ++ /** ++ * Returns the task scheduler for this entity. The entity scheduler can be used to schedule tasks ++ * that are guaranteed to always execute on the tick thread that owns the entity. ++ * @return the task scheduler for this entity. ++ * @see io.papermc.paper.threadedregions.scheduler.EntityScheduler ++ */ ++ @NotNull io.papermc.paper.threadedregions.scheduler.EntityScheduler getScheduler(); ++ // Folia end - region threading API + } diff --git a/patches/server/0004-Threaded-Regions.patch b/patches/server/0004-Threaded-Regions.patch index 98d39bc..34bd3d0 100644 --- a/patches/server/0004-Threaded-Regions.patch +++ b/patches/server/0004-Threaded-Regions.patch @@ -9410,6 +9410,94 @@ index 0000000000000000000000000000000000000000..5bf205d8c0a03ba932be85cc1a63d6ce + return new ArrayList<>(); + } +} +diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaEntityScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaEntityScheduler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b924146112006e85dfc12c2b303a2eecf23741ca +--- /dev/null ++++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaEntityScheduler.java +@@ -0,0 +1,39 @@ ++package io.papermc.paper.threadedregions.scheduler; ++ ++import ca.spottedleaf.concurrentutil.util.Validate; ++import org.bukkit.craftbukkit.entity.CraftEntity; ++import org.bukkit.entity.Entity; ++import org.bukkit.plugin.Plugin; ++import java.util.function.Consumer; ++import java.util.logging.Level; ++ ++public final class FoliaEntityScheduler implements EntityScheduler { ++ ++ private final CraftEntity entity; ++ ++ public FoliaEntityScheduler(final CraftEntity entity) { ++ this.entity = entity; ++ } ++ ++ private static Consumer wrap(final Plugin plugin, final Consumer consumer) { ++ Validate.notNull(plugin, "Plugin may not be null"); ++ Validate.notNull(consumer, "Consumer may not be null"); ++ ++ return (final net.minecraft.world.entity.Entity nmsEntity) -> { ++ try { ++ ((Consumer)consumer).accept(nmsEntity.getBukkitEntity()); ++ } catch (final Throwable throwable) { ++ plugin.getLogger().log(Level.WARNING, "Entity task for " + plugin.getDescription().getFullName() + " generated an exception", throwable); ++ } ++ }; ++ } ++ ++ @Override ++ public boolean execute(final Plugin plugin, final Consumer run, final Consumer retired, ++ final long delay) { ++ final Consumer runNMS = wrap(plugin, run); ++ final Consumer runRetired = retired == null ? null : wrap(plugin, retired); ++ ++ return this.entity.taskScheduler.schedule(runNMS, runRetired, delay); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaRegionisedScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaRegionisedScheduler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e4637086ba0095341da9c2bfe20083375c82bc01 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaRegionisedScheduler.java +@@ -0,0 +1,37 @@ ++package io.papermc.paper.threadedregions.scheduler; ++ ++import ca.spottedleaf.concurrentutil.util.Validate; ++import io.papermc.paper.threadedregions.RegionisedServer; ++import org.bukkit.Location; ++import org.bukkit.World; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.plugin.Plugin; ++import java.util.logging.Level; ++ ++public final class FoliaRegionisedScheduler implements RegionisedScheduler { ++ ++ private static Runnable wrap(final Plugin plugin, final Location location, final Runnable run) { ++ return () -> { ++ try { ++ run.run(); ++ } catch (final Throwable throwable) { ++ plugin.getLogger().log(Level.WARNING, "Location task for " + plugin.getDescription().getFullName() + " at location " + location + " generated an exception", throwable); ++ } ++ }; ++ } ++ ++ @Override ++ public void execute(final Plugin plugin, final Location location, final Runnable run) { ++ Validate.notNull(plugin, "Plugin may not be null"); ++ Validate.notNull(location, "Location may not be null"); ++ Validate.notNull(run, "Runnable may not be null"); ++ ++ final World world = Validate.notNull(location.getWorld(), "Location world may not be null"); ++ final int chunkX = location.getBlockX() >> 4; ++ final int chunkZ = location.getBlockZ() >> 4; ++ ++ RegionisedServer.getInstance().taskQueue.queueTickTaskQueue( ++ ((CraftWorld)world).getHandle(), chunkX, chunkZ, wrap(plugin, location.clone(), run) ++ ); ++ } ++} diff --git a/src/main/java/io/papermc/paper/util/CachedLists.java b/src/main/java/io/papermc/paper/util/CachedLists.java index e08f4e39db4ee3fed62e37364d17dcc5c5683504..03d239460a2e856c1f59d6bcd95811c8e4e0cf6d 100644 --- a/src/main/java/io/papermc/paper/util/CachedLists.java @@ -21671,10 +21759,26 @@ index 7f1ac2cb29eb84833c0895442d611dfa0504527e..c79cfebc65fd04994735dabcf5bb6e6c LevelChunkTicks levelChunkTicks = this.allContainers.get(l); if (levelChunkTicks == null) { diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 2ea3778ee1348e5d06b15a2c5dc5d9bd4136dbe3..c4c2a393ee2d5fbcdbf3abb4a49f1bfae2d2c618 100644 +index 2ea3778ee1348e5d06b15a2c5dc5d9bd4136dbe3..72a8e27c5ff6063d2e2b3590390b24a112387535 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -879,6 +879,9 @@ public final class CraftServer implements Server { +@@ -307,6 +307,15 @@ public final class CraftServer implements Server { + CraftItemFactory.instance(); + } + ++ // Folia start - region threading API ++ private final io.papermc.paper.threadedregions.scheduler.FoliaRegionisedScheduler regionisedScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaRegionisedScheduler(); ++ ++ @Override ++ public final io.papermc.paper.threadedregions.scheduler.RegionisedScheduler getRegionScheduler() { ++ return this.regionisedScheduler; ++ } ++ // Folia end - region threading API ++ + public CraftServer(DedicatedServer console, PlayerList playerList) { + this.console = console; + this.playerList = (DedicatedPlayerList) playerList; +@@ -879,6 +888,9 @@ public final class CraftServer implements Server { // NOTE: Should only be called from DedicatedServer.ah() public boolean dispatchServerCommand(CommandSender sender, ConsoleInput serverCommand) { @@ -21684,7 +21788,7 @@ index 2ea3778ee1348e5d06b15a2c5dc5d9bd4136dbe3..c4c2a393ee2d5fbcdbf3abb4a49f1bfa if (sender instanceof Conversable) { Conversable conversable = (Conversable) sender; -@@ -898,12 +901,44 @@ public final class CraftServer implements Server { +@@ -898,12 +910,44 @@ public final class CraftServer implements Server { } } @@ -21729,7 +21833,7 @@ index 2ea3778ee1348e5d06b15a2c5dc5d9bd4136dbe3..c4c2a393ee2d5fbcdbf3abb4a49f1bfa // Paper Start if (!org.spigotmc.AsyncCatcher.shuttingDown && !Bukkit.isPrimaryThread()) { final CommandSender fSender = sender; -@@ -2913,7 +2948,7 @@ public final class CraftServer implements Server { +@@ -2913,7 +2957,7 @@ public final class CraftServer implements Server { @Override public int getCurrentTick() { @@ -21861,18 +21965,27 @@ index cd4ad8261e56365850068db1d83d6a8454026737..c098ae9f057a3dcc77c61555feb87045 List offers = waitable.get(); if (offers == null) { diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 78f53ee557276de85f0431ebcb146445b1f4fb92..ab6db7c5193a7c4b3f9433c6997dd24c76a84904 100644 +index 78f53ee557276de85f0431ebcb146445b1f4fb92..91a11cfa430c63455e2d54125d6e1bd407f822ac 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -200,6 +200,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { +@@ -200,6 +200,16 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { private EntityDamageEvent lastDamageEvent; private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(CraftEntity.DATA_TYPE_REGISTRY); protected net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers + public final io.papermc.paper.threadedregions.EntityScheduler taskScheduler = new io.papermc.paper.threadedregions.EntityScheduler(this); // Folia - region threading ++ ++ // Folia start - region threading API ++ private final io.papermc.paper.threadedregions.scheduler.FoliaEntityScheduler apiScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaEntityScheduler(this); ++ ++ @Override ++ public final io.papermc.paper.threadedregions.scheduler.EntityScheduler getScheduler() { ++ return this.apiScheduler; ++ }; ++ // Folia end - region threading API public CraftEntity(final CraftServer server, final Entity entity) { this.server = server; -@@ -556,6 +557,11 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { +@@ -556,6 +566,11 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { @Override public boolean teleport(Location location, TeleportCause cause, boolean ignorePassengers, boolean dismount) { @@ -21884,7 +21997,7 @@ index 78f53ee557276de85f0431ebcb146445b1f4fb92..ab6db7c5193a7c4b3f9433c6997dd24c // Paper end Preconditions.checkArgument(location != null, "location cannot be null"); location.checkFinite(); -@@ -1206,7 +1212,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { +@@ -1206,7 +1221,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { } ServerLevel world = ((CraftWorld) this.getWorld()).getHandle(); @@ -21893,7 +22006,7 @@ index 78f53ee557276de85f0431ebcb146445b1f4fb92..ab6db7c5193a7c4b3f9433c6997dd24c if (entityTracker == null) { return; -@@ -1270,30 +1276,38 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { +@@ -1270,30 +1285,38 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { Preconditions.checkArgument(location != null, "location"); location.checkFinite(); Location locationClone = location.clone(); // clone so we don't need to worry about mutations after this call. diff --git a/regiontodo.txt b/regiontodo.txt index b1691d7..fa8dc18 100644 --- a/regiontodo.txt +++ b/regiontodo.txt @@ -1,4 +1,5 @@ Get done before testing: +- Mob#getTarget? Pre-Test: List of things not fully tested - Task queue