Skip to content

Commit f06283a

Browse files
ELDmentroflmuffin
andauthored
refactor: Fix memory leak caused by allocating Vector, QAngle, etc. class objects (#1182)
Co-authored-by: roflmuffin <[email protected]>
1 parent 60db1df commit f06283a

File tree

11 files changed

+968
-306
lines changed

11 files changed

+968
-306
lines changed

managed/CounterStrikeSharp.API/BaseNative.cs

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*
1+
/*
22
* This file is part of CounterStrikeSharp.
33
* CounterStrikeSharp is free software: you can redistribute it and/or modify
44
* it under the terms of the GNU General Public License as published by
@@ -15,22 +15,47 @@
1515
*/
1616

1717
using System;
18+
using System.Text;
1819
using System.Collections.Generic;
1920
using System.Reflection.Metadata;
2021
using System.Runtime.InteropServices;
21-
using System.Text;
2222

2323
namespace CounterStrikeSharp.API
2424
{
2525
public abstract class NativeObject
2626
{
27-
public IntPtr Handle { get; internal set; }
27+
private IntPtr _handle;
28+
29+
public IntPtr Handle
30+
{
31+
get
32+
{
33+
if (_handle == IntPtr.Zero)
34+
{
35+
EnsureNativeHandle();
36+
}
37+
38+
return _handle;
39+
}
40+
internal set => _handle = value;
41+
}
42+
43+
internal IntPtr RawHandle => _handle;
2844

2945
protected NativeObject(IntPtr pointer)
3046
{
31-
Handle = pointer;
47+
_handle = pointer;
3248
}
33-
49+
50+
protected void SetHandle(IntPtr pointer)
51+
{
52+
_handle = pointer;
53+
}
54+
55+
protected virtual void EnsureNativeHandle()
56+
{
57+
}
58+
3459
/// <summary>
3560
/// Returns a new instance of the specified type using the pointer from the passed in object.
3661
/// </summary>
Lines changed: 123 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,123 @@
1-
using System;
2-
using System.Numerics;
3-
using System.Runtime.InteropServices;
4-
using CounterStrikeSharp.API.Modules.Memory;
5-
using CounterStrikeSharp.API.Modules.Utils;
6-
using Vector = CounterStrikeSharp.API.Modules.Utils.Vector;
7-
8-
namespace CounterStrikeSharp.API.Core;
9-
10-
public partial class CBaseEntity
11-
{
12-
/// <exception cref="InvalidOperationException">Entity is not valid</exception>
13-
/// <exception cref="ArgumentNullException">At least one parameter must be specified</exception>
14-
public void Teleport(Vector? position = null, QAngle? angles = null, Vector? velocity = null)
15-
{
16-
Guard.IsValidEntity(this);
17-
18-
if (position == null && angles == null && velocity == null)
19-
throw new ArgumentException("At least one parameter must be specified");
20-
21-
nint _position = position?.Handle ?? 0;
22-
nint _angles = angles?.Handle ?? 0;
23-
nint _velocity = velocity?.Handle ?? 0;
24-
nint _handle = Handle;
25-
26-
VirtualFunction.CreateVoid<IntPtr, IntPtr, IntPtr, IntPtr>(_handle, GameData.GetOffset("CBaseEntity_Teleport"))(_handle, _position,
27-
_angles, _velocity);
28-
}
29-
30-
/// <summary>
31-
/// Teleports the entity to the specified position, angles, and velocity using Vector3 parameters.
32-
/// This overload is optimized for memory efficiency by directly working with a Vector3 struct.
33-
/// </summary>
34-
/// <exception cref="InvalidOperationException">Entity is not valid</exception>
35-
/// <exception cref="ArgumentException">At least one parameter must be specified</exception>
36-
public void Teleport(Vector3? position = null, Vector3? angles = null, Vector3? velocity = null)
37-
{
38-
Guard.IsValidEntity(this);
39-
40-
if (position == null && angles == null && velocity == null)
41-
throw new ArgumentException("At least one parameter must be specified");
42-
43-
unsafe
44-
{
45-
void* positionPtr = null, anglePtr = null, velocityPtr = null;
46-
47-
if (position.HasValue)
48-
{
49-
var pos = position.Value;
50-
positionPtr = &pos;
51-
}
52-
53-
if (angles.HasValue)
54-
{
55-
var ang = angles.Value;
56-
anglePtr = &ang;
57-
}
58-
59-
if (velocity.HasValue)
60-
{
61-
var vel = velocity.Value;
62-
velocityPtr = &vel;
63-
}
64-
65-
VirtualFunction.CreateVoid<IntPtr, IntPtr, IntPtr, IntPtr>(Handle, GameData.GetOffset("CBaseEntity_Teleport"))(Handle,
66-
(nint)positionPtr,
67-
(nint)anglePtr, (nint)velocityPtr);
68-
}
69-
}
70-
71-
/// <exception cref="InvalidOperationException">Entity is not valid</exception>
72-
public void DispatchSpawn(CEntityKeyValues? keyValues)
73-
{
74-
Guard.IsValidEntity(this);
75-
76-
NativeAPI.DispatchSpawn(Handle, keyValues?.Handle ?? IntPtr.Zero);
77-
}
78-
79-
public void DispatchSpawn()
80-
{
81-
Guard.IsValidEntity(this);
82-
NativeAPI.DispatchSpawn(Handle, IntPtr.Zero);
83-
}
84-
85-
/// <summary>
86-
/// Shorthand for accessing an entity's CBodyComponent?.SceneNode?.AbsOrigin;
87-
/// </summary>
88-
public Vector? AbsOrigin => CBodyComponent?.SceneNode?.AbsOrigin;
89-
90-
/// <summary>
91-
/// Shorthand for accessing an entity's CBodyComponent?.SceneNode?.AbsRotation;
92-
/// </summary>
93-
/// <exception cref="InvalidOperationException">Entity is not valid</exception>
94-
public QAngle? AbsRotation => CBodyComponent?.SceneNode?.AbsRotation;
95-
96-
public T? GetVData<T>() where T : CEntitySubclassVDataBase
97-
{
98-
Guard.IsValidEntity(this);
99-
100-
return (T)Activator.CreateInstance(typeof(T), Marshal.ReadIntPtr(SubclassID.Handle + 4));
101-
}
102-
103-
/// <summary>
104-
/// Emit a soundevent to all players.
105-
/// </summary>
106-
/// <param name="soundEventName">The name of the soundevent to emit.</param>
107-
/// <param name="recipients">The recipients of the soundevent.</param>
108-
/// <param name="volume">The volume of the soundevent.</param>
109-
/// <param name="pitch">The pitch of the soundevent.</param>
110-
/// <returns>The sound event guid.</returns>
111-
public uint EmitSound(string soundEventName, RecipientFilter? recipients = null, float volume = 1f, float pitch = 0)
112-
{
113-
Guard.IsValidEntity(this);
114-
115-
if (recipients == null)
116-
{
117-
recipients = new RecipientFilter();
118-
recipients.AddAllPlayers();
119-
}
120-
121-
return NativeAPI.EmitSoundFilter(recipients.GetRecipientMask(), this.Index, soundEventName, volume, pitch);
122-
}
123-
124-
/// <summary>
125-
/// Returns true if the entity is a player pawn.
126-
/// </summary>
127-
public bool IsPlayerPawn()
128-
{
129-
Guard.IsValidEntity(this);
130-
131-
return VirtualFunction.Create<IntPtr, bool>(Handle, GameData.GetOffset("CBaseEntity_IsPlayerPawn"))(Handle);
132-
}
133-
}
1+
using System;
2+
using System.Numerics;
3+
using System.Runtime.InteropServices;
4+
using CounterStrikeSharp.API.Modules.Memory;
5+
using CounterStrikeSharp.API.Modules.Utils;
6+
using Vector = CounterStrikeSharp.API.Modules.Utils.Vector;
7+
8+
namespace CounterStrikeSharp.API.Core;
9+
10+
public partial class CBaseEntity
11+
{
12+
/// <exception cref="InvalidOperationException">Entity is not valid</exception>
13+
/// <exception cref="ArgumentNullException">At least one parameter must be specified</exception>
14+
public void Teleport(Vector? position = null, QAngle? angles = null, Vector? velocity = null)
15+
{
16+
Teleport(position == null ? null : (Vector3)position, angles == null ? null : (Vector3)angles,
17+
velocity == null ? null : (Vector3)velocity);
18+
}
19+
20+
/// <summary>
21+
/// Teleports the entity to the specified position, angles, and velocity using Vector3 parameters.
22+
/// This overload is optimized for memory efficiency by directly working with a Vector3 struct.
23+
/// </summary>
24+
/// <exception cref="InvalidOperationException">Entity is not valid</exception>
25+
/// <exception cref="ArgumentException">At least one parameter must be specified</exception>
26+
public void Teleport(Vector3? position = null, Vector3? angles = null, Vector3? velocity = null)
27+
{
28+
Guard.IsValidEntity(this);
29+
30+
if (position == null && angles == null && velocity == null)
31+
throw new ArgumentException("At least one parameter must be specified");
32+
33+
unsafe
34+
{
35+
void* positionPtr = null, anglePtr = null, velocityPtr = null;
36+
37+
if (position.HasValue)
38+
{
39+
var pos = position.Value;
40+
positionPtr = &pos;
41+
}
42+
43+
if (angles.HasValue)
44+
{
45+
var ang = angles.Value;
46+
anglePtr = &ang;
47+
}
48+
49+
if (velocity.HasValue)
50+
{
51+
var vel = velocity.Value;
52+
velocityPtr = &vel;
53+
}
54+
55+
VirtualFunction.CreateVoid<IntPtr, IntPtr, IntPtr, IntPtr>(Handle, GameData.GetOffset("CBaseEntity_Teleport"))(Handle,
56+
(nint)positionPtr,
57+
(nint)anglePtr, (nint)velocityPtr);
58+
}
59+
}
60+
61+
/// <exception cref="InvalidOperationException">Entity is not valid</exception>
62+
public void DispatchSpawn(CEntityKeyValues? keyValues)
63+
{
64+
Guard.IsValidEntity(this);
65+
66+
NativeAPI.DispatchSpawn(Handle, keyValues?.Handle ?? IntPtr.Zero);
67+
}
68+
69+
public void DispatchSpawn()
70+
{
71+
Guard.IsValidEntity(this);
72+
NativeAPI.DispatchSpawn(Handle, IntPtr.Zero);
73+
}
74+
75+
/// <summary>
76+
/// Shorthand for accessing an entity's CBodyComponent?.SceneNode?.AbsOrigin;
77+
/// </summary>
78+
public Vector? AbsOrigin => CBodyComponent?.SceneNode?.AbsOrigin;
79+
80+
/// <summary>
81+
/// Shorthand for accessing an entity's CBodyComponent?.SceneNode?.AbsRotation;
82+
/// </summary>
83+
/// <exception cref="InvalidOperationException">Entity is not valid</exception>
84+
public QAngle? AbsRotation => CBodyComponent?.SceneNode?.AbsRotation;
85+
86+
public T? GetVData<T>() where T : CEntitySubclassVDataBase
87+
{
88+
Guard.IsValidEntity(this);
89+
90+
return (T)Activator.CreateInstance(typeof(T), Marshal.ReadIntPtr(SubclassID.Handle + 4));
91+
}
92+
93+
/// <summary>
94+
/// Emit a soundevent to all players.
95+
/// </summary>
96+
/// <param name="soundEventName">The name of the soundevent to emit.</param>
97+
/// <param name="recipients">The recipients of the soundevent.</param>
98+
/// <param name="volume">The volume of the soundevent.</param>
99+
/// <param name="pitch">The pitch of the soundevent.</param>
100+
/// <returns>The sound event guid.</returns>
101+
public uint EmitSound(string soundEventName, RecipientFilter? recipients = null, float volume = 1f, float pitch = 0)
102+
{
103+
Guard.IsValidEntity(this);
104+
105+
if (recipients == null)
106+
{
107+
recipients = new RecipientFilter();
108+
recipients.AddAllPlayers();
109+
}
110+
111+
return NativeAPI.EmitSoundFilter(recipients.GetRecipientMask(), this.Index, soundEventName, volume, pitch);
112+
}
113+
114+
/// <summary>
115+
/// Returns true if the entity is a player pawn.
116+
/// </summary>
117+
public bool IsPlayerPawn()
118+
{
119+
Guard.IsValidEntity(this);
120+
121+
return VirtualFunction.Create<IntPtr, bool>(Handle, GameData.GetOffset("CBaseEntity_IsPlayerPawn"))(Handle);
122+
}
123+
}

0 commit comments

Comments
 (0)