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