diff --git a/configs/addons/counterstrikesharp/configs/core.example.json b/configs/addons/counterstrikesharp/configs/core.example.json index 97dc9bfc3..282d19e1c 100644 --- a/configs/addons/counterstrikesharp/configs/core.example.json +++ b/configs/addons/counterstrikesharp/configs/core.example.json @@ -6,5 +6,7 @@ "PluginAutoLoadEnabled": true, "ServerLanguage": "en", "UnlockConCommands": true, - "UnlockConVars": true + "UnlockConVars": true, + "EnableMemoryManager": true, + "MemoryManagerInterval": 10000 } \ No newline at end of file diff --git a/managed/CounterStrikeSharp.API/Core/API.cs b/managed/CounterStrikeSharp.API/Core/API.cs index 94c794624..7dce88e0c 100644 --- a/managed/CounterStrikeSharp.API/Core/API.cs +++ b/managed/CounterStrikeSharp.API/Core/API.cs @@ -1172,6 +1172,73 @@ public static void RemoveAllNetworkVectorElements(IntPtr vec){ } } + public static IntPtr MemAllocAllocate(int size){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.Push(size); + ScriptContext.GlobalScriptContext.SetIdentifier(0x9F81F710); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + return (IntPtr)ScriptContext.GlobalScriptContext.GetResult(typeof(IntPtr)); + } + } + + public static IntPtr MemAllocAllocateAligned(int size, int align){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.Push(size); + ScriptContext.GlobalScriptContext.Push(align); + ScriptContext.GlobalScriptContext.SetIdentifier(0x7D6BFE63); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + return (IntPtr)ScriptContext.GlobalScriptContext.GetResult(typeof(IntPtr)); + } + } + + public static IntPtr MemAllocReallocateAligned(IntPtr pointer, int size, int align){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.Push(pointer); + ScriptContext.GlobalScriptContext.Push(size); + ScriptContext.GlobalScriptContext.Push(align); + ScriptContext.GlobalScriptContext.SetIdentifier(0x3C513734); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + return (IntPtr)ScriptContext.GlobalScriptContext.GetResult(typeof(IntPtr)); + } + } + + public static int MemAllocGetSizeAligned(IntPtr pointer){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.Push(pointer); + ScriptContext.GlobalScriptContext.SetIdentifier(0x814A9CB2); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + return (int)ScriptContext.GlobalScriptContext.GetResult(typeof(int)); + } + } + + public static void MemAllocFreePointer(IntPtr pointer){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.Push(pointer); + ScriptContext.GlobalScriptContext.SetIdentifier(0xC2100D1D); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + } + } + + public static void MemAllocFreePointerAligned(IntPtr pointer){ + lock (ScriptContext.GlobalScriptContext.Lock) { + ScriptContext.GlobalScriptContext.Reset(); + ScriptContext.GlobalScriptContext.Push(pointer); + ScriptContext.GlobalScriptContext.SetIdentifier(0x88571A2E); + ScriptContext.GlobalScriptContext.Invoke(); + ScriptContext.GlobalScriptContext.CheckErrors(); + } + } + public static short GetSchemaOffset(string classname, string propname){ lock (ScriptContext.GlobalScriptContext.Lock) { ScriptContext.GlobalScriptContext.Reset(); diff --git a/managed/CounterStrikeSharp.API/Core/Application.cs b/managed/CounterStrikeSharp.API/Core/Application.cs index bd948014f..4a5cc5e78 100644 --- a/managed/CounterStrikeSharp.API/Core/Application.cs +++ b/managed/CounterStrikeSharp.API/Core/Application.cs @@ -19,6 +19,7 @@ using System.Text; using CounterStrikeSharp.API.Core.Commands; using CounterStrikeSharp.API.Core.Hosting; +using CounterStrikeSharp.API.Core.Memory; using CounterStrikeSharp.API.Core.Plugin; using CounterStrikeSharp.API.Core.Plugin.Host; using CounterStrikeSharp.API.Core.Translations; @@ -48,11 +49,12 @@ public sealed class Application private readonly IPluginContextQueryHandler _pluginContextQueryHandler; private readonly IPlayerLanguageManager _playerLanguageManager; private readonly ICommandManager _commandManager; + private readonly IMemoryManager _memoryManager; public Application(ILoggerFactory loggerFactory, IScriptHostConfiguration scriptHostConfiguration, GameDataProvider gameDataProvider, CoreConfig coreConfig, IPluginManager pluginManager, IPluginContextQueryHandler pluginContextQueryHandler, IPlayerLanguageManager playerLanguageManager, - ICommandManager commandManager) + ICommandManager commandManager, IMemoryManager memoryManager) { Logger = loggerFactory.CreateLogger("Core"); _scriptHostConfiguration = scriptHostConfiguration; @@ -62,6 +64,7 @@ public Application(ILoggerFactory loggerFactory, IScriptHostConfiguration script _pluginContextQueryHandler = pluginContextQueryHandler; _playerLanguageManager = playerLanguageManager; _commandManager = commandManager; + _memoryManager = memoryManager; _instance = this; } @@ -90,6 +93,7 @@ public void Start() RegisterPluginCommands(); _pluginManager.Load(); + _memoryManager.Load(); for (var i = 1; i <= 9; i++) { diff --git a/managed/CounterStrikeSharp.API/Core/CoreConfig.cs b/managed/CounterStrikeSharp.API/Core/CoreConfig.cs index 7771456bf..e6c8fd567 100644 --- a/managed/CounterStrikeSharp.API/Core/CoreConfig.cs +++ b/managed/CounterStrikeSharp.API/Core/CoreConfig.cs @@ -61,6 +61,12 @@ internal sealed partial class CoreConfigData [JsonPropertyName("UnlockConVars")] public bool UnlockConVars { get; set; } = true; + + [JsonPropertyName("EnableMemoryManager")] + public bool EnableMemoryManager { get; set; } = true; + + [JsonPropertyName("MemoryManagerInterval")] + public int MemoryManagerInterval { get; set; } = 10000; } /// @@ -115,6 +121,15 @@ public partial class CoreConfig public static bool UnlockConVars => _coreConfig.UnlockConVars; + /// + /// Enable the EXPERIMENTAL MemoryManager. + /// + public static bool EnableMemoryManager => _coreConfig.EnableMemoryManager; + + /// + /// The interval (in milliseconds) at which the memory manager checks for leaking references. + /// + public static int MemoryManagerInterval => _coreConfig.MemoryManagerInterval; } public partial class CoreConfig : IStartupService diff --git a/managed/CounterStrikeSharp.API/Core/Memory/IMemoryManager.cs b/managed/CounterStrikeSharp.API/Core/Memory/IMemoryManager.cs new file mode 100644 index 000000000..b5be3af8b --- /dev/null +++ b/managed/CounterStrikeSharp.API/Core/Memory/IMemoryManager.cs @@ -0,0 +1,31 @@ +/* + * This file is part of CounterStrikeSharp. + * CounterStrikeSharp is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * CounterStrikeSharp is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CounterStrikeSharp. If not, see . * + */ + +namespace CounterStrikeSharp.API.Core.Memory +{ + public interface IMemoryManager + { + public void Load(); + + public void Start(); + + public void Stop(bool forceStop = false); + + public void Resume(); + + public void ForceCollect(int generation, GCCollectionMode mode, bool blocking); + } +} diff --git a/managed/CounterStrikeSharp.API/Core/Memory/MemoryManager.cs b/managed/CounterStrikeSharp.API/Core/Memory/MemoryManager.cs new file mode 100644 index 000000000..de28e0ab7 --- /dev/null +++ b/managed/CounterStrikeSharp.API/Core/Memory/MemoryManager.cs @@ -0,0 +1,325 @@ +/* + * This file is part of CounterStrikeSharp. + * CounterStrikeSharp is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * CounterStrikeSharp is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CounterStrikeSharp. If not, see . * + */ + +using System.Threading; +using System.Threading.Tasks; + +using CounterStrikeSharp.API.Core.Commands; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Commands; +using CounterStrikeSharp.API.Modules.Memory; + +using Microsoft.Extensions.Logging; + +namespace CounterStrikeSharp.API.Core.Memory +{ + /// + /// Represents the states. + /// + public enum MemoryManagerState : int + { + /// + /// Waiting in idle state. + /// + Idle, + + /// + /// Currently releasing resources. Normally after this it will enter if there is nothing else going on. + /// + InProgress, + + /// + /// Something caused to enter this state. ( has to be restarted in order to continue working.) + /// + Stopped, + + /// + /// Halted, once it can continue it will enter the or state. + /// + Halted, + + /// + /// We have lost this dude. + /// + Unknown + } + + /// + /// Handles forced garbage collection and + /// + public class MemoryManager : IMemoryManager, IStartupService + { + /// + /// Returns the total amount of released resources since startup. + /// + public ulong TotalReleased { get; private set; } = 0; + + /// + /// Returns how much resources were released last time. + /// + public int LastReleased { get; private set; } = 0; + + /// + /// Returns how much resources lives currently. + /// + public int CurrentResources => DisposableMemory.Instances; + + /// + /// Last time the run. + /// + public DateTime LastUpdated { get; private set; } = DateTime.UtcNow; + + /// + /// state. + /// + public MemoryManagerState State { get; private set; } = MemoryManagerState.Unknown; + + public long TotalMemory => GC.GetTotalMemory(false); + + public long TotalAllocated => GC.GetTotalAllocatedBytes(); + + public double TotalMemoryMB => (TotalMemory / (1024.0 * 1024.0)); + + public double TotalAllocatedMB => (TotalAllocated / (1024.0 * 1024.0)); + + private readonly ILogger _logger; + + private readonly ICommandManager _commandManager; + + private Thread _thread; + + public MemoryManager(ILogger logger, ICommandManager commandManager) + { + _logger = logger; + _commandManager = commandManager; + + _thread = new Thread(BackgroundThread); + } + + [RequiresPermissions("@css/generic")] + private void OnCommand(CCSPlayerController? caller, CommandInfo info) + { + switch (info.GetArg(1)) + { + case "stats": + { + PrintStatistics(info); + } break; + + case "start": + { + Start(); + } break; + + case "stop": + { + Stop(true); + } break; + + case "pause": + { + Stop(false); + } break; + + case "resume": + { + Resume(); + } break; + + default: + { + info.ReplyToCommand("Valid usage: css_memory [option]\n" + + " stats - Print garbage collector statistics.\n" + + " start - Starts the memory manager that handles leaking resources.\n" + + " stop - Stops the memory manager.\n" + + " pause - Stops the memory manager.\n" + + " resume - Resumes the memory manager.\n"); + } break; + } + } + + public void Load() + { + _commandManager.RegisterCommand(new("css_memory", "Counter-Strike Sharp Memory Manager options.", + OnCommand) + { + ExecutableBy = CommandUsage.CLIENT_AND_SERVER, + MinArgs = 1, + UsageHint = "[option]\n" + + " stats - Print garbage collector statistics.\n" + + " start - Starts the memory manager that handles leaking resources.\n" + + " stop - Stops the memory manager.\n" + + " pause - Stops the memory manager.\n" + + " resume - Resumes the memory manager.\n", + }); + + Start(); + } + + private void PrintStatistics(CommandInfo info) + { + info.ReplyToCommand("Memory Manager Statistics:\n" + + $"State: {State}\n" + + $"Total Released: {TotalReleased}\n" + + $"Last Released: {LastReleased}\n" + + $"Current Resources: {CurrentResources}\n" + + $"Last Updated: {LastUpdated.ToString("yyyy.MM.dd hh:mm:ss tt")} (UTC)\n" + + $"Heap Memory Usage: ~{TotalMemoryMB:F5} MB ({TotalMemory} bytes)\n" + + $"Total Allocated Bytes: ~{TotalAllocatedMB:F5} MB ({TotalAllocated} bytes)"); + } + + public void Start() + { + if (CoreConfig.EnableMemoryManager) + { + if (State == MemoryManagerState.Idle || State == MemoryManagerState.InProgress) + { + _logger.LogInformation("Service is already running"); + return; + } + + if (State == MemoryManagerState.Halted) + { + _logger.LogInformation("Service should be resumed, not started"); + return; + } + + _logger.LogInformation("Starting service..."); + + try + { + _thread.Start(); + } catch (ThreadStateException) + { + _thread = new Thread(BackgroundThread); + _thread.Start(); + } catch (Exception e) + { + _logger.LogCritical("Exception occured: '{0}' ({1})", e.Message, e); + } + } else + { + _logger.LogError("Unable to start 'MemoryManager' with CoreConfig option '{0}' disabled.", "EnableMemoryManager"); + } + } + + public void Stop(bool forceStop = false) + { + if (forceStop) + { + if (State == MemoryManagerState.Stopped) + { + _logger.LogInformation("Service is already stopped"); + return; + } + + _logger.LogInformation("Stopping service... (might take a while)"); + + Task.Run(() => + { + State = MemoryManagerState.Stopped; + + _thread.Join(); + _logger.LogInformation("Service has been stopped"); + }); + } else + { + if (State == MemoryManagerState.Halted) + { + _logger.LogInformation("Service is already paused"); + return; + } + + State = MemoryManagerState.Halted; + _logger.LogInformation("Service has been halted"); + } + } + + public void Resume() + { + if (State != MemoryManagerState.Halted) + { + _logger.LogWarning("Unable to resume service (state: {0})", State); + return; + } + + State = MemoryManagerState.Idle; + _logger.LogInformation("Service has been resumed"); + } + + public void ForceCollect(int generation, GCCollectionMode mode, bool blocking) + { + GC.Collect(generation, mode, blocking); + } + + private void BackgroundThread() + { + _logger.LogInformation("Service has been started"); + State = MemoryManagerState.Idle; + + while (State != MemoryManagerState.Stopped) + { + Thread.Sleep(CoreConfig.MemoryManagerInterval); + + if (State == MemoryManagerState.Stopped) + break; + + if (State == MemoryManagerState.Halted) + continue; + + int totalCount = CurrentResources; + + if (totalCount == 0) + continue; + + State = MemoryManagerState.InProgress; + + // TODO: replace with 'LogTrace' + _logger.LogInformation("Running garbage collector ({0} disposable memory in total)", totalCount); + DateTime startTime = DateTime.UtcNow; + + // some may go to gen1 or even gen2, but even those are released when this nondeterministic wonder wants so + ForceCollect(0, GCCollectionMode.Default, true); + + // this might be obsolete with 'blocking: false'? + GC.WaitForPendingFinalizers(); + + // this part is wrong here as there is a chance that new ones are instantiated during this time + // however, this is not used yet, not even sure it will be as it only serves statistics (TODO: fix) + int totalCountAfter = CurrentResources; + int difference = Math.Abs(totalCountAfter - totalCount); + + LastReleased = totalCountAfter == 0 ? totalCount : difference; + TotalReleased += (ulong)LastReleased; + LastUpdated = DateTime.UtcNow; + + if (State == MemoryManagerState.InProgress) + { + State = MemoryManagerState.Idle; + } + + if (LastReleased > 0) + { + // TODO: replace with 'LogTrace' + _logger.LogInformation("Released {0} leaking memory resources in {1}ms ({2} remains)", LastReleased, (LastUpdated - startTime).TotalMilliseconds, CurrentResources); + } else + { + Thread.Sleep(CoreConfig.MemoryManagerInterval); + } + } + } + } +} diff --git a/managed/CounterStrikeSharp.API/CounterStrikeSharp.API.csproj b/managed/CounterStrikeSharp.API/CounterStrikeSharp.API.csproj index bd39b2bc7..d968451e8 100644 --- a/managed/CounterStrikeSharp.API/CounterStrikeSharp.API.csproj +++ b/managed/CounterStrikeSharp.API/CounterStrikeSharp.API.csproj @@ -23,34 +23,34 @@ .\ApiCompat\v202.dll - - + + - - - - - - - - - - - - + + + + + + + + + + + + - - + + - + - + - + true @@ -58,12 +58,8 @@ true - - + + diff --git a/managed/CounterStrikeSharp.API/Modules/Memory/DisposableMemory.cs b/managed/CounterStrikeSharp.API/Modules/Memory/DisposableMemory.cs new file mode 100644 index 000000000..68091075d --- /dev/null +++ b/managed/CounterStrikeSharp.API/Modules/Memory/DisposableMemory.cs @@ -0,0 +1,200 @@ +/* + * This file is part of CounterStrikeSharp. + * CounterStrikeSharp is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * CounterStrikeSharp is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CounterStrikeSharp. If not, see . * + */ + +using System; + +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Utils; + +namespace CounterStrikeSharp.API.Modules.Memory +{ + public abstract class DisposableMemory : NativeObject, IDisposableMemory + { + internal static Type DisposableType = typeof(DisposableMemory); + + internal static int _instances; + + internal static int Instances + { + get { return _instances; } + set + { + _instances = value; + + // Should not happen? + if (_instances < 0) + { + _instances = 0; + } + } + } + + private bool _disposed; + + public bool IsDisposed + { + get => _disposed; + set => _disposed = value; + } + + /// + /// This is only if the under the hood is purely from the game. + /// + internal bool PurePointer { get; set; } = false; + + public DisposableMemory(IntPtr ptr) : base(ptr) + { + Instances++; + } + + ~DisposableMemory() + { + if (!PurePointer) + { + (this as IDisposableMemory).DisposeInternal(); + Instances--; + } + } + + public virtual void Dispose() + { + if (PurePointer) + return; + + // Dont call finalizer + GC.SuppressFinalize(this); + + (this as IDisposableMemory).DisposeInternal(); + Instances--; + } + + public virtual void ReleaseManaged() + { } + + public virtual void ReleaseUnmanaged() + { + MemAlloc.Free(Handle); + } + + /// + /// Recursively checks if the given type is (or contains) a + /// + /// + /// + internal static bool IsDisposableType(Type type) + { + /* This part will be only needed if we have a correct implementation for `NetworkedVector<>` or any class that has any `DisposableMemory` as generic. + * Until that, this would be overkill + if (type == DisposableType || DisposableType.IsAssignableFrom(type)) + return true; + + if (type.IsGenericType) + { + foreach (Type arg in type.GetGenericArguments()) + { + if (DisposableType.IsAssignableFrom(arg)) + { + return true; + } + + if (IsDisposableType(arg)) + { + return true; + } + } + } + + return type.BaseType != null && type.BaseType.Namespace!.StartsWith("CounterStrikeSharp") && IsDisposableType(type.BaseType); + */ + + return type == DisposableType || DisposableType.IsAssignableFrom(type); + } + + internal static void MarkAsPure(DisposableMemory disposable) + { + disposable.PurePointer = true; + + // we should not count these as they are not handled by us. + Instances--; + } + + /* + internal static void MarkCollectionAsPure(IEnumerable collection) where T: DisposableMemory + { + foreach (DisposableMemory disposable in collection) + { + MarkAsPure(disposable); + } + } + */ + + /* Span where T has pointer or reference: Only value types without pointers or references are supported. + internal static void MarkSpanAsPure(Span collection) + { + foreach (T instance in collection) + { + if (instance is DisposableMemory disposable) + { + MarkAsPure(disposable); + } + } + } + */ + + internal static void MarkAsPure(object? instance) + { + if (instance == null) + return; + + switch (instance) + { + case DisposableMemory disposable: + MarkAsPure(disposable); + break; + + /* since 'Networked vectors currently only support CHandle' lets stab ourselves in the back in the future + case NetworkedVector vectors: + MarkCollectionAsPure(vectors); + break; + + case NetworkedVector vector2ds: + MarkCollectionAsPure(vector2ds); + break; + + case NetworkedVector vector4ds: + MarkCollectionAsPure(vector4ds); + break; + + // 'Angle' is only referenced inside the 'Vector' class + case NetworkedVector angles: + MarkCollectionAsPure(angles); + break; + + case NetworkedVector quaternions: + MarkCollectionAsPure(quaternions); + break; + + case NetworkedVector matrixes: + MarkCollectionAsPure(matrixes); + break; + + default: throw new NotSupportedException($"'MarkAsPure': type '{instance.GetType().Name}' is not supported."); + */ + default: return; + } + } + } +} diff --git a/managed/CounterStrikeSharp.API/Modules/Memory/IDisposableMemory.cs b/managed/CounterStrikeSharp.API/Modules/Memory/IDisposableMemory.cs new file mode 100644 index 000000000..693ac8872 --- /dev/null +++ b/managed/CounterStrikeSharp.API/Modules/Memory/IDisposableMemory.cs @@ -0,0 +1,64 @@ +/* + * This file is part of CounterStrikeSharp. + * CounterStrikeSharp is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * CounterStrikeSharp is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CounterStrikeSharp. If not, see . * + */ + +namespace CounterStrikeSharp.API.Modules.Memory +{ + /// + /// Provides mechanism to release managed and unmanaged memory. + /// Used for garbage collecting. + /// + public interface IDisposableMemory : IDisposable + { + /// + /// Is the resource disposed? + /// The class should implement a disposed field. + /// + public bool IsDisposed { get; set; } + + /// + /// Clean up unmanaged resources inside this. + /// This is always called on the main thread, its safe to use natives. + /// USED INTERNALLY BY + /// + public void ReleaseUnmanaged(); + + /// + /// Acts as the normal managed 'Dispose'. + /// USED INTERNALLY BY + /// + public void ReleaseManaged(); + + /// + /// Dispose internally + /// + internal void DisposeInternal() + { + if (!IsDisposed) + { + // Free managed resources + ReleaseManaged(); + + Server.NextWorldUpdate(() => + { + // Free unmanaged resources on the main thread + ReleaseUnmanaged(); + }); + + IsDisposed = true; + } + } + } +} diff --git a/managed/CounterStrikeSharp.API/Modules/Memory/MemAlloc.cs b/managed/CounterStrikeSharp.API/Modules/Memory/MemAlloc.cs new file mode 100644 index 000000000..b8804bcb7 --- /dev/null +++ b/managed/CounterStrikeSharp.API/Modules/Memory/MemAlloc.cs @@ -0,0 +1,70 @@ +/* + * This file is part of CounterStrikeSharp. + * CounterStrikeSharp is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * CounterStrikeSharp is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CounterStrikeSharp. If not, see . * + */ + +namespace CounterStrikeSharp.API.Modules.Memory +{ + public static class MemAlloc + { + /// + /// Indirect allocation using 'MemAlloc' + /// + /// + /// + public static nint Allocate(int size) + { + return NativeAPI.MemAllocAllocate(size); + } + + /// + /// Indirect allocation using 'MemAlloc' + /// + /// + /// + /// + public static nint AllocateAligned(int size, int align) + { + return NativeAPI.MemAllocAllocateAligned(size, align); + } + + public static nint ReallocateAligned(nint pointer, int size, int align) + { + return NativeAPI.MemAllocReallocateAligned(pointer, size, align); + } + + public static int GetSizeAligned(nint pointer) + { + return NativeAPI.MemAllocGetSizeAligned(pointer); + } + + /// + /// Release pointer using 'MemAlloc' + /// + /// + public static void Free(nint ptr) + { + NativeAPI.MemAllocFreePointer(ptr); + } + + /// + /// Release pointer using 'MemAlloc' + /// + /// + public static void FreeAligned(nint ptr) + { + NativeAPI.MemAllocFreePointerAligned(ptr); + } + } +} diff --git a/managed/CounterStrikeSharp.API/Modules/Memory/Schema.cs b/managed/CounterStrikeSharp.API/Modules/Memory/Schema.cs index f754b67a8..9b0f4d854 100644 --- a/managed/CounterStrikeSharp.API/Modules/Memory/Schema.cs +++ b/managed/CounterStrikeSharp.API/Modules/Memory/Schema.cs @@ -100,7 +100,14 @@ public static T GetDeclaredClass(IntPtr pointer, string className, string mem { if (pointer == IntPtr.Zero) throw new ArgumentNullException(nameof(pointer), "Schema target points to null."); - return (T)Activator.CreateInstance(typeof(T), pointer + GetSchemaOffset(className, memberName)); + object? instance = Activator.CreateInstance(typeof(T), pointer + GetSchemaOffset(className, memberName)); + + if (DisposableMemory.IsDisposableType(typeof(T))) + { + DisposableMemory.MarkAsPure(instance); + } + + return (T)instance; } public static unsafe ref T GetRef(IntPtr pointer, string className, string memberName) @@ -139,6 +146,9 @@ public static unsafe Span GetFixedArray(IntPtr pointer, string className, if (pointer == IntPtr.Zero) throw new ArgumentNullException(nameof(pointer), "Schema target points to null."); Span span = new((void*)(pointer + GetSchemaOffset(className, memberName)), count); + + // TODO: once we get a correct implementation for this method check for `DisposableMemory` instances and mark them as pure + return span; } @@ -214,4 +224,4 @@ public static void SetCustomMarshalledType(IntPtr pointer, string className, throw new NotSupportedException(); } } -} \ No newline at end of file +} diff --git a/managed/CounterStrikeSharp.API/Modules/Utils/Angle.cs b/managed/CounterStrikeSharp.API/Modules/Utils/Angle.cs index 69eb7ce72..2993075a6 100644 --- a/managed/CounterStrikeSharp.API/Modules/Utils/Angle.cs +++ b/managed/CounterStrikeSharp.API/Modules/Utils/Angle.cs @@ -14,6 +14,8 @@ * along with CounterStrikeSharp. If not, see . * */ +using CounterStrikeSharp.API.Modules.Memory; + namespace CounterStrikeSharp.API.Modules.Utils { /// @@ -25,10 +27,10 @@ namespace CounterStrikeSharp.API.Modules.Utils /// Zroll +right/-left /// /// - public class Angle : NativeObject + public class Angle : DisposableMemory { public static readonly Angle Zero = new(); - + public Angle(IntPtr pointer) : base(pointer) { } @@ -368,10 +370,10 @@ public override bool Equals(object obj) protected override void OnDispose() { }*/ - + public override string ToString() { return $"{X:n2} {Y:n2} {Z:n2}"; } } -} \ No newline at end of file +} diff --git a/managed/CounterStrikeSharp.API/Modules/Utils/QAngle.cs b/managed/CounterStrikeSharp.API/Modules/Utils/QAngle.cs index e3a022083..8afb04506 100644 --- a/managed/CounterStrikeSharp.API/Modules/Utils/QAngle.cs +++ b/managed/CounterStrikeSharp.API/Modules/Utils/QAngle.cs @@ -16,12 +16,14 @@ using System.Runtime.CompilerServices; +using CounterStrikeSharp.API.Modules.Memory; + namespace CounterStrikeSharp.API.Modules.Utils { - public class QAngle : NativeObject + public class QAngle : DisposableMemory { public static readonly QAngle Zero = new(); - + public QAngle(IntPtr pointer) : base(pointer) { } @@ -36,10 +38,10 @@ public QAngle(float? x = null, float? y = null, float? z = null) : this(NativeAP public unsafe ref float X => ref Unsafe.Add(ref *(float*)Handle.ToPointer(), 0); public unsafe ref float Y => ref Unsafe.Add(ref *(float*)Handle, 1); public unsafe ref float Z => ref Unsafe.Add(ref *(float*)Handle, 2); - + public override string ToString() { return $"{X:n2} {Y:n2} {Z:n2}"; } } -} \ No newline at end of file +} diff --git a/managed/CounterStrikeSharp.API/Modules/Utils/Quaternion.cs b/managed/CounterStrikeSharp.API/Modules/Utils/Quaternion.cs index 84622c298..608dba033 100644 --- a/managed/CounterStrikeSharp.API/Modules/Utils/Quaternion.cs +++ b/managed/CounterStrikeSharp.API/Modules/Utils/Quaternion.cs @@ -14,13 +14,12 @@ * along with CounterStrikeSharp. If not, see . * */ -using System; using System.Runtime.CompilerServices; -using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Memory; namespace CounterStrikeSharp.API.Modules.Utils { - public class Quaternion : NativeObject + public class Quaternion : DisposableMemory { public Quaternion(IntPtr pointer) : base(pointer) { @@ -28,4 +27,4 @@ public Quaternion(IntPtr pointer) : base(pointer) public unsafe ref float Value => ref Unsafe.Add(ref *(float*)Handle.ToPointer(), 0); } -} \ No newline at end of file +} diff --git a/managed/CounterStrikeSharp.API/Modules/Utils/Vector.cs b/managed/CounterStrikeSharp.API/Modules/Utils/Vector.cs index 7dfe83990..e4382797a 100644 --- a/managed/CounterStrikeSharp.API/Modules/Utils/Vector.cs +++ b/managed/CounterStrikeSharp.API/Modules/Utils/Vector.cs @@ -1,4 +1,4 @@ -/* +/* * This file is part of CounterStrikeSharp. * CounterStrikeSharp is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,6 +17,8 @@ using System.Numerics; using System.Runtime.CompilerServices; +using CounterStrikeSharp.API.Modules.Memory; + namespace CounterStrikeSharp.API.Modules.Utils { /// @@ -28,7 +30,7 @@ namespace CounterStrikeSharp.API.Modules.Utils /// Z+up/-down /// /// - public class Vector : NativeObject, + public class Vector : DisposableMemory, IAdditionOperators, ISubtractionOperators, IMultiplyOperators, diff --git a/managed/CounterStrikeSharp.API/Modules/Utils/Vector2D.cs b/managed/CounterStrikeSharp.API/Modules/Utils/Vector2D.cs index c6047451c..817b94ecd 100644 --- a/managed/CounterStrikeSharp.API/Modules/Utils/Vector2D.cs +++ b/managed/CounterStrikeSharp.API/Modules/Utils/Vector2D.cs @@ -14,13 +14,12 @@ * along with CounterStrikeSharp. If not, see . * */ -using System; using System.Runtime.CompilerServices; -using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Memory; namespace CounterStrikeSharp.API.Modules.Utils { - public class Vector2D : NativeObject + public class Vector2D : DisposableMemory { public Vector2D(IntPtr pointer) : base(pointer) { @@ -29,4 +28,4 @@ public Vector2D(IntPtr pointer) : base(pointer) public unsafe ref float X => ref Unsafe.Add(ref *(float*)Handle.ToPointer(), 0); public unsafe ref float Y => ref Unsafe.Add(ref *(float*)Handle, 1); } -} \ No newline at end of file +} diff --git a/managed/CounterStrikeSharp.API/Modules/Utils/Vector4D.cs b/managed/CounterStrikeSharp.API/Modules/Utils/Vector4D.cs index 758719773..abacf07e9 100644 --- a/managed/CounterStrikeSharp.API/Modules/Utils/Vector4D.cs +++ b/managed/CounterStrikeSharp.API/Modules/Utils/Vector4D.cs @@ -14,13 +14,12 @@ * along with CounterStrikeSharp. If not, see . * */ -using System; using System.Runtime.CompilerServices; -using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Memory; namespace CounterStrikeSharp.API.Modules.Utils { - public class Vector4D : NativeObject + public class Vector4D : DisposableMemory { public Vector4D(IntPtr pointer) : base(pointer) { @@ -31,4 +30,4 @@ public Vector4D(IntPtr pointer) : base(pointer) public unsafe ref float Z => ref Unsafe.Add(ref *(float*)Handle, 2); public unsafe ref float W => ref Unsafe.Add(ref *(float*)Handle, 3); } -} \ No newline at end of file +} diff --git a/managed/CounterStrikeSharp.API/Modules/Utils/matrix3x4_t.cs b/managed/CounterStrikeSharp.API/Modules/Utils/matrix3x4_t.cs index 9b40c5be3..5804e25b9 100644 --- a/managed/CounterStrikeSharp.API/Modules/Utils/matrix3x4_t.cs +++ b/managed/CounterStrikeSharp.API/Modules/Utils/matrix3x4_t.cs @@ -1,13 +1,30 @@ -using System; -using System.Runtime.CompilerServices; +/* + * This file is part of CounterStrikeSharp. + * CounterStrikeSharp is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * CounterStrikeSharp is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CounterStrikeSharp. If not, see . * + */ + +using System.Runtime.CompilerServices; + +using CounterStrikeSharp.API.Modules.Memory; namespace CounterStrikeSharp.API.Modules.Utils; -public partial class matrix3x4_t : NativeObject +public partial class matrix3x4_t : DisposableMemory { public matrix3x4_t(IntPtr pointer) : base(pointer) { } public unsafe ref float this[int row, int column] => ref Unsafe.Add(ref *(float*)Handle, row * 4 + column); -} \ No newline at end of file +} diff --git a/src/scripting/natives/natives_memory.cpp b/src/scripting/natives/natives_memory.cpp index a31d0957b..b95be679b 100644 --- a/src/scripting/natives/natives_memory.cpp +++ b/src/scripting/natives/natives_memory.cpp @@ -159,6 +159,72 @@ void RemoveAllNetworkVectorElements(ScriptContext& script_context) vec->RemoveAll(); } +typedef void (*Release_Func)(void* ptr); + +static void ReleaseHelper(Release_Func func, ScriptContext& script_context, void* ptr) +{ + if (!ptr) + { + script_context.ThrowNativeError("Null pointer reference: %p", ptr); + return; + } + + // TODO: replace with CSSHARP_CORE_TRACE + CSSHARP_CORE_INFO("Releasing pointer: {}", ptr); + +#ifdef _WIN32 + _try + { + func(ptr); + } + _except (EXCEPTION_ACCESS_VIOLATION) + { + script_context.ThrowNativeError("Invalid pointer reference: %p (EXCEPTION_ACCESS_VIOLATION)", ptr); + } +#else + func(ptr); +#endif +} + +void Native_MemAlloc_FreePointer(ScriptContext& script_context) +{ + void* ptr = script_context.GetArgument(0); + ReleaseHelper(MemAlloc_Free, script_context, ptr); +} + +void Native_MemAlloc_FreePointerAligned(ScriptContext& script_context) +{ + void* ptr = script_context.GetArgument(0); + ReleaseHelper(MemAlloc_FreeAligned, script_context, ptr); +} + +void* Native_MemAlloc_Allocate(ScriptContext& script_context) +{ + size_t size = script_context.GetArgument(0); + return MemAlloc_Alloc(size); +} + +void* Native_MemAlloc_AllocateAligned(ScriptContext& script_context) +{ + size_t size = script_context.GetArgument(0); + size_t align = script_context.GetArgument(1); + return MemAlloc_AllocAlignedUnattributed(size, align); +} + +void* Native_MemAlloc_ReallocateAligned(ScriptContext& script_context) +{ + void* ptr = script_context.GetArgument(0); + size_t size = script_context.GetArgument(1); + size_t align = script_context.GetArgument(2); + return MemAlloc_ReallocAligned(ptr, size, align); +} + +size_t Native_MemAlloc_GetSizeAligned(ScriptContext& script_context) +{ + void* ptr = script_context.GetArgument(0); + return MemAlloc_GetSizeAligned(ptr); +} + REGISTER_NATIVES(memory, { ScriptEngine::RegisterNativeHandler("CREATE_VIRTUAL_FUNCTION", CreateVirtualFunction); ScriptEngine::RegisterNativeHandler("CREATE_VIRTUAL_FUNCTION_BY_SIGNATURE", CreateVirtualFunctionBySignature); @@ -169,5 +235,12 @@ REGISTER_NATIVES(memory, { ScriptEngine::RegisterNativeHandler("GET_NETWORK_VECTOR_SIZE", GetNetworkVectorSize); ScriptEngine::RegisterNativeHandler("GET_NETWORK_VECTOR_ELEMENT_AT", GetNetworkVectorElementAt); ScriptEngine::RegisterNativeHandler("REMOVE_ALL_NETWORK_VECTOR_ELEMENTS", RemoveAllNetworkVectorElements); + + ScriptEngine::RegisterNativeHandler("MEM_ALLOC_ALLOCATE", Native_MemAlloc_Allocate); + ScriptEngine::RegisterNativeHandler("MEM_ALLOC_ALLOCATE_ALIGNED", Native_MemAlloc_Allocate); + ScriptEngine::RegisterNativeHandler("MEM_ALLOC_REALLOCATE_ALIGNED", Native_MemAlloc_ReallocateAligned); + ScriptEngine::RegisterNativeHandler("MEM_ALLOC_GETSIZE_ALIGNED", Native_MemAlloc_GetSizeAligned); + ScriptEngine::RegisterNativeHandler("MEM_ALLOC_FREE_POINTER", Native_MemAlloc_FreePointer); + ScriptEngine::RegisterNativeHandler("MEM_ALLOC_FREE_POINTER_ALIGNED", Native_MemAlloc_FreePointerAligned); }) } // namespace counterstrikesharp diff --git a/src/scripting/natives/natives_memory.yaml b/src/scripting/natives/natives_memory.yaml index 7d8c9610b..46bf3a4ab 100644 --- a/src/scripting/natives/natives_memory.yaml +++ b/src/scripting/natives/natives_memory.yaml @@ -6,4 +6,10 @@ EXECUTE_VIRTUAL_FUNCTION: function:pointer,arguments:object[] -> any FIND_SIGNATURE: modulePath:string, signature:string -> pointer GET_NETWORK_VECTOR_SIZE: vec:pointer -> int GET_NETWORK_VECTOR_ELEMENT_AT: vec:pointer, index:int -> pointer -REMOVE_ALL_NETWORK_VECTOR_ELEMENTS: vec:pointer -> void \ No newline at end of file +REMOVE_ALL_NETWORK_VECTOR_ELEMENTS: vec:pointer -> void +MEM_ALLOC_ALLOCATE: size:int -> pointer +MEM_ALLOC_ALLOCATE_ALIGNED: size:int, align:int -> pointer +MEM_ALLOC_REALLOCATE_ALIGNED: pointer:pointer, size:int, align:int -> pointer +MEM_ALLOC_GET_SIZE_ALIGNED: pointer:pointer -> int +MEM_ALLOC_FREE_POINTER: pointer:pointer -> void +MEM_ALLOC_FREE_POINTER_ALIGNED: pointer:pointer -> void \ No newline at end of file diff --git a/src/scripting/natives/natives_vector.cpp b/src/scripting/natives/natives_vector.cpp index 4fca493a8..f0710ab4c 100644 --- a/src/scripting/natives/natives_vector.cpp +++ b/src/scripting/natives/natives_vector.cpp @@ -35,24 +35,14 @@ CREATE_SETTER_FUNCTION(Vector, float, X, Vector*, obj->x = value); CREATE_SETTER_FUNCTION(Vector, float, Y, Vector*, obj->y = value); CREATE_SETTER_FUNCTION(Vector, float, Z, Vector*, obj->z = value); -// TODO: These need to be cleared out somehow -std::vector managed_vectors; - Vector* VectorNew(ScriptContext& script_context) { - auto vec = new Vector(); - managed_vectors.push_back(vec); - return vec; + return new Vector(); } -// TODO: These need to be cleared out somehow -std::vector managed_angles; - QAngle* AngleNew(ScriptContext& script_context) { - auto ang = new QAngle(); - managed_angles.push_back(ang); - return ang; + return new QAngle(); } void NativeVectorAngles(ScriptContext& script_context)