mirror of
https://github.com/smartcmd/MinecraftConsoles.git
synced 2026-05-18 04:43:50 +00:00
Merge branch 'feature/plugin-api' of github.com:sylvessa/MinecraftConsoles into feature/plugin-api
This commit is contained in:
commit
8e66b2c19e
13 changed files with 570 additions and 5 deletions
|
|
@ -5,6 +5,7 @@ using Minecraft.Server.FourKit.Entity;
|
|||
using Minecraft.Server.FourKit.Event;
|
||||
using Minecraft.Server.FourKit.Inventory;
|
||||
using Minecraft.Server.FourKit.Plugin;
|
||||
using Minecraft.Server.FourKit.Scheduler;
|
||||
|
||||
/// <summary>
|
||||
/// The main entry point for the FourKit plugin API.
|
||||
|
|
@ -388,4 +389,6 @@ public static class FourKit
|
|||
/// </summary>
|
||||
/// <param name="plugin">Plugin to disable.</param>
|
||||
public static void disablePlugin(ServerPlugin plugin) => FourKitHost.s_loader?.DisablePlugin(plugin);
|
||||
|
||||
public static FourKitScheduler getScheduler() => FourKitHost.getScheduler();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,24 @@
|
|||
using Minecraft.Server.FourKit.Scheduler;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Minecraft.Server.FourKit;
|
||||
|
||||
public static partial class FourKitHost
|
||||
{
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
public static void SetSchedulerCallbacks(IntPtr add, IntPtr remove)
|
||||
{
|
||||
try
|
||||
{
|
||||
NativeBridge.SetSchedulerCallbacks(add, remove);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ServerLog.Error("fourkit", $"SetSchedulerCallbacks error: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
public static void SetNativeCallbacks(IntPtr damage, IntPtr setHealth, IntPtr teleport, IntPtr setGameMode, IntPtr broadcastMessage, IntPtr setFallDistance, IntPtr getPlayerSnapshot, IntPtr sendMessage, IntPtr setWalkSpeed, IntPtr teleportEntity)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -190,6 +190,19 @@ public static partial class FourKitHost
|
|||
}
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
public static void FireSchedulerCallback(int currentTick) {
|
||||
try
|
||||
{
|
||||
FourKit.getScheduler().update(currentTick);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ServerLog.Error("fourkit", $"FireSchedulerCallback error: {ex}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
public static int FirePlayerMove(int entityId,
|
||||
double fromX, double fromY, double fromZ,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using Minecraft.Server.FourKit.Entity;
|
||||
using Minecraft.Server.FourKit.Entity;
|
||||
using Minecraft.Server.FourKit.Event.Inventory;
|
||||
using Minecraft.Server.FourKit.Inventory;
|
||||
using Minecraft.Server.FourKit.Scheduler;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Minecraft.Server.FourKit;
|
||||
|
||||
public static partial class FourKitHost
|
||||
{
|
||||
internal static PluginLoader? s_loader;
|
||||
internal static FourKitScheduler? s_scheduler;
|
||||
|
||||
public static IReadOnlyList<Plugin.ServerPlugin> getLoadedPlugins() => s_loader?.Plugins ?? [];
|
||||
|
||||
|
|
@ -16,6 +18,9 @@ public static partial class FourKitHost
|
|||
{
|
||||
try
|
||||
{
|
||||
ServerLog.Info("fourkit", "Initializing Scheduler...");
|
||||
s_scheduler = new FourKitScheduler();
|
||||
|
||||
ServerLog.Info("fourkit", "Initializing plugin system...");
|
||||
|
||||
string pluginsDir = Path.Combine(AppContext.BaseDirectory, "plugins");
|
||||
|
|
@ -47,6 +52,17 @@ public static partial class FourKitHost
|
|||
}
|
||||
}
|
||||
|
||||
internal static FourKitScheduler getScheduler()
|
||||
{
|
||||
if (s_scheduler == null)
|
||||
{
|
||||
ServerLog.Warn("fourkit", "Scheduler accessed before initialization. Initializing now...");
|
||||
s_scheduler = new FourKitScheduler();
|
||||
}
|
||||
|
||||
return s_scheduler;
|
||||
}
|
||||
|
||||
|
||||
private static Guid ParseOrHashGuid(string s)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -236,6 +236,13 @@ internal static class NativeBridge
|
|||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
internal delegate void NativeSetBiomeIdDelegate(int dimId, int x, int z, int biomeId);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
internal delegate void NativeAddSchedulerDelegate(int taskid, int startDelay, int runCooldown);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
internal delegate void NativeRemoveSchedulerDelegate(int taskid);
|
||||
|
||||
internal static NativeDamageDelegate? DamagePlayer;
|
||||
internal static NativeSetHealthDelegate? SetPlayerHealth;
|
||||
internal static NativeTeleportDelegate? TeleportPlayer;
|
||||
|
|
@ -315,6 +322,15 @@ internal static class NativeBridge
|
|||
internal static NativeGetBiomeIdDelegate? GetBiomeId;
|
||||
internal static NativeSetBiomeIdDelegate? SetBiomeId;
|
||||
|
||||
internal static NativeAddSchedulerDelegate? AddScheduler;
|
||||
internal static NativeRemoveSchedulerDelegate? RemoveScheduler;
|
||||
|
||||
internal static void SetSchedulerCallbacks(IntPtr addScheduler, IntPtr removeScheduler)
|
||||
{
|
||||
AddScheduler = Marshal.GetDelegateForFunctionPointer<NativeAddSchedulerDelegate>(addScheduler);
|
||||
RemoveScheduler = Marshal.GetDelegateForFunctionPointer<NativeRemoveSchedulerDelegate>(removeScheduler);
|
||||
}
|
||||
|
||||
internal static void SetCallbacks(IntPtr damage, IntPtr setHealth, IntPtr teleport, IntPtr setGameMode, IntPtr broadcastMessage, IntPtr setFallDistance, IntPtr getPlayerSnapshot, IntPtr sendMessage, IntPtr setWalkSpeed, IntPtr teleportEntity)
|
||||
{
|
||||
DamagePlayer = Marshal.GetDelegateForFunctionPointer<NativeDamageDelegate>(damage);
|
||||
|
|
|
|||
245
Minecraft.Server.FourKit/Scheduler/FourKitScheduler.cs
Normal file
245
Minecraft.Server.FourKit/Scheduler/FourKitScheduler.cs
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
using Minecraft.Server.FourKit.Plugin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Minecraft.Server.FourKit.Scheduler
|
||||
{
|
||||
public class FourKitScheduler
|
||||
{
|
||||
private Dictionary<int, FourKitTask> _taskInstanceMap = new Dictionary<int, FourKitTask>();
|
||||
private Dictionary<int, Action> _taskActionMap = new Dictionary<int, Action>();
|
||||
//temp task based on about intmax / 3, leaves 715m (715827882) ids for real tasks and 1.4b(1431655765) for temp tasks
|
||||
//this is done to avoid needing to cleanup existing task ids, could be handled
|
||||
private int _nextTempTaskId = 715827882;
|
||||
private int _nextTaskId = 0;
|
||||
|
||||
private int _lastTick = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Removes all tasks from the scheduler.
|
||||
/// </summary>
|
||||
public void cancelAllTasks()
|
||||
{
|
||||
foreach (var task in _taskInstanceMap.Values)
|
||||
{
|
||||
NativeBridge.RemoveScheduler?.Invoke(task.getTaskId());
|
||||
}
|
||||
|
||||
_taskInstanceMap.Clear();
|
||||
_taskActionMap.Clear();
|
||||
|
||||
_nextTempTaskId = 715827882;
|
||||
_nextTaskId = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes task from scheduler.
|
||||
/// </summary>
|
||||
/// <param name="taskId">Id number of task to be removed.</param>
|
||||
public void cancelTask(int taskId)
|
||||
{
|
||||
if (_taskInstanceMap.ContainsKey(taskId))
|
||||
{
|
||||
NativeBridge.RemoveScheduler?.Invoke(taskId);
|
||||
_taskInstanceMap.Remove(taskId);
|
||||
_taskActionMap.Remove(taskId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all tasks associated with a particular plugin from the scheduler.
|
||||
/// </summary>
|
||||
/// <param name="plugin">Owner of tasks to be removed.</param>
|
||||
public void cancelTasks(ServerPlugin plugin)
|
||||
{
|
||||
List<int> tasksToRemove = new List<int>();
|
||||
|
||||
foreach (var task in _taskInstanceMap.Values)
|
||||
{
|
||||
if (task.getOwner() == plugin)
|
||||
{
|
||||
NativeBridge.RemoveScheduler?.Invoke(task.getTaskId());
|
||||
tasksToRemove.Add(task.getTaskId());
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var taskId in tasksToRemove)
|
||||
{
|
||||
_taskInstanceMap.Remove(taskId);
|
||||
_taskActionMap.Remove(taskId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of all pending tasks. The ordering of the tasks is not related to their order of execution.
|
||||
/// </summary>
|
||||
/// <returns>Active workers.</returns>
|
||||
public List<FourKitTask> getPendingTasks()
|
||||
{
|
||||
return new List<FourKitTask>(_taskInstanceMap.Values);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the task currently running.
|
||||
///
|
||||
/// A repeating task might not be running currently, but will be running in the future.
|
||||
/// A task that has finished, and does not repeat, will not be running ever again.
|
||||
///
|
||||
/// Explicitly, a task is running if there exists a thread for it, and that thread is alive.
|
||||
/// </summary>
|
||||
/// <param name="taskId">The task to check.</param>
|
||||
/// <returns>If the task is currently running.</returns>
|
||||
public bool isCurrentlyRunning(int taskId)
|
||||
{
|
||||
if (_taskInstanceMap.ContainsKey(taskId))
|
||||
{
|
||||
return _taskInstanceMap[taskId].startedRunning;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the task queued to be run later.
|
||||
///
|
||||
/// If a repeating task is currently running, it might not be queued now but could be in the future.
|
||||
/// A task that is not queued, and not running, will not be queued again.
|
||||
/// </summary>
|
||||
/// <param name="taskId">The task to check.</param>
|
||||
/// <returns>If the task is queued to be run.</returns>
|
||||
public bool isQueued(int taskId)
|
||||
{
|
||||
if (_taskInstanceMap.ContainsKey(taskId))
|
||||
{
|
||||
return !_taskInstanceMap[taskId].startedRunning;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a task that will run on the next server tick.
|
||||
/// </summary>
|
||||
/// <param name="plugin">The reference to the plugin scheduling task.</param>
|
||||
/// <param name="task">The task to be run.</param>
|
||||
/// <returns>A task that contains the id number.</returns>
|
||||
/// <exception cref="ArgumentNullException">If plugin is null.</exception>
|
||||
/// <exception cref="ArgumentNullException">If task is null.</exception>
|
||||
public FourKitTask runTask(ServerPlugin plugin, Action task)
|
||||
{
|
||||
FourKitTask fourKitTask = new FourKitTask(plugin, _nextTempTaskId++, 0, -1);
|
||||
|
||||
startTask(fourKitTask, task);
|
||||
|
||||
return fourKitTask;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a task that will run after the specified number of server ticks.
|
||||
/// </summary>
|
||||
/// <param name="plugin">The reference to the plugin scheduling task.</param>
|
||||
/// <param name="task">The task to be run.</param>
|
||||
/// <param name="delay">The ticks to wait before running the task.</param>
|
||||
/// <returns>A task that contains the id number.</returns>
|
||||
/// <exception cref="ArgumentNullException">If plugin is null.</exception>
|
||||
/// <exception cref="ArgumentNullException">If task is null.</exception>
|
||||
public FourKitTask runTaskLater(ServerPlugin plugin, Action task, int delay)
|
||||
{
|
||||
FourKitTask fourKitTask = new FourKitTask(plugin, _nextTempTaskId++, delay, -1);
|
||||
|
||||
startTask(fourKitTask, task);
|
||||
|
||||
return fourKitTask;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a task that will repeatedly run until cancelled, starting after the specified number of server ticks.
|
||||
/// </summary>
|
||||
/// <param name="plugin">The reference to the plugin scheduling task.</param>
|
||||
/// <param name="task">The task to be run.</param>
|
||||
/// <param name="delay">The ticks to wait before running the task.</param>
|
||||
/// <param name="period">The ticks to wait between runs.</param>
|
||||
/// <returns>A task that contains the id number.</returns>
|
||||
/// <exception cref="ArgumentNullException">If plugin is null.</exception>
|
||||
/// <exception cref="ArgumentNullException">If task is null.</exception>
|
||||
public FourKitTask runTaskTimer(ServerPlugin plugin, Action task, int delay, int period)
|
||||
{
|
||||
FourKitTask fourKitTask = new FourKitTask(plugin, _nextTempTaskId++, delay, period);
|
||||
|
||||
startTask(fourKitTask, task);
|
||||
|
||||
return fourKitTask;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts tracking a task instance and registers it with the native scheduler bridge.
|
||||
/// </summary>
|
||||
/// <param name="task">The task instance to schedule.</param>
|
||||
/// <param name="action">The callback action executed when the task runs.</param>
|
||||
internal void startTask(FourKitTask task, Action action)
|
||||
{
|
||||
task.startedRunning = true;
|
||||
_taskInstanceMap[task.getTaskId()] = task;
|
||||
_taskActionMap[task.getTaskId()] = action;
|
||||
NativeBridge.AddScheduler?.Invoke(task.getTaskId(), task.startDelay, task.runCooldown);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates scheduled tasks for the current server tick and runs due task callbacks.
|
||||
/// </summary>
|
||||
/// <param name="currentTick">The current server tick.</param>
|
||||
internal void update(int currentTick)
|
||||
{
|
||||
if (_lastTick == -1) _lastTick = currentTick;
|
||||
List<int> tasksToRemove = new List<int>();
|
||||
|
||||
foreach (var task in _taskInstanceMap.Values)
|
||||
{
|
||||
if (!task.shouldRun)
|
||||
{
|
||||
tasksToRemove.Add(task.getTaskId());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (task.startDelay > 0)
|
||||
{
|
||||
task.startDelay -= (currentTick - _lastTick);
|
||||
|
||||
if (task.startDelay <= 0)
|
||||
{
|
||||
task.startDelay = 0;
|
||||
_taskActionMap[task.getTaskId()]?.Invoke();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (task.runCooldown == -1)
|
||||
{
|
||||
task.lastRunTick = currentTick;
|
||||
_taskActionMap[task.getTaskId()]?.Invoke();
|
||||
tasksToRemove.Add(task.getTaskId());
|
||||
}
|
||||
else
|
||||
{
|
||||
int lastTaskTick = task.lastRunTick;
|
||||
if (lastTaskTick == -1 || (lastTaskTick + task.runCooldown) <= currentTick)
|
||||
{
|
||||
task.lastRunTick = currentTick;
|
||||
_taskActionMap[task.getTaskId()]?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var taskId in tasksToRemove)
|
||||
{
|
||||
_taskInstanceMap.Remove(taskId);
|
||||
_taskActionMap.Remove(taskId);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
52
Minecraft.Server.FourKit/Scheduler/FourKitTask.cs
Normal file
52
Minecraft.Server.FourKit/Scheduler/FourKitTask.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
using Minecraft.Server.FourKit.Plugin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Minecraft.Server.FourKit.Scheduler
|
||||
{
|
||||
public class FourKitTask
|
||||
{
|
||||
private ServerPlugin owner;
|
||||
private int taskId;
|
||||
internal bool shouldRun = true;
|
||||
|
||||
internal int startDelay;
|
||||
internal int runCooldown;
|
||||
internal int lastRunTick = -1;
|
||||
internal bool startedRunning = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new task instance with owner, task id, start delay, and run cooldown.
|
||||
/// </summary>
|
||||
/// <param name="owner">The plugin that owns this task.</param>
|
||||
/// <param name="taskId">Task id number.</param>
|
||||
/// <param name="startDelay">Delay in server ticks before executing the task.</param>
|
||||
/// <param name="runCooldown">Period in server ticks between runs, or -1 for one-shot tasks.</param>
|
||||
internal FourKitTask(ServerPlugin owner, int taskId, int startDelay, int runCooldown)
|
||||
{
|
||||
this.owner = owner;
|
||||
this.taskId = taskId;
|
||||
this.startDelay = startDelay;
|
||||
this.runCooldown = runCooldown;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will attempt to cancel this task.
|
||||
/// </summary>
|
||||
public void cancel() { shouldRun = false; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Plugin that owns this task.
|
||||
/// </summary>
|
||||
/// <returns>The Plugin that owns the task.</returns>
|
||||
public ServerPlugin getOwner() { return owner; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the taskId for the task.
|
||||
/// </summary>
|
||||
/// <returns>Task id number.</returns>
|
||||
public int getTaskId() { return taskId; }
|
||||
|
||||
}
|
||||
}
|
||||
98
Minecraft.Server.FourKit/docs/scheduler-programming.md
Normal file
98
Minecraft.Server.FourKit/docs/scheduler-programming.md
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
@page scheduler-programming Scheduler Programming
|
||||
|
||||
@section introduction Introduction
|
||||
Usually when we code in FourKit, everything is ran linearly. Despite this you may wish to schedule some code to be executed at a later point of time.
|
||||
|
||||
Do NOT use the built in C# Thread system, it is not safe for FourKit.
|
||||
|
||||
@section getting_started Getting Started
|
||||
|
||||
To get started we either need the FourKitScheduler instance. You can get this from the server instance.
|
||||
```csharp
|
||||
FourKitScheduler scheduler = FourKit.getScheduler();
|
||||
```
|
||||
|
||||
When scheduling a task, you will also need to pass your main plugin class instance. Here is an example of how it can be done:
|
||||
|
||||
```csharp
|
||||
public class ExampleOne
|
||||
{
|
||||
// you can also use a getter
|
||||
public static ExampleOne Instance { get; private set; }
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
}
|
||||
// then
|
||||
public class Other
|
||||
{
|
||||
private readonly ExampleOne plugin = ExampleOne.Instance;
|
||||
}
|
||||
```
|
||||
|
||||
@section scheduling_delayed_task Scheduling a Delayed Task
|
||||
|
||||
The scheduling itself is based on ticks, the Minecraft time unit. 1 tick = 0.05 seconds or 1 second = 20 ticks.
|
||||
|
||||
Let's say you want to schedule a task to run 30 seconds later which broadcasts a message:
|
||||
|
||||
```csharp
|
||||
FourKitScheduler scheduler = FourKit.getScheduler();
|
||||
|
||||
scheduler.runTaskLater(plugin, () => {
|
||||
FourKit.broadcastMessage("Mooooo!");
|
||||
}, 20 * 30 /*<-- the delay */);
|
||||
```
|
||||
|
||||
@section scheduling_repeating_task Scheduling a Repeating Task
|
||||
|
||||
Repeating tasks are tasks that can reschedule themselves.
|
||||
|
||||
Let's say you want to schedule a task to run 10 seconds later then after that it should repeat itself a finite amount of times with an interval of 5 seconds between each consecutive run:
|
||||
|
||||
```csharp
|
||||
FourKitScheduler scheduler = FourKit.getScheduler();
|
||||
|
||||
scheduler.runTaskTimer(plugin, () => {
|
||||
FourKit.broadcastMessage("Mooooo!");
|
||||
}, 20 * 10 /*<-- the initial delay */, 20 * 5 /*<-- the interval */);
|
||||
```
|
||||
|
||||
@section run_task_next_tick Running a Task on the Next Tick
|
||||
|
||||
Sometimes we just want to run some code on the next tick:
|
||||
|
||||
```csharp
|
||||
FourKitScheduler scheduler = FourKit.getScheduler();
|
||||
|
||||
scheduler.runTask(plugin, () => {
|
||||
FourKit.broadcastMessage("Mooooo again!");
|
||||
});
|
||||
```
|
||||
|
||||
@section canceling_tasks Canceling Tasks
|
||||
|
||||
Sometimes we want to just cancel a task!
|
||||
|
||||
```csharp
|
||||
FourKitScheduler scheduler = FourKit.getScheduler();
|
||||
|
||||
// Cancel outside
|
||||
FourKitTask task = scheduler.runTaskLater(plugin, () => {
|
||||
FourKit.broadcastMessage("Mooooo again!");
|
||||
}, 20 * 60);
|
||||
|
||||
// Cancel inside
|
||||
scheduler.runTaskTimer(plugin, () => {
|
||||
if (FourKit.getOnlinePlayers().Count == 0) {
|
||||
task.cancel(); // <--
|
||||
return;
|
||||
}
|
||||
FourKit.broadcastMessage("Mooooo again!");
|
||||
}, 0, 20 * 60);
|
||||
|
||||
// then
|
||||
task.cancel(); // <--
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue