From 80f7f9214235e269f86aa3b096d7cac5ff5da7c7 Mon Sep 17 00:00:00 2001 From: "George V." Date: Mon, 4 Nov 2024 19:39:45 +0200 Subject: [PATCH 01/11] Add abstraction for Minecraft Scheduler - Add `MinecraftBukkitScheduler` class to handle task scheduling and cancellation - Introduce `SchedulerTask` interface to represent scheduled tasks - Update `HoloEasy` class to include scheduler binding - Add `SchedulerRunnable` abstract class for scheduling runnable tasks - Define `MinecraftScheduler` interface for task scheduling and cancellation --- .../src/main/kotlin/org/holoeasy/HoloEasy.kt | 25 +- .../scheduler/MinecraftBukkitScheduler.kt | 289 ++++++++++++++++++ .../util/scheduler/MinecraftScheduler.kt | 219 +++++++++++++ .../util/scheduler/SchedulerRunnable.kt | 180 +++++++++++ .../holoeasy/util/scheduler/SchedulerTask.kt | 33 ++ 5 files changed, 743 insertions(+), 3 deletions(-) create mode 100644 holoeasy-core/src/main/kotlin/org/holoeasy/util/scheduler/MinecraftBukkitScheduler.kt create mode 100644 holoeasy-core/src/main/kotlin/org/holoeasy/util/scheduler/MinecraftScheduler.kt create mode 100644 holoeasy-core/src/main/kotlin/org/holoeasy/util/scheduler/SchedulerRunnable.kt create mode 100644 holoeasy-core/src/main/kotlin/org/holoeasy/util/scheduler/SchedulerTask.kt diff --git a/holoeasy-core/src/main/kotlin/org/holoeasy/HoloEasy.kt b/holoeasy-core/src/main/kotlin/org/holoeasy/HoloEasy.kt index 99f65fb..5e4afd3 100644 --- a/holoeasy-core/src/main/kotlin/org/holoeasy/HoloEasy.kt +++ b/holoeasy-core/src/main/kotlin/org/holoeasy/HoloEasy.kt @@ -1,6 +1,10 @@ package org.holoeasy +import org.bukkit.Chunk +import org.bukkit.Location +import org.bukkit.World +import org.bukkit.entity.Entity import org.bukkit.plugin.Plugin import org.holoeasy.action.ClickAction import org.holoeasy.hologram.Hologram @@ -9,13 +13,16 @@ import org.holoeasy.packet.PacketImpl import org.holoeasy.pool.HologramPool import org.holoeasy.pool.IHologramPool import org.holoeasy.pool.InteractiveHologramPool +import org.holoeasy.util.scheduler.MinecraftScheduler object HoloEasy { private var PLUGIN: Plugin? = null - private var PACKET_IMPL : IPacket? = null + private var PACKET_IMPL: IPacket? = null + + private var SCHEDULER: MinecraftScheduler? = null fun plugin(): Plugin { if (PLUGIN == null) { @@ -24,18 +31,30 @@ object HoloEasy { return PLUGIN!! } - fun packetImpl() : IPacket { + fun packetImpl(): IPacket { if (PACKET_IMPL == null) { throw IllegalStateException("HoloEasy PacketImpl is not set") } return PACKET_IMPL!! } + fun scheduler(): MinecraftScheduler { + if (SCHEDULER == null) { + throw IllegalStateException("HoloEasy Scheduler is not set") + } + return SCHEDULER!! + } + @JvmStatic @JvmOverloads - fun bind(plugin: Plugin, packetImpl : PacketImpl = PacketImpl.ProtocolLib) { + fun bind( + plugin: Plugin, + packetImpl: PacketImpl = PacketImpl.ProtocolLib, + scheduler: MinecraftScheduler + ) { this.PLUGIN = plugin this.PACKET_IMPL = packetImpl.impl + this.SCHEDULER = scheduler } @JvmStatic diff --git a/holoeasy-core/src/main/kotlin/org/holoeasy/util/scheduler/MinecraftBukkitScheduler.kt b/holoeasy-core/src/main/kotlin/org/holoeasy/util/scheduler/MinecraftBukkitScheduler.kt new file mode 100644 index 0000000..229c81c --- /dev/null +++ b/holoeasy-core/src/main/kotlin/org/holoeasy/util/scheduler/MinecraftBukkitScheduler.kt @@ -0,0 +1,289 @@ +package org.holoeasy.util.scheduler + +import org.bukkit.Bukkit +import org.bukkit.Chunk +import org.bukkit.Location +import org.bukkit.World +import org.bukkit.entity.Entity +import org.bukkit.plugin.Plugin +import org.bukkit.scheduler.BukkitTask +import org.jetbrains.annotations.ApiStatus + +@ApiStatus.NonExtendable +class MinecraftBukkitScheduler : MinecraftScheduler { + + /** + * Schedules a task to be executed synchronously on the server's main thread. + * + * @param plugin The plugin associated with this task. + * @param task The task to be executed. + */ + override fun runTask(plugin: Plugin, task: Runnable): SchedulerTask { + return BukkitSchedulerTask(Bukkit.getScheduler().runTask(plugin, task)) + } + + /** + * Schedules a task to be executed asynchronously on a separate thread. + * This method is suitable for handling time-consuming tasks to avoid blocking the main thread. + * + * @param plugin The plugin associated with this task. + * @param task The task to be executed. + */ + override fun runAsyncTask(plugin: Plugin, task: Runnable): SchedulerTask { + return BukkitSchedulerTask(Bukkit.getScheduler().runTaskAsynchronously(plugin, task)) + } + + /** + * Creates a delayed task that will run the specified `task` after the given `delay`. + * + * @param plugin The plugin associated with this task. + * @param task The task to be executed after the delay. + * @param delay The delay before the task is executed. + */ + override fun createDelayedTask(plugin: Plugin, task: Runnable, delay: Long): SchedulerTask { + return BukkitSchedulerTask(Bukkit.getScheduler().runTaskLater(plugin, task, delay)) + } + + /** + * Creates a repeating task that will run the specified `task` after an initial `delay`, + * and then repeatedly execute with the given `period` between executions. + * + * @param plugin The plugin associated with this task. + * @param task The task to be executed repeatedly. + * @param delay The delay before the first execution. + * @param period The time between successive executions. + */ + override fun createRepeatingTask(plugin: Plugin, task: Runnable, delay: Long, period: Long): SchedulerTask { + return BukkitSchedulerTask(Bukkit.getScheduler().runTaskTimer(plugin, task, delay, period)) + } + + /** + * Schedules an asynchronous delayed task that will run the specified `task` after the given `delay`. + * The task will be executed on a separate thread, making it suitable for non-blocking operations. + * + * @param plugin The plugin associated with this task. + * @param task The task to be executed after the delay. + * @param delay The delay before the task is executed. + */ + override fun createAsyncDelayedTask(plugin: Plugin, task: Runnable, delay: Long): SchedulerTask { + return BukkitSchedulerTask(Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, task, delay)) + } + + /** + * Schedules an asynchronous repeating task that will run the specified `task` after an initial `delay`, + * and then repeatedly execute with the given `period` between executions. + * The task will be executed on a separate thread, making it suitable for non-blocking operations. + * + * @param plugin The plugin associated with this task. + * @param task The task to be executed repeatedly. + * @param delay The delay before the first execution. + * @param period The time between successive executions. + */ + override fun createAsyncRepeatingTask(plugin: Plugin, task: Runnable, delay: Long, period: Long): SchedulerTask { + return BukkitSchedulerTask(Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, task, delay, period)) + } + + /** + * Cancels all tasks associated with the given `plugin`. + * + * @param plugin The plugin whose tasks should be canceled. + */ + override fun cancelTasks(plugin: Plugin) { + Bukkit.getScheduler().cancelTasks(plugin) + } + + /** + * Creates a delayed task for a specific world and chunk. + * + * @param plugin The plugin that owns this task. + * @param task The runnable task to execute. + * @param world The world in which the chunk is located. + * @param chunk The chunk in which the task will be executed. + * @param delay The delay in ticks before the task is executed. + * @return A SchedulerTask representing the created task. + */ + override fun createDelayedTaskForWorld( + plugin: Plugin, + task: Runnable, + world: World, + chunk: Chunk, + delay: Long + ): SchedulerTask { + return this.createDelayedTask(plugin, task, delay) + } + + /** + * Creates a delayed task for a specific location. + * + * @param plugin The plugin that owns this task. + * @param task The runnable task to execute. + * @param location The location at which the task will be executed. + * @param delay The delay in ticks before the task is executed. + * @return A SchedulerTask representing the created task. + */ + override fun createDelayedForLocation( + plugin: Plugin, + task: Runnable, + location: Location, + delay: Long + ): SchedulerTask { + return this.createDelayedTask(plugin, task, delay) + } + + /** + * Creates a delayed task for a specific entity. + * + * @param plugin The plugin that owns this task. + * @param task The runnable 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 entity The entity in which the task will be executed. + * @param delay The delay in ticks before the task is executed. + * @return A SchedulerTask representing the created task. + */ + override fun createDelayedForEntity( + plugin: Plugin, + task: Runnable, + retired: Runnable?, + entity: Entity, + delay: Long + ): SchedulerTask { + return this.createDelayedTask(plugin, task, delay) + } + + /** + * Creates a task for a specific world and chunk. + * + * @param plugin The plugin that owns this task. + * @param task The runnable task to execute. + * @param world The world in which the chunk is located. + * @param chunk The chunk in which the task will be executed. + * @return A SchedulerTask representing the created task. + */ + override fun createTaskForWorld(plugin: Plugin, task: Runnable, world: World, chunk: Chunk): SchedulerTask { + return this.runTask(plugin, task) + } + + /** + * Creates a task for a specific location. + * + * @param plugin The plugin that owns this task. + * @param task The runnable task to execute. + * @param location The location at which the task will be executed. + * @return A SchedulerTask representing the created task. + */ + override fun createTaskForLocation(plugin: Plugin, task: Runnable, location: Location): SchedulerTask { + return this.runTask(plugin, task) + } + + /** + * Creates a task for a specific entity. + * + * @param plugin The plugin that owns this task. + * @param task The runnable 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 entity The entity in which the task will be executed. + * @return A SchedulerTask representing the created task. + */ + override fun createTaskForEntity( + plugin: Plugin, + task: Runnable, + retired: Runnable?, + entity: Entity + ): SchedulerTask { + return this.runTask(plugin, task) + } + + /** + * Creates a repeating task for a specific world and chunk. + * + * @param plugin The plugin that owns this task. + * @param task The runnable task to execute. + * @param world The world in which the chunk is located. + * @param chunk The chunk in which the task will be executed. + * @param delay The initial delay in ticks before the first execution. + * @param period The period in ticks between consecutive executions. + * @return A SchedulerTask representing the created task. + */ + override fun createRepeatingTaskForWorld( + plugin: Plugin, + task: Runnable, + world: World, + chunk: Chunk, + delay: Long, + period: Long + ): SchedulerTask { + return this.createRepeatingTask(plugin, task, delay, period) + } + + /** + * Creates a repeating task for a specific location. + * + * @param plugin The plugin that owns this task. + * @param task The runnable task to execute. + * @param location The location at which the task will be executed. + * @param delay The initial delay in ticks before the first execution. + * @param period The period in ticks between consecutive executions. + * @return A SchedulerTask representing the created task. + */ + override fun createRepeatingTaskForLocation( + plugin: Plugin, + task: Runnable, + location: Location, + delay: Long, + period: Long + ): SchedulerTask { + return this.createRepeatingTask(plugin, task, delay, period) + } + + /** + * Creates a repeating task for a specific entity. + * + * @param plugin The plugin that owns this task. + * @param task The runnable 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 entity The entity in which the task will be executed. + * @param delay The initial delay in ticks before the first execution. + * @param period The period in ticks between consecutive executions. + * @return A SchedulerTask representing the created task. + */ + override fun createRepeatingTaskForEntity( + plugin: Plugin, + task: Runnable, + retired: Runnable?, + entity: Entity, + delay: Long, + period: Long + ): SchedulerTask { + return this.createRepeatingTask(plugin, task, delay, period) + } + + /** + * Gets the scheduler instance for this class. + * Since this class is already a subclass of {@link MinecraftScheduler}, + * it returns the current instance as the scheduler. + * + * @return The scheduler instance for this class (i.e., this). + */ + override fun getScheduler(): MinecraftScheduler { + return this + } + + private class BukkitSchedulerTask(private val bukkitTask: BukkitTask) : SchedulerTask { + + override fun cancel() { + bukkitTask.cancel() + } + + override fun isCancelled(): Boolean { + return bukkitTask.isCancelled + } + + override fun getTaskId(): Int { + return bukkitTask.taskId + } + + override fun isRunning(): Boolean { + return Bukkit.getScheduler().isCurrentlyRunning(bukkitTask.taskId) + } + } +} diff --git a/holoeasy-core/src/main/kotlin/org/holoeasy/util/scheduler/MinecraftScheduler.kt b/holoeasy-core/src/main/kotlin/org/holoeasy/util/scheduler/MinecraftScheduler.kt new file mode 100644 index 0000000..8289e9d --- /dev/null +++ b/holoeasy-core/src/main/kotlin/org/holoeasy/util/scheduler/MinecraftScheduler.kt @@ -0,0 +1,219 @@ +package org.holoeasy.util.scheduler + +import org.jetbrains.annotations.NotNull + +/** + * A non-extendable interface representing a scheduler for task scheduling and cancellation. + */ +interface MinecraftScheduler { + + /** + * Schedules a task to be executed synchronously on the server's main thread. + * + * @param plugin The plugin associated with this task. + * @param task The task to be executed. + */ + fun runTask(plugin: Plugin, task: Runnable): SchedulerTask + + /** + * Schedules a task to be executed asynchronously on a separate thread. + * This method is suitable for handling time-consuming tasks to avoid blocking the main thread. + * + * @param plugin The plugin associated with this task. + * @param task The task to be executed. + */ + fun runAsyncTask(plugin: Plugin, task: Runnable): SchedulerTask + + /** + * Creates a delayed task that will run the specified `task` after the given `delay`. + * + * @param plugin The plugin associated with this task. + * @param task The task to be executed after the delay. + * @param delay The delay before the task is executed. + */ + fun createDelayedTask(plugin: Plugin, task: Runnable, delay: Long): SchedulerTask + + /** + * Creates a repeating task that will run the specified `task` after an initial `delay`, + * and then repeatedly execute with the given `period` between executions. + * + * @param plugin The plugin associated with this task. + * @param task The task to be executed repeatedly. + * @param delay The delay before the first execution. + * @param period The time between successive executions. + */ + fun createRepeatingTask(plugin: Plugin, task: Runnable, delay: Long, period: Long): SchedulerTask + + /** + * Schedules an asynchronous delayed task that will run the specified `task` after the given `delay`. + * The task will be executed on a separate thread, making it suitable for non-blocking operations. + * + * @param plugin The plugin associated with this task. + * @param task The task to be executed after the delay. + * @param delay The delay before the task is executed. + */ + fun createAsyncDelayedTask(plugin: Plugin, task: Runnable, delay: Long): SchedulerTask + + /** + * Schedules an asynchronous repeating task that will run the specified `task` after an initial `delay`, + * and then repeatedly execute with the given `period` between executions. + * The task will be executed on a separate thread, making it suitable for non-blocking operations. + * + * @param plugin The plugin associated with this task. + * @param task The task to be executed repeatedly. + * @param delay The delay before the first execution. + * @param period The time between successive executions. + */ + fun createAsyncRepeatingTask(plugin: Plugin, task: Runnable, delay: Long, period: Long): SchedulerTask + + /** + * Creates a delayed task for a specific world and chunk. + * + * @param plugin The plugin that owns this task. + * @param task The runnable task to execute. + * @param world The world in which the chunk is located. + * @param chunk The chunk in which the task will be executed. + * @param delay The delay in ticks before the task is executed. + * @return A SchedulerTask representing the created task. + */ + fun createDelayedTaskForWorld( + plugin: Plugin, + task: Runnable, + world: World, + @NotNull chunk: Chunk, + delay: Long + ): SchedulerTask + + /** + * Creates a delayed task for a specific location. + * + * @param plugin The plugin that owns this task. + * @param task The runnable task to execute. + * @param location The location at which the task will be executed. + * @param delay The delay in ticks before the task is executed. + * @return A SchedulerTask representing the created task. + */ + fun createDelayedForLocation(plugin: Plugin, task: Runnable, location: Location, delay: Long): SchedulerTask + + /** + * Creates a delayed task for a specific entity. + * + * @param plugin The plugin that owns this task. + * @param task The runnable 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 entity The entity in which the task will be executed. + * @param delay The delay in ticks before the task is executed. + * @return A SchedulerTask representing the created task. + */ + fun createDelayedForEntity( + plugin: Plugin, + task: Runnable, + retired: Runnable?, + entity: Entity, + delay: Long + ): SchedulerTask + + /** + * Creates a task for a specific world and chunk. + * + * @param plugin The plugin that owns this task. + * @param task The runnable task to execute. + * @param world The world in which the chunk is located. + * @param chunk The chunk in which the task will be executed. + * @return A SchedulerTask representing the created task. + */ + fun createTaskForWorld(plugin: Plugin, task: Runnable, world: World, @NotNull chunk: Chunk): SchedulerTask + + /** + * Creates a task for a specific location. + * + * @param plugin The plugin that owns this task. + * @param task The runnable task to execute. + * @param location The location at which the task will be executed. + * @return A SchedulerTask representing the created task. + */ + fun createTaskForLocation(plugin: Plugin, task: Runnable, location: Location): SchedulerTask + + /** + * Creates a task for a specific entity. + * + * @param plugin The plugin that owns this task. + * @param task The runnable 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 entity The entity in which the task will be executed. + * @return A SchedulerTask representing the created task. + */ + fun createTaskForEntity(plugin: Plugin, task: Runnable, retired: Runnable?, entity: Entity): SchedulerTask + + /** + * Creates a repeating task for a specific world and chunk. + * + * @param plugin The plugin that owns this task. + * @param task The runnable task to execute. + * @param world The world in which the chunk is located. + * @param chunk The chunk in which the task will be executed. + * @param delay The initial delay in ticks before the first execution. + * @param period The period in ticks between consecutive executions. + * @return A SchedulerTask representing the created task. + */ + fun createRepeatingTaskForWorld( + plugin: Plugin, + task: Runnable, + world: World, + @NotNull chunk: Chunk, + delay: Long, + period: Long + ): SchedulerTask + + /** + * Creates a repeating task for a specific location. + * + * @param plugin The plugin that owns this task. + * @param task The runnable task to execute. + * @param location The location at which the task will be executed. + * @param delay The initial delay in ticks before the first execution. + * @param period The period in ticks between consecutive executions. + * @return A SchedulerTask representing the created task. + */ + fun createRepeatingTaskForLocation( + plugin: Plugin, + task: Runnable, + location: Location, + delay: Long, + period: Long + ): SchedulerTask + + /** + * Creates a repeating task for a specific entity. + * + * @param plugin The plugin that owns this task. + * @param task The runnable 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 entity The entity in which the task will be executed. + * @param delay The initial delay in ticks before the first execution. + * @param period The period in ticks between consecutive executions. + * @return A SchedulerTask representing the created task. + */ + fun createRepeatingTaskForEntity( + plugin: Plugin, + task: Runnable, + retired: Runnable?, + entity: Entity, + delay: Long, + period: Long + ): SchedulerTask + + /** + * Cancels all tasks associated with the given `plugin`. + * + * @param plugin The plugin whose tasks should be canceled. + */ + fun cancelTasks(plugin: Plugin) + + /** + * Gets the scheduler + * + * @return The scheduler + */ + fun getScheduler(): MinecraftScheduler +} diff --git a/holoeasy-core/src/main/kotlin/org/holoeasy/util/scheduler/SchedulerRunnable.kt b/holoeasy-core/src/main/kotlin/org/holoeasy/util/scheduler/SchedulerRunnable.kt new file mode 100644 index 0000000..7932e8b --- /dev/null +++ b/holoeasy-core/src/main/kotlin/org/holoeasy/util/scheduler/SchedulerRunnable.kt @@ -0,0 +1,180 @@ +package org.holoeasy.util.scheduler + +import org.jetbrains.annotations.NotNull + +/** + * An abstract class representing a runnable task that can be scheduled in the Minecraft scheduler. + * + * @param Plugin The type of the plugin. + * @param Location The type of location. + * @param World The type of world. + * @param Chunk The type of chunk. + * @param Entity The type of entity. + */ +abstract class SchedulerRunnable( + private val minecraftScheduler: MinecraftScheduler +) : Runnable { + + private var task: SchedulerTask? = null + + /** + * Returns true if this task has been cancelled. + * + * @return true if the task has been cancelled + * @throws IllegalStateException if task was not scheduled yet + */ + @Synchronized + fun isCancelled(): Boolean { + checkScheduled() + return task!!.isCancelled() + } + + /** + * Attempts to cancel this task. + * + * @throws IllegalStateException if task was not scheduled yet + */ + @Synchronized + fun cancel() { + checkScheduled() + task?.cancel() + } + + /** + * Schedules this in the Bukkit scheduler to run on the next tick. + * + * @param plugin The Plugin associated with this task. + * @return a Task that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalStateException if this was already scheduled + */ + @Synchronized + @NotNull + fun runTask(plugin: Plugin): SchedulerTask { + checkNotYetScheduled() + return setupTask(minecraftScheduler.runTask(plugin, this)) + } + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + * + * Schedules this in the Bukkit scheduler to run asynchronously. + * + * @param plugin The Plugin associated with this task. + * @return a Task that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalStateException if this was already scheduled + */ + @Synchronized + @NotNull + fun runTaskAsynchronously(@NotNull plugin: Plugin): SchedulerTask { + checkNotYetScheduled() + return setupTask(minecraftScheduler.runAsyncTask(plugin, this)) + } + + /** + * Schedules this to run after the specified number of server ticks. + * + * @param plugin The Plugin associated with this task. + * @param delay the ticks to wait before running the task + * @return a Task that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalStateException if this was already scheduled + */ + @Synchronized + @NotNull + fun runTaskLater(@NotNull plugin: Plugin, delay: Long): SchedulerTask { + checkNotYetScheduled() + return setupTask(minecraftScheduler.createDelayedTask(plugin, this, delay)) + } + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + * + * Schedules this to run asynchronously after the specified number of + * server ticks. + * + * @param plugin The Plugin associated with this task. + * @param delay the ticks to wait before running the task + * @return a Task that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalStateException if this was already scheduled + */ + @Synchronized + @NotNull + fun runTaskLaterAsynchronously(@NotNull plugin: Plugin, delay: Long): SchedulerTask { + checkNotYetScheduled() + return setupTask(minecraftScheduler.createAsyncDelayedTask(plugin, this, delay)) + } + + /** + * Schedules this to repeatedly run until cancelled, starting after the + * specified number of server ticks. + * + * @param plugin The Plugin associated with this task. + * @param delay the ticks to wait before running the task + * @param period the ticks to wait between runs + * @return a Task that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalStateException if this was already scheduled + */ + @Synchronized + @NotNull + fun runTaskTimer(@NotNull plugin: Plugin, delay: Long, period: Long): SchedulerTask { + checkNotYetScheduled() + return setupTask(minecraftScheduler.createRepeatingTask(plugin, this, delay, period)) + } + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + * + * Schedules this to repeatedly run asynchronously until cancelled, + * starting after the specified number of server ticks. + * + * @param plugin The Plugin associated with this task. + * @param delay the ticks to wait before running the task for the first time + * @param period the ticks to wait between runs + * @return a Task that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalStateException if this was already scheduled + */ + @Synchronized + @NotNull + fun runTaskTimerAsynchronously(@NotNull plugin: Plugin, delay: Long, period: Long): SchedulerTask { + checkNotYetScheduled() + return setupTask(minecraftScheduler.createAsyncRepeatingTask(plugin, this, delay, period)) + } + + /** + * Gets the task id for this runnable. + * + * @return the task id that this runnable was scheduled as + * @throws IllegalStateException if task was not scheduled yet + */ + @Synchronized + fun getTaskId(): Int { + checkScheduled() + return task!!.getTaskId() + } + + private fun checkScheduled() { + if (task == null) { + throw IllegalStateException("Not scheduled yet") + } + } + + private fun checkNotYetScheduled() { + if (task != null) { + throw IllegalStateException("Already scheduled as ${task!!.getTaskId()}") + } + } + + @NotNull + private fun setupTask(task: SchedulerTask): SchedulerTask { + this.task = task + return task + } +} diff --git a/holoeasy-core/src/main/kotlin/org/holoeasy/util/scheduler/SchedulerTask.kt b/holoeasy-core/src/main/kotlin/org/holoeasy/util/scheduler/SchedulerTask.kt new file mode 100644 index 0000000..8540ec3 --- /dev/null +++ b/holoeasy-core/src/main/kotlin/org/holoeasy/util/scheduler/SchedulerTask.kt @@ -0,0 +1,33 @@ +package org.holoeasy.util.scheduler + +/** + * Represents a task scheduled by the scheduler. + */ +interface SchedulerTask { + + /** + * Cancels the scheduled task. + */ + fun cancel() + + /** + * Checks if the task has been cancelled. + * + * @return `true` if the task is cancelled, `false` otherwise. + */ + fun isCancelled(): Boolean + + /** + * Gets the unique identifier for the task. + * + * @return The task ID. + */ + fun getTaskId(): Int + + /** + * Checks if the task is currently running. + * + * @return `true` if the task is running, `false` otherwise. + */ + fun isRunning(): Boolean +} From ed1d991d27fdc9688c0f1b5d64d548f69112f00f Mon Sep 17 00:00:00 2001 From: "George V." Date: Mon, 4 Nov 2024 19:40:28 +0200 Subject: [PATCH 02/11] Add PaperMC repository to build script. --- .../src/main/kotlin/buildlogic.java-conventions.gradle.kts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/buildSrc/src/main/kotlin/buildlogic.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/buildlogic.java-conventions.gradle.kts index bbbecde..9af760d 100644 --- a/buildSrc/src/main/kotlin/buildlogic.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/buildlogic.java-conventions.gradle.kts @@ -45,6 +45,10 @@ repositories { url = uri("https://repo.codemc.io/repository/maven-snapshots/") } + maven { + url = uri("https://repo.papermc.io/repository/maven-public/") + } + maven { url = uri("https://jitpack.io") } From 8ccc9dd043e3d2f69e623956bd0176027026af21 Mon Sep 17 00:00:00 2001 From: "George V." Date: Mon, 4 Nov 2024 19:49:00 +0200 Subject: [PATCH 03/11] Replace Bukkit scheduler with HoloEasy scheduler. --- .../kotlin/org/holoeasy/animation/Animations.kt | 9 +++------ .../src/main/kotlin/org/holoeasy/line/ILine.kt | 7 +++---- .../kotlin/org/holoeasy/pool/HologramPool.kt | 11 +++++------ .../holoeasy/pool/InteractiveHologramPool.kt | 16 ++++++++++------ .../kotlin/org/holoeasy/util/BukkitFuture.kt | 17 +++++++++-------- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/holoeasy-core/src/main/kotlin/org/holoeasy/animation/Animations.kt b/holoeasy-core/src/main/kotlin/org/holoeasy/animation/Animations.kt index ae34623..3c72428 100644 --- a/holoeasy-core/src/main/kotlin/org/holoeasy/animation/Animations.kt +++ b/holoeasy-core/src/main/kotlin/org/holoeasy/animation/Animations.kt @@ -1,19 +1,17 @@ package org.holoeasy.animation -import org.bukkit.Bukkit - -import org.bukkit.scheduler.BukkitTask import org.holoeasy.HoloEasy import org.holoeasy.line.ILine +import org.holoeasy.util.scheduler.SchedulerTask -enum class Animations(val task : (ILine<*>) -> BukkitTask) { +enum class Animations(val task: (ILine<*>) -> SchedulerTask) { CIRCLE({ line -> val holo = line.pvt.hologram var yaw = 0.0 - Bukkit.getScheduler().runTaskTimerAsynchronously(HoloEasy.plugin(), java.lang.Runnable { + HoloEasy.scheduler().createAsyncRepeatingTask(HoloEasy.plugin(), { holo.pvt.seeingPlayers.forEach { player -> @@ -30,5 +28,4 @@ enum class Animations(val task : (ILine<*>) -> BukkitTask) { ; - } \ No newline at end of file diff --git a/holoeasy-core/src/main/kotlin/org/holoeasy/line/ILine.kt b/holoeasy-core/src/main/kotlin/org/holoeasy/line/ILine.kt index f4d36e3..383eb64 100644 --- a/holoeasy-core/src/main/kotlin/org/holoeasy/line/ILine.kt +++ b/holoeasy-core/src/main/kotlin/org/holoeasy/line/ILine.kt @@ -2,11 +2,10 @@ package org.holoeasy.line import org.bukkit.Location import org.bukkit.entity.Player -import org.bukkit.plugin.Plugin -import org.bukkit.scheduler.BukkitTask import org.holoeasy.animation.Animations import org.holoeasy.hologram.Hologram import org.holoeasy.reactive.Observer +import org.holoeasy.util.scheduler.SchedulerTask import org.jetbrains.annotations.ApiStatus interface ILine { @@ -15,7 +14,7 @@ interface ILine { lateinit var hologram: Hologram - var animationTask: BukkitTask? = null + var animationTask: SchedulerTask? = null override fun observerUpdate() { for (player in hologram.pvt.seeingPlayers) { @@ -57,7 +56,7 @@ interface ILine { pvt.animationTask = null } - fun update(value : T) + fun update(value: T) enum class Type { diff --git a/holoeasy-core/src/main/kotlin/org/holoeasy/pool/HologramPool.kt b/holoeasy-core/src/main/kotlin/org/holoeasy/pool/HologramPool.kt index c13bcc9..14453b5 100644 --- a/holoeasy-core/src/main/kotlin/org/holoeasy/pool/HologramPool.kt +++ b/holoeasy-core/src/main/kotlin/org/holoeasy/pool/HologramPool.kt @@ -7,15 +7,14 @@ import org.bukkit.event.EventHandler import org.bukkit.event.Listener import org.bukkit.event.player.PlayerQuitEvent import org.bukkit.event.player.PlayerRespawnEvent -import org.bukkit.plugin.Plugin import org.holoeasy.HoloEasy import org.holoeasy.hologram.Hologram -import java.util.UUID +import java.util.* import java.util.concurrent.ConcurrentHashMap class KeyAlreadyExistsException(key: UUID) : IllegalStateException("Id '$key' already exists") -class HologramPool( private val spawnDistance: Double) : Listener, IHologramPool { +class HologramPool(private val spawnDistance: Double) : Listener, IHologramPool { override val holograms: Set = ConcurrentHashMap.newKeySet() @@ -44,7 +43,7 @@ class HologramPool( private val spawnDistance: Double) : Listener, * Starts the hologram tick. */ private fun hologramTick() { - Bukkit.getScheduler().runTaskTimerAsynchronously( HoloEasy.plugin(), Runnable { + HoloEasy.scheduler().createAsyncRepeatingTask(HoloEasy.plugin(), { for (player in ImmutableList.copyOf(Bukkit.getOnlinePlayers())) { for (hologram in this.holograms) { val holoLoc = hologram.location @@ -57,8 +56,8 @@ class HologramPool( private val spawnDistance: Double) : Listener, } continue } else if (!holoLoc.world - // todo: log - !!.isChunkLoaded(holoLoc.blockX shr 4, holoLoc.blockZ shr 4) && isShown + // todo: log + !!.isChunkLoaded(holoLoc.blockX shr 4, holoLoc.blockZ shr 4) && isShown ) { hologram.hide(player) continue diff --git a/holoeasy-core/src/main/kotlin/org/holoeasy/pool/InteractiveHologramPool.kt b/holoeasy-core/src/main/kotlin/org/holoeasy/pool/InteractiveHologramPool.kt index d1568ef..ca0254b 100644 --- a/holoeasy-core/src/main/kotlin/org/holoeasy/pool/InteractiveHologramPool.kt +++ b/holoeasy-core/src/main/kotlin/org/holoeasy/pool/InteractiveHologramPool.kt @@ -5,16 +5,19 @@ import org.bukkit.event.EventHandler import org.bukkit.event.Listener import org.bukkit.event.block.Action import org.bukkit.event.player.PlayerInteractEvent -import org.bukkit.plugin.Plugin import org.holoeasy.HoloEasy import org.holoeasy.action.ClickAction import org.holoeasy.hologram.Hologram import org.holoeasy.line.ILine import org.holoeasy.line.ITextLine import org.holoeasy.util.AABB -import java.util.UUID -class InteractiveHologramPool(private val pool: HologramPool, minHitDistance: Float, maxHitDistance: Float, val clickAction: ClickAction?) : Listener, +class InteractiveHologramPool( + private val pool: HologramPool, + minHitDistance: Float, + maxHitDistance: Float, + val clickAction: ClickAction? +) : Listener, IHologramPool { override val holograms: Set @@ -35,20 +38,21 @@ class InteractiveHologramPool(private val pool: HologramPool, m @EventHandler fun handleInteract(e: PlayerInteractEvent) { - Bukkit.getScheduler().runTaskAsynchronously(HoloEasy.plugin(), Runnable { + HoloEasy.scheduler().runAsyncTask(HoloEasy.plugin(), Runnable { val player = e.player - if(clickAction == null) { + if (clickAction == null) { if (e.action != Action.LEFT_CLICK_AIR && e.action != Action.RIGHT_CLICK_AIR) { return@Runnable } } else { - when(clickAction) { + when (clickAction) { ClickAction.LEFT_CLICK -> { if (e.action != Action.LEFT_CLICK_AIR) { return@Runnable } } + ClickAction.RIGHT_CLICK -> { if (e.action != Action.RIGHT_CLICK_AIR) { return@Runnable diff --git a/holoeasy-core/src/main/kotlin/org/holoeasy/util/BukkitFuture.kt b/holoeasy-core/src/main/kotlin/org/holoeasy/util/BukkitFuture.kt index af0e233..fe8225f 100644 --- a/holoeasy-core/src/main/kotlin/org/holoeasy/util/BukkitFuture.kt +++ b/holoeasy-core/src/main/kotlin/org/holoeasy/util/BukkitFuture.kt @@ -20,6 +20,7 @@ package org.holoeasy.util import org.bukkit.Bukkit import org.bukkit.plugin.Plugin +import org.holoeasy.HoloEasy import java.util.concurrent.CompletableFuture import java.util.function.BiConsumer import java.util.function.Supplier @@ -38,13 +39,13 @@ object BukkitFuture { supplier: Supplier ): CompletableFuture { val future = CompletableFuture() - Bukkit.getScheduler().runTaskAsynchronously(plugin, Runnable { + HoloEasy.scheduler().runAsyncTask(plugin) { try { future.complete(supplier.get()) } catch (t: Throwable) { future.completeExceptionally(t) } - }) + } return future } @@ -59,14 +60,14 @@ object BukkitFuture { runnable: Runnable ): CompletableFuture { val future = CompletableFuture() - Bukkit.getScheduler().runTaskAsynchronously(plugin, Runnable { + HoloEasy.scheduler().runAsyncTask(plugin) { try { runnable.run() future.complete(null) } catch (t: Throwable) { future.completeExceptionally(t) } - }) + } return future } @@ -90,13 +91,13 @@ object BukkitFuture { future.completeExceptionally(t) } } else { - Bukkit.getScheduler().runTask(plugin, Runnable { + HoloEasy.scheduler().runTask(plugin) { try { future.complete(supplier.get()) } catch (t: Throwable) { future.completeExceptionally(t) } - }) + } } return future } @@ -120,14 +121,14 @@ object BukkitFuture { future.completeExceptionally(t) } } else { - Bukkit.getScheduler().runTask(plugin, Runnable { + HoloEasy.scheduler().runTask(plugin) { try { runnable.run() future.complete(null) } catch (t: Throwable) { future.completeExceptionally(t) } - }) + } } return future } From e17bd6f10aeb4551e910488a74680bb9a25cdffa Mon Sep 17 00:00:00 2001 From: "George V." Date: Mon, 4 Nov 2024 19:49:22 +0200 Subject: [PATCH 04/11] Add Folia API support and initialize Folia-specific scheduler. --- gradle/libs.versions.toml | 2 + holoeasy-folia/build.gradle.kts | 35 ++ .../util/scheduler/MinecraftFoliaScheduler.kt | 344 ++++++++++++++++++ settings.gradle.kts | 1 + 4 files changed, 382 insertions(+) create mode 100644 holoeasy-folia/build.gradle.kts create mode 100644 holoeasy-folia/src/main/kotlin/org/holoeasy/util/scheduler/MinecraftFoliaScheduler.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3966c72..3931575 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,9 +3,11 @@ com-comphenix-protocol-protocollib = "5.3.0" com-github-retrooper-packetevents-spigot = "2.5.0" org-jetbrains-kotlin-kotlin-stdlib = "1.9.21" org-spigotmc-spigot-api = "1.16.5-R0.1-SNAPSHOT" +dev-folia-folia-api = "1.19.4-R0.1-SNAPSHOT" [libraries] com-comphenix-protocol-protocollib = { module = "com.comphenix.protocol:ProtocolLib", version.ref = "com-comphenix-protocol-protocollib" } com-github-retrooper-packetevents-spigot = { module = "com.github.retrooper:packetevents-spigot", version.ref = "com-github-retrooper-packetevents-spigot" } org-jetbrains-kotlin-kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "org-jetbrains-kotlin-kotlin-stdlib" } org-spigotmc-spigot-api = { module = "org.spigotmc:spigot-api", version.ref = "org-spigotmc-spigot-api" } +dev-folia-folia-api = { module = "dev.folia:folia-api", version.ref = "dev-folia-folia-api" } diff --git a/holoeasy-folia/build.gradle.kts b/holoeasy-folia/build.gradle.kts new file mode 100644 index 0000000..a2b6e5b --- /dev/null +++ b/holoeasy-folia/build.gradle.kts @@ -0,0 +1,35 @@ +/** + * Hologram-Lib - An asynchronous, high-performance Minecraft Hologram library + * for servers running versions 1.8 to 1.18. + * + * Copyright (C) 2023 unldenis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +plugins { + `kotlin-dsl` + id("buildlogic.java-conventions") +} + +dependencies { + compileOnly(project(":holoeasy-core")) + compileOnly(libs.org.jetbrains.kotlin.kotlin.stdlib) + compileOnly(libs.dev.folia.folia.api) +} + +kotlin.jvmToolchain(17) +java.sourceCompatibility = JavaVersion.VERSION_17 + +description = "holoeasy-folia" \ No newline at end of file diff --git a/holoeasy-folia/src/main/kotlin/org/holoeasy/util/scheduler/MinecraftFoliaScheduler.kt b/holoeasy-folia/src/main/kotlin/org/holoeasy/util/scheduler/MinecraftFoliaScheduler.kt new file mode 100644 index 0000000..cc26561 --- /dev/null +++ b/holoeasy-folia/src/main/kotlin/org/holoeasy/util/scheduler/MinecraftFoliaScheduler.kt @@ -0,0 +1,344 @@ +package org.holoeasy.util.scheduler + +import io.papermc.paper.threadedregions.scheduler.ScheduledTask +import org.bukkit.Bukkit +import org.bukkit.Chunk +import org.bukkit.Location +import org.bukkit.World +import org.bukkit.entity.Entity +import org.bukkit.plugin.Plugin +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.NotNull +import java.util.concurrent.TimeUnit + +@ApiStatus.NonExtendable +class MinecraftFoliaScheduler : MinecraftScheduler { + + /** + * Schedules a task to be executed synchronously on the server's main thread. + * + * @param plugin The plugin associated with this task. + * @param task The task to be executed. + */ + override fun runTask(plugin: Plugin, task: Runnable): SchedulerTask { + return FoliaSchedulerTask(Bukkit.getGlobalRegionScheduler().run(plugin) { task.run() }) + } + + /** + * Schedules a task to be executed asynchronously on a separate thread. + * This method is suitable for handling time-consuming tasks to avoid blocking the main thread. + * + * @param plugin The plugin associated with this task. + * @param task The task to be executed. + */ + override fun runAsyncTask(plugin: Plugin, task: Runnable): SchedulerTask { + return FoliaSchedulerTask(Bukkit.getAsyncScheduler().runNow(plugin) { task.run() }) + } + + /** + * Creates a delayed task that will run the specified `task` after the given `delay`. + * + * @param plugin The plugin associated with this task. + * @param task The task to be executed after the delay. + * @param delay The delay before the task is executed. + */ + override fun createDelayedTask(plugin: Plugin, task: Runnable, delay: Long): SchedulerTask { + return FoliaSchedulerTask(Bukkit.getGlobalRegionScheduler().runDelayed(plugin, { task.run() }, delay)) + } + + /** + * Creates a repeating task that will run the specified `task` after an initial `delay`, + * and then repeatedly execute with the given `period` between executions. + * + * @param plugin The plugin associated with this task. + * @param task The task to be executed repeatedly. + * @param delay The delay before the first execution. + * @param period The time between successive executions. + */ + override fun createRepeatingTask(plugin: Plugin, task: Runnable, delay: Long, period: Long): SchedulerTask { + return FoliaSchedulerTask( + Bukkit.getGlobalRegionScheduler().runAtFixedRate(plugin, { task.run() }, delay, period) + ) + } + + /** + * Schedules an asynchronous delayed task that will run the specified `task` after the given `delay`. + * The task will be executed on a separate thread, making it suitable for non-blocking operations. + * + * @param plugin The plugin associated with this task. + * @param task The task to be executed after the delay. + * @param delay The delay before the task is executed. + */ + override fun createAsyncDelayedTask(plugin: Plugin, task: Runnable, delay: Long): SchedulerTask { + return FoliaSchedulerTask( + Bukkit.getAsyncScheduler().runDelayed( + plugin, { task.run() }, + delay / 20, + TimeUnit.SECONDS + ) + ) + } + + /** + * Schedules an asynchronous repeating task that will run the specified `task` after an initial `delay`, + * and then repeatedly execute with the given `period` between executions. + * The task will be executed on a separate thread, making it suitable for non-blocking operations. + * + * @param plugin The plugin associated with this task. + * @param task The task to be executed repeatedly. + * @param delay The delay before the first execution. + * @param period The time between successive executions. + */ + override fun createAsyncRepeatingTask(plugin: Plugin, task: Runnable, delay: Long, period: Long): SchedulerTask { + return FoliaSchedulerTask( + Bukkit.getAsyncScheduler().runAtFixedRate( + plugin, { task.run() }, + delay / 20, + period / 20, + TimeUnit.SECONDS + ) + ) + } + + /** + * Creates a delayed task for a specific world and chunk. + * + * @param plugin The plugin that owns this task. + * @param task The runnable task to execute. + * @param world The world in which the chunk is located. + * @param chunk The chunk in which the task will be executed. + * @param delay The delay in ticks before the task is executed. + * @return A SchedulerTask representing the created task. + */ + override fun createDelayedTaskForWorld( + plugin: Plugin, + task: Runnable, + world: World, + @NotNull chunk: Chunk, + delay: Long + ): SchedulerTask { + return FoliaSchedulerTask( + Bukkit.getRegionScheduler().runDelayed( + plugin, world, chunk.x, chunk.z, { task.run() }, + delay + ) + ) + } + + /** + * Creates a delayed task for a specific location. + * + * @param plugin The plugin that owns this task. + * @param task The runnable task to execute. + * @param location The location at which the task will be executed. + * @param delay The delay in ticks before the task is executed. + * @return A SchedulerTask representing the created task. + */ + override fun createDelayedForLocation( + plugin: Plugin, + task: Runnable, + location: Location, + delay: Long + ): SchedulerTask { + return FoliaSchedulerTask( + Bukkit.getRegionScheduler().runDelayed( + plugin, location, { task.run() }, delay + ) + ) + } + + /** + * Creates a delayed task for a specific location. + * + * @param plugin The plugin that owns this task. + * @param task The runnable 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 entity The entity in which the task will be executed. + * @param delay The delay in ticks before the task is executed. + * @return A SchedulerTask representing the created task. + */ + override fun createDelayedForEntity( + plugin: Plugin, + task: Runnable, + retired: Runnable?, + @NotNull entity: Entity, + delay: Long + ): SchedulerTask { + return FoliaSchedulerTask(entity.scheduler.runDelayed(plugin, { task.run() }, retired, delay)) + } + + /** + * Creates a task for a specific world and chunk. + * + * @param plugin The plugin that owns this task. + * @param task The runnable task to execute. + * @param world The world in which the chunk is located. + * @param chunk The chunk in which the task will be executed. + * @return A SchedulerTask representing the created task. + */ + override fun createTaskForWorld( + plugin: Plugin, + task: Runnable, + world: World, + @NotNull chunk: Chunk + ): SchedulerTask { + return FoliaSchedulerTask( + Bukkit.getRegionScheduler().run(plugin, world, chunk.x, chunk.z) { task.run() }) + } + + /** + * Creates a task for a specific location. + * + * @param plugin The plugin that owns this task. + * @param task The runnable task to execute. + * @param location The location at which the task will be executed. + * @return A SchedulerTask representing the created task. + */ + override fun createTaskForLocation(plugin: Plugin, task: Runnable, location: Location): SchedulerTask { + return FoliaSchedulerTask(Bukkit.getRegionScheduler().run(plugin, location) { task.run() }) + } + + /** + * Creates a task for a specific location. + * + * @param plugin The plugin that owns this task. + * @param task The runnable 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 entity The entity in which the task will be executed. + * @return A SchedulerTask representing the created task. + */ + override fun createTaskForEntity( + plugin: Plugin, + task: Runnable, + retired: Runnable?, + @NotNull entity: Entity + ): SchedulerTask { + return FoliaSchedulerTask(entity.scheduler.run(plugin, { task.run() }, retired)) + } + + /** + * Creates a repeating task for a specific world and chunk. + * + * @param plugin The plugin that owns this task. + * @param task The runnable task to execute. + * @param world The world in which the chunk is located. + * @param chunk The chunk in which the task will be executed. + * @param delay The initial delay in ticks before the first execution. + * @param period The period in ticks between consecutive executions. + * @return A SchedulerTask representing the created task. + */ + override fun createRepeatingTaskForWorld( + plugin: Plugin, + task: Runnable, + world: World, + @NotNull chunk: Chunk, + delay: Long, + period: Long + ): SchedulerTask { + return FoliaSchedulerTask( + Bukkit.getRegionScheduler().runAtFixedRate( + plugin, world, chunk.x, chunk.z, + { task.run() }, + delay, + period + ) + ) + } + + /** + * Creates a repeating task for a specific location. + * + * @param plugin The plugin that owns this task. + * @param task The runnable task to execute. + * @param location The location at which the task will be executed. + * @param delay The initial delay in ticks before the first execution. + * @param period The period in ticks between consecutive executions. + * @return A SchedulerTask representing the created task. + */ + override fun createRepeatingTaskForLocation( + plugin: Plugin, + task: Runnable, + location: Location, + delay: Long, + period: Long + ): SchedulerTask { + return FoliaSchedulerTask( + Bukkit.getRegionScheduler().runAtFixedRate( + plugin, location, { task.run() }, delay, period + ) + ) + } + + /** + * Creates a repeating task for a specific location. + * + * @param plugin The plugin that owns this task. + * @param task The runnable 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 entity The entity in which the task will be executed. + * @param delay The initial delay in ticks before the first execution. + * @param period The period in ticks between consecutive executions. + * @return A SchedulerTask representing the created task. + */ + override fun createRepeatingTaskForEntity( + plugin: Plugin, + task: Runnable, + retired: Runnable?, + @NotNull entity: Entity, + delay: Long, + period: Long + ): SchedulerTask { + return FoliaSchedulerTask( + entity.scheduler.runAtFixedRate( + plugin, + { task.run() }, + retired, + delay, + period + ) + ) + } + + /** + * Cancels all tasks associated with the given `plugin`. + * + * @param plugin The plugin whose tasks should be canceled. + */ + override fun cancelTasks(plugin: Plugin) { + Bukkit.getGlobalRegionScheduler().cancelTasks(plugin) + } + + /** + * Gets the scheduler instance for this class. + * Since this class is already a subclass of {@link MinecraftScheduler}, + * it returns the current instance as the scheduler. + * + * @return The scheduler instance for this class (i.e., this). + */ + override fun getScheduler(): MinecraftScheduler { + return this + } + + class FoliaSchedulerTask(private val scheduledTask: ScheduledTask?) : SchedulerTask { + + override fun cancel() { + scheduledTask?.cancel() + } + + override fun isCancelled(): Boolean { + if (scheduledTask == null) return true + return scheduledTask.isCancelled + } + + override fun getTaskId(): Int { + return 0 + } + + override fun isRunning(): Boolean { + return when (scheduledTask?.executionState) { + ScheduledTask.ExecutionState.IDLE, ScheduledTask.ExecutionState.RUNNING -> true + else -> false + } + } + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 20e820c..bdd6d33 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,3 +22,4 @@ rootProject.name = "holoeasy" include(":holoeasy-core") include(":holoeasy-example-packetevents") include(":holoeasy-example-protocollib") +include("holoeasy-folia") From 8b2d68e58d357efc97cf4762362b0ed451d15c17 Mon Sep 17 00:00:00 2001 From: "George V." Date: Mon, 4 Nov 2024 19:49:38 +0200 Subject: [PATCH 05/11] Refactor scheduler usage to use `MinecraftBukkitScheduler` for better compatibility. --- .../src/main/java/org/holoeasy/plugin/ExamplePlugin.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/holoeasy-example-packetevents/src/main/java/org/holoeasy/plugin/ExamplePlugin.java b/holoeasy-example-packetevents/src/main/java/org/holoeasy/plugin/ExamplePlugin.java index 7a94346..b4a26a4 100644 --- a/holoeasy-example-packetevents/src/main/java/org/holoeasy/plugin/ExamplePlugin.java +++ b/holoeasy-example-packetevents/src/main/java/org/holoeasy/plugin/ExamplePlugin.java @@ -3,7 +3,6 @@ import com.github.retrooper.packetevents.PacketEvents; import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder; -import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.Material; @@ -18,6 +17,7 @@ import org.holoeasy.packet.PacketImpl; import org.holoeasy.pool.IHologramPool; import org.holoeasy.reactive.MutableState; +import org.holoeasy.util.scheduler.MinecraftBukkitScheduler; import org.jetbrains.annotations.NotNull; @@ -34,6 +34,7 @@ public void onLoad() { public void onDisable() { //Terminate the instance (clean up process) PacketEvents.getAPI().terminate(); + HoloEasy.INSTANCE.scheduler().cancelTasks(this); } @Override @@ -42,7 +43,8 @@ public void onEnable() { PacketEvents.getAPI().init(); // ** Bind the library - HoloEasy.bind(this, PacketImpl.PacketEvents); + HoloEasy.bind(this, PacketImpl.PacketEvents, new MinecraftBukkitScheduler()); + // For Folia use new MinecraftFoliaScheduler() // ** Create a MyHolo Pool, why not? IHologramPool myPool = HoloEasy.startInteractivePool(60); @@ -62,7 +64,7 @@ public void onEnable() { // ** Why not update all holograms 'status' item after 30 seconds? - Bukkit.getScheduler().runTaskLaterAsynchronously(this, () -> { + HoloEasy.INSTANCE.scheduler().createAsyncDelayedTask(this, () -> { for (MyHolo hologram : myPool.getHolograms()) { From 52dad6d1bc472ea5a129a4c673bc07c600cf57a6 Mon Sep 17 00:00:00 2001 From: "George V." Date: Mon, 4 Nov 2024 20:04:51 +0200 Subject: [PATCH 06/11] Add jitpack.yml and ensure-java-17 script for Java 17 build setup. --- ensure-java-17 | 20 ++++++++++++++++++++ jitpack.yml | 11 +++++++++++ 2 files changed, 31 insertions(+) create mode 100644 ensure-java-17 create mode 100644 jitpack.yml diff --git a/ensure-java-17 b/ensure-java-17 new file mode 100644 index 0000000..5b0e0ad --- /dev/null +++ b/ensure-java-17 @@ -0,0 +1,20 @@ +#!/bin/bash + +JV=`java -version 2>&1 >/dev/null | head -1` +echo $JV | sed -E 's/^.*version "([^".]*)\.[^"]*".*$/\1/' + +if [ "$JV" != 17 ]; then + case "$1" in + install) + echo "Installing SDKMAN..." + curl -s "https://get.sdkman.io" | bash + source ~/.sdkman/bin/sdkman-init.sh + sdk version + sdk install java 17.0.1-open + ;; + use) + echo "must source ~/.sdkman/bin/sdkman-init.sh" + exit 1 + ;; + esac +fi \ No newline at end of file diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 0000000..11219c9 --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,11 @@ +jdk: + - openjdk17 +before_install: + - echo "Before Install" + - bash ensure-java-17 install +install: + - echo "Install" + - if ! bash ensure-java-17 use; then source ~/.sdkman/bin/sdkman-init.sh; fi + - java -version + - chmod +x ./gradlew + - ./gradlew clean build shadowJar publishToMavenLocal \ No newline at end of file From 6e6d70b6d8f0715a7320a263123413ed31c9e71c Mon Sep 17 00:00:00 2001 From: "George V." Date: Mon, 4 Nov 2024 20:10:08 +0200 Subject: [PATCH 07/11] holoeasy-example-protocollib: Add MinecraftBukkitScheduler in the HoloEasy binding --- .../src/main/kotlin/org/holoeasy/plugin/ExamplePlugin.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/holoeasy-example-protocollib/src/main/kotlin/org/holoeasy/plugin/ExamplePlugin.kt b/holoeasy-example-protocollib/src/main/kotlin/org/holoeasy/plugin/ExamplePlugin.kt index 4a65a58..641625b 100644 --- a/holoeasy-example-protocollib/src/main/kotlin/org/holoeasy/plugin/ExamplePlugin.kt +++ b/holoeasy-example-protocollib/src/main/kotlin/org/holoeasy/plugin/ExamplePlugin.kt @@ -10,13 +10,14 @@ import org.holoeasy.HoloEasy import org.holoeasy.builder.TextLineModifiers import org.holoeasy.hologram.Hologram import org.holoeasy.packet.PacketImpl +import org.holoeasy.util.scheduler.MinecraftBukkitScheduler class ExamplePlugin : JavaPlugin() { override fun onEnable() { // ** Bind the library - HoloEasy.bind(this, PacketImpl.ProtocolLib) + HoloEasy.bind(this, PacketImpl.ProtocolLib, MinecraftBukkitScheduler()) getCommand("hologram")?.setExecutor { sender, _, _, _ -> @@ -34,7 +35,7 @@ class ExamplePlugin : JavaPlugin() { hologram.hide() // ** Deserialize the previous hologram - val deserialized : HelloWorldHologram = Hologram.deserialize(serialized) + val deserialized: HelloWorldHologram = Hologram.deserialize(serialized) // ** Show it deserialized.show() @@ -52,7 +53,7 @@ class ExamplePlugin : JavaPlugin() { .args(clickCount) .clickable { _ -> clickCount.update { it + 1 } }) - var status= blockLine(ItemStack(Material.RED_DYE)) + var status = blockLine(ItemStack(Material.RED_DYE)) } From 9595e99106315f24ce3a57b0ddddeb9c7efa0799 Mon Sep 17 00:00:00 2001 From: "George V." Date: Mon, 4 Nov 2024 20:53:08 +0200 Subject: [PATCH 08/11] Update `com-github-retrooper-packetevents-spigot` to version 2.6.0. --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3931575..aaaa7f1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] com-comphenix-protocol-protocollib = "5.3.0" -com-github-retrooper-packetevents-spigot = "2.5.0" +com-github-retrooper-packetevents-spigot = "2.6.0" org-jetbrains-kotlin-kotlin-stdlib = "1.9.21" org-spigotmc-spigot-api = "1.16.5-R0.1-SNAPSHOT" dev-folia-folia-api = "1.19.4-R0.1-SNAPSHOT" From e99eb18fc2420ac8faa111b253c1ae3b6e02cff5 Mon Sep 17 00:00:00 2001 From: "George V." Date: Mon, 4 Nov 2024 20:53:20 +0200 Subject: [PATCH 09/11] Convert delay and period from ticks to nanoseconds for async tasks. --- .../util/scheduler/MinecraftFoliaScheduler.kt | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/holoeasy-folia/src/main/kotlin/org/holoeasy/util/scheduler/MinecraftFoliaScheduler.kt b/holoeasy-folia/src/main/kotlin/org/holoeasy/util/scheduler/MinecraftFoliaScheduler.kt index cc26561..51957f7 100644 --- a/holoeasy-folia/src/main/kotlin/org/holoeasy/util/scheduler/MinecraftFoliaScheduler.kt +++ b/holoeasy-folia/src/main/kotlin/org/holoeasy/util/scheduler/MinecraftFoliaScheduler.kt @@ -70,12 +70,9 @@ class MinecraftFoliaScheduler : MinecraftScheduler Date: Mon, 4 Nov 2024 20:53:29 +0200 Subject: [PATCH 10/11] Add colon to module name in settings.gradle.kts for consistency. --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index bdd6d33..33db5fe 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,4 +22,4 @@ rootProject.name = "holoeasy" include(":holoeasy-core") include(":holoeasy-example-packetevents") include(":holoeasy-example-protocollib") -include("holoeasy-folia") +include(":holoeasy-folia") From 3f6f5540985226ed3fa9a4497fe1b40c828a911b Mon Sep 17 00:00:00 2001 From: "George V." Date: Mon, 4 Nov 2024 20:53:56 +0200 Subject: [PATCH 11/11] Example: Adds Folia support and updates scheduler logic (Java 17) --- .../build.gradle.kts | 8 ++++++ .../org/holoeasy/plugin/ExamplePlugin.java | 25 ++++++++++++++++--- .../src/main/resources/plugin.yml | 1 + 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/holoeasy-example-packetevents/build.gradle.kts b/holoeasy-example-packetevents/build.gradle.kts index 38a13f0..f65a8e2 100644 --- a/holoeasy-example-packetevents/build.gradle.kts +++ b/holoeasy-example-packetevents/build.gradle.kts @@ -29,10 +29,18 @@ dependencies { // Set the transitive dependency to false to avoid shading HoloEasy dependencies isTransitive = false } + implementation(project(":holoeasy-folia")) { + // Set the transitive dependency to false to avoid shading HoloEasy dependencies + isTransitive = false + } implementation(libs.com.github.retrooper.packetevents.spigot) compileOnly(libs.org.spigotmc.spigot.api) } +// Folia requires Java 17 +java.sourceCompatibility = JavaVersion.VERSION_17 +java.targetCompatibility = JavaVersion.VERSION_17 + description = "holoeasy-example-packetevents" tasks { diff --git a/holoeasy-example-packetevents/src/main/java/org/holoeasy/plugin/ExamplePlugin.java b/holoeasy-example-packetevents/src/main/java/org/holoeasy/plugin/ExamplePlugin.java index b4a26a4..ff3a7fa 100644 --- a/holoeasy-example-packetevents/src/main/java/org/holoeasy/plugin/ExamplePlugin.java +++ b/holoeasy-example-packetevents/src/main/java/org/holoeasy/plugin/ExamplePlugin.java @@ -3,11 +3,11 @@ import com.github.retrooper.packetevents.PacketEvents; import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder; -import org.bukkit.ChatColor; -import org.bukkit.Location; -import org.bukkit.Material; +import org.bukkit.*; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; import org.holoeasy.HoloEasy; import org.holoeasy.builder.TextLineModifiers; @@ -18,6 +18,8 @@ import org.holoeasy.pool.IHologramPool; import org.holoeasy.reactive.MutableState; import org.holoeasy.util.scheduler.MinecraftBukkitScheduler; +import org.holoeasy.util.scheduler.MinecraftFoliaScheduler; +import org.holoeasy.util.scheduler.MinecraftScheduler; import org.jetbrains.annotations.NotNull; @@ -43,7 +45,13 @@ public void onEnable() { PacketEvents.getAPI().init(); // ** Bind the library - HoloEasy.bind(this, PacketImpl.PacketEvents, new MinecraftBukkitScheduler()); + MinecraftScheduler scheduler; + if (isFolia()) { + scheduler = new MinecraftFoliaScheduler(); + } else { + scheduler = new MinecraftBukkitScheduler(); + } + HoloEasy.bind(this, PacketImpl.PacketEvents, scheduler); // For Folia use new MinecraftFoliaScheduler() // ** Create a MyHolo Pool, why not? @@ -76,6 +84,15 @@ public void onEnable() { }, 20L * 30); } + public boolean isFolia() { + try { + Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + public static class MyHolo extends Hologram { diff --git a/holoeasy-example-packetevents/src/main/resources/plugin.yml b/holoeasy-example-packetevents/src/main/resources/plugin.yml index fb1d939..fb7db91 100644 --- a/holoeasy-example-packetevents/src/main/resources/plugin.yml +++ b/holoeasy-example-packetevents/src/main/resources/plugin.yml @@ -2,5 +2,6 @@ name: ExamplePlugin main: org.holoeasy.plugin.ExamplePlugin version: 1.0 api-version: 1.13 +folia-supported: true commands: hologram: \ No newline at end of file