mirror of
https://github.com/PaperMC/Folia.git
synced 2024-12-22 16:47:51 +01:00
Add some scheduling API
This commit is contained in:
parent
8ffa40246a
commit
282ded3b44
1
install.bat
Normal file
1
install.bat
Normal file
@ -0,0 +1 @@
|
||||
./gradlew publishToMavenLocal
|
162
patches/api/0002-Region-scheduler-API.patch
Normal file
162
patches/api/0002-Region-scheduler-API.patch
Normal file
@ -0,0 +1,162 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
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.
|
||||
+ *
|
||||
+ * <p>
|
||||
+ * 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.
|
||||
+ * </p>
|
||||
+ */
|
||||
+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.
|
||||
+ *
|
||||
+ * <p>
|
||||
+ * It is guaranteed that the run and retired callback are invoked on the region which owns the entity.
|
||||
+ * </p>
|
||||
+ * @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<? extends Entity> run,
|
||||
+ @Nullable final Consumer<? extends Entity> 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.
|
||||
+ * <p>
|
||||
+ * <b>Note</b>: 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.
|
||||
+ * </p>
|
||||
+ */
|
||||
+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.
|
||||
+ * <p>
|
||||
+ * <b>Note</b>: 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.
|
||||
+ * </p>
|
||||
+ * @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.
|
||||
+ * <p>
|
||||
+ * <b>Note</b>: 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.
|
||||
+ * </p>
|
||||
+ * @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
|
||||
}
|
@ -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<? extends net.minecraft.world.entity.Entity> wrap(final Plugin plugin, final Consumer<? extends Entity> 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<Entity>)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<? extends Entity> run, final Consumer<? extends Entity> retired,
|
||||
+ final long delay) {
|
||||
+ final Consumer<? extends net.minecraft.world.entity.Entity> runNMS = wrap(plugin, run);
|
||||
+ final Consumer<? extends net.minecraft.world.entity.Entity> 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<T> 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<String> 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.
|
||||
|
@ -1,4 +1,5 @@
|
||||
Get done before testing:
|
||||
- Mob#getTarget?
|
||||
|
||||
Pre-Test: List of things not fully tested
|
||||
- Task queue
|
||||
|
Loading…
Reference in New Issue
Block a user