From 76270d7f3cb29f204d9959b41e160737d96f6d05 Mon Sep 17 00:00:00 2001
From: DrPerkyLegit <116128211+DrPerkyLegit@users.noreply.github.com>
Date: Wed, 6 May 2026 18:36:47 -0400
Subject: [PATCH] FourKitScheduler & FourKitTask (#17)
* scheduler
* add docs + make cancel public lol
---------
Co-authored-by: sylvessa <225480449+sylvessa@users.noreply.github.com>
---
Minecraft.Client/MinecraftServer.cpp | 10 +
Minecraft.Server.FourKit/FourKit.cs | 3 +
.../FourKitHost.Callbacks.cs | 15 ++
.../FourKitHost.Events.cs | 13 +
Minecraft.Server.FourKit/FourKitHost.cs | 20 +-
Minecraft.Server.FourKit/NativeBridge.cs | 16 ++
.../Scheduler/FourKitScheduler.cs | 245 ++++++++++++++++++
.../Scheduler/FourKitTask.cs | 52 ++++
.../docs/scheduler-programming.md | 98 +++++++
Minecraft.Server/FourKitBridge.cpp | 26 +-
Minecraft.Server/FourKitBridge.h | 1 +
Minecraft.Server/FourKitNatives.cpp | 70 ++++-
Minecraft.Server/FourKitNatives.h | 6 +
13 files changed, 570 insertions(+), 5 deletions(-)
create mode 100644 Minecraft.Server.FourKit/Scheduler/FourKitScheduler.cs
create mode 100644 Minecraft.Server.FourKit/Scheduler/FourKitTask.cs
create mode 100644 Minecraft.Server.FourKit/docs/scheduler-programming.md
diff --git a/Minecraft.Client/MinecraftServer.cpp b/Minecraft.Client/MinecraftServer.cpp
index 27ee68b65..36d021d8e 100644
--- a/Minecraft.Client/MinecraftServer.cpp
+++ b/Minecraft.Client/MinecraftServer.cpp
@@ -63,6 +63,10 @@
#include "Durango\Network\NetworkPlayerDurango.h"
#endif
+#if defined(MINECRAFT_SERVER_BUILD)
+#include "..\Minecraft.Server\FourKitNatives.h"
+#endif
+
#define DEBUG_SERVER_DONT_SPAWN_MOBS 0
//4J Added
@@ -2208,6 +2212,12 @@ void MinecraftServer::tick()
connection->tick();
PIXEndNamedEvent();
+#if defined(MINECRAFT_SERVER_BUILD)
+ PIXBeginNamedEvent(0, "FourKit Scheduler tick");
+ FourKitBridge::ServerTickCallback(tickCount);
+ PIXEndNamedEvent();
+#endif
+
// 4J - removed
#if 0
for (size_t i = 0; i < tickables.size(); i++) {
diff --git a/Minecraft.Server.FourKit/FourKit.cs b/Minecraft.Server.FourKit/FourKit.cs
index 7da6bcae5..d4eebaa26 100644
--- a/Minecraft.Server.FourKit/FourKit.cs
+++ b/Minecraft.Server.FourKit/FourKit.cs
@@ -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;
///
/// The main entry point for the FourKit plugin API.
@@ -388,4 +389,6 @@ public static class FourKit
///
/// Plugin to disable.
public static void disablePlugin(ServerPlugin plugin) => FourKitHost.s_loader?.DisablePlugin(plugin);
+
+ public static FourKitScheduler getScheduler() => FourKitHost.getScheduler();
}
diff --git a/Minecraft.Server.FourKit/FourKitHost.Callbacks.cs b/Minecraft.Server.FourKit/FourKitHost.Callbacks.cs
index 575c2e001..22b7b7d05 100644
--- a/Minecraft.Server.FourKit/FourKitHost.Callbacks.cs
+++ b/Minecraft.Server.FourKit/FourKitHost.Callbacks.cs
@@ -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)
{
diff --git a/Minecraft.Server.FourKit/FourKitHost.Events.cs b/Minecraft.Server.FourKit/FourKitHost.Events.cs
index f6c6ef253..dbac6b1bd 100644
--- a/Minecraft.Server.FourKit/FourKitHost.Events.cs
+++ b/Minecraft.Server.FourKit/FourKitHost.Events.cs
@@ -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,
diff --git a/Minecraft.Server.FourKit/FourKitHost.cs b/Minecraft.Server.FourKit/FourKitHost.cs
index b87d0a5a5..57cc9513d 100644
--- a/Minecraft.Server.FourKit/FourKitHost.cs
+++ b/Minecraft.Server.FourKit/FourKitHost.cs
@@ -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 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)
{
diff --git a/Minecraft.Server.FourKit/NativeBridge.cs b/Minecraft.Server.FourKit/NativeBridge.cs
index b936e390e..f48f96f6c 100644
--- a/Minecraft.Server.FourKit/NativeBridge.cs
+++ b/Minecraft.Server.FourKit/NativeBridge.cs
@@ -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(addScheduler);
+ RemoveScheduler = Marshal.GetDelegateForFunctionPointer(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(damage);
diff --git a/Minecraft.Server.FourKit/Scheduler/FourKitScheduler.cs b/Minecraft.Server.FourKit/Scheduler/FourKitScheduler.cs
new file mode 100644
index 000000000..c0bdb15dd
--- /dev/null
+++ b/Minecraft.Server.FourKit/Scheduler/FourKitScheduler.cs
@@ -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 _taskInstanceMap = new Dictionary();
+ private Dictionary _taskActionMap = new Dictionary();
+ //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;
+
+ ///
+ /// Removes all tasks from the scheduler.
+ ///
+ public void cancelAllTasks()
+ {
+ foreach (var task in _taskInstanceMap.Values)
+ {
+ NativeBridge.RemoveScheduler?.Invoke(task.getTaskId());
+ }
+
+ _taskInstanceMap.Clear();
+ _taskActionMap.Clear();
+
+ _nextTempTaskId = 715827882;
+ _nextTaskId = 0;
+ }
+
+ ///
+ /// Removes task from scheduler.
+ ///
+ /// Id number of task to be removed.
+ public void cancelTask(int taskId)
+ {
+ if (_taskInstanceMap.ContainsKey(taskId))
+ {
+ NativeBridge.RemoveScheduler?.Invoke(taskId);
+ _taskInstanceMap.Remove(taskId);
+ _taskActionMap.Remove(taskId);
+ }
+ }
+
+ ///
+ /// Removes all tasks associated with a particular plugin from the scheduler.
+ ///
+ /// Owner of tasks to be removed.
+ public void cancelTasks(ServerPlugin plugin)
+ {
+ List tasksToRemove = new List();
+
+ 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);
+ }
+ }
+
+ ///
+ /// Returns a list of all pending tasks. The ordering of the tasks is not related to their order of execution.
+ ///
+ /// Active workers.
+ public List getPendingTasks()
+ {
+ return new List(_taskInstanceMap.Values);
+ }
+
+ ///
+ /// 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.
+ ///
+ /// The task to check.
+ /// If the task is currently running.
+ public bool isCurrentlyRunning(int taskId)
+ {
+ if (_taskInstanceMap.ContainsKey(taskId))
+ {
+ return _taskInstanceMap[taskId].startedRunning;
+ }
+
+ return false;
+ }
+
+ ///
+ /// 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.
+ ///
+ /// The task to check.
+ /// If the task is queued to be run.
+ public bool isQueued(int taskId)
+ {
+ if (_taskInstanceMap.ContainsKey(taskId))
+ {
+ return !_taskInstanceMap[taskId].startedRunning;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Returns a task that will run on the next server tick.
+ ///
+ /// The reference to the plugin scheduling task.
+ /// The task to be run.
+ /// A task that contains the id number.
+ /// If plugin is null.
+ /// If task is null.
+ public FourKitTask runTask(ServerPlugin plugin, Action task)
+ {
+ FourKitTask fourKitTask = new FourKitTask(plugin, _nextTempTaskId++, 0, -1);
+
+ startTask(fourKitTask, task);
+
+ return fourKitTask;
+
+ }
+
+ ///
+ /// Returns a task that will run after the specified number of server ticks.
+ ///
+ /// The reference to the plugin scheduling task.
+ /// The task to be run.
+ /// The ticks to wait before running the task.
+ /// A task that contains the id number.
+ /// If plugin is null.
+ /// If task is null.
+ public FourKitTask runTaskLater(ServerPlugin plugin, Action task, int delay)
+ {
+ FourKitTask fourKitTask = new FourKitTask(plugin, _nextTempTaskId++, delay, -1);
+
+ startTask(fourKitTask, task);
+
+ return fourKitTask;
+
+ }
+
+ ///
+ /// Returns a task that will repeatedly run until cancelled, starting after the specified number of server ticks.
+ ///
+ /// The reference to the plugin scheduling task.
+ /// The task to be run.
+ /// The ticks to wait before running the task.
+ /// The ticks to wait between runs.
+ /// A task that contains the id number.
+ /// If plugin is null.
+ /// If task is null.
+ public FourKitTask runTaskTimer(ServerPlugin plugin, Action task, int delay, int period)
+ {
+ FourKitTask fourKitTask = new FourKitTask(plugin, _nextTempTaskId++, delay, period);
+
+ startTask(fourKitTask, task);
+
+ return fourKitTask;
+
+ }
+
+ ///
+ /// Starts tracking a task instance and registers it with the native scheduler bridge.
+ ///
+ /// The task instance to schedule.
+ /// The callback action executed when the task runs.
+ 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);
+ }
+
+ ///
+ /// Updates scheduled tasks for the current server tick and runs due task callbacks.
+ ///
+ /// The current server tick.
+ internal void update(int currentTick)
+ {
+ if (_lastTick == -1) _lastTick = currentTick;
+ List tasksToRemove = new List();
+
+ 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);
+ }
+ }
+
+ }
+}
diff --git a/Minecraft.Server.FourKit/Scheduler/FourKitTask.cs b/Minecraft.Server.FourKit/Scheduler/FourKitTask.cs
new file mode 100644
index 000000000..005abbde1
--- /dev/null
+++ b/Minecraft.Server.FourKit/Scheduler/FourKitTask.cs
@@ -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;
+
+ ///
+ /// Initializes a new task instance with owner, task id, start delay, and run cooldown.
+ ///
+ /// The plugin that owns this task.
+ /// Task id number.
+ /// Delay in server ticks before executing the task.
+ /// Period in server ticks between runs, or -1 for one-shot tasks.
+ internal FourKitTask(ServerPlugin owner, int taskId, int startDelay, int runCooldown)
+ {
+ this.owner = owner;
+ this.taskId = taskId;
+ this.startDelay = startDelay;
+ this.runCooldown = runCooldown;
+ }
+
+ ///
+ /// Will attempt to cancel this task.
+ ///
+ public void cancel() { shouldRun = false; }
+
+ ///
+ /// Returns the Plugin that owns this task.
+ ///
+ /// The Plugin that owns the task.
+ public ServerPlugin getOwner() { return owner; }
+
+ ///
+ /// Returns the taskId for the task.
+ ///
+ /// Task id number.
+ public int getTaskId() { return taskId; }
+
+ }
+}
diff --git a/Minecraft.Server.FourKit/docs/scheduler-programming.md b/Minecraft.Server.FourKit/docs/scheduler-programming.md
new file mode 100644
index 000000000..99d961997
--- /dev/null
+++ b/Minecraft.Server.FourKit/docs/scheduler-programming.md
@@ -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(); // <--
+```
\ No newline at end of file
diff --git a/Minecraft.Server/FourKitBridge.cpp b/Minecraft.Server/FourKitBridge.cpp
index e7f64cd19..33fecb4c2 100644
--- a/Minecraft.Server/FourKitBridge.cpp
+++ b/Minecraft.Server/FourKitBridge.cpp
@@ -30,6 +30,8 @@ typedef int(__stdcall *fn_fire_player_move)(int entityId,
double fromX, double fromY, double fromZ,
double toX, double toY, double toZ,
double *outCoords);
+typedef void(__stdcall* fn_set_scheduler_callbacks)(void* add, void* remove);
+typedef void(__stdcall* fn_fire_scheduler_callback)(int currentTick);
typedef void(__stdcall *fn_set_native_callbacks)(void *damage, void *setHealth, void *teleport, void *setGameMode, void *broadcastMessage, void *setFallDistance, void *getPlayerSnapshot, void *sendMessage, void *setWalkSpeed, void *teleportEntity);
typedef void(__stdcall *fn_set_world_callbacks)(void *getTileId, void *getTileData, void *setTile, void *setTileData, void *breakBlock, void *getHighestBlockY, void *getWorldInfo, void *setWorldTime, void *setWeather, void *createExplosion, void *strikeLightning, void *setSpawnLocation, void *dropItem);
typedef void(__stdcall *fn_update_entity_id)(int oldEntityId, int newEntityId);
@@ -127,6 +129,8 @@ static fn_update_entity_id s_managedUpdateEntityId = nullptr;
static fn_fire_player_quit s_managedFireQuit = nullptr;
static fn_fire_player_kick s_managedFireKick = nullptr;
static fn_fire_player_move s_managedFireMove = nullptr;
+static fn_set_scheduler_callbacks s_managedSetSchedulerCallbacks = nullptr;
+static fn_fire_scheduler_callback s_managedFireSchedulerCallback = nullptr;
static fn_set_native_callbacks s_managedSetCallbacks = nullptr;
static fn_set_world_callbacks s_managedSetWorldCallbacks = nullptr;
static fn_fire_structure_grow s_managedFireStructureGrow = nullptr;
@@ -209,6 +213,8 @@ void Initialize()
{L"FirePlayerQuit", (void **)&s_managedFireQuit},
{L"FirePlayerKick", (void **)&s_managedFireKick},
{L"FirePlayerMove", (void **)&s_managedFireMove},
+ {L"SetSchedulerCallbacks", (void**)&s_managedSetSchedulerCallbacks},
+ {L"FireSchedulerCallback", (void**)&s_managedFireSchedulerCallback},
{L"SetNativeCallbacks", (void **)&s_managedSetCallbacks},
{L"SetWorldCallbacks", (void **)&s_managedSetWorldCallbacks},
{L"UpdatePlayerEntityId", (void **)&s_managedUpdateEntityId},
@@ -267,9 +273,11 @@ void Initialize()
return;
}
- s_initialized = true;
- s_managedInit();
+ s_managedSetSchedulerCallbacks(
+ (void*)&NativeAddScheduler,
+ (void*)&NativeRemoveScheduler
+ );
s_managedSetCallbacks(
(void *)&NativeDamagePlayer,
@@ -372,6 +380,10 @@ void Initialize()
(void *)&NativeGetWorldEntities,
(void *)&NativeGetChunkEntities);
+ //we should init after setting callbacks so we have access inside the plugins on enable
+ s_initialized = true;
+ s_managedInit();
+
LogInfo("fourkit", "FourKit initialized successfully.");
}
@@ -512,6 +524,16 @@ void UpdatePlayerEntityId(int oldEntityId, int newEntityId)
LogDebugf("fourkit", "UpdatePlayerEntityId: %d -> %d", oldEntityId, newEntityId);
}
+void FireSchedulerCallback(int currentTick)
+{
+ if (!s_initialized || !s_managedFireSchedulerCallback)
+ {
+ return;
+ }
+
+ s_managedFireSchedulerCallback(currentTick);
+}
+
bool FirePlayerMove(int entityId,
double fromX, double fromY, double fromZ,
double toX, double toY, double toZ,
diff --git a/Minecraft.Server/FourKitBridge.h b/Minecraft.Server/FourKitBridge.h
index eafb3c827..99308fbd6 100644
--- a/Minecraft.Server/FourKitBridge.h
+++ b/Minecraft.Server/FourKitBridge.h
@@ -12,6 +12,7 @@ namespace FourKitBridge
bool FirePlayerQuit(int entityId);
bool FirePlayerKick(int entityId, int disconnectReason,
std::wstring &outLeaveMessage);
+ void FireSchedulerCallback(int currentTick);
bool FirePlayerMove(int entityId,
double fromX, double fromY, double fromZ,
double toX, double toY, double toZ,
diff --git a/Minecraft.Server/FourKitNatives.cpp b/Minecraft.Server/FourKitNatives.cpp
index f60658268..f79e4407e 100644
--- a/Minecraft.Server/FourKitNatives.cpp
+++ b/Minecraft.Server/FourKitNatives.cpp
@@ -47,6 +47,7 @@
#include "Common\NetworkUtils.h"
#include "ServerLogManager.h"
#include "../Minecraft.World/ItemInstance.cpp"
+#include
namespace
{
@@ -104,12 +105,79 @@ class VirtualContainer : public SimpleContainer
return m_containerType;
}
};
+class NativeFourKitTask;
+
+static int64_t STATIC_lastTick = -1;
+static std::unordered_map> _taskCache;
+static std::mutex _taskMutex;
+
+class NativeFourKitTask {
+public:
+ int startDelay;
+ int runCooldown;
+
+ int lastRunTick;
+
+ NativeFourKitTask(int _startDelay, int _runCooldown) : startDelay(_startDelay), runCooldown(_runCooldown), lastRunTick(-1) {};
+};
}
namespace FourKitBridge
{
-void __cdecl NativeDamagePlayer(int entityId, float amount)
+
+ void __cdecl NativeAddScheduler(int taskid, int startDelay, int runCooldown)
+ {
+ std::lock_guard g(_taskMutex);
+ _taskCache.emplace(taskid, std::make_shared(startDelay, runCooldown));
+ }
+
+ void __cdecl NativeRemoveScheduler(int taskid)
+ {
+ std::lock_guard g(_taskMutex);
+ auto it = _taskCache.find(taskid);
+
+ if (it != _taskCache.end()) {
+ _taskCache.erase(it);
+ }
+ }
+
+ void ServerTickCallback(int currentTick)
+ {
+ bool callManagedFunction = false;
+
+ if (STATIC_lastTick == -1) {
+ callManagedFunction = true;
+ STATIC_lastTick = currentTick;
+ }
+
+ {
+ std::lock_guard g(_taskMutex);
+ for (const auto& [taskid, task] : _taskCache)
+ {
+ NativeFourKitTask* taskPointer = task.get();
+ if (taskPointer->startDelay > 0) {
+ taskPointer->startDelay -= (currentTick - STATIC_lastTick);
+
+ if (taskPointer->startDelay <= 0) {
+ callManagedFunction = true; //make c# update the tasks so its not queued anymore but now its running
+ taskPointer->startDelay = 0; //ensure it stays 0
+ }
+ continue;
+ }
+
+ int lastTaskTick = taskPointer->lastRunTick;
+ if (lastTaskTick == -1 || (lastTaskTick + taskPointer->runCooldown) <= currentTick) {
+ callManagedFunction = true;
+ taskPointer->lastRunTick = currentTick;
+ }
+ }
+ }
+
+ if (callManagedFunction) FireSchedulerCallback(currentTick);
+ STATIC_lastTick = currentTick;
+ }
+ void __cdecl NativeDamagePlayer(int entityId, float amount)
{
auto player = FindPlayer(entityId);
if (player)
diff --git a/Minecraft.Server/FourKitNatives.h b/Minecraft.Server/FourKitNatives.h
index ae241e862..64a107458 100644
--- a/Minecraft.Server/FourKitNatives.h
+++ b/Minecraft.Server/FourKitNatives.h
@@ -3,6 +3,12 @@
namespace FourKitBridge
{
+ //scheduler
+ void __cdecl NativeAddScheduler(int taskid, int startDelay, int runCooldown);
+ void __cdecl NativeRemoveScheduler(int taskid);
+
+ void ServerTickCallback(int currentTick);
+
// core
void __cdecl NativeDamagePlayer(int entityId, float amount);
void __cdecl NativeSetPlayerHealth(int entityId, float health);