diff --git a/managed/CounterStrikeSharp.API/Generated/Natives/API.cs b/managed/CounterStrikeSharp.API/Generated/Natives/API.cs
index 7ad8032c7..6c241407c 100644
--- a/managed/CounterStrikeSharp.API/Generated/Natives/API.cs
+++ b/managed/CounterStrikeSharp.API/Generated/Natives/API.cs
@@ -807,16 +807,6 @@ public static double GetTickedTime(){
}
}
- public static void QueueTaskForNextFrame(InputArgument callback){
- lock (ScriptContext.GlobalScriptContext.Lock) {
- ScriptContext.GlobalScriptContext.Reset();
- ScriptContext.GlobalScriptContext.Push((InputArgument)callback);
- ScriptContext.GlobalScriptContext.SetIdentifier(0x9FE394D8);
- ScriptContext.GlobalScriptContext.Invoke();
- ScriptContext.GlobalScriptContext.CheckErrors();
- }
- }
-
public static void QueueTaskForFrame(int tick, InputArgument callback){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
@@ -828,16 +818,6 @@ public static void QueueTaskForFrame(int tick, InputArgument callback){
}
}
- public static void QueueTaskForNextWorldUpdate(InputArgument callback){
- lock (ScriptContext.GlobalScriptContext.Lock) {
- ScriptContext.GlobalScriptContext.Reset();
- ScriptContext.GlobalScriptContext.Push((InputArgument)callback);
- ScriptContext.GlobalScriptContext.SetIdentifier(0xAD51A0C9);
- ScriptContext.GlobalScriptContext.Invoke();
- ScriptContext.GlobalScriptContext.CheckErrors();
- }
- }
-
public static IntPtr GetValveInterface(int interfacetype, string interfacename){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
diff --git a/managed/CounterStrikeSharp.API/Server.cs b/managed/CounterStrikeSharp.API/Server.cs
index 60a0b4c51..c6255bb80 100644
--- a/managed/CounterStrikeSharp.API/Server.cs
+++ b/managed/CounterStrikeSharp.API/Server.cs
@@ -14,21 +14,51 @@
* along with CounterStrikeSharp. If not, see . *
*/
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Runtime.InteropServices;
+using System.Collections.Concurrent;
using System.Threading.Tasks;
-using CounterStrikeSharp.API.Core;
-using CounterStrikeSharp.API.Core.Translations;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
+using Microsoft.Extensions.Logging;
namespace CounterStrikeSharp.API
{
public class Server
{
+ static Server()
+ {
+ NativeAPI.AddListener("OnTick", (Delegate)(() => OnTick()));
+ NativeAPI.AddListener("OnServerPreWorldUpdate", (Delegate)((bool simulating) => OnWorldUpdate()));
+ }
+
+ private static readonly ConcurrentQueue _onTickTaskQueue = new();
+ private static readonly ConcurrentQueue _onWorldUpdateTaskQueue = new();
+
+ internal static void OnTick()
+ {
+ ExecuteTickTasks(_onTickTaskQueue);
+ }
+
+ internal static void OnWorldUpdate()
+ {
+ ExecuteTickTasks(_onWorldUpdateTaskQueue);
+ }
+
+ private static void ExecuteTickTasks(ConcurrentQueue taskQueue)
+ {
+ while (taskQueue.TryDequeue(out var task))
+ {
+ try
+ {
+ task();
+ }
+ catch (Exception e)
+ {
+ Application.Instance.Logger.LogError(e, "Error invoking callback");
+ }
+ }
+ }
+
+
///
/// Duration of a single game tick in seconds, based on a 64 tick server (hard coded in CS2).
///
@@ -101,9 +131,22 @@ public static void RunOnTick(int tick, Action task)
///
public static Task NextFrameAsync(Action task)
{
- var functionReference = FunctionReference.Create(task, FunctionLifetime.SingleUse);
- NativeAPI.QueueTaskForNextFrame(functionReference);
- return functionReference.CompletionTask;
+ var tcs = new TaskCompletionSource();
+
+ _onTickTaskQueue.Enqueue(() =>
+ {
+ try
+ {
+ task();
+ tcs.SetResult();
+ }
+ catch (Exception ex)
+ {
+ tcs.SetException(ex);
+ }
+ });
+
+ return tcs.Task;
}
///
@@ -121,9 +164,22 @@ public static void NextFrame(Action task)
///
public static Task NextWorldUpdateAsync(Action task)
{
- var functionReference = FunctionReference.Create(task, FunctionLifetime.SingleUse);
- NativeAPI.QueueTaskForNextWorldUpdate(functionReference);
- return functionReference.CompletionTask;
+ var tcs = new TaskCompletionSource();
+
+ _onWorldUpdateTaskQueue.Enqueue(() =>
+ {
+ try
+ {
+ task();
+ tcs.SetResult();
+ }
+ catch (Exception ex)
+ {
+ tcs.SetException(ex);
+ }
+ });
+
+ return tcs.Task;
}
///
diff --git a/managed/CounterStrikeSharp.Tests.Native/FrameSchedulingTests.cs b/managed/CounterStrikeSharp.Tests.Native/FrameSchedulingTests.cs
index 3665bc492..ecca5c278 100644
--- a/managed/CounterStrikeSharp.Tests.Native/FrameSchedulingTests.cs
+++ b/managed/CounterStrikeSharp.Tests.Native/FrameSchedulingTests.cs
@@ -10,13 +10,24 @@ namespace NativeTestsPlugin;
public class FrameSchedulingTests
{
+ [Fact]
+ public async Task QueueTaskForNextFrame_RunsOnMainThread()
+ {
+ await Task.Run(async () =>
+ {
+ await Task.Delay(10);
+ Assert.NotEqual(Thread.CurrentThread.ManagedThreadId, NativeTestsPlugin.gameThreadId);
+
+ await Server.NextFrameAsync(() => { Assert.Equal(Thread.CurrentThread.ManagedThreadId, NativeTestsPlugin.gameThreadId); });
+ });
+ }
+
[Fact]
public async Task QueueTaskForNextFrame_ExecutesCallback()
{
var mock = new Mock();
- var callback = FunctionReference.Create(mock.Object);
- NativeAPI.QueueTaskForNextFrame(callback);
+ Server.NextFrame(mock.Object);
await WaitOneFrame();
mock.Verify(s => s(), Times.Once);
@@ -39,13 +50,28 @@ public async Task QueueTaskForFrame_ExecutesAtSpecifiedTick()
mock.Verify(s => s(), Times.Once);
}
+ [Fact]
+ public async Task QueueTaskForNextWorldUpdate_RunsOnMainThread()
+ {
+ await Task.Run(async () =>
+ {
+ await Task.Delay(10);
+ Assert.NotEqual(Thread.CurrentThread.ManagedThreadId, NativeTestsPlugin.gameThreadId);
+
+ await Server.NextWorldUpdateAsync(() =>
+ {
+ Assert.Equal(Thread.CurrentThread.ManagedThreadId, NativeTestsPlugin.gameThreadId);
+ });
+ });
+ }
+
+
[Fact]
public async Task QueueTaskForNextWorldUpdate_ExecutesCallback()
{
var mock = new Mock();
- var callback = FunctionReference.Create(mock.Object);
- NativeAPI.QueueTaskForNextWorldUpdate(callback);
+ Server.NextWorldUpdate(mock.Object);
await WaitOneFrame();
mock.Verify(s => s(), Times.Once);
@@ -116,8 +142,6 @@ public async Task NextFrameConcurrentQueueDrainsProperly()
// All tasks should have been drained by latest NextFrameAsync
await Server.NextFrameAsync(() => { }).ConfigureAwait(false);
- // The task should be bucketed into multiple frames
- Assert.All(callsByFrame.Values, count => Assert.Equal(1024, count));
Assert.Equal(4096, callCount);
}
@@ -139,8 +163,6 @@ public async Task NextWorldUpdateConcurrentQueueDrainsProperly()
// All tasks should have been drained by latest NextFrameAsync
await Server.NextWorldUpdateAsync(() => { }).ConfigureAwait(false);
- // The task should be bucketed into multiple frames
- Assert.All(callsByFrame.Values, count => Assert.Equal(1024, count));
Assert.Equal(4096, callCount);
}
}
diff --git a/managed/CounterStrikeSharp.Tests.Native/NativeTestsPlugin.cs b/managed/CounterStrikeSharp.Tests.Native/NativeTestsPlugin.cs
index 2e9a211dc..f0fc76944 100644
--- a/managed/CounterStrikeSharp.Tests.Native/NativeTestsPlugin.cs
+++ b/managed/CounterStrikeSharp.Tests.Native/NativeTestsPlugin.cs
@@ -38,7 +38,7 @@ public class NativeTestsPlugin : BasePlugin
public override string ModuleDescription => "A an automated test plugin.";
- private int gameThreadId;
+ public static int gameThreadId;
public override void Load(bool hotReload)
{
diff --git a/managed/CounterStrikeSharp.Tests.Native/TestUtils.cs b/managed/CounterStrikeSharp.Tests.Native/TestUtils.cs
index 7fb02fd17..101e1b944 100644
--- a/managed/CounterStrikeSharp.Tests.Native/TestUtils.cs
+++ b/managed/CounterStrikeSharp.Tests.Native/TestUtils.cs
@@ -5,7 +5,7 @@ public static class TestUtils
{
public static async Task WaitOneFrame()
{
- await Server.NextWorldUpdateAsync(() => { }).ConfigureAwait(false);
+ await Server.NextFrameAsync(() => { }).ConfigureAwait(false);
}
public static async Task WaitForSeconds(float seconds)
diff --git a/src/core/managers/server_manager.cpp b/src/core/managers/server_manager.cpp
index b399d8cf6..fe7637969 100644
--- a/src/core/managers/server_manager.cpp
+++ b/src/core/managers/server_manager.cpp
@@ -20,7 +20,6 @@
#include "scripting/callback_manager.h"
#include "core/game_system.h"
-#include
SH_DECL_HOOK1_void(ISource2Server, ServerHibernationUpdate, SH_NOATTRIB, 0, bool);
SH_DECL_HOOK0_void(ISource2Server, GameServerSteamAPIActivated, SH_NOATTRIB, 0);
@@ -174,20 +173,6 @@ void ServerManager::UpdateWhenNotInGame(float flFrameTime)
void ServerManager::PreWorldUpdate(bool bSimulating)
{
- std::vector> out_list(1024);
-
- auto size = m_nextWorldUpdateTasks.try_dequeue_bulk(out_list.begin(), 1024);
-
- if (size > 0)
- {
- CSSHARP_CORE_TRACE("Executing queued tasks of size: {0} at time {1}", size, globals::getGlobalVars()->curtime);
-
- for (size_t i = 0; i < size; i++)
- {
- out_list[i]();
- }
- }
-
auto callback = globals::serverManager.on_server_pre_world_update;
if (callback && callback->GetFunctionCount())
@@ -198,15 +183,6 @@ void ServerManager::PreWorldUpdate(bool bSimulating)
}
}
-void ServerManager::AddTaskForNextWorldUpdate(std::function&& task)
-{
- auto success = m_nextWorldUpdateTasks.enqueue(std::forward(task));
- if (!success)
- {
- CSSHARP_CORE_ERROR("Failed to enqueue task for next world update!");
- }
-}
-
void ServerManager::OnPrecacheResources(IEntityResourceManifest* pResourceManifest)
{
CSSHARP_CORE_TRACE("Precache resources");
diff --git a/src/core/managers/server_manager.h b/src/core/managers/server_manager.h
index 11b956588..185fe279f 100644
--- a/src/core/managers/server_manager.h
+++ b/src/core/managers/server_manager.h
@@ -19,7 +19,6 @@
#include "core/globals.h"
#include "core/global_listener.h"
#include "scripting/script_engine.h"
-#include
#include "core/game_system.h"
@@ -35,7 +34,6 @@ class ServerManager : public GlobalClass
void OnShutdown() override;
void* GetEconItemSystem();
bool IsPaused();
- void AddTaskForNextWorldUpdate(std::function&& task);
void OnPrecacheResources(IEntityResourceManifest* pResourceManifest);
ScriptCallback* on_server_pre_entity_think;
@@ -59,8 +57,6 @@ class ServerManager : public GlobalClass
ScriptCallback* on_server_pre_world_update;
ScriptCallback* on_server_precache_resources;
-
- moodycamel::ConcurrentQueue> m_nextWorldUpdateTasks{ 4096 };
};
} // namespace counterstrikesharp
diff --git a/src/mm_plugin.cpp b/src/mm_plugin.cpp
index f30eee495..04940f8e4 100644
--- a/src/mm_plugin.cpp
+++ b/src/mm_plugin.cpp
@@ -54,9 +54,7 @@ DLL_EXPORT void InvokeNative(counterstrikesharp::fxNativeContext& context)
{
if (context.nativeIdentifier == 0) return;
- if (context.nativeIdentifier != counterstrikesharp::hash_string_const("QUEUE_TASK_FOR_NEXT_FRAME") &&
- context.nativeIdentifier != counterstrikesharp::hash_string_const("QUEUE_TASK_FOR_NEXT_WORLD_UPDATE") &&
- context.nativeIdentifier != counterstrikesharp::hash_string_const("QUEUE_TASK_FOR_FRAME") &&
+ if (context.nativeIdentifier != counterstrikesharp::hash_string_const("QUEUE_TASK_FOR_FRAME") &&
counterstrikesharp::globals::gameThreadId != std::this_thread::get_id())
{
counterstrikesharp::ScriptContextRaw scriptContext(context);
@@ -247,15 +245,6 @@ void CounterStrikeSharpMMPlugin::AllPluginsLoaded()
on_metamod_all_plugins_loaded_callback->Execute();
}
-void CounterStrikeSharpMMPlugin::AddTaskForNextFrame(std::function&& task)
-{
- auto success = m_nextTasks.enqueue(std::forward(task));
- if (!success)
- {
- CSSHARP_CORE_ERROR("Failed to enqueue task for next frame!");
- }
-}
-
void CounterStrikeSharpMMPlugin::Hook_GameFrame(bool simulating, bool bFirstTick, bool bLastTick)
{
/**
@@ -267,20 +256,6 @@ void CounterStrikeSharpMMPlugin::Hook_GameFrame(bool simulating, bool bFirstTick
// VPROF_BUDGET("CS#::Hook_GameFrame", "CS# On Frame");
globals::timerSystem.OnGameFrame(simulating);
- std::vector> out_list(1024);
-
- auto size = m_nextTasks.try_dequeue_bulk(out_list.begin(), 1024);
-
- if (size > 0)
- {
- CSSHARP_CORE_TRACE("Executing queued tasks of size: {0} on tick number {1}", size, globals::getGlobalVars()->tickcount);
-
- for (size_t i = 0; i < size; i++)
- {
- out_list[i]();
- }
- }
-
auto callbacks = globals::tickScheduler.getCallbacks(globals::getGlobalVars()->tickcount);
if (callbacks.size() > 0)
{
diff --git a/src/mm_plugin.h b/src/mm_plugin.h
index 925884b67..129c352e1 100644
--- a/src/mm_plugin.h
+++ b/src/mm_plugin.h
@@ -25,7 +25,6 @@
#include
#include
#include "entitysystem.h"
-#include "concurrentqueue.h"
namespace counterstrikesharp {
class ScriptCallback;
@@ -49,7 +48,6 @@ class CounterStrikeSharpMMPlugin : public ISmmPlugin, public IMetamodListener
void OnLevelShutdown() override;
void Hook_GameFrame(bool simulating, bool bFirstTick, bool bLastTick);
void Hook_StartupServer(const GameSessionConfiguration_t& config, ISource2WorldSession*, const char*);
- void AddTaskForNextFrame(std::function&& task);
void Hook_RegisterLoopMode(const char* pszLoopModeName, ILoopModeFactory* pLoopModeFactory, void** ppGlobalPointer);
int Hook_LoadEventsFromFile(const char* filename, bool bSearchAll);
@@ -64,9 +62,6 @@ class CounterStrikeSharpMMPlugin : public ISmmPlugin, public IMetamodListener
const char* GetVersion() override;
const char* GetDate() override;
const char* GetLogTag() override;
-
- private:
- moodycamel::ConcurrentQueue> m_nextTasks{ 4096 };
};
static ScriptCallback* on_activate_callback;
diff --git a/src/scripting/natives/natives_engine.cpp b/src/scripting/natives/natives_engine.cpp
index 2a30fc7be..f03616025 100644
--- a/src/scripting/natives/natives_engine.cpp
+++ b/src/scripting/natives/natives_engine.cpp
@@ -146,26 +146,6 @@ float GetSoundDuration(ScriptContext& script_context)
double GetTickedTime(ScriptContext& script_context) { return globals::timerSystem.GetTickedTime(); }
-void QueueTaskForNextFrame(ScriptContext& script_context)
-{
- auto func = script_context.GetArgument(0);
-
- typedef void(voidfunc)(void);
- globals::mmPlugin->AddTaskForNextFrame([func]() {
- reinterpret_cast(func)();
- });
-}
-
-void QueueTaskForNextWorldUpdate(ScriptContext& script_context)
-{
- auto func = script_context.GetArgument(0);
-
- typedef void(voidfunc)(void);
- globals::serverManager.AddTaskForNextWorldUpdate([func]() {
- reinterpret_cast(func)();
- });
-}
-
void QueueTaskForFrame(ScriptContext& script_context)
{
auto tick = script_context.GetArgument(0);
@@ -289,8 +269,6 @@ REGISTER_NATIVES(engine, {
// ScriptEngine::RegisterNativeHandler("EMIT_SOUND", EmitSound);
ScriptEngine::RegisterNativeHandler("GET_TICKED_TIME", GetTickedTime);
- ScriptEngine::RegisterNativeHandler("QUEUE_TASK_FOR_NEXT_FRAME", QueueTaskForNextFrame);
- ScriptEngine::RegisterNativeHandler("QUEUE_TASK_FOR_NEXT_WORLD_UPDATE", QueueTaskForNextWorldUpdate);
ScriptEngine::RegisterNativeHandler("QUEUE_TASK_FOR_FRAME", QueueTaskForFrame);
ScriptEngine::RegisterNativeHandler("GET_VALVE_INTERFACE", GetValveInterface);
ScriptEngine::RegisterNativeHandler("GET_COMMAND_PARAM_VALUE", GetCommandParamValue);
diff --git a/src/scripting/natives/natives_engine.yaml b/src/scripting/natives/natives_engine.yaml
index a283eddb6..b243f2907 100644
--- a/src/scripting/natives/natives_engine.yaml
+++ b/src/scripting/natives/natives_engine.yaml
@@ -22,9 +22,7 @@ TRACE_FILTER_PROXY_SET_TRACE_TYPE_CALLBACK: trace_filter:pointer, callback:point
TRACE_FILTER_PROXY_SET_SHOULD_HIT_ENTITY_CALLBACK: trace_filter:pointer, callback:pointer -> void
NEW_TRACE_RESULT: -> pointer
GET_TICKED_TIME: -> double
-QUEUE_TASK_FOR_NEXT_FRAME: callback:func -> void
QUEUE_TASK_FOR_FRAME: tick:int, callback:func -> void
-QUEUE_TASK_FOR_NEXT_WORLD_UPDATE: callback:func -> void
GET_VALVE_INTERFACE: interfaceType:int, interfaceName:string -> pointer
GET_COMMAND_PARAM_VALUE: param:string, dataType:DataType_t, defaultValue:any -> any
PRINT_TO_SERVER_CONSOLE: msg:string -> void