From ee83d0bbdc7afa41aaa13f4533d2b60dec133ee8 Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Thu, 16 Oct 2025 21:08:37 +0800 Subject: [PATCH 01/16] chore: Minor improvements to Command system --- .../src/SwiftlyS2.Core/Modules/Commands/CommandCallback.cs | 2 +- src/server/commands/manager.cpp | 6 ++++++ vendor/metamod | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/managed/src/SwiftlyS2.Core/Modules/Commands/CommandCallback.cs b/managed/src/SwiftlyS2.Core/Modules/Commands/CommandCallback.cs index 43715699e..b4de05d26 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Commands/CommandCallback.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Commands/CommandCallback.cs @@ -76,7 +76,7 @@ public CommandCallback(string commandName, bool registerRaw, ICommandService.Com var args = argsString.Split('\x01'); var context = new CommandContext(playerId, args, commandNameString, prefixString, slient == 1); - if (string.IsNullOrWhiteSpace(_permissions) || _permissionManager.PlayerHasPermission(_playerManagerService.GetPlayer(playerId).SteamID, _permissions)) + if (!context.IsSentByPlayer || string.IsNullOrWhiteSpace(_permissions) || _permissionManager.PlayerHasPermission(_playerManagerService.GetPlayer(playerId).SteamID, _permissions)) { _handler(context); } diff --git a/src/server/commands/manager.cpp b/src/server/commands/manager.cpp index 0386ba872..52d3df9e9 100644 --- a/src/server/commands/manager.cpp +++ b/src/server/commands/manager.cpp @@ -47,6 +47,8 @@ static void commandsCallback(const CCommandContext& context, const CCommand& arg tokenizedArgs.Tokenize(args.GetCommandString()); std::string commandName = tokenizedArgs[0]; + + std::transform(commandName.begin(), commandName.end(), commandName.begin(), ::tolower); std::string originalCommandName = commandName; if (!g_mCommandHandlers.contains(commandName)) commandName = "sw_" + commandName; if (!g_mCommandHandlers.contains(commandName)) return; @@ -147,7 +149,9 @@ int CServerCommands::HandleCommand(int playerid, const std::string& text) return 0; commandName.erase(0, selectedPrefix.size()); + std::transform(commandName.begin(), commandName.end(), commandName.begin(), ::tolower); + std::cout << "Command name: " << commandName << std::endl; std::string originalCommandName = commandName; if (!g_mCommandHandlers.contains(commandName)) commandName = "sw_" + commandName; if (!g_mCommandHandlers.contains(commandName)) return 0; @@ -187,6 +191,8 @@ bool CServerCommands::HandleClientChat(int playerid, const std::string& text, bo uint64_t CServerCommands::RegisterCommand(std::string command_name, std::function, std::string, std::string, bool)> handler, bool registerRaw) { + std::transform(command_name.begin(), command_name.end(), command_name.begin(), ::tolower); + if (!registerRaw) { if (conCommandCreated.contains(command_name)) diff --git a/vendor/metamod b/vendor/metamod index c1cc0a613..4399ff0fd 160000 --- a/vendor/metamod +++ b/vendor/metamod @@ -1 +1 @@ -Subproject commit c1cc0a6134b3503abb40e18227ef04619db33841 +Subproject commit 4399ff0fd4ad7a6620f6763afe6fc0e30005496a From 45df7f214686d6e1bfb1a85e3b8e64f1a443d188 Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Thu, 16 Oct 2025 21:10:27 +0800 Subject: [PATCH 02/16] chore: Cleanup code --- src/server/commands/manager.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/server/commands/manager.cpp b/src/server/commands/manager.cpp index 52d3df9e9..5fb4efc25 100644 --- a/src/server/commands/manager.cpp +++ b/src/server/commands/manager.cpp @@ -151,7 +151,6 @@ int CServerCommands::HandleCommand(int playerid, const std::string& text) commandName.erase(0, selectedPrefix.size()); std::transform(commandName.begin(), commandName.end(), commandName.begin(), ::tolower); - std::cout << "Command name: " << commandName << std::endl; std::string originalCommandName = commandName; if (!g_mCommandHandlers.contains(commandName)) commandName = "sw_" + commandName; if (!g_mCommandHandlers.contains(commandName)) return 0; From 3dfc0dffbfec4723bd5eb92ff6cc8d73495b6e76 Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Thu, 16 Oct 2025 22:01:16 +0800 Subject: [PATCH 03/16] chore: Minor changes to HandlePluginChange logic --- managed/src/SwiftlyS2.Core/Modules/Plugins/PluginManager.cs | 4 ++-- managed/src/TestPlugin/TestPlugin.cs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/managed/src/SwiftlyS2.Core/Modules/Plugins/PluginManager.cs b/managed/src/SwiftlyS2.Core/Modules/Plugins/PluginManager.cs index 96a7ca228..38988bcd3 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Plugins/PluginManager.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Plugins/PluginManager.cs @@ -77,7 +77,7 @@ public void HandlePluginChange(object sender, FileSystemEventArgs e) { try { - // why i have to make a debounce here? + // Windows FileSystemWatcher triggers multiple (open, write, close) events for a single file change if (DateTime.Now - lastRead < TimeSpan.FromSeconds(1)) { return; @@ -91,7 +91,7 @@ public void HandlePluginChange(object sender, FileSystemEventArgs e) foreach (var plugin in _Plugins) { - if (plugin.Metadata?.Id == Path.GetFileName(directory)) + if (Path.GetFileName(plugin?.PluginDirectory) == Path.GetFileName(directory)) { lastRead = DateTime.Now; ReloadPlugin(plugin.Metadata!.Id); diff --git a/managed/src/TestPlugin/TestPlugin.cs b/managed/src/TestPlugin/TestPlugin.cs index d6de4ceec..444c53f4e 100644 --- a/managed/src/TestPlugin/TestPlugin.cs +++ b/managed/src/TestPlugin/TestPlugin.cs @@ -83,6 +83,8 @@ public override void Load(bool hotReload) Core.Engine.ExecuteCommandWithBuffer("@ping", (buffer) => { Console.WriteLine($"pong: {buffer}"); + Console.WriteLine($"pong: {buffer}"); + }); Core.GameEvent.HookPre(@event => From 9fc9b2835869e5f95ff9797e6ae634da980fce24 Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Fri, 17 Oct 2025 00:01:23 +0800 Subject: [PATCH 04/16] feat: Introduce configuration for plugins auto-reload system --- .../Modules/Plugins/PluginManager.cs | 92 ++++++++++++++++++- .../Natives/ServerHelpers.cs | 14 +++ natives/server/helpers.native | 3 +- plugin_files/configs/core.example.jsonc | 5 + src/scripting/server/helpers.cpp | 29 +++++- src/server/configuration/configuration.cpp | 3 + 6 files changed, 143 insertions(+), 3 deletions(-) diff --git a/managed/src/SwiftlyS2.Core/Modules/Plugins/PluginManager.cs b/managed/src/SwiftlyS2.Core/Modules/Plugins/PluginManager.cs index 38988bcd3..3aedd27a9 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Plugins/PluginManager.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Plugins/PluginManager.cs @@ -2,6 +2,7 @@ using McMaster.NETCore.Plugins; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using SwiftlyS2.Core.Natives; using SwiftlyS2.Core.Modules.Plugins; using SwiftlyS2.Core.Services; using SwiftlyS2.Shared; @@ -12,6 +13,13 @@ namespace SwiftlyS2.Core.Plugins; internal class PluginManager { + private struct PluginSettings + { + public bool AutoReload; + public int ReloadRetryAttempts; + public int ReloadRetryDelayMs; + } + private IServiceProvider _Provider { get; init; } private RootDirService _RootDirService { get; init; } private ILogger _Logger { get; init; } @@ -19,8 +27,10 @@ internal class PluginManager private FileSystemWatcher? _Watcher { get; set; } private InterfaceManager _InterfaceManager { get; set; } = new(); private List _SharedTypes { get; set; } = new(); + private PluginSettings Settings { get; set; } = new() { AutoReload = true, ReloadRetryAttempts = 3, ReloadRetryDelayMs = 500 }; private DateTime lastRead = DateTime.MinValue; + private readonly HashSet reloadingPlugins = new(); public PluginManager( IServiceProvider provider, @@ -39,6 +49,21 @@ RootDirService rootDirService NotifyFilter = NotifyFilters.LastWrite, }; + try + { + var settings = NativeServerHelpers.GetPluginSettings(); + var parts = settings.Split('\x01'); + Settings = new PluginSettings + { + AutoReload = bool.Parse(parts[0]), + ReloadRetryAttempts = int.Parse(parts[1]), + ReloadRetryDelayMs = int.Parse(parts[2]), + }; + } + catch (Exception ex) + { + _Logger.LogError(ex, "Failed to get plugin settings"); + } _Watcher.Changed += HandlePluginChange; _Watcher.EnableRaisingEvents = true; @@ -77,6 +102,11 @@ public void HandlePluginChange(object sender, FileSystemEventArgs e) { try { + if (!Settings.AutoReload) + { + return; + } + // Windows FileSystemWatcher triggers multiple (open, write, close) events for a single file change if (DateTime.Now - lastRead < TimeSpan.FromSeconds(1)) { @@ -93,8 +123,68 @@ public void HandlePluginChange(object sender, FileSystemEventArgs e) { if (Path.GetFileName(plugin?.PluginDirectory) == Path.GetFileName(directory)) { + var pluginId = plugin.Metadata?.Id; + if (string.IsNullOrWhiteSpace(pluginId)) + { + break; + } + + lock (reloadingPlugins) + { + if (reloadingPlugins.Contains(pluginId)) + { + return; + } + reloadingPlugins.Add(pluginId); + } + lastRead = DateTime.Now; - ReloadPlugin(plugin.Metadata!.Id); + + // meh, Idk why, but when using Mstsc to copy and overwrite files + // it sometimes triggers: "System.IO.IOException: The process cannot access the file because it is being used by another process." + // therefore, we use a retry mechanism + Task.Run(async () => + { + try + { + await Task.Delay(Settings.ReloadRetryDelayMs); + + bool fileLockSuccess = false; + for (int attempt = 0; attempt < Settings.ReloadRetryAttempts; attempt++) + { + try + { + using (var stream = File.Open(e.FullPath, FileMode.Open, FileAccess.Read, FileShare.None)) + { + } + fileLockSuccess = true; + break; + } + catch (IOException) when (attempt < Settings.ReloadRetryAttempts - 1) + { + _Logger.LogWarning($"{Path.GetFileName(plugin?.PluginDirectory)} is locked, retrying in {Settings.ReloadRetryDelayMs}ms... (Attempt {attempt + 1}/{Settings.ReloadRetryAttempts})"); + await Task.Delay(Settings.ReloadRetryDelayMs); + } + catch (IOException) + { + _Logger.LogError($"Failed to reload {Path.GetFileName(plugin?.PluginDirectory)} after {Settings.ReloadRetryAttempts} attempts"); + } + } + + if (fileLockSuccess) + { + ReloadPlugin(pluginId); + } + } + finally + { + lock (reloadingPlugins) + { + reloadingPlugins.Remove(pluginId); + } + } + }); + break; } } diff --git a/managed/src/SwiftlyS2.Generated/Natives/ServerHelpers.cs b/managed/src/SwiftlyS2.Generated/Natives/ServerHelpers.cs index a2b22ad6d..2c41077ef 100644 --- a/managed/src/SwiftlyS2.Generated/Natives/ServerHelpers.cs +++ b/managed/src/SwiftlyS2.Generated/Natives/ServerHelpers.cs @@ -38,4 +38,18 @@ public unsafe static bool IsFollowingServerGuidelines() { var ret = _IsFollowingServerGuidelines(); return ret == 1; } + + private unsafe static delegate* unmanaged _GetPluginSettings; + + public unsafe static string GetPluginSettings() { + var ret = _GetPluginSettings(null); + var pool = ArrayPool.Shared; + var retBuffer = pool.Rent(ret + 1); + fixed (byte* retBufferPtr = retBuffer) { + ret = _GetPluginSettings(retBufferPtr); + var retString = Encoding.UTF8.GetString(retBufferPtr, ret); + pool.Return(retBuffer); + return retString; + } + } } \ No newline at end of file diff --git a/natives/server/helpers.native b/natives/server/helpers.native index 4fbaa820f..86eaf4dfd 100644 --- a/natives/server/helpers.native +++ b/natives/server/helpers.native @@ -2,4 +2,5 @@ class ServerHelpers string GetServerLanguage = void bool UsePlayerLanguage = void -bool IsFollowingServerGuidelines = void \ No newline at end of file +bool IsFollowingServerGuidelines = void +string GetPluginSettings = void \ No newline at end of file diff --git a/plugin_files/configs/core.example.jsonc b/plugin_files/configs/core.example.jsonc index 4396d74ad..16eeca2d3 100644 --- a/plugin_files/configs/core.example.jsonc +++ b/plugin_files/configs/core.example.jsonc @@ -1,4 +1,9 @@ { + "Plugin": { + "AutoReload": true, + "ReloadRetryAttempts": 3, + "ReloadRetryDelayMs": 500 + }, "CS2ServerGuidelines": "https://blog.counter-strike.net/index.php/server_guidelines/", "CommandPrefixes": [ "!" diff --git a/src/scripting/server/helpers.cpp b/src/scripting/server/helpers.cpp index d25e4e77f..da7d7e9e8 100644 --- a/src/scripting/server/helpers.cpp +++ b/src/scripting/server/helpers.cpp @@ -18,6 +18,9 @@ #include #include +#include +#include +#include int Bridge_ServerHelpers_GetServerLanguage(char* out) { @@ -43,6 +46,30 @@ bool Bridge_ServerHelpers_IsFollowingServerGuidelines() return std::get(configuration->GetValue("core.FollowCS2ServerGuidelines")); } +int Bridge_ServerHelpers_GetPluginSettings(char* out) +{ + static std::string s; + + auto configuration = g_ifaceService.FetchInterface(CONFIGURATION_INTERFACE_VERSION); + try { + std::vector settings = { + std::get(configuration->GetValue("core.Plugin.AutoReload")) ? "true" : "false", + std::to_string(std::get(configuration->GetValue("core.Plugin.ReloadRetryAttempts"))), + std::to_string(std::get(configuration->GetValue("core.Plugin.ReloadRetryDelayMs"))), + }; + + s = implode(settings, "\x01"); + } + catch (std::exception& e) + { + printf("Exception: %s\n", e.what()); + } + + if (out != nullptr) strcpy(out, s.c_str()); + return s.size(); +} + DEFINE_NATIVE("ServerHelpers.GetServerLanguage", Bridge_ServerHelpers_GetServerLanguage); DEFINE_NATIVE("ServerHelpers.UsePlayerLanguage", Bridge_ServerHelpers_UsePlayerLanguage); -DEFINE_NATIVE("ServerHelpers.IsFollowingServerGuidelines", Bridge_ServerHelpers_IsFollowingServerGuidelines); \ No newline at end of file +DEFINE_NATIVE("ServerHelpers.IsFollowingServerGuidelines", Bridge_ServerHelpers_IsFollowingServerGuidelines); +DEFINE_NATIVE("ServerHelpers.GetPluginSettings", Bridge_ServerHelpers_GetPluginSettings); \ No newline at end of file diff --git a/src/server/configuration/configuration.cpp b/src/server/configuration/configuration.cpp index 6b855e87d..5a45eb6f3 100644 --- a/src/server/configuration/configuration.cpp +++ b/src/server/configuration/configuration.cpp @@ -460,6 +460,9 @@ bool Configuration::Load() bool wasEdited = false; + RegisterConfiguration(wasEdited, config_json, "core", "core", "Plugin.AutoReload", true); + RegisterConfiguration(wasEdited, config_json, "core", "core", "Plugin.ReloadRetryAttempts", 3); + RegisterConfiguration(wasEdited, config_json, "core", "core", "Plugin.ReloadRetryDelayMs", 500); RegisterConfigurationVector(wasEdited, config_json, "core", "core", "CommandPrefixes", { "!" }, true, " "); RegisterConfigurationVector(wasEdited, config_json, "core", "core", "CommandSilentPrefixes", { "/" }, true, " "); RegisterConfiguration(wasEdited, config_json, "core", "core", "ConsoleFilter", true); From 818e93fb87177c6eb6bfc813d4dd89c833ff9378 Mon Sep 17 00:00:00 2001 From: skuzzis Date: Thu, 16 Oct 2025 19:01:59 +0300 Subject: [PATCH 05/16] update(build): Add loader & PDB's --- .github/workflows/build.yml | 91 +++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 66b33713a..1d9aa6de8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -118,6 +118,71 @@ jobs: name: swiftlys2_windows_pdb path: build/windows/x64/release/swiftlys2.pdb + linux_loader: + needs: versioning + runs-on: ubuntu-latest + container: + image: registry.gitlab.steamos.cloud/steamrt/sniper/sdk + steps: + - name: Install dependencies + run: | + apt-get update + apt install libreadline-dev + - name: Checkout repository and submodules + uses: actions/checkout@v4 + with: + repository: swiftly-solution/swiftlys2-loader + submodules: recursive + fetch-depth: 0 + - name: Setup xmake + uses: xmake-io/github-action-setup-xmake@v1 + with: + xmake-version: latest + actions-cache-folder: '.xmake-cache' + actions-cache-key: xmake-loader-${{ runner.os }} + - name: Build + run: | + xmake f --cc=gcc-14 --cxx=g++-14 -y + xmake build -y + env: + SWIFTLY_VERSION: ${{ needs.versioning.outputs.semVer }} + - name: Upload plugin as artifact + uses: actions/upload-artifact@v4 + with: + name: swiftlys2_loader_linux + path: build/package/addons + + windows_loader: + needs: versioning + runs-on: windows-latest + steps: + - name: Checkout repository and submodules + uses: actions/checkout@v4 + with: + repository: swiftly-solution/swiftlys2-loader + submodules: recursive + fetch-depth: 0 + - name: Setup xmake + uses: xmake-io/github-action-setup-xmake@v1 + with: + xmake-version: latest + actions-cache-key: xmake-loader-${{ runner.os }} + - name: Build + run: | + xmake build -y + env: + SWIFTLY_VERSION: ${{ needs.versioning.outputs.semVer }} + - name: Upload plugin as artifact + uses: actions/upload-artifact@v4 + with: + name: swiftlys2_loader_windows + path: build/package/addons + - name: Upload PDB as artifact + uses: actions/upload-artifact@v4 + with: + name: swiftlys2_loader_windows_pdb + path: build/windows/x64/release/server.pdb + packaging: needs: [versioning, linux_core, windows_core, managed] runs-on: ubuntu-latest @@ -130,11 +195,31 @@ jobs: with: name: swiftlys2_linux path: ./${{ env.LINUX_FOLDER }} + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: swiftlys2_loader_linux + path: ./${{ env.LINUX_FOLDER }}-loader - name: Download artifacts uses: actions/download-artifact@v4 with: name: swiftlys2_windows path: ./${{ env.WINDOWS_FOLDER }} + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: swiftlys2_windows_pdb + path: ./swiftlys2.pdb + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: swiftlys2_loader_windows + path: ./${{ env.WINDOWS_FOLDER }}-loader + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: swiftlys2_loader_windows_pdb + path: ./server.pdb - name: Download artifacts uses: actions/download-artifact@v4 @@ -161,6 +246,12 @@ jobs: cp -r managed/SwiftlyS2 ${{ env.LINUX_FOLDER }}/swiftlys2/bin/managed cp -r managed/SwiftlyS2 ${{ env.WINDOWS_FOLDER }}/swiftlys2/bin/managed + cp -r ${{ env.LINUX_FOLDER }}-loader/* ${{ env.LINUX_FOLDER }}/ + cp -r ${{ env.WINDOWS_FOLDER }}-loader/* ${{ env.WINDOWS_FOLDER }}/ + + cp -r ./swiftlys2.pdb ${{ env.WINDOWS_FOLDER }}/swiftlys2/bin/win64/ + cp -r ./server.pdb ${{ env.WINDOWS_FOLDER }}/swiftlys2/bin/win64/ + mkdir -p ${{ env.LINUX_FOLDER }}/swiftlys2/plugins/ mkdir -p ${{ env.WINDOWS_FOLDER }}/swiftlys2/plugins/ From d414623e951e87e6bee19082bcdfe3f86fec2082 Mon Sep 17 00:00:00 2001 From: skuzzis Date: Thu, 16 Oct 2025 19:03:45 +0300 Subject: [PATCH 06/16] fix(workflows): Dependency --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1d9aa6de8..5c8158f32 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -184,7 +184,7 @@ jobs: path: build/windows/x64/release/server.pdb packaging: - needs: [versioning, linux_core, windows_core, managed] + needs: [versioning, linux_core, windows_core, managed, linux_loader, windows_loader] runs-on: ubuntu-latest env: LINUX_FOLDER: swiftlys2-linux-v${{ needs.versioning.outputs.semVer }} From aa7c028c4347442f344a564843a09c503ac6ff0f Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Fri, 17 Oct 2025 00:19:52 +0800 Subject: [PATCH 07/16] chore: Cleanup code --- managed/src/TestPlugin/TestPlugin.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/managed/src/TestPlugin/TestPlugin.cs b/managed/src/TestPlugin/TestPlugin.cs index 444c53f4e..d6de4ceec 100644 --- a/managed/src/TestPlugin/TestPlugin.cs +++ b/managed/src/TestPlugin/TestPlugin.cs @@ -83,8 +83,6 @@ public override void Load(bool hotReload) Core.Engine.ExecuteCommandWithBuffer("@ping", (buffer) => { Console.WriteLine($"pong: {buffer}"); - Console.WriteLine($"pong: {buffer}"); - }); Core.GameEvent.HookPre(@event => From ca372b9433fefa998687688e8eb784a1835695a2 Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Fri, 17 Oct 2025 00:44:07 +0800 Subject: [PATCH 08/16] refactor: Remove config file dependency for plugin auto-reload --- .../Modules/Plugins/PluginManager.cs | 40 +++---------------- .../Natives/ServerHelpers.cs | 14 ------- natives/server/helpers.native | 3 +- plugin_files/configs/core.example.jsonc | 5 --- src/scripting/server/helpers.cpp | 26 +----------- src/server/configuration/configuration.cpp | 3 -- 6 files changed, 8 insertions(+), 83 deletions(-) diff --git a/managed/src/SwiftlyS2.Core/Modules/Plugins/PluginManager.cs b/managed/src/SwiftlyS2.Core/Modules/Plugins/PluginManager.cs index 3aedd27a9..a738231a7 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Plugins/PluginManager.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Plugins/PluginManager.cs @@ -13,13 +13,6 @@ namespace SwiftlyS2.Core.Plugins; internal class PluginManager { - private struct PluginSettings - { - public bool AutoReload; - public int ReloadRetryAttempts; - public int ReloadRetryDelayMs; - } - private IServiceProvider _Provider { get; init; } private RootDirService _RootDirService { get; init; } private ILogger _Logger { get; init; } @@ -27,7 +20,6 @@ private struct PluginSettings private FileSystemWatcher? _Watcher { get; set; } private InterfaceManager _InterfaceManager { get; set; } = new(); private List _SharedTypes { get; set; } = new(); - private PluginSettings Settings { get; set; } = new() { AutoReload = true, ReloadRetryAttempts = 3, ReloadRetryDelayMs = 500 }; private DateTime lastRead = DateTime.MinValue; private readonly HashSet reloadingPlugins = new(); @@ -49,21 +41,6 @@ RootDirService rootDirService NotifyFilter = NotifyFilters.LastWrite, }; - try - { - var settings = NativeServerHelpers.GetPluginSettings(); - var parts = settings.Split('\x01'); - Settings = new PluginSettings - { - AutoReload = bool.Parse(parts[0]), - ReloadRetryAttempts = int.Parse(parts[1]), - ReloadRetryDelayMs = int.Parse(parts[2]), - }; - } - catch (Exception ex) - { - _Logger.LogError(ex, "Failed to get plugin settings"); - } _Watcher.Changed += HandlePluginChange; _Watcher.EnableRaisingEvents = true; @@ -102,11 +79,6 @@ public void HandlePluginChange(object sender, FileSystemEventArgs e) { try { - if (!Settings.AutoReload) - { - return; - } - // Windows FileSystemWatcher triggers multiple (open, write, close) events for a single file change if (DateTime.Now - lastRead < TimeSpan.FromSeconds(1)) { @@ -147,10 +119,10 @@ public void HandlePluginChange(object sender, FileSystemEventArgs e) { try { - await Task.Delay(Settings.ReloadRetryDelayMs); + await Task.Delay(500); bool fileLockSuccess = false; - for (int attempt = 0; attempt < Settings.ReloadRetryAttempts; attempt++) + for (int attempt = 0; attempt < 3; attempt++) { try { @@ -160,14 +132,14 @@ public void HandlePluginChange(object sender, FileSystemEventArgs e) fileLockSuccess = true; break; } - catch (IOException) when (attempt < Settings.ReloadRetryAttempts - 1) + catch (IOException) when (attempt < 1) { - _Logger.LogWarning($"{Path.GetFileName(plugin?.PluginDirectory)} is locked, retrying in {Settings.ReloadRetryDelayMs}ms... (Attempt {attempt + 1}/{Settings.ReloadRetryAttempts})"); - await Task.Delay(Settings.ReloadRetryDelayMs); + _Logger.LogWarning($"{Path.GetFileName(plugin?.PluginDirectory)} is locked, retrying in 500ms... (Attempt {attempt + 1}/3)"); + await Task.Delay(500); } catch (IOException) { - _Logger.LogError($"Failed to reload {Path.GetFileName(plugin?.PluginDirectory)} after {Settings.ReloadRetryAttempts} attempts"); + _Logger.LogError($"Failed to reload {Path.GetFileName(plugin?.PluginDirectory)} after 3 attempts"); } } diff --git a/managed/src/SwiftlyS2.Generated/Natives/ServerHelpers.cs b/managed/src/SwiftlyS2.Generated/Natives/ServerHelpers.cs index 2c41077ef..a2b22ad6d 100644 --- a/managed/src/SwiftlyS2.Generated/Natives/ServerHelpers.cs +++ b/managed/src/SwiftlyS2.Generated/Natives/ServerHelpers.cs @@ -38,18 +38,4 @@ public unsafe static bool IsFollowingServerGuidelines() { var ret = _IsFollowingServerGuidelines(); return ret == 1; } - - private unsafe static delegate* unmanaged _GetPluginSettings; - - public unsafe static string GetPluginSettings() { - var ret = _GetPluginSettings(null); - var pool = ArrayPool.Shared; - var retBuffer = pool.Rent(ret + 1); - fixed (byte* retBufferPtr = retBuffer) { - ret = _GetPluginSettings(retBufferPtr); - var retString = Encoding.UTF8.GetString(retBufferPtr, ret); - pool.Return(retBuffer); - return retString; - } - } } \ No newline at end of file diff --git a/natives/server/helpers.native b/natives/server/helpers.native index 86eaf4dfd..4fbaa820f 100644 --- a/natives/server/helpers.native +++ b/natives/server/helpers.native @@ -2,5 +2,4 @@ class ServerHelpers string GetServerLanguage = void bool UsePlayerLanguage = void -bool IsFollowingServerGuidelines = void -string GetPluginSettings = void \ No newline at end of file +bool IsFollowingServerGuidelines = void \ No newline at end of file diff --git a/plugin_files/configs/core.example.jsonc b/plugin_files/configs/core.example.jsonc index 16eeca2d3..4396d74ad 100644 --- a/plugin_files/configs/core.example.jsonc +++ b/plugin_files/configs/core.example.jsonc @@ -1,9 +1,4 @@ { - "Plugin": { - "AutoReload": true, - "ReloadRetryAttempts": 3, - "ReloadRetryDelayMs": 500 - }, "CS2ServerGuidelines": "https://blog.counter-strike.net/index.php/server_guidelines/", "CommandPrefixes": [ "!" diff --git a/src/scripting/server/helpers.cpp b/src/scripting/server/helpers.cpp index da7d7e9e8..d8a0ddae7 100644 --- a/src/scripting/server/helpers.cpp +++ b/src/scripting/server/helpers.cpp @@ -46,30 +46,6 @@ bool Bridge_ServerHelpers_IsFollowingServerGuidelines() return std::get(configuration->GetValue("core.FollowCS2ServerGuidelines")); } -int Bridge_ServerHelpers_GetPluginSettings(char* out) -{ - static std::string s; - - auto configuration = g_ifaceService.FetchInterface(CONFIGURATION_INTERFACE_VERSION); - try { - std::vector settings = { - std::get(configuration->GetValue("core.Plugin.AutoReload")) ? "true" : "false", - std::to_string(std::get(configuration->GetValue("core.Plugin.ReloadRetryAttempts"))), - std::to_string(std::get(configuration->GetValue("core.Plugin.ReloadRetryDelayMs"))), - }; - - s = implode(settings, "\x01"); - } - catch (std::exception& e) - { - printf("Exception: %s\n", e.what()); - } - - if (out != nullptr) strcpy(out, s.c_str()); - return s.size(); -} - DEFINE_NATIVE("ServerHelpers.GetServerLanguage", Bridge_ServerHelpers_GetServerLanguage); DEFINE_NATIVE("ServerHelpers.UsePlayerLanguage", Bridge_ServerHelpers_UsePlayerLanguage); -DEFINE_NATIVE("ServerHelpers.IsFollowingServerGuidelines", Bridge_ServerHelpers_IsFollowingServerGuidelines); -DEFINE_NATIVE("ServerHelpers.GetPluginSettings", Bridge_ServerHelpers_GetPluginSettings); \ No newline at end of file +DEFINE_NATIVE("ServerHelpers.IsFollowingServerGuidelines", Bridge_ServerHelpers_IsFollowingServerGuidelines); \ No newline at end of file diff --git a/src/server/configuration/configuration.cpp b/src/server/configuration/configuration.cpp index 5a45eb6f3..6b855e87d 100644 --- a/src/server/configuration/configuration.cpp +++ b/src/server/configuration/configuration.cpp @@ -460,9 +460,6 @@ bool Configuration::Load() bool wasEdited = false; - RegisterConfiguration(wasEdited, config_json, "core", "core", "Plugin.AutoReload", true); - RegisterConfiguration(wasEdited, config_json, "core", "core", "Plugin.ReloadRetryAttempts", 3); - RegisterConfiguration(wasEdited, config_json, "core", "core", "Plugin.ReloadRetryDelayMs", 500); RegisterConfigurationVector(wasEdited, config_json, "core", "core", "CommandPrefixes", { "!" }, true, " "); RegisterConfigurationVector(wasEdited, config_json, "core", "core", "CommandSilentPrefixes", { "/" }, true, " "); RegisterConfiguration(wasEdited, config_json, "core", "core", "ConsoleFilter", true); From ac0a2b313a5c0acca446eb46c933f146bef61c15 Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Fri, 17 Oct 2025 00:48:45 +0800 Subject: [PATCH 09/16] chore: Cleanup code --- src/scripting/server/helpers.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/scripting/server/helpers.cpp b/src/scripting/server/helpers.cpp index d8a0ddae7..d25e4e77f 100644 --- a/src/scripting/server/helpers.cpp +++ b/src/scripting/server/helpers.cpp @@ -18,9 +18,6 @@ #include #include -#include -#include -#include int Bridge_ServerHelpers_GetServerLanguage(char* out) { From 3179c3226a76f478ca6cc978108e237593eca96a Mon Sep 17 00:00:00 2001 From: skuzzis Date: Thu, 16 Oct 2025 21:08:26 +0300 Subject: [PATCH 10/16] fix(packaging): PDB location --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5c8158f32..85d2f6396 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -249,8 +249,8 @@ jobs: cp -r ${{ env.LINUX_FOLDER }}-loader/* ${{ env.LINUX_FOLDER }}/ cp -r ${{ env.WINDOWS_FOLDER }}-loader/* ${{ env.WINDOWS_FOLDER }}/ - cp -r ./swiftlys2.pdb ${{ env.WINDOWS_FOLDER }}/swiftlys2/bin/win64/ - cp -r ./server.pdb ${{ env.WINDOWS_FOLDER }}/swiftlys2/bin/win64/ + cp -r ./swiftlys2.pdb/swiftlys2.pdb ${{ env.WINDOWS_FOLDER }}/swiftlys2/bin/win64/ + cp -r ./server.pdb/server.pdb ${{ env.WINDOWS_FOLDER }}/swiftlys2/bin/win64/ mkdir -p ${{ env.LINUX_FOLDER }}/swiftlys2/plugins/ mkdir -p ${{ env.WINDOWS_FOLDER }}/swiftlys2/plugins/ From 25b2a92af1864de810e4605328e44d5a6f9e378d Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Fri, 17 Oct 2025 10:15:35 +0800 Subject: [PATCH 11/16] feat: Add AutoHotReload key to configuration --- .../src/SwiftlyS2.Core/Modules/Plugins/PluginManager.cs | 5 +++++ managed/src/SwiftlyS2.Generated/Natives/ServerHelpers.cs | 7 +++++++ natives/server/helpers.native | 3 ++- plugin_files/configs/core.example.jsonc | 1 + src/scripting/server/helpers.cpp | 9 ++++++++- src/server/configuration/configuration.cpp | 1 + 6 files changed, 24 insertions(+), 2 deletions(-) diff --git a/managed/src/SwiftlyS2.Core/Modules/Plugins/PluginManager.cs b/managed/src/SwiftlyS2.Core/Modules/Plugins/PluginManager.cs index a738231a7..be8741f58 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Plugins/PluginManager.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Plugins/PluginManager.cs @@ -79,6 +79,11 @@ public void HandlePluginChange(object sender, FileSystemEventArgs e) { try { + if (!NativeServerHelpers.UseAutoHotReload()) + { + return; + } + // Windows FileSystemWatcher triggers multiple (open, write, close) events for a single file change if (DateTime.Now - lastRead < TimeSpan.FromSeconds(1)) { diff --git a/managed/src/SwiftlyS2.Generated/Natives/ServerHelpers.cs b/managed/src/SwiftlyS2.Generated/Natives/ServerHelpers.cs index a2b22ad6d..d3cc02596 100644 --- a/managed/src/SwiftlyS2.Generated/Natives/ServerHelpers.cs +++ b/managed/src/SwiftlyS2.Generated/Natives/ServerHelpers.cs @@ -38,4 +38,11 @@ public unsafe static bool IsFollowingServerGuidelines() { var ret = _IsFollowingServerGuidelines(); return ret == 1; } + + private unsafe static delegate* unmanaged _UseAutoHotReload; + + public unsafe static bool UseAutoHotReload() { + var ret = _UseAutoHotReload(); + return ret == 1; + } } \ No newline at end of file diff --git a/natives/server/helpers.native b/natives/server/helpers.native index 4fbaa820f..004c5719c 100644 --- a/natives/server/helpers.native +++ b/natives/server/helpers.native @@ -2,4 +2,5 @@ class ServerHelpers string GetServerLanguage = void bool UsePlayerLanguage = void -bool IsFollowingServerGuidelines = void \ No newline at end of file +bool IsFollowingServerGuidelines = void +bool UseAutoHotReload = void \ No newline at end of file diff --git a/plugin_files/configs/core.example.jsonc b/plugin_files/configs/core.example.jsonc index 4396d74ad..82f60fe43 100644 --- a/plugin_files/configs/core.example.jsonc +++ b/plugin_files/configs/core.example.jsonc @@ -6,6 +6,7 @@ "CommandSilentPrefixes": [ "/" ], + "AutoHotReload": true, "ConsoleFilter": true, "FollowCS2ServerGuidelines": true, "Language": "en", diff --git a/src/scripting/server/helpers.cpp b/src/scripting/server/helpers.cpp index d25e4e77f..05eda8ff9 100644 --- a/src/scripting/server/helpers.cpp +++ b/src/scripting/server/helpers.cpp @@ -43,6 +43,13 @@ bool Bridge_ServerHelpers_IsFollowingServerGuidelines() return std::get(configuration->GetValue("core.FollowCS2ServerGuidelines")); } +bool Bridge_ServerHelpers_UseAutoHotReload() +{ + static auto configuration = g_ifaceService.FetchInterface(CONFIGURATION_INTERFACE_VERSION); + return std::get(configuration->GetValue("core.AutoHotReload")); +} + DEFINE_NATIVE("ServerHelpers.GetServerLanguage", Bridge_ServerHelpers_GetServerLanguage); DEFINE_NATIVE("ServerHelpers.UsePlayerLanguage", Bridge_ServerHelpers_UsePlayerLanguage); -DEFINE_NATIVE("ServerHelpers.IsFollowingServerGuidelines", Bridge_ServerHelpers_IsFollowingServerGuidelines); \ No newline at end of file +DEFINE_NATIVE("ServerHelpers.IsFollowingServerGuidelines", Bridge_ServerHelpers_IsFollowingServerGuidelines); +DEFINE_NATIVE("ServerHelpers.UseAutoHotReload", Bridge_ServerHelpers_UseAutoHotReload); \ No newline at end of file diff --git a/src/server/configuration/configuration.cpp b/src/server/configuration/configuration.cpp index 6b855e87d..4593f10fd 100644 --- a/src/server/configuration/configuration.cpp +++ b/src/server/configuration/configuration.cpp @@ -462,6 +462,7 @@ bool Configuration::Load() RegisterConfigurationVector(wasEdited, config_json, "core", "core", "CommandPrefixes", { "!" }, true, " "); RegisterConfigurationVector(wasEdited, config_json, "core", "core", "CommandSilentPrefixes", { "/" }, true, " "); + RegisterConfiguration(wasEdited, config_json, "core", "core", "AutoHotReload", true); RegisterConfiguration(wasEdited, config_json, "core", "core", "ConsoleFilter", true); RegisterConfigurationVector(wasEdited, config_json, "core", "core", "PatchesToPerform", {}, true, " "); From 605938a8ba4bec9e7d1d9cbd332f7fd895618d82 Mon Sep 17 00:00:00 2001 From: samyyc Date: Fri, 17 Oct 2025 18:23:04 +0800 Subject: [PATCH 12/16] feat(managed): Add FindGameSystemByName --- managed/src/SwiftlyS2.Core/Modules/Engine/EngineService.cs | 6 ++++++ .../src/SwiftlyS2.Shared/Modules/Engine/IEngineService.cs | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/managed/src/SwiftlyS2.Core/Modules/Engine/EngineService.cs b/managed/src/SwiftlyS2.Core/Modules/Engine/EngineService.cs index b95139c5d..97eaa387c 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Engine/EngineService.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Engine/EngineService.cs @@ -38,4 +38,10 @@ public bool IsMapValid(string map) { return NativeEngineHelpers.IsMapValid(map); } + + public nint? FindGameSystemByName(string name) + { + var handle = NativeEngineHelpers.FindGameSystemByName(name); + return handle == nint.Zero ? null : handle; + } } \ No newline at end of file diff --git a/managed/src/SwiftlyS2.Shared/Modules/Engine/IEngineService.cs b/managed/src/SwiftlyS2.Shared/Modules/Engine/IEngineService.cs index e88431e7f..f9c296f28 100644 --- a/managed/src/SwiftlyS2.Shared/Modules/Engine/IEngineService.cs +++ b/managed/src/SwiftlyS2.Shared/Modules/Engine/IEngineService.cs @@ -46,4 +46,11 @@ public interface IEngineService /// The number of simulation ticks that have occurred since the server started. /// int TickCount { get; } + + /// + /// Find a game system by name. + /// + /// The name of the game system. + /// The game system handle. Null if not found. + nint? FindGameSystemByName(string name); } \ No newline at end of file From 60c1448d1d9d4daedbf27d6b6525efb9f44238f6 Mon Sep 17 00:00:00 2001 From: samyyc Date: Sat, 18 Oct 2025 13:37:34 +0800 Subject: [PATCH 13/16] feat(core): Improve FindGameSystemByName --- .../Modules/EntitySystem/CEntityKeyValues.cs | 2 +- .../Natives/Structs/ManagedCUtlVector.cs | 1 + src/scripting/engine/enginehelpers.cpp | 20 ++++++++++++++++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/managed/src/SwiftlyS2.Shared/Modules/EntitySystem/CEntityKeyValues.cs b/managed/src/SwiftlyS2.Shared/Modules/EntitySystem/CEntityKeyValues.cs index f7e2f9610..1780641c9 100644 --- a/managed/src/SwiftlyS2.Shared/Modules/EntitySystem/CEntityKeyValues.cs +++ b/managed/src/SwiftlyS2.Shared/Modules/EntitySystem/CEntityKeyValues.cs @@ -16,7 +16,7 @@ public void Dispose() { _handle.Dispose(); } - internal nint Address => _handle.Address; + public nint Address => _handle.Address; public void SetBool(string key, bool value) { NativeCEntityKeyValues.SetBool(Address, key, value); diff --git a/managed/src/SwiftlyS2.Shared/Natives/Structs/ManagedCUtlVector.cs b/managed/src/SwiftlyS2.Shared/Natives/Structs/ManagedCUtlVector.cs index d7204d731..5fb380818 100644 --- a/managed/src/SwiftlyS2.Shared/Natives/Structs/ManagedCUtlVector.cs +++ b/managed/src/SwiftlyS2.Shared/Natives/Structs/ManagedCUtlVector.cs @@ -21,6 +21,7 @@ public void Dispose() public nint Base => _vector.Base; public int Count => _vector.Count; + public ref CUtlVector Value => ref _vector; public ref T this[int index] => ref _vector[index]; } \ No newline at end of file diff --git a/src/scripting/engine/enginehelpers.cpp b/src/scripting/engine/enginehelpers.cpp index 24ebd7344..aecdbabfa 100644 --- a/src/scripting/engine/enginehelpers.cpp +++ b/src/scripting/engine/enginehelpers.cpp @@ -31,6 +31,12 @@ #include +struct CBaseGameSystemFactory_t : public IGameSystemFactory { + CBaseGameSystemFactory_t* m_pNext; + const char* m_pName; + void** reallocating_ptr; +}; + int Bridge_EngineHelpers_GetServerIP(char* out) { static std::string s; @@ -117,7 +123,19 @@ int Bridge_EngineHelpers_GetServerTickCount() void* Bridge_EngineHelpers_FindGameSystemByName(const char* name) { - return CBaseGameSystemFactory::GetGlobalPtrByName(name); + CBaseGameSystemFactory_t* pFactoryList = *reinterpret_cast(CBaseGameSystemFactory::sm_pFirst); + while (pFactoryList) + { + if (strcmp(pFactoryList->m_pName, name) == 0) + { + if (pFactoryList->IsReallocating()) { + return *pFactoryList->reallocating_ptr; + } + return pFactoryList->GetStaticGameSystem(); + } + pFactoryList = pFactoryList->m_pNext; + } + return nullptr; } void Bridge_EngineHelpers_SendMessageToConsole(const char* message) From d402f962c7446aee5e83db3c296dc6e9004bdd60 Mon Sep 17 00:00:00 2001 From: samyyc Date: Sat, 18 Oct 2025 20:45:03 +0800 Subject: [PATCH 14/16] fix(managed): Fix vector serialization --- .../src/SwiftlyS2.Core/Services/PluginConfigurationService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/managed/src/SwiftlyS2.Core/Services/PluginConfigurationService.cs b/managed/src/SwiftlyS2.Core/Services/PluginConfigurationService.cs index 65ee0e670..2bd92c92f 100644 --- a/managed/src/SwiftlyS2.Core/Services/PluginConfigurationService.cs +++ b/managed/src/SwiftlyS2.Core/Services/PluginConfigurationService.cs @@ -86,6 +86,7 @@ public IPluginConfigurationService InitializeWithTemplate(string name, string te var options = new JsonSerializerOptions { WriteIndented = true, + IncludeFields = true, PropertyNamingPolicy = null }; From d6ce96d95f2d96a29a1767bbc3010a66d48f3d61 Mon Sep 17 00:00:00 2001 From: samyyc Date: Sat, 18 Oct 2025 23:11:09 +0800 Subject: [PATCH 15/16] fix(managed): Improve native wrappers --- .../Natives/Structs/CUtlLeanVector.cs | 8 ++++ .../Natives/Structs/CUtlMemory.cs | 15 +++--- .../Natives/Structs/CUtlVector.cs | 17 ++++--- .../Natives/Structs/ManagedCUtlLeanVector.cs | 47 +++++++++++++++++++ .../Natives/Structs/ManagedCUtlMemory.cs | 30 +++++++++--- .../Natives/Structs/ManagedCUtlVector.cs | 34 +++++++++++--- 6 files changed, 125 insertions(+), 26 deletions(-) create mode 100644 managed/src/SwiftlyS2.Shared/Natives/Structs/ManagedCUtlLeanVector.cs diff --git a/managed/src/SwiftlyS2.Shared/Natives/Structs/CUtlLeanVector.cs b/managed/src/SwiftlyS2.Shared/Natives/Structs/CUtlLeanVector.cs index 8cef2d1f4..3c7ce0358 100644 --- a/managed/src/SwiftlyS2.Shared/Natives/Structs/CUtlLeanVector.cs +++ b/managed/src/SwiftlyS2.Shared/Natives/Structs/CUtlLeanVector.cs @@ -40,6 +40,10 @@ public override bool Equals(object? obj) private I ExternalBufferMarker => I.One << ((Marshal.SizeOf() * 8) - 1); + /// + /// Please use instead to construct it. + /// If you really want to use this, you should call after you are done with it. + /// public CUtlLeanVector(I growSize, I initSize) { Count = (I)(object)0; @@ -47,6 +51,10 @@ public CUtlLeanVector(I growSize, I initSize) EnsureCapacity(int.CreateChecked(initSize), true); } + /// + /// Please use instead to construct it. + /// If you really want to use this, you should call after you are done with it. + /// public CUtlLeanVector(nint memory, I allocationCount, I numElements) { Count = numElements; diff --git a/managed/src/SwiftlyS2.Shared/Natives/Structs/CUtlMemory.cs b/managed/src/SwiftlyS2.Shared/Natives/Structs/CUtlMemory.cs index 41e6b0f25..4de228972 100644 --- a/managed/src/SwiftlyS2.Shared/Natives/Structs/CUtlMemory.cs +++ b/managed/src/SwiftlyS2.Shared/Natives/Structs/CUtlMemory.cs @@ -14,7 +14,7 @@ public enum BufferMarkers } [StructLayout(LayoutKind.Sequential)] -public struct CUtlMemory : IDisposable +public struct CUtlMemory { private nint _memory; private uint _allocationCount; @@ -22,6 +22,10 @@ public struct CUtlMemory : IDisposable public int ElementSize => SchemaSize.Get(); + /// + /// Please use instead to construct it. + /// If you really want to use this, you should call after you are done with it. + /// public CUtlMemory(int growSize, int initSize) { _memory = 0; @@ -30,6 +34,10 @@ public CUtlMemory(int growSize, int initSize) Init(growSize, initSize); } + /// + /// Please use instead to construct it. + /// If you really want to use this, you should call after you are done with it. + /// public CUtlMemory(nint memory, int numelements, bool readOnly) { _memory = 0; @@ -38,11 +46,6 @@ public CUtlMemory(nint memory, int numelements, bool readOnly) SetExternalBuffer(memory, numelements, readOnly); } - public void Dispose() - { - Purge(); - } - public void Init(int growSize, int initSize) { Purge(); diff --git a/managed/src/SwiftlyS2.Shared/Natives/Structs/CUtlVector.cs b/managed/src/SwiftlyS2.Shared/Natives/Structs/CUtlVector.cs index ca3e9d832..9ab921c79 100644 --- a/managed/src/SwiftlyS2.Shared/Natives/Structs/CUtlVector.cs +++ b/managed/src/SwiftlyS2.Shared/Natives/Structs/CUtlVector.cs @@ -7,31 +7,34 @@ using SwiftlyS2.Shared.Schemas; [StructLayout(LayoutKind.Sequential, Pack = 8, Size = 24)] -public struct CUtlVector : IDisposable, IEnumerable +public struct CUtlVector : IEnumerable { private int _size; private CUtlMemory _memory; public int ElementSize => SchemaSize.Get(); + /// + /// Please use instead to construct it. + /// If you really want to use this, you should call after you are done with it. + /// public CUtlVector(int growSize, int initSize) { _memory = new(growSize, initSize); _size = 0; } + /// + /// Please use instead to construct it. + /// If you really want to use this, you should call after you are done with it. + /// public CUtlVector(nint memory, int allocationCount, int numElements) { _memory = new(memory, allocationCount, false); _size = numElements; } - public void Dispose() - { - Purge(); - } - - void Purge() + public void Purge() { RemoveAll(); _memory.Purge(); diff --git a/managed/src/SwiftlyS2.Shared/Natives/Structs/ManagedCUtlLeanVector.cs b/managed/src/SwiftlyS2.Shared/Natives/Structs/ManagedCUtlLeanVector.cs new file mode 100644 index 000000000..65b268290 --- /dev/null +++ b/managed/src/SwiftlyS2.Shared/Natives/Structs/ManagedCUtlLeanVector.cs @@ -0,0 +1,47 @@ +using System.Numerics; + +namespace SwiftlyS2.Shared.Natives; + +public class ManagedCUtlLeanVector : IDisposable where T : unmanaged where I : unmanaged, IBinaryInteger, IMinMaxValue +{ + private CUtlLeanVector _vector; + private bool _disposed; + + public ManagedCUtlLeanVector() + { + _vector = new CUtlLeanVector(I.Zero, I.One); + } + + public ManagedCUtlLeanVector(I growSize, I initSize) + { + _vector = new CUtlLeanVector(growSize, initSize); + } + + ~ManagedCUtlLeanVector() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) return; + + _vector.Purge(); + + _disposed = true; + } + + private void ThrowIfDisposed() + { + if (_disposed) + throw new ObjectDisposedException(nameof(ManagedCUtlLeanVector)); + } + + public ref CUtlLeanVector Value { get { ThrowIfDisposed(); return ref _vector; } } +} \ No newline at end of file diff --git a/managed/src/SwiftlyS2.Shared/Natives/Structs/ManagedCUtlMemory.cs b/managed/src/SwiftlyS2.Shared/Natives/Structs/ManagedCUtlMemory.cs index e28989c33..b9b7711e0 100644 --- a/managed/src/SwiftlyS2.Shared/Natives/Structs/ManagedCUtlMemory.cs +++ b/managed/src/SwiftlyS2.Shared/Natives/Structs/ManagedCUtlMemory.cs @@ -3,24 +3,42 @@ namespace SwiftlyS2.Shared.Natives; public class ManagedCUtlMemory : IDisposable where T : unmanaged { private CUtlMemory _memory; + private bool _disposed; + public ManagedCUtlMemory() + { + _memory = new CUtlMemory(0, 1); + } public ManagedCUtlMemory(int growSize, int initSize) { _memory = new CUtlMemory(growSize, initSize); } - public ManagedCUtlMemory(nint memory, int numelements, bool readOnly) + ~ManagedCUtlMemory() { - _memory = new CUtlMemory(memory, numelements, readOnly); + Dispose(false); } public void Dispose() { - _memory.Dispose(); + Dispose(true); + GC.SuppressFinalize(this); } - public nint Base => _memory.Base; - public int Count => _memory.Count; + protected virtual void Dispose(bool disposing) + { + if (_disposed) return; + + _memory.Purge(); + + _disposed = true; + } + + private void ThrowIfDisposed() + { + if (_disposed) + throw new ObjectDisposedException(nameof(ManagedCUtlMemory)); + } - public ref T this[int index] => ref _memory[index]; + public ref CUtlMemory Value { get { ThrowIfDisposed(); return ref _memory; } } } \ No newline at end of file diff --git a/managed/src/SwiftlyS2.Shared/Natives/Structs/ManagedCUtlVector.cs b/managed/src/SwiftlyS2.Shared/Natives/Structs/ManagedCUtlVector.cs index 5fb380818..4060b6044 100644 --- a/managed/src/SwiftlyS2.Shared/Natives/Structs/ManagedCUtlVector.cs +++ b/managed/src/SwiftlyS2.Shared/Natives/Structs/ManagedCUtlVector.cs @@ -3,25 +3,45 @@ namespace SwiftlyS2.Shared.Natives; public class ManagedCUtlVector : IDisposable where T : unmanaged { private CUtlVector _vector; + private bool _disposed; + + public ManagedCUtlVector() + { + _vector = new CUtlVector(0, 1); + } public ManagedCUtlVector(int growSize, int initSize) { _vector = new CUtlVector(growSize, initSize); } - public ManagedCUtlVector(nint memory, int allocationCount, int numElements) + ~ManagedCUtlVector() { - _vector = new CUtlVector(memory, allocationCount, numElements); + Dispose(false); } public void Dispose() { - _vector.Dispose(); + Dispose(true); + GC.SuppressFinalize(this); + } + + + protected virtual void Dispose(bool disposing) + { + if (_disposed) return; + + _vector.Purge(); + + _disposed = true; + } + + private void ThrowIfDisposed() + { + if (_disposed) + throw new ObjectDisposedException(nameof(ManagedCUtlVector)); } - public nint Base => _vector.Base; - public int Count => _vector.Count; - public ref CUtlVector Value => ref _vector; - public ref T this[int index] => ref _vector[index]; + public ref CUtlVector Value { get { ThrowIfDisposed(); return ref _vector; } } } \ No newline at end of file From 540fa821e8c2fbceade2b91ab11e61bc33e192b2 Mon Sep 17 00:00:00 2001 From: samyyc Date: Sun, 19 Oct 2025 00:10:25 +0800 Subject: [PATCH 16/16] feat(managed): Add DisableRuntimeMarshalling --- managed/managed.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/managed/managed.csproj b/managed/managed.csproj index d885e4871..ff5a11582 100644 --- a/managed/managed.csproj +++ b/managed/managed.csproj @@ -21,6 +21,7 @@ build\ $(BaseOutputPath)Release\SwiftlyS2 SwiftlyS2.CS2 + true true $(NoWarn);CS1591