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 baa9543c2..23e4666bc 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 f1a166d53..f8c0fbe23 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); @@ -128,6 +130,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; @@ -211,6 +215,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}, @@ -270,9 +276,11 @@ void Initialize() return; } - s_initialized = true; - s_managedInit(); + s_managedSetSchedulerCallbacks( + (void*)&NativeAddScheduler, + (void*)&NativeRemoveScheduler + ); s_managedSetCallbacks( (void *)&NativeDamagePlayer, @@ -375,6 +383,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."); } @@ -515,6 +527,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 754807700..5a0198d43 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);