From f2c6ed44757bc618ce969bd894042ac24376b512 Mon Sep 17 00:00:00 2001 From: Martin Sulikowski Date: Sat, 21 Sep 2024 22:14:11 +0200 Subject: [PATCH] GH-30 Add folia scheduler (#30) * [WIP] Add folia scheduler basics * Fix folia api. * Add entity, region, location. scheduelrs. * Implement bukkit scheduler. * Add MinecraftScheduler. * Fix imports * Remove getPlugin method --------- Co-authored-by: Rollczi --- .../main/kotlin/commons-publish.gradle.kts | 4 +- .../kotlin/commons-repositories.gradle.kts | 2 +- .../bukkit/scheduler/BukkitSchedulerImpl.java | 43 +++- .../bukkit/scheduler/BukkitTaskImpl.java | 23 ++ .../bukkit/scheduler/MinecraftScheduler.java | 69 ++++++ eternalcode-commons-folia/build.gradle.kts | 20 ++ .../folia/scheduler/FoliaSchedulerImpl.java | 198 ++++++++++++++++++ .../folia/scheduler/FoliaTaskImpl.java | 47 +++++ .../commons/RandomElementUtil.java | 1 + .../commons/scheduler/Scheduler.java | 13 +- .../eternalcode/commons/scheduler/Task.java | 4 + settings.gradle.kts | 1 + 12 files changed, 407 insertions(+), 18 deletions(-) create mode 100644 eternalcode-commons-bukkit/src/main/java/com/eternalcode/commons/bukkit/scheduler/MinecraftScheduler.java create mode 100644 eternalcode-commons-folia/build.gradle.kts create mode 100644 eternalcode-commons-folia/src/main/java/com/eternalcode/commons/folia/scheduler/FoliaSchedulerImpl.java create mode 100644 eternalcode-commons-folia/src/main/java/com/eternalcode/commons/folia/scheduler/FoliaTaskImpl.java diff --git a/buildSrc/src/main/kotlin/commons-publish.gradle.kts b/buildSrc/src/main/kotlin/commons-publish.gradle.kts index e6ea912..b1d4fa3 100644 --- a/buildSrc/src/main/kotlin/commons-publish.gradle.kts +++ b/buildSrc/src/main/kotlin/commons-publish.gradle.kts @@ -4,11 +4,11 @@ plugins { } group = "com.eternalcode" -version = "1.1.3" +version = "1.1.4-SNAPSHOT" java { withSourcesJar() - withJavadocJar() +// withJavadocJar() } publishing { diff --git a/buildSrc/src/main/kotlin/commons-repositories.gradle.kts b/buildSrc/src/main/kotlin/commons-repositories.gradle.kts index 4a1e1e9..101c9ee 100644 --- a/buildSrc/src/main/kotlin/commons-repositories.gradle.kts +++ b/buildSrc/src/main/kotlin/commons-repositories.gradle.kts @@ -5,6 +5,6 @@ plugins { repositories { mavenCentral() - maven("https://papermc.io/repo/repository/maven-public/") // paper, adventure, velocity + maven("https://repo.papermc.io/repository/maven-public/") // paper, adventure, velocity maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") // spigot } diff --git a/eternalcode-commons-bukkit/src/main/java/com/eternalcode/commons/bukkit/scheduler/BukkitSchedulerImpl.java b/eternalcode-commons-bukkit/src/main/java/com/eternalcode/commons/bukkit/scheduler/BukkitSchedulerImpl.java index baaf0e1..d004b95 100644 --- a/eternalcode-commons-bukkit/src/main/java/com/eternalcode/commons/bukkit/scheduler/BukkitSchedulerImpl.java +++ b/eternalcode-commons-bukkit/src/main/java/com/eternalcode/commons/bukkit/scheduler/BukkitSchedulerImpl.java @@ -3,54 +3,79 @@ import com.eternalcode.commons.scheduler.Scheduler; import com.eternalcode.commons.scheduler.Task; import java.util.concurrent.CompletableFuture; +import org.bukkit.Location; +import org.bukkit.Server; +import org.bukkit.entity.Entity; import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitScheduler; import java.time.Duration; import java.util.function.Supplier; -public class BukkitSchedulerImpl implements Scheduler { +public class BukkitSchedulerImpl implements MinecraftScheduler { private final Plugin plugin; + private final Server server; private final BukkitScheduler rootScheduler; public BukkitSchedulerImpl(Plugin plugin) { this.plugin = plugin; + this.server = plugin.getServer(); this.rootScheduler = plugin.getServer().getScheduler(); } @Override - public Task sync(Runnable task) { + public boolean isGlobalTickThread() { + return this.server.isPrimaryThread(); + } + + @Override + public boolean isPrimaryThread() { + return this.server.isPrimaryThread(); + } + + @Override + public boolean isRegionThread(Entity entity) { + return this.server.isPrimaryThread(); + } + + @Override + public boolean isRegionThread(Location location) { + return this.server.isPrimaryThread(); + } + + @Override + public Task run(Runnable task) { return new BukkitTaskImpl(this.rootScheduler.runTask(this.plugin, task)); } @Override - public Task async(Runnable task) { + public Task runAsync(Runnable task) { return new BukkitTaskImpl(this.rootScheduler.runTaskAsynchronously(this.plugin, task)); } @Override - public Task laterSync(Runnable task, Duration delay) { + public Task runLater(Runnable task, Duration delay) { return new BukkitTaskImpl(this.rootScheduler.runTaskLater(this.plugin, task, this.toTick(delay))); } @Override - public Task laterAsync(Runnable task, Duration delay) { + public Task runLaterAsync(Runnable task, Duration delay) { return new BukkitTaskImpl(this.rootScheduler.runTaskLaterAsynchronously(this.plugin, task, this.toTick(delay))); } @Override - public Task timerSync(Runnable task, Duration delay, Duration period) { - return new BukkitTaskImpl(this.rootScheduler.runTaskTimer(this.plugin, task, this.toTick(delay), this.toTick(period))); + public Task timer(Runnable task, Duration delay, Duration period) { + return new BukkitTaskImpl(this.rootScheduler.runTaskTimer(this.plugin, task, this.toTick(delay), this.toTick(period)), true); } @Override public Task timerAsync(Runnable task, Duration delay, Duration period) { - return new BukkitTaskImpl(this.rootScheduler.runTaskTimerAsynchronously(this.plugin, task, this.toTick(delay), this.toTick(period))); + return new BukkitTaskImpl(this.rootScheduler.runTaskTimerAsynchronously(this.plugin, task, this.toTick(delay), this.toTick(period)), true); } @Override - public CompletableFuture completeSync(Supplier task) { + public CompletableFuture complete(Supplier task) { CompletableFuture completable = new CompletableFuture<>(); this.rootScheduler.runTask(this.plugin, () -> completable.complete(task.get())); return completable; diff --git a/eternalcode-commons-bukkit/src/main/java/com/eternalcode/commons/bukkit/scheduler/BukkitTaskImpl.java b/eternalcode-commons-bukkit/src/main/java/com/eternalcode/commons/bukkit/scheduler/BukkitTaskImpl.java index 1bb1d6b..9688be1 100644 --- a/eternalcode-commons-bukkit/src/main/java/com/eternalcode/commons/bukkit/scheduler/BukkitTaskImpl.java +++ b/eternalcode-commons-bukkit/src/main/java/com/eternalcode/commons/bukkit/scheduler/BukkitTaskImpl.java @@ -1,14 +1,24 @@ package com.eternalcode.commons.bukkit.scheduler; import com.eternalcode.commons.scheduler.Task; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitTask; class BukkitTaskImpl implements Task { private final BukkitTask rootTask; + private boolean isRepeating; + BukkitTaskImpl(BukkitTask rootTask) { this.rootTask = rootTask; + this.isRepeating = false; + } + + public BukkitTaskImpl(BukkitTask rootTask, boolean isRepeating) { + this.rootTask = rootTask; + this.isRepeating = isRepeating; } @Override @@ -26,4 +36,17 @@ public boolean isAsync() { return !this.rootTask.isSync(); } + @Override + public boolean isRunning() { + // There's no other way, + // there's no other way + // All that you can do is [...]] + // https://www.youtube.com/watch?v=LJzCYSdrHMI + return Bukkit.getServer().getScheduler().isCurrentlyRunning(this.rootTask.getTaskId()); + } + + @Override + public boolean isRepeating() { + return this.isRepeating; + } } diff --git a/eternalcode-commons-bukkit/src/main/java/com/eternalcode/commons/bukkit/scheduler/MinecraftScheduler.java b/eternalcode-commons-bukkit/src/main/java/com/eternalcode/commons/bukkit/scheduler/MinecraftScheduler.java new file mode 100644 index 0000000..8bbaba4 --- /dev/null +++ b/eternalcode-commons-bukkit/src/main/java/com/eternalcode/commons/bukkit/scheduler/MinecraftScheduler.java @@ -0,0 +1,69 @@ +package com.eternalcode.commons.bukkit.scheduler; + +import com.eternalcode.commons.scheduler.Scheduler; +import com.eternalcode.commons.scheduler.Task; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; +import org.bukkit.Location; +import org.bukkit.entity.Entity; + +public interface MinecraftScheduler extends Scheduler { + + boolean isGlobalTickThread(); + + boolean isPrimaryThread(); + + boolean isRegionThread(Entity entity); + + boolean isRegionThread(Location location); + + Task run(Runnable task); + + Task runAsync(Runnable task); + + default Task run(Location location, Runnable task) { + return run(task); + } + + default Task run(Entity entity, Runnable task) { + return run(task); + } + + Task runLater(Runnable task, Duration delay); + + Task runLaterAsync(Runnable task, Duration delay); + + default Task runLater(Location location, Runnable task, Duration delay) { + return runLater(task, delay); + } + + default Task runLater(Entity entity, Runnable task, Duration delay) { + return runLater(task, delay); + } + + Task timer(Runnable task, Duration delay, Duration period); + + Task timerAsync(Runnable task, Duration delay, Duration period); + + default Task timer(Location location, Runnable task, Duration delay, Duration period) { + return timer(task, delay, period); + } + + default Task timer(Entity entity, Runnable task, Duration delay, Duration period) { + return timer(task, delay, period); + } + + CompletableFuture complete(Supplier task); + + CompletableFuture completeAsync(Supplier task); + + default CompletableFuture complete(Location location, Supplier task) { + return complete(task); + } + + default CompletableFuture complete(Entity entity, Supplier task) { + return complete(task); + } + +} diff --git a/eternalcode-commons-folia/build.gradle.kts b/eternalcode-commons-folia/build.gradle.kts new file mode 100644 index 0000000..290a62c --- /dev/null +++ b/eternalcode-commons-folia/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + `commons-java-17` + `commons-publish` + `commons-repositories` + `commons-java-unit-test` +} + + +dependencies { + api(project(":eternalcode-commons-shared")) + api(project(":eternalcode-commons-bukkit")) + + compileOnlyApi("dev.folia:folia-api:1.20.1-R0.1-SNAPSHOT") + + api("org.jetbrains:annotations:24.1.0") +} + +tasks.test { + useJUnitPlatform() +} diff --git a/eternalcode-commons-folia/src/main/java/com/eternalcode/commons/folia/scheduler/FoliaSchedulerImpl.java b/eternalcode-commons-folia/src/main/java/com/eternalcode/commons/folia/scheduler/FoliaSchedulerImpl.java new file mode 100644 index 0000000..e349ec7 --- /dev/null +++ b/eternalcode-commons-folia/src/main/java/com/eternalcode/commons/folia/scheduler/FoliaSchedulerImpl.java @@ -0,0 +1,198 @@ +package com.eternalcode.commons.folia.scheduler; + +import com.eternalcode.commons.bukkit.scheduler.MinecraftScheduler; +import com.eternalcode.commons.scheduler.Scheduler; +import com.eternalcode.commons.scheduler.Task; +import io.papermc.paper.threadedregions.scheduler.AsyncScheduler; +import io.papermc.paper.threadedregions.scheduler.GlobalRegionScheduler; +import io.papermc.paper.threadedregions.scheduler.RegionScheduler; +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import org.bukkit.Location; +import org.bukkit.Server; +import org.bukkit.entity.Entity; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitScheduler; +import org.jetbrains.annotations.NotNull; + +public class FoliaSchedulerImpl implements MinecraftScheduler { + + public final Plugin plugin; + private final Server server; + + private final GlobalRegionScheduler globalRegionScheduler; + private final AsyncScheduler asyncScheduler; + private final RegionScheduler regionScheduler; + + public FoliaSchedulerImpl(Plugin plugin) { + this.plugin = plugin; + this.server = this.plugin.getServer(); + + this.globalRegionScheduler = this.server.getGlobalRegionScheduler(); + this.asyncScheduler = this.server.getAsyncScheduler(); + this.regionScheduler = this.server.getRegionScheduler(); + this.server.getScheduler(); + } + + @Override + public boolean isGlobalTickThread() { + return this.server.isGlobalTickThread(); + } + + @Override + public boolean isPrimaryThread() { + return this.server.isPrimaryThread(); + } + + @Override + public boolean isRegionThread(Entity entity) { + return this.server.isOwnedByCurrentRegion(entity); + } + + @Override + public boolean isRegionThread(Location location) { + return this.server.isOwnedByCurrentRegion(location); + } + + @Override + public Task run(Runnable task) { + return wrap(this.globalRegionScheduler.run(plugin, runnable -> task.run())); + } + + @Override + public Task runAsync(Runnable task) { + return wrapAsync(this.asyncScheduler.runNow(plugin, runnable -> task.run())); + } + + @Override + public Task run(Location location, Runnable task) { + return wrap(this.regionScheduler.run(plugin, location, runnable -> task.run())); + } + + @Override + public Task run(Entity entity, Runnable task) { + return wrap(entity.getScheduler().run(plugin, runnable -> task.run(), null)); + } + + @Override + public Task runLater(Runnable task, Duration delay) { + return wrap(this.globalRegionScheduler.runDelayed(plugin, scheduledTask -> task.run(), toTick(delay))); + } + + @Override + public Task runLaterAsync(Runnable task, Duration delay) { + return wrapAsync(this.asyncScheduler.runDelayed( + plugin, + runnable -> task.run(), + delay.toMillis(), + TimeUnit.MILLISECONDS + )); + } + + @Override + public Task runLater(Location location, Runnable task, Duration delay) { + return wrap(this.regionScheduler.runDelayed( + plugin, + location, + runnable -> task.run(), + toTick(delay) + )); + } + + @Override + public Task runLater(Entity entity, Runnable task, Duration delay) { + return wrap(entity.getScheduler().runDelayed( + plugin, + runnable -> task.run(), + null, + toTick(delay) + )); + } + + @Override + public Task timer(Runnable task, Duration delay, Duration period) { + return wrap(this.globalRegionScheduler.runAtFixedRate( + plugin, + runnable -> task.run(), + toTick(delay), + toTick(period) + )); + } + + @Override + public Task timerAsync(Runnable task, Duration delay, Duration period) { + return wrapAsync(this.asyncScheduler.runAtFixedRate( + plugin, + runnable -> task.run(), + delay.toMillis(), + period.toMillis(), + TimeUnit.MILLISECONDS + )); + } + + @Override + public Task timer(Location location, Runnable task, Duration delay, Duration period) { + return wrap(this.regionScheduler.runAtFixedRate( + plugin, + location, + runnable -> task.run(), + toTick(delay), + toTick(period) + )); + } + + @Override + public Task timer(Entity entity, Runnable task, Duration delay, Duration period) { + return wrap(entity.getScheduler().runAtFixedRate( + plugin, + runnable -> task.run(), + null, + toTick(delay), + toTick(period) + )); + } + + @Override + public CompletableFuture complete(Supplier task) { + CompletableFuture completable = new CompletableFuture<>(); + this.globalRegionScheduler.run(plugin, scheduledTask -> completable.complete(task.get())); + return completable; + } + + @Override + public CompletableFuture completeAsync(Supplier task) { + CompletableFuture completable = new CompletableFuture<>(); + this.asyncScheduler.runNow(plugin, scheduledTask -> completable.complete(task.get())); + return completable; + } + + @Override + public CompletableFuture complete(Location location, Supplier task) { + CompletableFuture completable = new CompletableFuture<>(); + this.regionScheduler.run(plugin, location, scheduledTask -> completable.complete(task.get())); + return completable; + } + + @Override + public CompletableFuture complete(Entity entity, Supplier task) { + CompletableFuture completable = new CompletableFuture<>(); + entity.getScheduler().run(plugin, scheduledTask -> completable.complete(task.get()), null); + return completable; + } + + private long toTick(Duration duration) { + return duration.toMillis() / 50L; + } + + private Task wrap(ScheduledTask task) { + return new FoliaTaskImpl(task); + } + + private Task wrapAsync(ScheduledTask task) { + return new FoliaTaskImpl(task, true); + } + +} diff --git a/eternalcode-commons-folia/src/main/java/com/eternalcode/commons/folia/scheduler/FoliaTaskImpl.java b/eternalcode-commons-folia/src/main/java/com/eternalcode/commons/folia/scheduler/FoliaTaskImpl.java new file mode 100644 index 0000000..f560f23 --- /dev/null +++ b/eternalcode-commons-folia/src/main/java/com/eternalcode/commons/folia/scheduler/FoliaTaskImpl.java @@ -0,0 +1,47 @@ +package com.eternalcode.commons.folia.scheduler; + +import com.eternalcode.commons.scheduler.Task; +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; + +public class FoliaTaskImpl implements Task { + + private final ScheduledTask rootTask; + private final boolean async; + + public FoliaTaskImpl(ScheduledTask rootTask) { + this.rootTask = rootTask; + this.async = false; + } + + public FoliaTaskImpl(ScheduledTask rootTask, boolean async) { + this.rootTask = rootTask; + this.async = async; + } + + @Override + public void cancel() { + this.rootTask.cancel(); + } + + @Override + public boolean isCanceled() { + return this.rootTask.isCancelled(); + } + + @Override + public boolean isAsync() { + return async; + } + + @Override + public boolean isRunning() { + ScheduledTask.ExecutionState state = this.rootTask.getExecutionState(); + + return state == ScheduledTask.ExecutionState.RUNNING || state == ScheduledTask.ExecutionState.CANCELLED_RUNNING; + } + + @Override + public boolean isRepeating() { + return this.rootTask.isRepeatingTask(); + } +} diff --git a/eternalcode-commons-shared/src/main/java/com/eternalcode/commons/RandomElementUtil.java b/eternalcode-commons-shared/src/main/java/com/eternalcode/commons/RandomElementUtil.java index d614d9e..c30ee22 100644 --- a/eternalcode-commons-shared/src/main/java/com/eternalcode/commons/RandomElementUtil.java +++ b/eternalcode-commons-shared/src/main/java/com/eternalcode/commons/RandomElementUtil.java @@ -1,5 +1,6 @@ package com.eternalcode.commons; +import com.eternalcode.commons.scheduler.Scheduler; import java.util.Collection; import java.util.Optional; import java.util.concurrent.ThreadLocalRandom; diff --git a/eternalcode-commons-shared/src/main/java/com/eternalcode/commons/scheduler/Scheduler.java b/eternalcode-commons-shared/src/main/java/com/eternalcode/commons/scheduler/Scheduler.java index caa48dc..1cc382e 100644 --- a/eternalcode-commons-shared/src/main/java/com/eternalcode/commons/scheduler/Scheduler.java +++ b/eternalcode-commons-shared/src/main/java/com/eternalcode/commons/scheduler/Scheduler.java @@ -6,19 +6,20 @@ public interface Scheduler { - Task sync(Runnable task); + Task run(Runnable task); - Task async(Runnable task); + Task runAsync(Runnable task); - Task laterSync(Runnable task, Duration delay); + Task runLater(Runnable task, Duration delay); - Task laterAsync(Runnable task, Duration delay); + Task runLaterAsync(Runnable task, Duration delay); - Task timerSync(Runnable task, Duration delay, Duration period); + Task timer(Runnable task, Duration delay, Duration period); Task timerAsync(Runnable task, Duration delay, Duration period); - CompletableFuture completeSync(Supplier task); + CompletableFuture complete(Supplier task); CompletableFuture completeAsync(Supplier task); + } diff --git a/eternalcode-commons-shared/src/main/java/com/eternalcode/commons/scheduler/Task.java b/eternalcode-commons-shared/src/main/java/com/eternalcode/commons/scheduler/Task.java index 98507c0..6267090 100644 --- a/eternalcode-commons-shared/src/main/java/com/eternalcode/commons/scheduler/Task.java +++ b/eternalcode-commons-shared/src/main/java/com/eternalcode/commons/scheduler/Task.java @@ -8,4 +8,8 @@ public interface Task { boolean isAsync(); + boolean isRunning(); + + boolean isRepeating(); + } diff --git a/settings.gradle.kts b/settings.gradle.kts index 6213aef..661c551 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,3 +3,4 @@ rootProject.name = "eternalcode-commons" include(":eternalcode-commons-bukkit") include(":eternalcode-commons-adventure") include(":eternalcode-commons-shared") +include("eternalcode-commons-folia")