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