Skip to content

Commit e444864

Browse files
IO: Remake BytePayload without RecyclableMemoryStreams. Support custom resize strategies.
Transport: Allow different transports and decouple library logic from ENet entirely. Work in progress
1 parent 13a4393 commit e444864

File tree

9 files changed

+444
-20
lines changed

9 files changed

+444
-20
lines changed

Source/Data/IDataHolder.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System;
88
using System.Collections.Generic;
99
using System.Diagnostics.CodeAnalysis;
10+
using System.Runtime.CompilerServices;
1011

1112
namespace EppNet.Data
1213
{
@@ -23,7 +24,7 @@ public interface IDataHolder : IDisposable
2324
/// just hide poor memory management.
2425
/// <br></br>Just make sure you call dispose!
2526
/// </summary>
26-
internal static readonly Dictionary<IDataHolder, Dictionary<string, object>> _globalDataDict = new();
27+
internal static readonly ConditionalWeakTable<IDataHolder, Dictionary<string, object>> _globalDataDict = new();
2728

2829
/// <summary>
2930
/// Tries to get all data associated with a particular <see cref="IDataHolder"/><br></br>

Source/Data/IO/BytePayload.cs

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
///////////////////////////////////////////////////////
2+
/// Filename: BytePayload.cs
3+
/// Date: June 8, 2025
4+
/// Author: Maverick Liberty
5+
///////////////////////////////////////////////////////
6+
7+
using System;
8+
9+
public sealed class BytePayload : IDisposable
10+
{
11+
12+
public int Capacity { internal set; get; }
13+
14+
public int Length { internal set; get; }
15+
16+
public DateTime LastUse { internal set; get; }
17+
18+
public bool IsDestroyed { internal set; get; }
19+
20+
public byte[] Buffer { internal set; get; }
21+
22+
public bool IsPooled { internal set; get; }
23+
24+
public IResizeStrategy ResizeStrategy;
25+
private readonly BytePayloadPool _pool;
26+
27+
internal BytePayload(BytePayloadPool pool, int capacity)
28+
{
29+
this._pool = pool ?? throw new ArgumentNullException(nameof(pool));
30+
31+
this.IsDestroyed = IsPooled = false;
32+
this.Capacity = capacity;
33+
this.Length = 0;
34+
this.LastUse = DateTime.Now;
35+
this.Buffer = new byte[capacity];
36+
this.ResizeStrategy = pool.ResizeStrategy;
37+
}
38+
39+
internal BytePayload(IResizeStrategy resizeStrategy, int capacity)
40+
{
41+
this._pool = null;
42+
43+
this.IsDestroyed = IsPooled = false;
44+
this.Capacity = capacity;
45+
this.Length = 0;
46+
this.LastUse = DateTime.Now;
47+
this.Buffer = new byte[capacity];
48+
this.ResizeStrategy = resizeStrategy ?? throw new ArgumentNullException(nameof(resizeStrategy));
49+
}
50+
51+
public Span<byte> AsSpan() =>
52+
new(Buffer, 0, Length);
53+
54+
public ReadOnlySpan<byte> AsReadOnlySpan() =>
55+
new(Buffer, 0, Length);
56+
57+
public void EnsureCapacity(int neededBytes, bool exact = false)
58+
{
59+
LastUse = DateTime.Now;
60+
61+
if (neededBytes < 0)
62+
throw new ArgumentOutOfRangeException(nameof(neededBytes));
63+
64+
if (Length + neededBytes <= Capacity)
65+
return;
66+
67+
int additional = neededBytes - (Capacity - Length);
68+
int newSize = exact ? Capacity + additional : ResizeStrategy.Resize(Capacity + additional);
69+
Resize(newSize);
70+
}
71+
72+
public void Resize(int newSize)
73+
{
74+
if (IsDestroyed || newSize <= Capacity)
75+
return;
76+
77+
byte[] newBuffer = new byte[newSize];
78+
System.Buffer.BlockCopy(Buffer, 0, newBuffer, 0, Length);
79+
80+
this.Buffer = newBuffer;
81+
this.Capacity = newSize;
82+
}
83+
84+
public void Reset()
85+
{
86+
if (Buffer is not null)
87+
Array.Clear(Buffer, 0, Length);
88+
89+
Length = 0;
90+
LastUse = DateTime.Now;
91+
}
92+
93+
/// <summary>
94+
/// Resets this payload and returns it to the pool (if-applicable)
95+
/// </summary>
96+
97+
public void Dispose()
98+
{
99+
if (IsDestroyed || Buffer is null)
100+
return;
101+
102+
Reset();
103+
104+
if (_pool is not null)
105+
{
106+
_pool.Return(this);
107+
this.IsPooled = true;
108+
}
109+
}
110+
111+
/// <summary>
112+
/// Destroys this payload and frees it memory. It is no longer capable of being pooled.
113+
/// </summary>
114+
/// <exception cref="InvalidOperationException"></exception>
115+
116+
public void Destroy()
117+
{
118+
if (!IsDestroyed && Buffer is not null)
119+
{
120+
this.Buffer = Array.Empty<byte>();
121+
this.Length = this.Capacity = 0;
122+
this.IsDestroyed = true;
123+
this.IsPooled = false;
124+
}
125+
}
126+
127+
}
128+
129+
public ref struct BytePayloadWriter
130+
{
131+
132+
public int Position
133+
{
134+
internal set;
135+
get;
136+
}
137+
138+
private readonly BytePayload _owner;
139+
140+
public BytePayloadWriter(BytePayload owner)
141+
{
142+
if (owner.IsDestroyed)
143+
throw new ObjectDisposedException("BytePayload has been disposed!");
144+
145+
this._owner = owner;
146+
this.Position = 0;
147+
}
148+
149+
public BytePayloadWriter(BytePayload owner, int offset)
150+
{
151+
if (owner.IsDestroyed)
152+
throw new ObjectDisposedException("BytePayload has been disposed!");
153+
154+
if (offset < 0 || offset >= owner.Length)
155+
throw new ArgumentOutOfRangeException("Offset is out of bounds!");
156+
157+
this._owner = owner;
158+
this.Position = offset;
159+
}
160+
161+
public void WriteByte(byte value)
162+
{
163+
#if DEBUG
164+
if (_owner.IsDestroyed)
165+
throw new ObjectDisposedException("BytePayload has been disposed!");
166+
#endif
167+
168+
_owner.EnsureCapacity(1);
169+
_owner.Buffer[Position++] = value;
170+
_owner.Length++;
171+
}
172+
173+
public void WriteBytes(ReadOnlySpan<byte> data)
174+
{
175+
#if DEBUG
176+
if (_owner.IsDestroyed)
177+
throw new ObjectDisposedException("BytePayload has been disposed!");
178+
#endif
179+
180+
_owner.EnsureCapacity(data.Length);
181+
data.CopyTo(new Span<byte>(_owner.Buffer, Position, data.Length));
182+
Position += data.Length;
183+
184+
// Update buffer length and last use
185+
_owner.Length = Math.Max(Position, _owner.Length);
186+
_owner.LastUse = DateTime.Now;
187+
}
188+
189+
public Span<byte> Reserve(int count)
190+
{
191+
#if DEBUG
192+
if (_owner.IsDestroyed)
193+
throw new ObjectDisposedException("BytePayload has been disposed!");
194+
#endif
195+
196+
_owner.EnsureCapacity(count);
197+
Span<byte> slice = new(_owner.Buffer, Position, count);
198+
Position += count;
199+
200+
// Update buffer length and last use
201+
_owner.Length = Math.Max(Position, _owner.Length);
202+
_owner.LastUse = DateTime.Now;
203+
return slice;
204+
}
205+
206+
}
207+
208+
public ref struct BytePayloadReader
209+
{
210+
211+
public int Position { internal set; get; }
212+
213+
private readonly BytePayload _owner;
214+
215+
public BytePayloadReader(BytePayload owner)
216+
{
217+
if (owner.IsDestroyed)
218+
throw new ObjectDisposedException(nameof(owner));
219+
220+
this._owner = owner;
221+
this.Position = 0;
222+
}
223+
224+
public BytePayloadReader(BytePayload owner, int offset)
225+
{
226+
if (owner.IsDestroyed)
227+
throw new ObjectDisposedException(nameof(owner));
228+
229+
if (offset < 0 || offset >= owner.Length)
230+
throw new ArgumentOutOfRangeException("Offset is out of bounds!");
231+
232+
this._owner = owner;
233+
this.Position = offset;
234+
}
235+
236+
public byte ReadByte()
237+
{
238+
#if DEBUG
239+
if (_owner.IsDestroyed)
240+
throw new ObjectDisposedException("BytePayload has been disposed!");
241+
242+
if (Position >= _owner.Length)
243+
throw new IndexOutOfRangeException("Attempted to read beyond buffer length!");
244+
#endif
245+
return _owner.Buffer[Position++];
246+
}
247+
248+
public ReadOnlySpan<byte> ReadBytes(int count)
249+
{
250+
#if DEBUG
251+
if (_owner.IsDestroyed)
252+
throw new ObjectDisposedException("BytePayload has been disposed!");
253+
254+
if (Position + count > _owner.Length)
255+
throw new IndexOutOfRangeException("Attempted to read beyond buffer length!");
256+
#endif
257+
ReadOnlySpan<byte> span = new(_owner.Buffer, Position, count);
258+
Position += count;
259+
return span;
260+
}
261+
262+
}

Source/Data/IO/BytePayloadPool.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
///////////////////////////////////////////////////////
2+
/// Filename: BytePayloadPool.cs
3+
/// Date: June 8, 2025
4+
/// Author: Maverick Liberty
5+
///////////////////////////////////////////////////////
6+
7+
using System.Collections.Concurrent;
8+
using EppNet.Node;
9+
10+
public class BytePayloadPool : INodeDescendant
11+
{
12+
public NetworkNode Node { get; }
13+
14+
public int DefaultCapacity { get; }
15+
16+
private readonly ConcurrentBag<BytePayload> _pool;
17+
18+
public IResizeStrategy ResizeStrategy { get; }
19+
20+
internal BytePayloadPool(NetworkNode node, int defaultCapacity = 1024)
21+
{
22+
this.Node = node;
23+
this.DefaultCapacity = defaultCapacity;
24+
this.ResizeStrategy = new ExponentialResizeStrategy();
25+
this._pool = new();
26+
}
27+
28+
public BytePayload Rent(int capacity)
29+
{
30+
if (!_pool.TryTake(out BytePayload payload) || payload.IsDestroyed)
31+
payload = new BytePayload(this, capacity);
32+
33+
return payload;
34+
}
35+
36+
internal bool Return(BytePayload payload)
37+
{
38+
if (payload.IsDestroyed)
39+
return false;
40+
41+
_pool.Add(payload);
42+
return true;
43+
}
44+
45+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
///////////////////////////////////////////////////////
2+
/// Filename: ExponentialResizeStrategy.cs
3+
/// Date: June 9, 2025
4+
/// Author: Maverick Liberty
5+
///////////////////////////////////////////////////////
6+
7+
public sealed class ExponentialResizeStrategy : IResizeStrategy
8+
{
9+
10+
public int Multiplier { get; }
11+
12+
public ExponentialResizeStrategy(int multiplier = 2)
13+
{
14+
this.Multiplier = multiplier;
15+
}
16+
17+
public int Resize(int neededBytes) =>
18+
neededBytes * Multiplier;
19+
20+
}

Source/Data/IO/IResizeStrategy.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
///////////////////////////////////////////////////////
2+
/// Filename: IResizeStrategy.cs
3+
/// Date: June 9, 2025
4+
/// Author: Maverick Liberty
5+
///////////////////////////////////////////////////////
6+
7+
public interface IResizeStrategy
8+
{
9+
10+
int Resize(int neededBytes);
11+
12+
}

0 commit comments

Comments
 (0)