diff --git a/core/build.gradle.kts b/core/build.gradle.kts
index cebfbc6c5..1cf20ed96 100644
--- a/core/build.gradle.kts
+++ b/core/build.gradle.kts
@@ -70,6 +70,7 @@ dependencies {
implementation("net.kyori:adventure-platform-bukkit:4.3.4")
implementation("net.kyori:adventure-api:4.17.0")
implementation("org.bstats:bstats-bukkit:3.1.0")
+ implementation(project(":scheduler"))
//implementation("org.bytedeco:javacpp:1.5.10")
//implementation("org.bytedeco:cuda-platform:12.3-8.9-1.5.10")
diff --git a/scheduler/build.gradle.kts b/scheduler/build.gradle.kts
new file mode 100644
index 000000000..cdb3e49b1
--- /dev/null
+++ b/scheduler/build.gradle.kts
@@ -0,0 +1,7 @@
+plugins {
+ java
+}
+
+dependencies {
+ compileOnly("dev.folia:folia-api:1.20.1-R0.1-SNAPSHOT")
+}
diff --git a/scheduler/src/main/java/com/volmit/iris/util/scheduling/Platform.java b/scheduler/src/main/java/com/volmit/iris/util/scheduling/Platform.java
new file mode 100644
index 000000000..95db49223
--- /dev/null
+++ b/scheduler/src/main/java/com/volmit/iris/util/scheduling/Platform.java
@@ -0,0 +1,190 @@
+package com.volmit.iris.util.scheduling;
+
+import com.volmit.iris.util.scheduling.paper.PaperPlatform;
+import com.volmit.iris.util.scheduling.spigot.SpigotPlatform;
+import com.volmit.iris.util.scheduling.split.IAsyncScheduler;
+import com.volmit.iris.util.scheduling.split.IEntityScheduler;
+import com.volmit.iris.util.scheduling.split.IGlobalScheduler;
+import com.volmit.iris.util.scheduling.split.IRegionScheduler;
+import org.bukkit.Location;
+import org.bukkit.Server;
+import org.bukkit.World;
+import org.bukkit.block.Block;
+import org.bukkit.entity.Entity;
+import org.bukkit.event.player.PlayerTeleportEvent;
+import org.bukkit.plugin.Plugin;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.concurrent.CompletableFuture;
+
+@SuppressWarnings("unused")
+public interface Platform {
+ /**
+ * Folia: Returns whether the current thread is ticking a region and that
+ * the region being ticked owns the chunk at the specified world and block
+ * position as included in the specified location.
+ * Paper/Spigot: Returns {@link Server#isPrimaryThread()}
+ *
+ * @param location Specified location, must have a non-null world
+ * @return true if the current thread is ticking the region that owns the chunk at the specified location
+ */
+ boolean isOwnedByCurrentRegion(@NotNull Location location);
+
+ /**
+ * Folia: Returns whether the current thread is ticking a region and that
+ * the region being ticked owns the chunks centered at the specified world
+ * and block position as included in the specified location within the
+ * specified square radius. Specifically, this function checks that every
+ * chunk with position x in [centerX - radius, centerX + radius] and
+ * position z in [centerZ - radius, centerZ + radius] is owned by the
+ * current ticking region.
+ * Paper/Spigot: Returns {@link Server#isPrimaryThread()}
+ *
+ * @param location Specified location, must have a non-null world
+ * @param squareRadiusChunks Specified square radius. Must be >= 0. Note that this parameter is not a squared radius, but rather a Chebyshev Distance
+ * @return true if the current thread is ticking the region that owns the chunks centered at the specified location within the specified square radius
+ */
+ boolean isOwnedByCurrentRegion(@NotNull Location location, int squareRadiusChunks);
+
+ /**
+ * Folia: Returns whether the current thread is ticking a region and that
+ * the region being ticked owns the chunk at the specified block position.
+ * Paper/Spigot: Returns {@link Server#isPrimaryThread()}
+ *
+ * @param block Specified block position
+ * @return true if the current thread is ticking the region that owns the chunk at the specified block position
+ */
+ boolean isOwnedByCurrentRegion(@NotNull Block block);
+
+ /**
+ * Folia: Returns whether the current thread is ticking a region and that
+ * the region being ticked owns the chunk at the specified world and chunk
+ * position.
+ * Paper/Spigot: Returns {@link Server#isPrimaryThread()}
+ *
+ * @param world Specified world
+ * @param chunkX Specified x-coordinate of the chunk position
+ * @param chunkZ Specified z-coordinate of the chunk position
+ * @return true if the current thread is ticking the region that owns the chunk at the specified world and chunk position
+ */
+ boolean isOwnedByCurrentRegion(@NotNull World world, int chunkX, int chunkZ);
+
+ /**
+ * Folia: Returns whether the current thread is ticking a region and that
+ * the region being ticked owns the chunks centered at the specified world
+ * and chunk position within the specified square radius. Specifically,
+ * this function checks that every chunk with position x in [centerX -
+ * radius, centerX + radius] and position z in [centerZ - radius, centerZ +
+ * radius] is owned by the current ticking region.
+ * Paper/Spigot: Returns {@link Server#isPrimaryThread()}
+ *
+ * @param world Specified world
+ * @param chunkX Specified x-coordinate of the chunk position
+ * @param chunkZ Specified z-coordinate of the chunk position
+ * @param squareRadiusChunks Specified square radius. Must be >= 0. Note that this parameter is not a squared radius, but rather a Chebyshev Distance.
+ * @return true if the current thread is ticking the region that owns the chunks centered at the specified world and chunk position within the specified square radius
+ */
+ boolean isOwnedByCurrentRegion(@NotNull World world, int chunkX, int chunkZ, int squareRadiusChunks);
+
+ /**
+ * Folia: Returns whether the current thread is ticking a region and that
+ * the region being ticked owns the specified entity. Note that this
+ * function is the only appropriate method of checking for ownership of an
+ * entity, as retrieving the entity's location is undefined unless the
+ * entity is owned by the current region.
+ * Paper/Spigot: Returns {@link Server#isPrimaryThread()}
+ *
+ * @param entity Specified entity
+ * @return true if the current thread is ticking the region that owns the specified entity
+ */
+ boolean isOwnedByCurrentRegion(@NotNull Entity entity);
+
+ /**
+ * Folia: Returns whether the current thread is ticking the global region.
+ * Paper/Spigot: Returns {@link Server#isPrimaryThread()}
+ *
+ * @return true if the current thread is ticking the global region
+ */
+ boolean isGlobalTickThread();
+
+ /**
+ * Scheduler that may be used by plugins to schedule tasks to execute asynchronously from the server tick process.
+ */
+ @NotNull IAsyncScheduler async();
+
+ /**
+ * 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.
+ *
+ */
+ @NotNull IEntityScheduler entity(@NotNull Entity entity);
+
+ /**
+ * The global region task scheduler may be used to schedule tasks that will execute on the global region.
+ *
+ * The global region is responsible for maintaining world day time, world game time, weather cycle,
+ * sleep night skipping, executing commands for console, and other misc. tasks that do not belong to any specific region.
+ *
+ */
+ @NotNull IGlobalScheduler global();
+
+ /**
+ * 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.
+ *
+ */
+ @NotNull IRegionScheduler region();
+
+ /**
+ * Teleport an entity to a location async
+ * @param entity Entity to teleport
+ * @param location Location to teleport to
+ * @return Future when the teleport is completed or failed
+ */
+ default @NotNull CompletableFuture<@NotNull Boolean> teleportAsync(@NotNull Entity entity, @NotNull Location location) {
+ return teleportAsync(entity, location, PlayerTeleportEvent.TeleportCause.PLUGIN);
+ }
+
+ /**
+ * Teleport an entity to a location async with a cause
+ * @param entity Entity to teleport
+ * @param location Location to teleport to
+ * @param cause Cause of the teleport
+ * @return Future when the teleport is completed or failed
+ */
+ @NotNull CompletableFuture<@NotNull Boolean> teleportAsync(@NotNull Entity entity, @NotNull Location location, @NotNull PlayerTeleportEvent.TeleportCause cause);
+
+ static @NotNull Platform create(@NotNull Plugin plugin) {
+ if (hasClass("com.destroystokyo.paper.PaperConfig") || hasClass("io.papermc.paper.configuration.Configuration"))
+ return new PaperPlatform(plugin);
+ if (hasClass("org.spigotmc.SpigotConfig"))
+ return new SpigotPlatform(plugin);
+ throw new IllegalStateException("Unsupported platform!");
+ }
+
+ static boolean hasClass(String name) {
+ try {
+ Class.forName(name);
+ return true;
+ } catch (Throwable e) {
+ return false;
+ }
+ }
+}
diff --git a/scheduler/src/main/java/com/volmit/iris/util/scheduling/Ref.java b/scheduler/src/main/java/com/volmit/iris/util/scheduling/Ref.java
new file mode 100644
index 000000000..c9a212e2e
--- /dev/null
+++ b/scheduler/src/main/java/com/volmit/iris/util/scheduling/Ref.java
@@ -0,0 +1,5 @@
+package com.volmit.iris.util.scheduling;
+
+public final class Ref {
+ public transient T value;
+}
diff --git a/scheduler/src/main/java/com/volmit/iris/util/scheduling/Task.java b/scheduler/src/main/java/com/volmit/iris/util/scheduling/Task.java
new file mode 100644
index 000000000..4a09ad4fb
--- /dev/null
+++ b/scheduler/src/main/java/com/volmit/iris/util/scheduling/Task.java
@@ -0,0 +1,27 @@
+package com.volmit.iris.util.scheduling;
+
+import org.bukkit.plugin.Plugin;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+
+public interface Task {
+ void cancel();
+ boolean cancelled();
+ @NotNull Plugin owner();
+ boolean async();
+
+ interface Completable extends Task {
+ @NotNull CompletableFuture result();
+
+ default void complete(Function, T> function) {
+ var future = result();
+ try {
+ future.complete(function.apply(this));
+ } catch (Throwable e) {
+ future.completeExceptionally(e);
+ }
+ }
+ }
+}
diff --git a/scheduler/src/main/java/com/volmit/iris/util/scheduling/paper/PaperPlatform.java b/scheduler/src/main/java/com/volmit/iris/util/scheduling/paper/PaperPlatform.java
new file mode 100644
index 000000000..53e7bdb2e
--- /dev/null
+++ b/scheduler/src/main/java/com/volmit/iris/util/scheduling/paper/PaperPlatform.java
@@ -0,0 +1,107 @@
+package com.volmit.iris.util.scheduling.paper;
+
+import com.volmit.iris.util.scheduling.Platform;
+import com.volmit.iris.util.scheduling.paper.split.PaperAsyncScheduler;
+import com.volmit.iris.util.scheduling.paper.split.PaperEntityScheduler;
+import com.volmit.iris.util.scheduling.paper.split.PaperGlobalScheduler;
+import com.volmit.iris.util.scheduling.paper.split.PaperRegionScheduler;
+import com.volmit.iris.util.scheduling.split.IAsyncScheduler;
+import com.volmit.iris.util.scheduling.split.IEntityScheduler;
+import com.volmit.iris.util.scheduling.split.IGlobalScheduler;
+import com.volmit.iris.util.scheduling.split.IRegionScheduler;
+import org.bukkit.Location;
+import org.bukkit.Server;
+import org.bukkit.World;
+import org.bukkit.block.Block;
+import org.bukkit.entity.Entity;
+import org.bukkit.event.player.PlayerTeleportEvent;
+import org.bukkit.plugin.Plugin;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BooleanSupplier;
+
+public class PaperPlatform implements Platform {
+ private final Plugin plugin;
+ private final Server server;
+ private final IAsyncScheduler async;
+ private final IGlobalScheduler global;
+ private final IRegionScheduler region;
+ private final BooleanSupplier globalTickThread;
+
+ public PaperPlatform(@NotNull Plugin plugin) {
+ this.plugin = plugin;
+ this.server = plugin.getServer();
+ async = new PaperAsyncScheduler(plugin, server.getAsyncScheduler());
+ global = new PaperGlobalScheduler(plugin, server.getGlobalRegionScheduler());
+ region = new PaperRegionScheduler(plugin, server.getRegionScheduler());
+
+ BooleanSupplier method;
+ try {
+ method = server::isGlobalTickThread;
+ } catch (Throwable e) {
+ method = server::isPrimaryThread;
+ }
+ globalTickThread = method;
+ }
+
+ @Override
+ public boolean isOwnedByCurrentRegion(@NotNull Location location) {
+ return server.isOwnedByCurrentRegion(location);
+ }
+
+ @Override
+ public boolean isOwnedByCurrentRegion(@NotNull Location location, int squareRadiusChunks) {
+ return server.isOwnedByCurrentRegion(location, squareRadiusChunks);
+ }
+
+ @Override
+ public boolean isOwnedByCurrentRegion(@NotNull Block block) {
+ return server.isOwnedByCurrentRegion(block);
+ }
+
+ @Override
+ public boolean isOwnedByCurrentRegion(@NotNull World world, int chunkX, int chunkZ) {
+ return server.isOwnedByCurrentRegion(world, chunkX, chunkZ);
+ }
+
+ @Override
+ public boolean isOwnedByCurrentRegion(@NotNull World world, int chunkX, int chunkZ, int squareRadiusChunks) {
+ return server.isOwnedByCurrentRegion(world, chunkX, chunkZ, squareRadiusChunks);
+ }
+
+ @Override
+ public boolean isOwnedByCurrentRegion(@NotNull Entity entity) {
+ return server.isOwnedByCurrentRegion(entity);
+ }
+
+ @Override
+ public boolean isGlobalTickThread() {
+ return globalTickThread.getAsBoolean();
+ }
+
+ @Override
+ public @NotNull IAsyncScheduler async() {
+ return async;
+ }
+
+ @Override
+ public @NotNull IEntityScheduler entity(@NotNull Entity entity) {
+ return new PaperEntityScheduler(plugin, entity.getScheduler());
+ }
+
+ @Override
+ public @NotNull IGlobalScheduler global() {
+ return global;
+ }
+
+ @Override
+ public @NotNull IRegionScheduler region() {
+ return region;
+ }
+
+ @Override
+ public @NotNull CompletableFuture<@NotNull Boolean> teleportAsync(@NotNull Entity entity, @NotNull Location location, PlayerTeleportEvent.@NotNull TeleportCause cause) {
+ return entity.teleportAsync(location, cause);
+ }
+}
diff --git a/scheduler/src/main/java/com/volmit/iris/util/scheduling/paper/PaperTask.java b/scheduler/src/main/java/com/volmit/iris/util/scheduling/paper/PaperTask.java
new file mode 100644
index 000000000..ea90b8371
--- /dev/null
+++ b/scheduler/src/main/java/com/volmit/iris/util/scheduling/paper/PaperTask.java
@@ -0,0 +1,59 @@
+package com.volmit.iris.util.scheduling.paper;
+
+import com.volmit.iris.util.scheduling.Task;
+import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
+import org.bukkit.plugin.Plugin;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.concurrent.CompletableFuture;
+
+public class PaperTask implements Task {
+ protected final ScheduledTask task;
+ protected final boolean async;
+
+ public PaperTask(@NotNull ScheduledTask task, boolean async) {
+ this.task = task;
+ this.async = async;
+ }
+
+ @Override
+ public void cancel() {
+ task.cancel();
+ }
+
+ @Override
+ public boolean cancelled() {
+ return task.isCancelled();
+ }
+
+ @Override
+ public @NotNull Plugin owner() {
+ return task.getOwningPlugin();
+ }
+
+ @Override
+ public boolean async() {
+ return async;
+ }
+
+ public static class Completable extends PaperTask implements Task.Completable {
+ private final CompletableFuture result = new CompletableFuture<>();
+
+ public Completable(@NotNull ScheduledTask task, boolean async) {
+ super(task, async);
+ }
+
+ @Override
+ public void cancel() {
+ ScheduledTask.CancelledState cancel = task.cancel();
+ if (cancel == ScheduledTask.CancelledState.CANCELLED_BY_CALLER || cancel == ScheduledTask.CancelledState.NEXT_RUNS_CANCELLED || cancel == ScheduledTask.CancelledState.CANCELLED_ALREADY) {
+ result.cancel(false);
+ }
+ }
+
+ @Override
+ public @NotNull CompletableFuture result() {
+ return result;
+ }
+ }
+}
diff --git a/scheduler/src/main/java/com/volmit/iris/util/scheduling/paper/split/PaperAsyncScheduler.java b/scheduler/src/main/java/com/volmit/iris/util/scheduling/paper/split/PaperAsyncScheduler.java
new file mode 100644
index 000000000..f35b644b6
--- /dev/null
+++ b/scheduler/src/main/java/com/volmit/iris/util/scheduling/paper/split/PaperAsyncScheduler.java
@@ -0,0 +1,48 @@
+package com.volmit.iris.util.scheduling.paper.split;
+
+import com.volmit.iris.util.scheduling.Ref;
+import com.volmit.iris.util.scheduling.Task;
+import com.volmit.iris.util.scheduling.Task.Completable;
+import com.volmit.iris.util.scheduling.paper.PaperTask;
+import com.volmit.iris.util.scheduling.split.IAsyncScheduler;
+import io.papermc.paper.threadedregions.scheduler.AsyncScheduler;
+import org.bukkit.plugin.Plugin;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Range;
+
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class PaperAsyncScheduler implements IAsyncScheduler {
+ private final Plugin plugin;
+ private final AsyncScheduler scheduler;
+
+ public PaperAsyncScheduler(Plugin plugin, AsyncScheduler scheduler) {
+ this.plugin = plugin;
+ this.scheduler = scheduler;
+ }
+
+ @Override
+ public @NotNull Completable run(@NotNull Function, R> task) {
+ Ref> ref = new Ref<>();
+ return ref.value = new PaperTask.Completable<>(scheduler.runNow(plugin, t -> ref.value.complete(task)), true);
+ }
+
+ @Override
+ public @NotNull Completable runDelayed(@NotNull Function, R> task,
+ @Range(from = 1, to = Long.MAX_VALUE) long delay,
+ @NotNull TimeUnit unit) {
+ Ref> ref = new Ref<>();
+ return ref.value = new PaperTask.Completable<>(scheduler.runDelayed(plugin, t -> ref.value.complete(task), delay, unit), true);
+ }
+
+ @Override
+ public @NotNull Task runAtFixedRate(@NotNull Consumer task,
+ @Range(from = 1, to = Long.MAX_VALUE) long initialDelay,
+ @Range(from = 1, to = Long.MAX_VALUE) long period,
+ @NotNull TimeUnit unit) {
+ Ref ref = new Ref<>();
+ return ref.value = new PaperTask(scheduler.runAtFixedRate(plugin, t -> task.accept(ref.value), initialDelay, period, unit), true);
+ }
+}
diff --git a/scheduler/src/main/java/com/volmit/iris/util/scheduling/paper/split/PaperEntityScheduler.java b/scheduler/src/main/java/com/volmit/iris/util/scheduling/paper/split/PaperEntityScheduler.java
new file mode 100644
index 000000000..4748398db
--- /dev/null
+++ b/scheduler/src/main/java/com/volmit/iris/util/scheduling/paper/split/PaperEntityScheduler.java
@@ -0,0 +1,49 @@
+package com.volmit.iris.util.scheduling.paper.split;
+
+import com.volmit.iris.util.scheduling.Ref;
+import com.volmit.iris.util.scheduling.Task;
+import com.volmit.iris.util.scheduling.Task.Completable;
+import com.volmit.iris.util.scheduling.paper.PaperTask;
+import com.volmit.iris.util.scheduling.split.IEntityScheduler;
+import io.papermc.paper.threadedregions.scheduler.EntityScheduler;
+import org.bukkit.plugin.Plugin;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.Range;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class PaperEntityScheduler implements IEntityScheduler {
+ private final Plugin plugin;
+ private final EntityScheduler scheduler;
+
+ public PaperEntityScheduler(Plugin plugin, EntityScheduler scheduler) {
+ this.plugin = plugin;
+ this.scheduler = scheduler;
+ }
+
+ @Override
+ public @Nullable Completable runDelayed(@NotNull Function, R> task,
+ @Nullable Runnable retired,
+ @Range(from = 1, to = Long.MAX_VALUE) long delayTicks) {
+ Ref> ref = new Ref<>();
+ var raw = scheduler.runDelayed(plugin, t -> ref.value.complete(task), () -> {
+ if (retired != null) retired.run();
+ ref.value.cancel();
+ }, delayTicks);
+ if (raw == null) return null;
+ return ref.value = new PaperTask.Completable<>(raw, false);
+ }
+
+ @Override
+ public @Nullable Task runAtFixedRate(@NotNull Consumer task,
+ @Nullable Runnable retired,
+ @Range(from = 1, to = Long.MAX_VALUE) long initialDelayTicks,
+ @Range(from = 1, to = Long.MAX_VALUE) long periodTicks) {
+ Ref ref = new Ref<>();
+ var raw = scheduler.runAtFixedRate(plugin, t -> task.accept(ref.value), retired, initialDelayTicks, periodTicks);
+ if (raw == null) return null;
+ return ref.value = new PaperTask(raw, false);
+ }
+}
diff --git a/scheduler/src/main/java/com/volmit/iris/util/scheduling/paper/split/PaperGlobalScheduler.java b/scheduler/src/main/java/com/volmit/iris/util/scheduling/paper/split/PaperGlobalScheduler.java
new file mode 100644
index 000000000..de369951f
--- /dev/null
+++ b/scheduler/src/main/java/com/volmit/iris/util/scheduling/paper/split/PaperGlobalScheduler.java
@@ -0,0 +1,39 @@
+package com.volmit.iris.util.scheduling.paper.split;
+
+import com.volmit.iris.util.scheduling.Ref;
+import com.volmit.iris.util.scheduling.Task;
+import com.volmit.iris.util.scheduling.Task.Completable;
+import com.volmit.iris.util.scheduling.paper.PaperTask;
+import com.volmit.iris.util.scheduling.split.IGlobalScheduler;
+import io.papermc.paper.threadedregions.scheduler.GlobalRegionScheduler;
+import org.bukkit.plugin.Plugin;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Range;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class PaperGlobalScheduler implements IGlobalScheduler {
+ private final Plugin plugin;
+ private final GlobalRegionScheduler scheduler;
+
+ public PaperGlobalScheduler(Plugin plugin, GlobalRegionScheduler scheduler) {
+ this.plugin = plugin;
+ this.scheduler = scheduler;
+ }
+
+ @Override
+ public @NotNull Completable runDelayed(@NotNull Function, R> task,
+ @Range(from = 1, to = Long.MAX_VALUE) long delayTicks) {
+ Ref> ref = new Ref<>();
+ return ref.value = new PaperTask.Completable<>(scheduler.runDelayed(plugin, t -> ref.value.complete(task), delayTicks), false);
+ }
+
+ @Override
+ public @NotNull Task runAtFixedRate(@NotNull Consumer task,
+ @Range(from = 1, to = Long.MAX_VALUE) long initialDelayTicks,
+ @Range(from = 1, to = Long.MAX_VALUE) long periodTicks) {
+ Ref ref = new Ref<>();
+ return ref.value = new PaperTask(scheduler.runAtFixedRate(plugin, t -> task.accept(ref.value), initialDelayTicks, periodTicks), false);
+ }
+}
diff --git a/scheduler/src/main/java/com/volmit/iris/util/scheduling/paper/split/PaperRegionScheduler.java b/scheduler/src/main/java/com/volmit/iris/util/scheduling/paper/split/PaperRegionScheduler.java
new file mode 100644
index 000000000..7ac140b2f
--- /dev/null
+++ b/scheduler/src/main/java/com/volmit/iris/util/scheduling/paper/split/PaperRegionScheduler.java
@@ -0,0 +1,45 @@
+package com.volmit.iris.util.scheduling.paper.split;
+
+import com.volmit.iris.util.scheduling.Ref;
+import com.volmit.iris.util.scheduling.Task;
+import com.volmit.iris.util.scheduling.Task.Completable;
+import com.volmit.iris.util.scheduling.paper.PaperTask;
+import com.volmit.iris.util.scheduling.split.IRegionScheduler;
+import io.papermc.paper.threadedregions.scheduler.RegionScheduler;
+import org.bukkit.World;
+import org.bukkit.plugin.Plugin;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Range;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class PaperRegionScheduler implements IRegionScheduler {
+ private final Plugin plugin;
+ private final RegionScheduler scheduler;
+
+ public PaperRegionScheduler(Plugin plugin, RegionScheduler scheduler) {
+ this.plugin = plugin;
+ this.scheduler = scheduler;
+ }
+
+ @Override
+ public @NotNull Completable runDelayed(@NotNull World world,
+ int chunkX,
+ int chunkZ,
+ @NotNull Function, R> task, @Range(from = 1, to = Long.MAX_VALUE) long delayTicks) {
+ Ref> ref = new Ref<>();
+ return ref.value = new PaperTask.Completable<>(scheduler.runDelayed(plugin, world, chunkX, chunkZ, t -> ref.value.complete(task), delayTicks), false);
+ }
+
+ @Override
+ public @NotNull Task runAtFixedRate(@NotNull World world,
+ int chunkX,
+ int chunkZ,
+ @NotNull Consumer task,
+ @Range(from = 1, to = Long.MAX_VALUE) long initialDelayTicks,
+ @Range(from = 1, to = Long.MAX_VALUE) long periodTicks) {
+ Ref ref = new Ref<>();
+ return ref.value = new PaperTask(scheduler.runAtFixedRate(plugin, world, chunkX, chunkZ, t -> task.accept(ref.value), initialDelayTicks, periodTicks), false);
+ }
+}
diff --git a/scheduler/src/main/java/com/volmit/iris/util/scheduling/spigot/SpigotPlatform.java b/scheduler/src/main/java/com/volmit/iris/util/scheduling/spigot/SpigotPlatform.java
new file mode 100644
index 000000000..22f84dae9
--- /dev/null
+++ b/scheduler/src/main/java/com/volmit/iris/util/scheduling/spigot/SpigotPlatform.java
@@ -0,0 +1,105 @@
+package com.volmit.iris.util.scheduling.spigot;
+
+import com.volmit.iris.util.scheduling.Platform;
+import com.volmit.iris.util.scheduling.spigot.split.SpigotAsyncScheduler;
+import com.volmit.iris.util.scheduling.spigot.split.SpigotEntityScheduler;
+import com.volmit.iris.util.scheduling.spigot.split.SpigotGlobalScheduler;
+import com.volmit.iris.util.scheduling.spigot.split.SpigotRegionScheduler;
+import com.volmit.iris.util.scheduling.split.IAsyncScheduler;
+import com.volmit.iris.util.scheduling.split.IEntityScheduler;
+import com.volmit.iris.util.scheduling.split.IGlobalScheduler;
+import com.volmit.iris.util.scheduling.split.IRegionScheduler;
+import org.bukkit.Location;
+import org.bukkit.Server;
+import org.bukkit.World;
+import org.bukkit.block.Block;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.entity.Projectile;
+import org.bukkit.event.player.PlayerTeleportEvent;
+import org.bukkit.plugin.Plugin;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.concurrent.CompletableFuture;
+
+public class SpigotPlatform implements Platform {
+ private final Server server;
+ private final IAsyncScheduler async;
+ private final IGlobalScheduler global;
+ private final IRegionScheduler region;
+
+ public SpigotPlatform(@NotNull Plugin plugin) {
+ server = plugin.getServer();
+ var scheduler = server.getScheduler();
+ async = new SpigotAsyncScheduler(plugin, scheduler);
+ global = new SpigotGlobalScheduler(plugin, scheduler);
+ region = new SpigotRegionScheduler(global);
+ }
+
+ @Override
+ public boolean isOwnedByCurrentRegion(@NotNull Location location) {
+ return server.isPrimaryThread();
+ }
+
+ @Override
+ public boolean isOwnedByCurrentRegion(@NotNull Location location, int squareRadiusChunks) {
+ return server.isPrimaryThread();
+ }
+
+ @Override
+ public boolean isOwnedByCurrentRegion(@NotNull Block block) {
+ return server.isPrimaryThread();
+ }
+
+ @Override
+ public boolean isOwnedByCurrentRegion(@NotNull World world, int chunkX, int chunkZ) {
+ return server.isPrimaryThread();
+ }
+
+ @Override
+ public boolean isOwnedByCurrentRegion(@NotNull World world, int chunkX, int chunkZ, int squareRadiusChunks) {
+ return server.isPrimaryThread();
+ }
+
+ @Override
+ public boolean isOwnedByCurrentRegion(@NotNull Entity entity) {
+ return server.isPrimaryThread();
+ }
+
+ @Override
+ public boolean isGlobalTickThread() {
+ return server.isPrimaryThread();
+ }
+
+ @Override
+ public @NotNull IAsyncScheduler async() {
+ return async;
+ }
+
+ @Override
+ public @NotNull IEntityScheduler entity(@NotNull Entity entity) {
+ return new SpigotEntityScheduler(global, entity);
+ }
+
+ @Override
+ public @NotNull IGlobalScheduler global() {
+ return global;
+ }
+
+ @Override
+ public @NotNull IRegionScheduler region() {
+ return region;
+ }
+
+ @Override
+ public @NotNull CompletableFuture<@NotNull Boolean> teleportAsync(@NotNull Entity entity, @NotNull Location location, PlayerTeleportEvent.@NotNull TeleportCause cause) {
+ return global().run(task -> isValid(entity) && entity.teleport(location)).result().thenApply(b -> b != null ? b : false);
+ }
+
+ public static boolean isValid(Entity entity) {
+ if (entity.isValid()) {
+ return !(entity instanceof Player) || ((Player) entity).isOnline();
+ }
+ return entity instanceof Projectile && !entity.isDead();
+ }
+}
diff --git a/scheduler/src/main/java/com/volmit/iris/util/scheduling/spigot/SpigotTask.java b/scheduler/src/main/java/com/volmit/iris/util/scheduling/spigot/SpigotTask.java
new file mode 100644
index 000000000..3991b1f22
--- /dev/null
+++ b/scheduler/src/main/java/com/volmit/iris/util/scheduling/spigot/SpigotTask.java
@@ -0,0 +1,60 @@
+package com.volmit.iris.util.scheduling.spigot;
+
+import com.volmit.iris.util.scheduling.Task;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.scheduler.BukkitScheduler;
+import org.bukkit.scheduler.BukkitTask;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.concurrent.CompletableFuture;
+
+public class SpigotTask implements Task {
+ protected final BukkitTask task;
+
+ public SpigotTask(@NotNull BukkitTask task) {
+ this.task = task;
+ }
+
+ @Override
+ public void cancel() {
+ task.cancel();
+ }
+
+ @Override
+ public boolean cancelled() {
+ return task.isCancelled();
+ }
+
+ @Override
+ public @NotNull Plugin owner() {
+ return task.getOwner();
+ }
+
+ @Override
+ public boolean async() {
+ return !task.isSync();
+ }
+
+ @SuppressWarnings("deprecation")
+ public static class Completable extends SpigotTask implements Task.Completable {
+ private final CompletableFuture result = new CompletableFuture<>();
+ private final BukkitScheduler scheduler;
+
+ public Completable(@NotNull BukkitTask task, @NotNull BukkitScheduler scheduler) {
+ super(task);
+ this.scheduler = scheduler;
+ }
+
+ @Override
+ public void cancel() {
+ scheduler.cancelTask(task.getTaskId());
+ if (scheduler.isCurrentlyRunning(task.getTaskId())) return;
+ result.cancel(false);
+ }
+
+ @Override
+ public @NotNull CompletableFuture result() {
+ return result;
+ }
+ }
+}
diff --git a/scheduler/src/main/java/com/volmit/iris/util/scheduling/spigot/split/SpigotAsyncScheduler.java b/scheduler/src/main/java/com/volmit/iris/util/scheduling/spigot/split/SpigotAsyncScheduler.java
new file mode 100644
index 000000000..ea474f377
--- /dev/null
+++ b/scheduler/src/main/java/com/volmit/iris/util/scheduling/spigot/split/SpigotAsyncScheduler.java
@@ -0,0 +1,44 @@
+package com.volmit.iris.util.scheduling.spigot.split;
+
+import com.volmit.iris.util.scheduling.Ref;
+import com.volmit.iris.util.scheduling.Task;
+import com.volmit.iris.util.scheduling.Task.Completable;
+import com.volmit.iris.util.scheduling.spigot.SpigotTask;
+import com.volmit.iris.util.scheduling.split.IAsyncScheduler;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.scheduler.BukkitScheduler;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Range;
+
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+@SuppressWarnings("deprecation")
+public class SpigotAsyncScheduler implements IAsyncScheduler {
+ private final Plugin plugin;
+ private final BukkitScheduler scheduler;
+
+ public SpigotAsyncScheduler(Plugin plugin, BukkitScheduler scheduler) {
+ this.plugin = plugin;
+ this.scheduler = scheduler;
+ }
+
+ @Override
+ public @NotNull Completable run(@NotNull Function, R> task) {
+ Ref> ref = new Ref<>();
+ return ref.value = new SpigotTask.Completable<>(scheduler.runTaskAsynchronously(plugin, () -> ref.value.complete(task)), scheduler);
+ }
+
+ @Override
+ public @NotNull Completable runDelayed(@NotNull Function, R> task, @Range(from = 0, to = Long.MAX_VALUE) long delay, @NotNull TimeUnit unit) {
+ Ref> ref = new Ref<>();
+ return ref.value = new SpigotTask.Completable<>(scheduler.runTaskLaterAsynchronously(plugin, () -> ref.value.complete(task), unit.toMillis(delay) / 50), scheduler);
+ }
+
+ @Override
+ public @NotNull Task runAtFixedRate(@NotNull Consumer task, @Range(from = 0, to = Long.MAX_VALUE) long initialDelay, @Range(from = 0, to = Long.MAX_VALUE) long period, @NotNull TimeUnit unit) {
+ Ref ref = new Ref<>();
+ return ref.value = new SpigotTask(scheduler.runTaskTimerAsynchronously(plugin, () -> task.accept(ref.value), unit.toMillis(initialDelay) / 50, unit.toMillis(period) / 50));
+ }
+}
diff --git a/scheduler/src/main/java/com/volmit/iris/util/scheduling/spigot/split/SpigotEntityScheduler.java b/scheduler/src/main/java/com/volmit/iris/util/scheduling/spigot/split/SpigotEntityScheduler.java
new file mode 100644
index 000000000..45d390354
--- /dev/null
+++ b/scheduler/src/main/java/com/volmit/iris/util/scheduling/spigot/split/SpigotEntityScheduler.java
@@ -0,0 +1,53 @@
+package com.volmit.iris.util.scheduling.spigot.split;
+
+import com.volmit.iris.util.scheduling.Task;
+import com.volmit.iris.util.scheduling.Task.Completable;
+import com.volmit.iris.util.scheduling.split.IEntityScheduler;
+import com.volmit.iris.util.scheduling.split.IGlobalScheduler;
+import org.bukkit.entity.Entity;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.Range;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import static com.volmit.iris.util.scheduling.spigot.SpigotPlatform.isValid;
+
+public class SpigotEntityScheduler implements IEntityScheduler {
+ private final IGlobalScheduler scheduler;
+ private final Entity entity;
+
+ public SpigotEntityScheduler(IGlobalScheduler scheduler, Entity entity) {
+ this.scheduler = scheduler;
+ this.entity = entity;
+ }
+
+ @Override
+ public @Nullable Completable runDelayed(@NotNull Function, R> task, @Nullable Runnable retired, @Range(from = 1, to = Long.MAX_VALUE) long delayTicks) {
+ if (!isValid(entity)) return null;
+ return scheduler.runDelayed(t -> {
+ if (!isValid(entity)) {
+ t.cancel();
+ if (retired != null)
+ retired.run();
+ return null;
+ }
+ return task.apply(t);
+ }, delayTicks);
+ }
+
+ @Override
+ public @Nullable Task runAtFixedRate(@NotNull Consumer task, @Nullable Runnable retired, @Range(from = 1, to = Long.MAX_VALUE) long initialDelayTicks, @Range(from = 1, to = Long.MAX_VALUE) long periodTicks) {
+ if (!isValid(entity)) return null;
+ return scheduler.runAtFixedRate(t -> {
+ if (!isValid(entity)) {
+ t.cancel();
+ if (retired != null)
+ retired.run();
+ return;
+ }
+ task.accept(t);
+ }, initialDelayTicks, periodTicks);
+ }
+}
diff --git a/scheduler/src/main/java/com/volmit/iris/util/scheduling/spigot/split/SpigotGlobalScheduler.java b/scheduler/src/main/java/com/volmit/iris/util/scheduling/spigot/split/SpigotGlobalScheduler.java
new file mode 100644
index 000000000..9b82eba56
--- /dev/null
+++ b/scheduler/src/main/java/com/volmit/iris/util/scheduling/spigot/split/SpigotGlobalScheduler.java
@@ -0,0 +1,37 @@
+package com.volmit.iris.util.scheduling.spigot.split;
+
+import com.volmit.iris.util.scheduling.Ref;
+import com.volmit.iris.util.scheduling.Task;
+import com.volmit.iris.util.scheduling.Task.Completable;
+import com.volmit.iris.util.scheduling.spigot.SpigotTask;
+import com.volmit.iris.util.scheduling.split.IGlobalScheduler;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.scheduler.BukkitScheduler;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Range;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+@SuppressWarnings("deprecation")
+public class SpigotGlobalScheduler implements IGlobalScheduler {
+ private final Plugin plugin;
+ private final BukkitScheduler scheduler;
+
+ public SpigotGlobalScheduler(Plugin plugin, BukkitScheduler scheduler) {
+ this.plugin = plugin;
+ this.scheduler = scheduler;
+ }
+
+ @Override
+ public @NotNull Completable runDelayed(@NotNull Function, R> task, @Range(from = 1, to = Long.MAX_VALUE) long delayTicks) {
+ Ref> ref = new Ref<>();
+ return ref.value = new SpigotTask.Completable<>(scheduler.runTaskLater(plugin, () -> ref.value.complete(task), delayTicks), scheduler);
+ }
+
+ @Override
+ public @NotNull Task runAtFixedRate(@NotNull Consumer task, @Range(from = 1, to = Long.MAX_VALUE) long initialDelayTicks, @Range(from = 1, to = Long.MAX_VALUE) long periodTicks) {
+ Ref ref = new Ref<>();
+ return ref.value = new SpigotTask(scheduler.runTaskTimer(plugin, () -> task.accept(ref.value), initialDelayTicks, periodTicks));
+ }
+}
diff --git a/scheduler/src/main/java/com/volmit/iris/util/scheduling/spigot/split/SpigotRegionScheduler.java b/scheduler/src/main/java/com/volmit/iris/util/scheduling/spigot/split/SpigotRegionScheduler.java
new file mode 100644
index 000000000..75c83e58e
--- /dev/null
+++ b/scheduler/src/main/java/com/volmit/iris/util/scheduling/spigot/split/SpigotRegionScheduler.java
@@ -0,0 +1,30 @@
+package com.volmit.iris.util.scheduling.spigot.split;
+
+import com.volmit.iris.util.scheduling.Task;
+import com.volmit.iris.util.scheduling.Task.Completable;
+import com.volmit.iris.util.scheduling.split.IGlobalScheduler;
+import com.volmit.iris.util.scheduling.split.IRegionScheduler;
+import org.bukkit.World;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Range;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class SpigotRegionScheduler implements IRegionScheduler {
+ private final IGlobalScheduler scheduler;
+
+ public SpigotRegionScheduler(IGlobalScheduler scheduler) {
+ this.scheduler = scheduler;
+ }
+
+ @Override
+ public @NotNull Completable runDelayed(@NotNull World world, int chunkX, int chunkZ, @NotNull Function, R> task, @Range(from = 1, to = Long.MAX_VALUE) long delayTicks) {
+ return scheduler.runDelayed(task, delayTicks);
+ }
+
+ @Override
+ public @NotNull Task runAtFixedRate(@NotNull World world, int chunkX, int chunkZ, @NotNull Consumer task, @Range(from = 1, to = Long.MAX_VALUE) long initialDelayTicks, @Range(from = 1, to = Long.MAX_VALUE) long periodTicks) {
+ return scheduler.runAtFixedRate(task, initialDelayTicks, periodTicks);
+ }
+}
diff --git a/scheduler/src/main/java/com/volmit/iris/util/scheduling/split/IAsyncScheduler.java b/scheduler/src/main/java/com/volmit/iris/util/scheduling/split/IAsyncScheduler.java
new file mode 100644
index 000000000..d4b91cf80
--- /dev/null
+++ b/scheduler/src/main/java/com/volmit/iris/util/scheduling/split/IAsyncScheduler.java
@@ -0,0 +1,75 @@
+package com.volmit.iris.util.scheduling.split;
+
+import com.volmit.iris.util.scheduling.Task;
+import com.volmit.iris.util.scheduling.Task.Completable;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Range;
+
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * Scheduler that may be used by plugins to schedule tasks to execute asynchronously from the server tick process.
+ */
+public interface IAsyncScheduler {
+ /**
+ * Schedules the specified task to be executed asynchronously immediately.
+ * @param task Specified task.
+ * @return The {@link Task} that represents the scheduled task.
+ */
+ default @NotNull Task run(@NotNull Consumer task) {
+ return run(t -> {
+ task.accept(t);
+ return null;
+ });
+ }
+
+ /**
+ * Schedules the specified task to be executed asynchronously immediately.
+ * @param task Specified task.
+ * @return The {@link Completable} that represents the scheduled task.
+ */
+ @NotNull Completable run(@NotNull Function, R> task);
+
+ /**
+ * Schedules the specified task to be executed asynchronously after the time delay has passed.
+ * @param task Specified task.
+ * @param delay The time delay to pass before the task should be executed.
+ * @param unit The time unit for the time delay.
+ * @return The {@link Task} that represents the scheduled task.
+ */
+ default @NotNull Task runDelayed(@NotNull Consumer task,
+ @Range(from = 0, to = Long.MAX_VALUE) long delay,
+ @NotNull TimeUnit unit) {
+ return runDelayed(t -> {
+ task.accept(t);
+ return null;
+ }, delay, unit);
+ }
+
+ /**
+ * Schedules the specified task to be executed asynchronously after the time delay has passed.
+ * @param task Specified task.
+ * @param delay The time delay to pass before the task should be executed.
+ * @param unit The time unit for the time delay.
+ * @return The {@link Completable} that represents the scheduled task.
+ */
+ @NotNull Completable runDelayed(@NotNull Function, R> task,
+ @Range(from = 0, to = Long.MAX_VALUE) long delay,
+ @NotNull TimeUnit unit);
+
+ /**
+ * Schedules the specified task to be executed asynchronously after the initial delay has passed,
+ * and then periodically executed with the specified period.
+ * @param task Specified task.
+ * @param initialDelay The time delay to pass before the first execution of the task.
+ * @param period The time between task executions after the first execution of the task.
+ * @param unit The time unit for the initial delay and period.
+ * @return The {@link Task} that represents the scheduled task.
+ */
+ @NotNull Task runAtFixedRate(@NotNull Consumer task,
+ @Range(from = 0, to = Long.MAX_VALUE) long initialDelay,
+ @Range(from = 0, to = Long.MAX_VALUE) long period,
+ @NotNull TimeUnit unit);
+}
diff --git a/scheduler/src/main/java/com/volmit/iris/util/scheduling/split/IEntityScheduler.java b/scheduler/src/main/java/com/volmit/iris/util/scheduling/split/IEntityScheduler.java
new file mode 100644
index 000000000..0bfc22124
--- /dev/null
+++ b/scheduler/src/main/java/com/volmit/iris/util/scheduling/split/IEntityScheduler.java
@@ -0,0 +1,136 @@
+package com.volmit.iris.util.scheduling.split;
+
+import com.volmit.iris.util.scheduling.Task;
+import com.volmit.iris.util.scheduling.Task.Completable;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.Range;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * 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 IEntityScheduler {
+
+ /**
+ * Schedules a task to execute on the next tick. If the task failed to schedule because the scheduler is retired (entity
+ * removed), then returns {@code null}. Otherwise, either the task 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 task The task to execute
+ * @param retired Retire callback to run if the entity is retired before the run callback can be invoked, may be null.
+ * @return The {@link Task} that represents the scheduled task, or {@code null} if the entity has been removed.
+ */
+ default @Nullable Task run(@NotNull Consumer task,
+ @Nullable Runnable retired) {
+ return run(t -> {
+ task.accept(t);
+ return null;
+ }, retired);
+ }
+
+ /**
+ * Schedules a task to execute on the next tick. If the task failed to schedule because the scheduler is retired (entity
+ * removed), then returns {@code null}. Otherwise, either the task 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 task The task to execute
+ * @param retired Retire callback to run if the entity is retired before the run callback can be invoked, may be null.
+ * @return The {@link Completable} that represents the scheduled task, or {@code null} if the entity has been removed.
+ */
+ default @Nullable Completable run(@NotNull Function, R> task,
+ @Nullable Runnable retired) {
+ return runDelayed(task, retired, 1);
+ }
+
+ /**
+ * Schedules a task with the given delay. If the task failed to schedule because the scheduler is retired (entity
+ * removed), then returns {@code null}. Otherwise, either the task 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 task The task to execute
+ * @param retired Retire callback to run if the entity is retired before the run callback can be invoked, may be null.
+ * @param delayTicks The delay, in ticks.
+ * @return The {@link Task} that represents the scheduled task, or {@code null} if the entity has been removed.
+ */
+ default @Nullable Task runDelayed(@NotNull Consumer task,
+ @Nullable Runnable retired,
+ @Range(from = 1, to = Long.MAX_VALUE) long delayTicks) {
+ return runDelayed(t -> {
+ task.accept(t);
+ return null;
+ }, retired, delayTicks);
+ }
+
+ /**
+ * Schedules a task with the given delay. If the task failed to schedule because the scheduler is retired (entity
+ * removed), then returns {@code null}. Otherwise, either the task 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 task The task to execute
+ * @param retired Retire callback to run if the entity is retired before the run callback can be invoked, may be null.
+ * @param delayTicks The delay, in ticks.
+ * @return The {@link Completable} that represents the scheduled task, or {@code null} if the entity has been removed.
+ */
+ @Nullable Completable runDelayed(@NotNull Function, R> task,
+ @Nullable Runnable retired,
+ @Range(from = 1, to = Long.MAX_VALUE) long delayTicks);
+
+ /**
+ * Schedules a repeating task with the given delay and period. If the task failed to schedule because the scheduler
+ * is retired (entity removed), then returns {@code null}. Otherwise, either the task 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 task The task to execute
+ * @param retired Retire callback to run if the entity is retired before the run callback can be invoked, may be null.
+ * @param initialDelayTicks The initial delay, in ticks.
+ * @param periodTicks The period, in ticks.
+ * @return The {@link Task} that represents the scheduled task, or {@code null} if the entity has been removed.
+ */
+ @Nullable Task runAtFixedRate(@NotNull Consumer task,
+ @Nullable Runnable retired,
+ @Range(from = 1, to = Long.MAX_VALUE) long initialDelayTicks,
+ @Range(from = 1, to = Long.MAX_VALUE) long periodTicks);
+}
diff --git a/scheduler/src/main/java/com/volmit/iris/util/scheduling/split/IGlobalScheduler.java b/scheduler/src/main/java/com/volmit/iris/util/scheduling/split/IGlobalScheduler.java
new file mode 100644
index 000000000..9dea29cb8
--- /dev/null
+++ b/scheduler/src/main/java/com/volmit/iris/util/scheduling/split/IGlobalScheduler.java
@@ -0,0 +1,74 @@
+package com.volmit.iris.util.scheduling.split;
+
+import com.volmit.iris.util.scheduling.Task;
+import com.volmit.iris.util.scheduling.Task.Completable;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Range;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * The global region task scheduler may be used to schedule tasks that will execute on the global region.
+ *
+ * The global region is responsible for maintaining world day time, world game time, weather cycle,
+ * sleep night skipping, executing commands for console, and other misc. tasks that do not belong to any specific region.
+ *
+ */
+public interface IGlobalScheduler {
+ /**
+ * Schedules a task to be executed on the global region on the next tick.
+ * @param task The task to execute
+ * @return The {@link Task} that represents the scheduled task.
+ */
+ default @NotNull Task run(@NotNull Consumer task) {
+ return run(t -> {
+ task.accept(t);
+ return null;
+ });
+ }
+
+ /**
+ * Schedules a task to be executed on the global region on the next tick.
+ * @param task The task to execute
+ * @return The {@link Completable} that represents the scheduled task.
+ */
+ default @NotNull Completable run(@NotNull Function, R> task) {
+ return runDelayed(task, 1);
+ }
+
+ /**
+ * Schedules a task to be executed on the global region after the specified delay in ticks.
+ * @param task The task to execute
+ * @param delayTicks The delay, in ticks.
+ * @return The {@link Task} that represents the scheduled task.
+ */
+ default @NotNull Task runDelayed(@NotNull Consumer task,
+ @Range(from = 1, to = Long.MAX_VALUE) long delayTicks) {
+ return runDelayed(t -> {
+ task.accept(t);
+ return null;
+ }, delayTicks);
+ }
+
+ /**
+ * Schedules a task to be executed on the global region after the specified delay in ticks.
+ * @param task The task to execute
+ * @param delayTicks The delay, in ticks.
+ * @return The {@link Completable} that represents the scheduled task.
+ */
+ @NotNull Completable runDelayed(@NotNull Function, R> task,
+ @Range(from = 1, to = Long.MAX_VALUE) long delayTicks);
+
+ /**
+ * Schedules a repeating task to be executed on the global region after the initial delay with the
+ * specified period.
+ * @param task The task to execute
+ * @param initialDelayTicks The initial delay, in ticks.
+ * @param periodTicks The period, in ticks.
+ * @return The {@link Task} that represents the scheduled task.
+ */
+ @NotNull Task runAtFixedRate(@NotNull Consumer task,
+ @Range(from = 1, to = Long.MAX_VALUE) long initialDelayTicks,
+ @Range(from = 1, to = Long.MAX_VALUE) long periodTicks);
+}
diff --git a/scheduler/src/main/java/com/volmit/iris/util/scheduling/split/IRegionScheduler.java b/scheduler/src/main/java/com/volmit/iris/util/scheduling/split/IRegionScheduler.java
new file mode 100644
index 000000000..55607f2b9
--- /dev/null
+++ b/scheduler/src/main/java/com/volmit/iris/util/scheduling/split/IRegionScheduler.java
@@ -0,0 +1,182 @@
+package com.volmit.iris.util.scheduling.split;
+
+import com.volmit.iris.util.scheduling.Task;
+import com.volmit.iris.util.scheduling.Task.Completable;
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.bukkit.entity.Entity;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Range;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * 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 IRegionScheduler {
+ /**
+ * Schedules a task to be executed on the region which owns the location on the next tick.
+ *
+ * @param world The world of the region that owns the task
+ * @param chunkX The chunk X coordinate of the region that owns the task
+ * @param chunkZ The chunk Z coordinate of the region that owns the task
+ * @param task The task to execute
+ * @return The {@link Task} that represents the scheduled task.
+ */
+ default @NotNull Task run(@NotNull World world,
+ int chunkX,
+ int chunkZ,
+ @NotNull Consumer task) {
+ return run(world, chunkX, chunkZ, t -> {
+ task.accept(t);
+ return null;
+ });
+ }
+
+ /**
+ * Schedules a task to be executed on the region which owns the location on the next tick.
+ *
+ * @param world The world of the region that owns the task
+ * @param chunkX The chunk X coordinate of the region that owns the task
+ * @param chunkZ The chunk Z coordinate of the region that owns the task
+ * @param task The task to execute
+ * @return The {@link Completable} that represents the scheduled task.
+ */
+ default @NotNull Completable run(@NotNull World world,
+ int chunkX,
+ int chunkZ,
+ @NotNull Function, R> task) {
+ return runDelayed(world, chunkX, chunkZ, task, 1);
+ }
+
+ /**
+ * Schedules a task to be executed on the region which owns the location on the next tick.
+ *
+ * @param location The location at which the region executing should own
+ * @param task The task to execute
+ * @return The {@link Task} that represents the scheduled task.
+ */
+ default @NotNull Task run(@NotNull Location location,
+ @NotNull Consumer task) {
+ return run(location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4, task);
+ }
+
+ /**
+ * Schedules a task to be executed on the region which owns the location on the next tick.
+ *
+ * @param location The location at which the region executing should own
+ * @param task The task to execute
+ * @return The {@link Completable} that represents the scheduled task.
+ */
+ default @NotNull Completable run(@NotNull Location location,
+ @NotNull Function, R> task) {
+ return run(location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4, task);
+ }
+
+ /**
+ * Schedules a task to be executed on the region which owns the location after the specified delay in ticks.
+ *
+ * @param world The world of the region that owns the task
+ * @param chunkX The chunk X coordinate of the region that owns the task
+ * @param chunkZ The chunk Z coordinate of the region that owns the task
+ * @param task The task to execute
+ * @param delayTicks The delay, in ticks.
+ * @return The {@link Task} that represents the scheduled task.
+ */
+ default @NotNull Task runDelayed(@NotNull World world,
+ int chunkX,
+ int chunkZ,
+ @NotNull Consumer task,
+ @Range(from = 1, to = Long.MAX_VALUE) long delayTicks) {
+ return runDelayed(world, chunkX, chunkZ, t -> {
+ task.accept(t);
+ return null;
+ }, delayTicks);
+ }
+
+ /**
+ * Schedules a task to be executed on the region which owns the location after the specified delay in ticks.
+ *
+ * @param world The world of the region that owns the task
+ * @param chunkX The chunk X coordinate of the region that owns the task
+ * @param chunkZ The chunk Z coordinate of the region that owns the task
+ * @param task The task to execute
+ * @param delayTicks The delay, in ticks.
+ * @return The {@link Completable} that represents the scheduled task.
+ */
+ @NotNull Completable runDelayed(@NotNull World world,
+ int chunkX,
+ int chunkZ,
+ @NotNull Function, R> task,
+ @Range(from = 1, to = Long.MAX_VALUE) long delayTicks);
+
+ /**
+ * Schedules a task to be executed on the region which owns the location after the specified delay in ticks.
+ *
+ * @param location The location at which the region executing should own
+ * @param task The task to execute
+ * @param delayTicks The delay, in ticks.
+ * @return The {@link Task} that represents the scheduled task.
+ */
+ default @NotNull Task runDelayed(@NotNull Location location,
+ @NotNull Consumer task,
+ @Range(from = 1, to = Long.MAX_VALUE) long delayTicks) {
+ return runDelayed(location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4, task, delayTicks);
+ }
+ /**
+ * Schedules a task to be executed on the region which owns the location after the specified delay in ticks.
+ *
+ * @param location The location at which the region executing should own
+ * @param task The task to execute
+ * @param delayTicks The delay, in ticks.
+ * @return The {@link Completable} that represents the scheduled task.
+ */
+ default @NotNull Completable runDelayed(@NotNull Location location,
+ @NotNull Function, R> task,
+ @Range(from = 1, to = Long.MAX_VALUE) long delayTicks) {
+ return runDelayed(location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4, task, delayTicks);
+ }
+
+ /**
+ * Schedules a repeating task to be executed on the region which owns the location after the initial delay with the
+ * specified period.
+ *
+ * @param world The world of the region that owns the task
+ * @param chunkX The chunk X coordinate of the region that owns the task
+ * @param chunkZ The chunk Z coordinate of the region that owns the task
+ * @param task The task to execute
+ * @param initialDelayTicks The initial delay, in ticks.
+ * @param periodTicks The period, in ticks.
+ * @return The {@link Task} that represents the scheduled task.
+ */
+ @NotNull Task runAtFixedRate(@NotNull World world,
+ int chunkX,
+ int chunkZ,
+ @NotNull Consumer task,
+ @Range(from = 1, to = Long.MAX_VALUE) long initialDelayTicks,
+ @Range(from = 1, to = Long.MAX_VALUE) long periodTicks);
+
+ /**
+ * Schedules a repeating task to be executed on the region which owns the location after the initial delay with the
+ * specified period.
+ *
+ * @param location The location at which the region executing should own
+ * @param task The task to execute
+ * @param initialDelayTicks The initial delay, in ticks.
+ * @param periodTicks The period, in ticks.
+ * @return The {@link Task} that represents the scheduled task.
+ */
+ default @NotNull Task runAtFixedRate(@NotNull Location location,
+ @NotNull Consumer task,
+ @Range(from = 1, to = Long.MAX_VALUE) long initialDelayTicks,
+ @Range(from = 1, to = Long.MAX_VALUE) long periodTicks) {
+ return runAtFixedRate(location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4, task, initialDelayTicks, periodTicks);
+ }
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index c45462895..89f8e7dc4 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -23,19 +23,20 @@ pluginManagement {
}
}
plugins {
- id ("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
+ id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}
rootProject.name = "Iris"
include(":core")
include(
- ":nms:v1_21_R4",
- ":nms:v1_21_R3",
- ":nms:v1_21_R2",
- ":nms:v1_21_R1",
- ":nms:v1_20_R4",
- ":nms:v1_20_R3",
- ":nms:v1_20_R2",
- ":nms:v1_20_R1",
-)
\ No newline at end of file
+ ":nms:v1_21_R4",
+ ":nms:v1_21_R3",
+ ":nms:v1_21_R2",
+ ":nms:v1_21_R1",
+ ":nms:v1_20_R4",
+ ":nms:v1_20_R3",
+ ":nms:v1_20_R2",
+ ":nms:v1_20_R1",
+)
+include("scheduler")
\ No newline at end of file