diff --git a/managed/CounterStrikeSharp.API/BaseNative.cs b/managed/CounterStrikeSharp.API/BaseNative.cs index cab9896a0..f34e368e0 100644 --- a/managed/CounterStrikeSharp.API/BaseNative.cs +++ b/managed/CounterStrikeSharp.API/BaseNative.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 @@ -15,22 +15,47 @@ */ using System; +using System.Text; using System.Collections.Generic; using System.Reflection.Metadata; using System.Runtime.InteropServices; -using System.Text; namespace CounterStrikeSharp.API { public abstract class NativeObject { - public IntPtr Handle { get; internal set; } + private IntPtr _handle; + + public IntPtr Handle + { + get + { + if (_handle == IntPtr.Zero) + { + EnsureNativeHandle(); + } + + return _handle; + } + internal set => _handle = value; + } + + internal IntPtr RawHandle => _handle; protected NativeObject(IntPtr pointer) { - Handle = pointer; + _handle = pointer; } - + + protected void SetHandle(IntPtr pointer) + { + _handle = pointer; + } + + protected virtual void EnsureNativeHandle() + { + } + /// /// Returns a new instance of the specified type using the pointer from the passed in object. /// diff --git a/managed/CounterStrikeSharp.API/Core/Model/CBaseEntity.cs b/managed/CounterStrikeSharp.API/Core/Model/CBaseEntity.cs index 6a10eb3f9..52cf5410e 100644 --- a/managed/CounterStrikeSharp.API/Core/Model/CBaseEntity.cs +++ b/managed/CounterStrikeSharp.API/Core/Model/CBaseEntity.cs @@ -1,133 +1,123 @@ -using System; -using System.Numerics; -using System.Runtime.InteropServices; -using CounterStrikeSharp.API.Modules.Memory; -using CounterStrikeSharp.API.Modules.Utils; -using Vector = CounterStrikeSharp.API.Modules.Utils.Vector; - -namespace CounterStrikeSharp.API.Core; - -public partial class CBaseEntity -{ - /// Entity is not valid - /// At least one parameter must be specified - public void Teleport(Vector? position = null, QAngle? angles = null, Vector? velocity = null) - { - Guard.IsValidEntity(this); - - if (position == null && angles == null && velocity == null) - throw new ArgumentException("At least one parameter must be specified"); - - nint _position = position?.Handle ?? 0; - nint _angles = angles?.Handle ?? 0; - nint _velocity = velocity?.Handle ?? 0; - nint _handle = Handle; - - VirtualFunction.CreateVoid(_handle, GameData.GetOffset("CBaseEntity_Teleport"))(_handle, _position, - _angles, _velocity); - } - - /// - /// Teleports the entity to the specified position, angles, and velocity using Vector3 parameters. - /// This overload is optimized for memory efficiency by directly working with a Vector3 struct. - /// - /// Entity is not valid - /// At least one parameter must be specified - public void Teleport(Vector3? position = null, Vector3? angles = null, Vector3? velocity = null) - { - Guard.IsValidEntity(this); - - if (position == null && angles == null && velocity == null) - throw new ArgumentException("At least one parameter must be specified"); - - unsafe - { - void* positionPtr = null, anglePtr = null, velocityPtr = null; - - if (position.HasValue) - { - var pos = position.Value; - positionPtr = &pos; - } - - if (angles.HasValue) - { - var ang = angles.Value; - anglePtr = ∠ - } - - if (velocity.HasValue) - { - var vel = velocity.Value; - velocityPtr = &vel; - } - - VirtualFunction.CreateVoid(Handle, GameData.GetOffset("CBaseEntity_Teleport"))(Handle, - (nint)positionPtr, - (nint)anglePtr, (nint)velocityPtr); - } - } - - /// Entity is not valid - public void DispatchSpawn(CEntityKeyValues? keyValues) - { - Guard.IsValidEntity(this); - - NativeAPI.DispatchSpawn(Handle, keyValues?.Handle ?? IntPtr.Zero); - } - - public void DispatchSpawn() - { - Guard.IsValidEntity(this); - NativeAPI.DispatchSpawn(Handle, IntPtr.Zero); - } - - /// - /// Shorthand for accessing an entity's CBodyComponent?.SceneNode?.AbsOrigin; - /// - public Vector? AbsOrigin => CBodyComponent?.SceneNode?.AbsOrigin; - - /// - /// Shorthand for accessing an entity's CBodyComponent?.SceneNode?.AbsRotation; - /// - /// Entity is not valid - public QAngle? AbsRotation => CBodyComponent?.SceneNode?.AbsRotation; - - public T? GetVData() where T : CEntitySubclassVDataBase - { - Guard.IsValidEntity(this); - - return (T)Activator.CreateInstance(typeof(T), Marshal.ReadIntPtr(SubclassID.Handle + 4)); - } - - /// - /// Emit a soundevent to all players. - /// - /// The name of the soundevent to emit. - /// The recipients of the soundevent. - /// The volume of the soundevent. - /// The pitch of the soundevent. - /// The sound event guid. - public uint EmitSound(string soundEventName, RecipientFilter? recipients = null, float volume = 1f, float pitch = 0) - { - Guard.IsValidEntity(this); - - if (recipients == null) - { - recipients = new RecipientFilter(); - recipients.AddAllPlayers(); - } - - return NativeAPI.EmitSoundFilter(recipients.GetRecipientMask(), this.Index, soundEventName, volume, pitch); - } - - /// - /// Returns true if the entity is a player pawn. - /// - public bool IsPlayerPawn() - { - Guard.IsValidEntity(this); - - return VirtualFunction.Create(Handle, GameData.GetOffset("CBaseEntity_IsPlayerPawn"))(Handle); - } -} +using System; +using System.Numerics; +using System.Runtime.InteropServices; +using CounterStrikeSharp.API.Modules.Memory; +using CounterStrikeSharp.API.Modules.Utils; +using Vector = CounterStrikeSharp.API.Modules.Utils.Vector; + +namespace CounterStrikeSharp.API.Core; + +public partial class CBaseEntity +{ + /// Entity is not valid + /// At least one parameter must be specified + public void Teleport(Vector? position = null, QAngle? angles = null, Vector? velocity = null) + { + Teleport(position == null ? null : (Vector3)position, angles == null ? null : (Vector3)angles, + velocity == null ? null : (Vector3)velocity); + } + + /// + /// Teleports the entity to the specified position, angles, and velocity using Vector3 parameters. + /// This overload is optimized for memory efficiency by directly working with a Vector3 struct. + /// + /// Entity is not valid + /// At least one parameter must be specified + public void Teleport(Vector3? position = null, Vector3? angles = null, Vector3? velocity = null) + { + Guard.IsValidEntity(this); + + if (position == null && angles == null && velocity == null) + throw new ArgumentException("At least one parameter must be specified"); + + unsafe + { + void* positionPtr = null, anglePtr = null, velocityPtr = null; + + if (position.HasValue) + { + var pos = position.Value; + positionPtr = &pos; + } + + if (angles.HasValue) + { + var ang = angles.Value; + anglePtr = ∠ + } + + if (velocity.HasValue) + { + var vel = velocity.Value; + velocityPtr = &vel; + } + + VirtualFunction.CreateVoid(Handle, GameData.GetOffset("CBaseEntity_Teleport"))(Handle, + (nint)positionPtr, + (nint)anglePtr, (nint)velocityPtr); + } + } + + /// Entity is not valid + public void DispatchSpawn(CEntityKeyValues? keyValues) + { + Guard.IsValidEntity(this); + + NativeAPI.DispatchSpawn(Handle, keyValues?.Handle ?? IntPtr.Zero); + } + + public void DispatchSpawn() + { + Guard.IsValidEntity(this); + NativeAPI.DispatchSpawn(Handle, IntPtr.Zero); + } + + /// + /// Shorthand for accessing an entity's CBodyComponent?.SceneNode?.AbsOrigin; + /// + public Vector? AbsOrigin => CBodyComponent?.SceneNode?.AbsOrigin; + + /// + /// Shorthand for accessing an entity's CBodyComponent?.SceneNode?.AbsRotation; + /// + /// Entity is not valid + public QAngle? AbsRotation => CBodyComponent?.SceneNode?.AbsRotation; + + public T? GetVData() where T : CEntitySubclassVDataBase + { + Guard.IsValidEntity(this); + + return (T)Activator.CreateInstance(typeof(T), Marshal.ReadIntPtr(SubclassID.Handle + 4)); + } + + /// + /// Emit a soundevent to all players. + /// + /// The name of the soundevent to emit. + /// The recipients of the soundevent. + /// The volume of the soundevent. + /// The pitch of the soundevent. + /// The sound event guid. + public uint EmitSound(string soundEventName, RecipientFilter? recipients = null, float volume = 1f, float pitch = 0) + { + Guard.IsValidEntity(this); + + if (recipients == null) + { + recipients = new RecipientFilter(); + recipients.AddAllPlayers(); + } + + return NativeAPI.EmitSoundFilter(recipients.GetRecipientMask(), this.Index, soundEventName, volume, pitch); + } + + /// + /// Returns true if the entity is a player pawn. + /// + public bool IsPlayerPawn() + { + Guard.IsValidEntity(this); + + return VirtualFunction.Create(Handle, GameData.GetOffset("CBaseEntity_IsPlayerPawn"))(Handle); + } +} \ 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..344cbf9fa 100644 --- a/managed/CounterStrikeSharp.API/Modules/Utils/Angle.cs +++ b/managed/CounterStrikeSharp.API/Modules/Utils/Angle.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 @@ -14,6 +14,11 @@ * along with CounterStrikeSharp. If not, see . * */ + +using System.Threading; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; + namespace CounterStrikeSharp.API.Modules.Utils { /// @@ -28,7 +33,33 @@ namespace CounterStrikeSharp.API.Modules.Utils public class Angle : NativeObject { public static readonly Angle Zero = new(); - + + private float _x; + private float _y; + private float _z; + private IntPtr _ownedHandle; + + private unsafe ref float GetElementRef(int index) + { + var handle = RawHandle; + if (handle != IntPtr.Zero) + { + return ref Unsafe.Add(ref *(float*)handle, index); + } + + switch (index) + { + case 0: + return ref _x; + case 1: + return ref _y; + case 2: + return ref _z; + default: + throw new ArgumentOutOfRangeException(nameof(index)); + } + } + public Angle(IntPtr pointer) : base(pointer) { } @@ -39,11 +70,46 @@ public Angle(IntPtr pointer) : base(pointer) /// Pitch /// Yaw /// Roll - public Angle(float? x = null, float? y = null, float? z = null) : this(NativeAPI.AngleNew()) + public Angle(float? x = null, float? y = null, float? z = null) : base(IntPtr.Zero) + { + _x = x ?? 0; + _y = y ?? 0; + _z = z ?? 0; + } + + protected override void EnsureNativeHandle() { - this.X = x ?? 0; - this.Y = y ?? 0; - this.Z = z ?? 0; + if (RawHandle != IntPtr.Zero) + { + return; + } + + if (_ownedHandle != IntPtr.Zero) + { + SetHandle(_ownedHandle); + return; + } + + var allocated = Marshal.AllocHGlobal(sizeof(float) * 3); + + unsafe + { + var buffer = (float*)allocated; + buffer[0] = _x; + buffer[1] = _y; + buffer[2] = _z; + } + + var existing = Interlocked.CompareExchange(ref _ownedHandle, allocated, IntPtr.Zero); + if (existing != IntPtr.Zero) + { + Marshal.FreeHGlobal(allocated); + SetHandle(existing); + return; + } + + NativeHandleTracker.Track(this, allocated); + SetHandle(allocated); } #region Accessors @@ -83,7 +149,7 @@ public float P get => X; set => X = value; } - + /// /// Roll of angle /// @@ -100,8 +166,8 @@ public float R /// public float X { - set => NativeAPI.VectorSetX(Handle, value); - get => NativeAPI.VectorGetX(Handle); + get => GetElementRef(0); + set => GetElementRef(0) = value; } /// @@ -109,8 +175,8 @@ public float X /// public float Y { - set => NativeAPI.VectorSetY(Handle, value); - get => NativeAPI.VectorGetY(Handle); + get => GetElementRef(1); + set => GetElementRef(1) = value; } /// @@ -118,8 +184,8 @@ public float Y /// public float Z { - set => NativeAPI.VectorSetZ(Handle, value); - get => NativeAPI.VectorGetZ(Handle); + get => GetElementRef(2); + set => GetElementRef(2) = value; } /* @@ -308,7 +374,7 @@ public float this[int i] return this.X; case 1: return this.Y; - case 2: + case 2: return this.Z; } @@ -323,7 +389,7 @@ public float this[int i] case 1: this.Y = value; break; - case 2: + case 2: this.Z = value; break; } @@ -368,10 +434,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 99ab3dfdc..ad0465768 100644 --- a/managed/CounterStrikeSharp.API/Modules/Utils/QAngle.cs +++ b/managed/CounterStrikeSharp.API/Modules/Utils/QAngle.cs @@ -15,6 +15,8 @@ */ using System.Numerics; +using System.Threading; +using System.Runtime.InteropServices; using System.Runtime.CompilerServices; namespace CounterStrikeSharp.API.Modules.Utils @@ -27,16 +29,77 @@ public QAngle(IntPtr pointer) : base(pointer) { } - public QAngle(float? x = null, float? y = null, float? z = null) : this(NativeAPI.AngleNew()) + private float _x; + private float _y; + private float _z; + private IntPtr _ownedHandle; + + public QAngle(float? x = null, float? y = null, float? z = null) : base(IntPtr.Zero) { - this.X = x ?? 0; - this.Y = y ?? 0; - this.Z = z ?? 0; + _x = x ?? 0; + _y = y ?? 0; + _z = z ?? 0; } - 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); + protected override void EnsureNativeHandle() + { + if (RawHandle != IntPtr.Zero) + { + return; + } + + if (_ownedHandle != IntPtr.Zero) + { + SetHandle(_ownedHandle); + return; + } + + var allocated = Marshal.AllocHGlobal(sizeof(float) * 3); + + unsafe + { + var buffer = (float*)allocated; + buffer[0] = _x; + buffer[1] = _y; + buffer[2] = _z; + } + + var existing = Interlocked.CompareExchange(ref _ownedHandle, allocated, IntPtr.Zero); + if (existing != IntPtr.Zero) + { + Marshal.FreeHGlobal(allocated); + SetHandle(existing); + return; + } + + NativeHandleTracker.Track(this, allocated); + SetHandle(allocated); + } + + public unsafe ref float X => ref GetElementRef(0); + public unsafe ref float Y => ref GetElementRef(1); + public unsafe ref float Z => ref GetElementRef(2); + + private unsafe ref float GetElementRef(int index) + { + var handle = RawHandle; + if (handle != IntPtr.Zero) + { + return ref Unsafe.Add(ref *(float*)handle, index); + } + + switch (index) + { + case 0: + return ref _x; + case 1: + return ref _y; + case 2: + return ref _z; + default: + throw new ArgumentOutOfRangeException(nameof(index)); + } + } public override string ToString() { @@ -45,15 +108,12 @@ public override string ToString() public static explicit operator Vector3(QAngle q) { - unsafe + if (q is null) { - if (q is null) - { - throw new ArgumentNullException(nameof(q), "Input QAngle cannot be null."); - } - - return new Vector3(new ReadOnlySpan(q.Handle.ToPointer(), 3)); + throw new ArgumentNullException(nameof(q), "Input QAngle cannot be null."); } + + return new Vector3(q.X, q.Y, q.Z); } } -} \ 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 0a1497831..421e1e05d 100644 --- a/managed/CounterStrikeSharp.API/Modules/Utils/Quaternion.cs +++ b/managed/CounterStrikeSharp.API/Modules/Utils/Quaternion.cs @@ -14,33 +14,100 @@ * along with CounterStrikeSharp. If not, see . * */ +using System.Threading; +using System.Runtime.InteropServices; using System.Runtime.CompilerServices; namespace CounterStrikeSharp.API.Modules.Utils { public class Quaternion : NativeObject { + private float _x; + private float _y; + private float _z; + private float _w; + private IntPtr _ownedHandle; + // Not sure who made this one? maybe mark it as 'obsolete' to don't break existing plugins but warn them? - public unsafe ref float Value => ref Unsafe.Add(ref *(float*)Handle.ToPointer(), 0); + public unsafe ref float Value => ref GetElementRef(0); + + public unsafe ref float X => ref GetElementRef(0); - public unsafe ref float X => ref Unsafe.Add(ref *(float*)Handle.ToPointer(), 0); + public unsafe ref float Y => ref GetElementRef(1); - public unsafe ref float Y => ref Unsafe.Add(ref *(float*)Handle.ToPointer(), 1); + public unsafe ref float Z => ref GetElementRef(2); - public unsafe ref float Z => ref Unsafe.Add(ref *(float*)Handle.ToPointer(), 2); + public unsafe ref float W => ref GetElementRef(3); - public unsafe ref float W => ref Unsafe.Add(ref *(float*)Handle.ToPointer(), 3); + private unsafe ref float GetElementRef(int index) + { + var handle = RawHandle; + if (handle != IntPtr.Zero) + { + return ref Unsafe.Add(ref *(float*)handle, index); + } + + switch (index) + { + case 0: + return ref _x; + case 1: + return ref _y; + case 2: + return ref _z; + case 3: + return ref _w; + default: + throw new ArgumentOutOfRangeException(nameof(index)); + } + } public Quaternion(IntPtr pointer) : base(pointer) { } - public Quaternion(float? x = null, float? y = null, float? z = null, float? w = null) : this(NativeAPI.QuaternionNew()) + public Quaternion(float? x = null, float? y = null, float? z = null, float? w = null) : base(IntPtr.Zero) { - this.X = x ?? 0; - this.Y = y ?? 0; - this.Z = z ?? 0; - this.W = w ?? 0; + _x = x ?? 0; + _y = y ?? 0; + _z = z ?? 0; + _w = w ?? 0; + } + + protected override void EnsureNativeHandle() + { + if (RawHandle != IntPtr.Zero) + { + return; + } + + if (_ownedHandle != IntPtr.Zero) + { + SetHandle(_ownedHandle); + return; + } + + var allocated = Marshal.AllocHGlobal(sizeof(float) * 4); + + unsafe + { + var buffer = (float*)allocated; + buffer[0] = _x; + buffer[1] = _y; + buffer[2] = _z; + buffer[3] = _w; + } + + var existing = Interlocked.CompareExchange(ref _ownedHandle, allocated, IntPtr.Zero); + if (existing != IntPtr.Zero) + { + Marshal.FreeHGlobal(allocated); + SetHandle(existing); + return; + } + + NativeHandleTracker.Track(this, allocated); + SetHandle(allocated); } public override string ToString() @@ -48,4 +115,4 @@ public override string ToString() return $"{X:n2} {Y:n2} {Z:n2} {W:n2}"; } } -} \ 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 e802ca390..dba801ad7 100644 --- a/managed/CounterStrikeSharp.API/Modules/Utils/Vector.cs +++ b/managed/CounterStrikeSharp.API/Modules/Utils/Vector.cs @@ -15,6 +15,8 @@ */ using System.Numerics; +using System.Threading; +using System.Runtime.InteropServices; using System.Runtime.CompilerServices; namespace CounterStrikeSharp.API.Modules.Utils @@ -28,11 +30,7 @@ namespace CounterStrikeSharp.API.Modules.Utils /// Z+up/-down /// /// - public class Vector : NativeObject, - IAdditionOperators, - ISubtractionOperators, - IMultiplyOperators, - IDivisionOperators + public class Vector : NativeObject, IAdditionOperators, ISubtractionOperators, IMultiplyOperators, IDivisionOperators { public static readonly Vector Zero = new(); @@ -40,16 +38,77 @@ public Vector(IntPtr pointer) : base(pointer) { } - public Vector(float? x = null, float? y = null, float? z = null) : this(NativeAPI.VectorNew()) + private float _x; + private float _y; + private float _z; + private IntPtr _ownedHandle; + + public Vector(float? x = null, float? y = null, float? z = null) : base(IntPtr.Zero) + { + _x = x ?? 0; + _y = y ?? 0; + _z = z ?? 0; + } + + protected override void EnsureNativeHandle() { - this.X = x ?? 0; - this.Y = y ?? 0; - this.Z = z ?? 0; + if (RawHandle != IntPtr.Zero) + { + return; + } + + if (_ownedHandle != IntPtr.Zero) + { + SetHandle(_ownedHandle); + return; + } + + var allocated = Marshal.AllocHGlobal(sizeof(float) * 3); + + unsafe + { + var buffer = (float*)allocated; + buffer[0] = _x; + buffer[1] = _y; + buffer[2] = _z; + } + + var existing = Interlocked.CompareExchange(ref _ownedHandle, allocated, IntPtr.Zero); + if (existing != IntPtr.Zero) + { + Marshal.FreeHGlobal(allocated); + SetHandle(existing); + return; + } + + NativeHandleTracker.Track(this, allocated); + SetHandle(allocated); } - public unsafe ref float X => ref Unsafe.Add(ref *(float*)Handle, 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 unsafe ref float X => ref GetElementRef(0); + public unsafe ref float Y => ref GetElementRef(1); + public unsafe ref float Z => ref GetElementRef(2); + + private unsafe ref float GetElementRef(int index) + { + var handle = RawHandle; + if (handle != IntPtr.Zero) + { + return ref Unsafe.Add(ref *(float*)handle, index); + } + + switch (index) + { + case 0: + return ref _x; + case 1: + return ref _y; + case 2: + return ref _z; + default: + throw new ArgumentOutOfRangeException(nameof(index)); + } + } /// /// Returns a copy of the vector with values replaced. @@ -88,7 +147,25 @@ public void Add(Vector vector) public Angle Angle() { var angle = new Angle(); - NativeAPI.VectorAngles(Handle, IntPtr.Zero, angle.Handle); + unsafe + { + float* vec = stackalloc float[3]; + var handle = RawHandle; + var vecPtr = handle != IntPtr.Zero ? handle : (IntPtr)vec; + if (handle == IntPtr.Zero) + { + vec[0] = X; + vec[1] = Y; + vec[2] = Z; + } + + float* outAng = stackalloc float[3]; + NativeAPI.VectorAngles(vecPtr, IntPtr.Zero, (IntPtr)outAng); + + angle.X = outAng[0]; + angle.Y = outAng[1]; + angle.Z = outAng[2]; + } return angle; } @@ -100,7 +177,42 @@ public Angle Angle() public Angle Angle(Vector up) { var angle = new Angle(); - NativeAPI.VectorAngles(Handle, up.Handle, angle.Handle); + unsafe + { + float* vec = stackalloc float[3]; + var handle = RawHandle; + var vecPtr = handle != IntPtr.Zero ? handle : (IntPtr)vec; + if (handle == IntPtr.Zero) + { + vec[0] = X; + vec[1] = Y; + vec[2] = Z; + } + + var upHandle = up?.RawHandle ?? IntPtr.Zero; + float* upVec = stackalloc float[3]; + var upPtr = upHandle != IntPtr.Zero ? upHandle : (IntPtr)upVec; + if (upHandle == IntPtr.Zero) + { + if (up is null) + { + upPtr = IntPtr.Zero; + } + else + { + upVec[0] = up.X; + upVec[1] = up.Y; + upVec[2] = up.Z; + } + } + + float* outAng = stackalloc float[3]; + NativeAPI.VectorAngles(vecPtr, upPtr, (IntPtr)outAng); + + angle.X = outAng[0]; + angle.Y = outAng[1]; + angle.Z = outAng[2]; + } return angle; } @@ -183,31 +295,31 @@ public bool IsEqualTol(Vector compare, float tolerance) /// Returns whether all fields on the Vector are 0. /// /// - public bool IsZero() => NativeAPI.VectorIsZero(Handle); + public bool IsZero() => X == 0 && Y == 0 && Z == 0; /// /// Returns the Euclidean length of the vector: √x² + y² + z² /// /// Euclidean length of vector - public float Length() => NativeAPI.VectorLength(Handle); + public float Length() => MathF.Sqrt(LengthSqr()); /// /// Returns length of Vector excluding Z axis. /// /// 2D Length - public float Length2D() => NativeAPI.VectorLength2d(Handle); + public float Length2D() => MathF.Sqrt(Length2DSqr()); /// /// Returns the squared length of the vector, x² + y² + z². Faster than /// /// - public float LengthSqr() => NativeAPI.VectorLengthSqr(Handle); + public float LengthSqr() => (X * X) + (Y * Y) + (Z * Z); /// /// Returns the squared length of the vectors x and y value, x² + y². Faster than /// /// - public float Length2DSqr() => NativeAPI.VectorLength2dSqr(Handle); + public float Length2DSqr() => (X * X) + (Y * Y); /* @@ -360,15 +472,12 @@ public float this[int i] public static explicit operator Vector3(Vector v) { - unsafe + if (v is null) { - if (v is null) - { - throw new ArgumentNullException(nameof(v), "Input Vector cannot be null."); - } - - return new Vector3(new ReadOnlySpan(v.Handle.ToPointer(), 3)); + throw new ArgumentNullException(nameof(v), "Input Vector cannot be null."); } + + return new Vector3(v.X, v.Y, v.Z); } #endregion @@ -390,9 +499,5 @@ public override string ToString() { return $"{X:n2} {Y:n2} {Z:n2}"; } - - /* - - */ } } diff --git a/managed/CounterStrikeSharp.API/Modules/Utils/Vector2D.cs b/managed/CounterStrikeSharp.API/Modules/Utils/Vector2D.cs index e80563015..260a079b4 100644 --- a/managed/CounterStrikeSharp.API/Modules/Utils/Vector2D.cs +++ b/managed/CounterStrikeSharp.API/Modules/Utils/Vector2D.cs @@ -15,23 +15,82 @@ */ using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; namespace CounterStrikeSharp.API.Modules.Utils { public class Vector2D : NativeObject { - public unsafe ref float X => ref Unsafe.Add(ref *(float*)Handle.ToPointer(), 0); + private float _x; + private float _y; + private IntPtr _ownedHandle; - public unsafe ref float Y => ref Unsafe.Add(ref *(float*)Handle.ToPointer(), 1); + public unsafe ref float X => ref GetElementRef(0); + + public unsafe ref float Y => ref GetElementRef(1); + + private unsafe ref float GetElementRef(int index) + { + var handle = RawHandle; + if (handle != IntPtr.Zero) + { + return ref Unsafe.Add(ref *(float*)handle, index); + } + + switch (index) + { + case 0: + return ref _x; + case 1: + return ref _y; + default: + throw new ArgumentOutOfRangeException(nameof(index)); + } + } public Vector2D(IntPtr pointer) : base(pointer) { } - public Vector2D(float? x = null, float? y = null) : this(NativeAPI.Vector2dNew()) + public Vector2D(float? x = null, float? y = null) : base(IntPtr.Zero) { - this.X = x ?? 0; - this.Y = y ?? 0; + _x = x ?? 0; + _y = y ?? 0; + } + + protected override void EnsureNativeHandle() + { + if (RawHandle != IntPtr.Zero) + { + return; + } + + if (_ownedHandle != IntPtr.Zero) + { + SetHandle(_ownedHandle); + return; + } + + var allocated = Marshal.AllocHGlobal(sizeof(float) * 2); + + unsafe + { + var buffer = (float*)allocated; + buffer[0] = _x; + buffer[1] = _y; + } + + var existing = Interlocked.CompareExchange(ref _ownedHandle, allocated, IntPtr.Zero); + if (existing != IntPtr.Zero) + { + Marshal.FreeHGlobal(allocated); + SetHandle(existing); + return; + } + + NativeHandleTracker.Track(this, allocated); + SetHandle(allocated); } public override string ToString() @@ -39,4 +98,4 @@ public override string ToString() return $"{X:n2} {Y:n2}"; } } -} \ 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 6a9267f76..ae222d2d9 100644 --- a/managed/CounterStrikeSharp.API/Modules/Utils/Vector4D.cs +++ b/managed/CounterStrikeSharp.API/Modules/Utils/Vector4D.cs @@ -15,29 +15,96 @@ */ using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; namespace CounterStrikeSharp.API.Modules.Utils { public class Vector4D : NativeObject { - public unsafe ref float X => ref Unsafe.Add(ref *(float*)Handle.ToPointer(), 0); + private float _x; + private float _y; + private float _z; + private float _w; + private IntPtr _ownedHandle; - public unsafe ref float Y => ref Unsafe.Add(ref *(float*)Handle.ToPointer(), 1); + public unsafe ref float X => ref GetElementRef(0); - public unsafe ref float Z => ref Unsafe.Add(ref *(float*)Handle.ToPointer(), 2); + public unsafe ref float Y => ref GetElementRef(1); - public unsafe ref float W => ref Unsafe.Add(ref *(float*)Handle.ToPointer(), 3); + public unsafe ref float Z => ref GetElementRef(2); + + public unsafe ref float W => ref GetElementRef(3); + + private unsafe ref float GetElementRef(int index) + { + var handle = RawHandle; + if (handle != IntPtr.Zero) + { + return ref Unsafe.Add(ref *(float*)handle, index); + } + + switch (index) + { + case 0: + return ref _x; + case 1: + return ref _y; + case 2: + return ref _z; + case 3: + return ref _w; + default: + throw new ArgumentOutOfRangeException(nameof(index)); + } + } public Vector4D(IntPtr pointer) : base(pointer) { } - public Vector4D(float? x = null, float? y = null, float? z = null, float? w = null) : this(NativeAPI.Vector4dNew()) + public Vector4D(float? x = null, float? y = null, float? z = null, float? w = null) : base(IntPtr.Zero) { - this.X = x ?? 0; - this.Y = y ?? 0; - this.Z = z ?? 0; - this.W = w ?? 0; + _x = x ?? 0; + _y = y ?? 0; + _z = z ?? 0; + _w = w ?? 0; + } + + protected override void EnsureNativeHandle() + { + if (RawHandle != IntPtr.Zero) + { + return; + } + + if (_ownedHandle != IntPtr.Zero) + { + SetHandle(_ownedHandle); + return; + } + + var allocated = Marshal.AllocHGlobal(sizeof(float) * 4); + + unsafe + { + var buffer = (float*)allocated; + buffer[0] = _x; + buffer[1] = _y; + buffer[2] = _z; + buffer[3] = _w; + } + + var existing = Interlocked.CompareExchange(ref _ownedHandle, allocated, IntPtr.Zero); + if (existing != IntPtr.Zero) + { + Marshal.FreeHGlobal(allocated); + SetHandle(existing); + return; + } + + NativeHandleTracker.Track(this, allocated); + SetHandle(allocated); } public override string ToString() @@ -45,4 +112,4 @@ public override string ToString() return $"{X:n2} {Y:n2} {Z:n2} {W:n2}"; } } -} \ 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 fa11f567c..7b701ab40 100644 --- a/managed/CounterStrikeSharp.API/Modules/Utils/matrix3x4_t.cs +++ b/managed/CounterStrikeSharp.API/Modules/Utils/matrix3x4_t.cs @@ -1,79 +1,176 @@ -/* - * 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; - -namespace CounterStrikeSharp.API.Modules.Utils; - -public partial class matrix3x4_t : NativeObject -{ - public unsafe ref float this[int row, int column] => ref Unsafe.Add(ref *(float*)Handle, row * 4 + column); - - public float M00 => this[0, 0]; - - public float M01 => this[0, 1]; - - public float M02 => this[0, 2]; - - public float M03 => this[0, 3]; - - public float M10 => this[1, 0]; - - public float M11 => this[1, 1]; - - public float M12 => this[1, 2]; - - public float M13 => this[1, 3]; - - public float M20 => this[2, 0]; - - public float M21 => this[2, 1]; - - public float M22 => this[2, 2]; - - public float M23 => this[2, 3]; - - public matrix3x4_t(IntPtr pointer) : base(pointer) - { - } - - public matrix3x4_t(float? m00 = null, float? m01 = null, float? m02 = null, float? m03 = null, - float? m10 = null, float? m11 = null, float? m12 = null, float? m13 = null, - float? m20 = null, float? m21 = null, float? m22 = null, float? m23 = null) : this(NativeAPI.Matrix3x4New()) - { - this[0, 0] = m00 ?? 0; - this[0, 1] = m01 ?? 0; - this[0, 2] = m02 ?? 0; - this[0, 3] = m03 ?? 0; - - this[1, 0] = m10 ?? 0; - this[1, 1] = m11 ?? 0; - this[1, 2] = m12 ?? 0; - this[1, 3] = m13 ?? 0; - - this[2, 0] = m20 ?? 0; - this[2, 1] = m21 ?? 0; - this[2, 2] = m22 ?? 0; - this[2, 3] = m23 ?? 0; - } - - public override string ToString() - { - return $"{this[0, 0]:n2} {this[0, 1]:n2} {this[0, 2]:n2} {this[0, 3]:n2}\n" + - $"{this[1, 0]:n2} {this[1, 1]:n2} {this[1, 2]:n2} {this[1, 3]:n2}\n" + - $"{this[2, 0]:n2} {this[2, 1]:n2} {this[2, 2]:n2} {this[2, 3]:n2}"; - } -} \ No newline at end of file +/* + * 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 System.Runtime.InteropServices; +using System.Threading; + +namespace CounterStrikeSharp.API.Modules.Utils; + +public partial class matrix3x4_t : NativeObject +{ + private float _m00; + private float _m01; + private float _m02; + private float _m03; + private float _m10; + private float _m11; + private float _m12; + private float _m13; + private float _m20; + private float _m21; + private float _m22; + private float _m23; + private IntPtr _ownedHandle; + + public unsafe ref float this[int row, int column] => ref GetElementRef(row * 4 + column); + + private unsafe ref float GetElementRef(int index) + { + var handle = RawHandle; + if (handle != IntPtr.Zero) + { + return ref Unsafe.Add(ref *(float*)handle, index); + } + + switch (index) + { + case 0: + return ref _m00; + case 1: + return ref _m01; + case 2: + return ref _m02; + case 3: + return ref _m03; + case 4: + return ref _m10; + case 5: + return ref _m11; + case 6: + return ref _m12; + case 7: + return ref _m13; + case 8: + return ref _m20; + case 9: + return ref _m21; + case 10: + return ref _m22; + case 11: + return ref _m23; + default: + throw new ArgumentOutOfRangeException(nameof(index)); + } + } + + public float M00 => this[0, 0]; + + public float M01 => this[0, 1]; + + public float M02 => this[0, 2]; + + public float M03 => this[0, 3]; + + public float M10 => this[1, 0]; + + public float M11 => this[1, 1]; + + public float M12 => this[1, 2]; + + public float M13 => this[1, 3]; + + public float M20 => this[2, 0]; + + public float M21 => this[2, 1]; + + public float M22 => this[2, 2]; + + public float M23 => this[2, 3]; + + public matrix3x4_t(IntPtr pointer) : base(pointer) + { + } + + public matrix3x4_t(float? m00 = null, float? m01 = null, float? m02 = null, float? m03 = null, + float? m10 = null, float? m11 = null, float? m12 = null, float? m13 = null, + float? m20 = null, float? m21 = null, float? m22 = null, float? m23 = null) : base(IntPtr.Zero) + { + this[0, 0] = m00 ?? 0; + this[0, 1] = m01 ?? 0; + this[0, 2] = m02 ?? 0; + this[0, 3] = m03 ?? 0; + + this[1, 0] = m10 ?? 0; + this[1, 1] = m11 ?? 0; + this[1, 2] = m12 ?? 0; + this[1, 3] = m13 ?? 0; + + this[2, 0] = m20 ?? 0; + this[2, 1] = m21 ?? 0; + this[2, 2] = m22 ?? 0; + this[2, 3] = m23 ?? 0; + } + + protected override void EnsureNativeHandle() + { + if (RawHandle != IntPtr.Zero) + { + return; + } + + if (_ownedHandle != IntPtr.Zero) + { + SetHandle(_ownedHandle); + return; + } + + var allocated = Marshal.AllocHGlobal(sizeof(float) * 12); + + unsafe + { + var buffer = (float*)allocated; + buffer[0] = _m00; + buffer[1] = _m01; + buffer[2] = _m02; + buffer[3] = _m03; + buffer[4] = _m10; + buffer[5] = _m11; + buffer[6] = _m12; + buffer[7] = _m13; + buffer[8] = _m20; + buffer[9] = _m21; + buffer[10] = _m22; + buffer[11] = _m23; + } + + var existing = Interlocked.CompareExchange(ref _ownedHandle, allocated, IntPtr.Zero); + if (existing != IntPtr.Zero) + { + Marshal.FreeHGlobal(allocated); + SetHandle(existing); + return; + } + + NativeHandleTracker.Track(this, allocated); + SetHandle(allocated); + } + + public override string ToString() + { + return $"{this[0, 0]:n2} {this[0, 1]:n2} {this[0, 2]:n2} {this[0, 3]:n2}\n{this[1, 0]:n2} {this[1, 1]:n2} {this[1, 2]:n2} {this[1, 3]:n2}\n{this[2, 0]:n2} {this[2, 1]:n2} {this[2, 2]:n2} {this[2, 3]:n2}"; + } +} diff --git a/managed/CounterStrikeSharp.API/NativeHandleTracker.cs b/managed/CounterStrikeSharp.API/NativeHandleTracker.cs new file mode 100644 index 000000000..259fe4a2e --- /dev/null +++ b/managed/CounterStrikeSharp.API/NativeHandleTracker.cs @@ -0,0 +1,97 @@ +using System.Threading; +using System.Runtime.InteropServices; + +namespace CounterStrikeSharp.API +{ + internal static class NativeHandleTracker + { + internal sealed class Entry + { + public WeakReference Target { get; } + public IntPtr Handle { get; } + + public Entry(NativeObject target, IntPtr handle) + { + Target = new WeakReference(target); + Handle = handle; + } + } + + internal static readonly List _entries = new(); + private static readonly object _lockObj = new(); + private static int _nextCleanupIndex; + private static Timer? _timer; + + public static void Track(NativeObject target, IntPtr handle) + { + if (handle == IntPtr.Zero) + { + return; + } + + lock (_lockObj) + { + _entries.Add(new Entry(target, handle)); + EnsureTimerStartedLocked(); + CleanupSomeLocked(1); + } + } + + private static void EnsureTimerStartedLocked() + { + if (_timer != null) + { + return; + } + + _timer = new Timer(_ => CleanupSome(2048), null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1)); + } + + private static void CleanupSome(int budget) + { + lock (_lockObj) + { + CleanupSomeLocked(budget); + } + } + + private static void CleanupSomeLocked(int budget) + { + if (_entries.Count == 0) + { + return; + } + + var processedCount = 0; + + while (processedCount < budget && _entries.Count > 0) + { + if (_nextCleanupIndex >= _entries.Count) + { + _nextCleanupIndex = 0; + } + + var entry = _entries[_nextCleanupIndex]; + + if (!entry.Target.TryGetTarget(out _)) + { + Marshal.FreeHGlobal(entry.Handle); + + var lastIndex = _entries.Count - 1; + _entries[_nextCleanupIndex] = _entries[lastIndex]; + _entries.RemoveAt(lastIndex); + processedCount++; + continue; + } + + _nextCleanupIndex++; + processedCount++; + } + + if (_entries.Count == 0) + { + _nextCleanupIndex = 0; + } + } + } +} diff --git a/managed/CounterStrikeSharp.Tests.Native/NativeObjectTests.cs b/managed/CounterStrikeSharp.Tests.Native/NativeObjectTests.cs new file mode 100644 index 000000000..0945237eb --- /dev/null +++ b/managed/CounterStrikeSharp.Tests.Native/NativeObjectTests.cs @@ -0,0 +1,29 @@ +using System.Threading.Tasks; +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Utils; +using Xunit; + +namespace NativeTestsPlugin; + +public class NativeObjectsTests +{ + [Fact] + public async Task EnsureNativeHandle_IsFreed_Vector3() + { + await Server.NextFrameAsync(() => + { + var vector = new Vector(0, 0, 500); + Assert.Equal(IntPtr.Zero, vector.RawHandle); + Assert.Equal(500, NativeAPI.VectorGetZ(vector.Handle)); + Assert.Single(NativeHandleTracker._entries); + }); + + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + await Task.Delay(1000); + Assert.Empty(NativeHandleTracker._entries); + } +} \ No newline at end of file