Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions managed/CounterStrikeSharp.API/Generated/Natives/API.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();
Expand Down
82 changes: 69 additions & 13 deletions managed/CounterStrikeSharp.API/Server.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,51 @@
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
*/

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<Action> _onTickTaskQueue = new();
private static readonly ConcurrentQueue<Action> _onWorldUpdateTaskQueue = new();

internal static void OnTick()
{
ExecuteTickTasks(_onTickTaskQueue);
}

internal static void OnWorldUpdate()
{
ExecuteTickTasks(_onWorldUpdateTaskQueue);
}

private static void ExecuteTickTasks(ConcurrentQueue<Action> taskQueue)
{
while (taskQueue.TryDequeue(out var task))
{
try
{
task();
}
catch (Exception e)
{
Application.Instance.Logger.LogError(e, "Error invoking callback");
}
}
}


/// <summary>
/// Duration of a single game tick in seconds, based on a 64 tick server (hard coded in CS2).
/// </summary>
Expand Down Expand Up @@ -101,9 +131,22 @@ public static void RunOnTick(int tick, Action task)
/// </summary>
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;
}

/// <summary>
Expand All @@ -121,9 +164,22 @@ public static void NextFrame(Action task)
/// </summary>
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;
}

/// <summary>
Expand Down
38 changes: 30 additions & 8 deletions managed/CounterStrikeSharp.Tests.Native/FrameSchedulingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Action>();
var callback = FunctionReference.Create(mock.Object);

NativeAPI.QueueTaskForNextFrame(callback);
Server.NextFrame(mock.Object);
await WaitOneFrame();

mock.Verify(s => s(), Times.Once);
Expand All @@ -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<Action>();
var callback = FunctionReference.Create(mock.Object);

NativeAPI.QueueTaskForNextWorldUpdate(callback);
Server.NextWorldUpdate(mock.Object);
await WaitOneFrame();

mock.Verify(s => s(), Times.Once);
Expand Down Expand Up @@ -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);
}

Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
2 changes: 1 addition & 1 deletion managed/CounterStrikeSharp.Tests.Native/TestUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
24 changes: 0 additions & 24 deletions src/core/managers/server_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
#include "scripting/callback_manager.h"

#include "core/game_system.h"
#include <concurrentqueue.h>

SH_DECL_HOOK1_void(ISource2Server, ServerHibernationUpdate, SH_NOATTRIB, 0, bool);
SH_DECL_HOOK0_void(ISource2Server, GameServerSteamAPIActivated, SH_NOATTRIB, 0);
Expand Down Expand Up @@ -174,20 +173,6 @@ void ServerManager::UpdateWhenNotInGame(float flFrameTime)

void ServerManager::PreWorldUpdate(bool bSimulating)
{
std::vector<std::function<void()>> 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())
Expand All @@ -198,15 +183,6 @@ void ServerManager::PreWorldUpdate(bool bSimulating)
}
}

void ServerManager::AddTaskForNextWorldUpdate(std::function<void()>&& task)
{
auto success = m_nextWorldUpdateTasks.enqueue(std::forward<decltype(task)>(task));
if (!success)
{
CSSHARP_CORE_ERROR("Failed to enqueue task for next world update!");
}
}

void ServerManager::OnPrecacheResources(IEntityResourceManifest* pResourceManifest)
{
CSSHARP_CORE_TRACE("Precache resources");
Expand Down
4 changes: 0 additions & 4 deletions src/core/managers/server_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
#include "core/globals.h"
#include "core/global_listener.h"
#include "scripting/script_engine.h"
#include <concurrentqueue.h>

#include "core/game_system.h"

Expand All @@ -35,7 +34,6 @@ class ServerManager : public GlobalClass
void OnShutdown() override;
void* GetEconItemSystem();
bool IsPaused();
void AddTaskForNextWorldUpdate(std::function<void()>&& task);
void OnPrecacheResources(IEntityResourceManifest* pResourceManifest);

ScriptCallback* on_server_pre_entity_think;
Expand All @@ -59,8 +57,6 @@ class ServerManager : public GlobalClass
ScriptCallback* on_server_pre_world_update;

ScriptCallback* on_server_precache_resources;

moodycamel::ConcurrentQueue<std::function<void()>> m_nextWorldUpdateTasks{ 4096 };
};

} // namespace counterstrikesharp
27 changes: 1 addition & 26 deletions src/mm_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -247,15 +245,6 @@ void CounterStrikeSharpMMPlugin::AllPluginsLoaded()
on_metamod_all_plugins_loaded_callback->Execute();
}

void CounterStrikeSharpMMPlugin::AddTaskForNextFrame(std::function<void()>&& task)
{
auto success = m_nextTasks.enqueue(std::forward<decltype(task)>(task));
if (!success)
{
CSSHARP_CORE_ERROR("Failed to enqueue task for next frame!");
}
}

void CounterStrikeSharpMMPlugin::Hook_GameFrame(bool simulating, bool bFirstTick, bool bLastTick)
{
/**
Expand All @@ -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<std::function<void()>> 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)
{
Expand Down
5 changes: 0 additions & 5 deletions src/mm_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
#include <sh_vector.h>
#include <vector>
#include "entitysystem.h"
#include "concurrentqueue.h"

namespace counterstrikesharp {
class ScriptCallback;
Expand All @@ -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<void()>&& task);

void Hook_RegisterLoopMode(const char* pszLoopModeName, ILoopModeFactory* pLoopModeFactory, void** ppGlobalPointer);
int Hook_LoadEventsFromFile(const char* filename, bool bSearchAll);
Expand All @@ -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<std::function<void()>> m_nextTasks{ 4096 };
};

static ScriptCallback* on_activate_callback;
Expand Down
Loading
Loading