Skip to content

Commit caab4d0

Browse files
code refactor
This commit proposed the following: - more efficient client layer - more efficient middleware layer - better disconnect logic to identify the reason of disconnection
1 parent 3910eb2 commit caab4d0

File tree

9 files changed

+1665
-292
lines changed

9 files changed

+1665
-292
lines changed

Miku.Core/INetMiddleware.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Buffers;
23

34
namespace Miku.Core
45
{
@@ -10,17 +11,16 @@ public interface INetMiddleware
1011
/// <summary>
1112
/// Process data before sending
1213
/// </summary>
13-
/// <param name="input">A data to be sent (might be processed by other middleware)</param>
14-
/// <param name="output">A processed data to be sent</param>
15-
void ProcessSend(ref ReadOnlyMemory<byte> input, out ReadOnlyMemory<byte> output);
14+
/// <param name="src">Source data to be processed</param>
15+
/// <param name="dst">Destination buffer to write processed data to</param>
16+
void ProcessSend(ReadOnlyMemory<byte> src, ArrayBufferWriter<byte> dst);
1617

1718
/// <summary>
1819
/// Process data after receiving
1920
/// </summary>
20-
/// <param name="input">A received data (might be processed by other middleware)</param>
21-
/// <param name="output">A processed data to be passed to the next middleware</param>
22-
/// <returns>Whether to halt the processing and how many bytes are consumed</returns>
23-
(bool halt, int consumedFromOrigin) ProcessReceive(ref ReadOnlyMemory<byte> input,
24-
out ReadOnlyMemory<byte> output);
21+
/// <param name="src">Source data to be processed</param>
22+
/// <param name="dst">Destination buffer to write processed data to</param>
23+
/// <returns>Whether to halt the processing and how many bytes are consumed from the original input</returns>
24+
(bool halt, int consumedFromOrigin) ProcessReceive(ReadOnlyMemory<byte> src, ArrayBufferWriter<byte> dst);
2525
}
2626
}

Miku.Core/Miku.Core.csproj

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
<PropertyGroup>
44
<ImplicitUsings>disable</ImplicitUsings>
55
<Nullable>disable</Nullable>
6-
<LangVersion>9</LangVersion>
6+
<LangVersion>11</LangVersion>
77
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
8-
<Version>1.0.6</Version>
8+
<Version>2.0.0</Version>
99
<PackageId>Miku</PackageId>
1010
<Title>Miku</Title>
1111
<Authors>JasonXuDeveloper</Authors>
@@ -17,14 +17,13 @@
1717
<PackageTags>TCP;Networking;High-Perofrmance</PackageTags>
1818
<PackageLicenseExpression>MIT</PackageLicenseExpression>
1919
<PackageReadmeFile>README.md</PackageReadmeFile>
20-
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
20+
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
2121
<BranchName>refs/heads/main</BranchName>
22-
<TargetFrameworks>net6.0;netstandard2.1;</TargetFrameworks>
22+
<TargetFrameworks>net6.0;net8.0;netstandard2.1;</TargetFrameworks>
2323
<PublishRepositoryUrl>true</PublishRepositoryUrl>
2424
<EmbedUntrackedSources>true</EmbedUntrackedSources>
2525
<IncludeSource>true</IncludeSource>
2626
<DebugType>embedded</DebugType>
27-
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
2827
</PropertyGroup>
2928

3029
<!-- for .NET Standard -->
@@ -48,6 +47,7 @@
4847
<PackageReference Include="System.Buffers" Version="4.6.0"/>
4948
<PackageReference Include="System.Memory" Version="4.6.0"/>
5049
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.1.0"/>
50+
<PackageReference Include="System.Threading.Channels" Version="7.0.0"/>
5151
</ItemGroup>
5252

5353
</Project>

Miku.Core/MultiMemory.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
3+
public readonly struct MultiMemory<T>
4+
{
5+
public readonly Memory<T> First;
6+
public readonly Memory<T> Second;
7+
8+
public MultiMemory(Memory<T> first, Memory<T> second)
9+
{
10+
First = first;
11+
Second = second;
12+
}
13+
14+
public static MultiMemory<T> Empty => new(Memory<T>.Empty, Memory<T>.Empty);
15+
public bool IsEmpty => First.IsEmpty && Second.IsEmpty;
16+
public int Length => First.Length + Second.Length;
17+
18+
public void CopyTo(Span<T> span)
19+
{
20+
if (span.Length < Length)
21+
throw new ArgumentException("Span is too small to copy the data.");
22+
23+
First.Span.CopyTo(span);
24+
Second.Span.CopyTo(span.Slice(First.Length));
25+
}
26+
}

Miku.Core/NetBuffer.cs

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
using System;
2+
using System.Buffers;
3+
using System.Threading;
4+
5+
public class NetBuffer : IDisposable
6+
{
7+
private readonly byte[] _buffer;
8+
private int _head; // next write position (producer)
9+
private int _tail; // next read position (consumer)
10+
private bool _disposed;
11+
12+
public event Action<string> OnWarning;
13+
14+
public NetBuffer(int capacity = 8192)
15+
{
16+
if (capacity < 2) throw new ArgumentOutOfRangeException(nameof(capacity));
17+
_buffer = ArrayPool<byte>.Shared.Rent(capacity);
18+
_head = 0;
19+
_tail = 0;
20+
}
21+
22+
// How many bytes are available to read.
23+
public int Length
24+
{
25+
get
26+
{
27+
int head = Volatile.Read(ref _head);
28+
int tail = Volatile.Read(ref _tail);
29+
return head >= tail
30+
? head - tail
31+
: head + _buffer.Length - tail;
32+
}
33+
}
34+
35+
// How many bytes we can write without overwriting unread data.
36+
public int FreeSpace
37+
{
38+
get
39+
{
40+
int head = Volatile.Read(ref _head);
41+
int tail = Volatile.Read(ref _tail);
42+
// leave one slot empty so head==tail always means "empty"
43+
return tail > head
44+
? tail - head - 1
45+
: tail + _buffer.Length - head - 1;
46+
}
47+
}
48+
49+
// Total capacity of the buffer
50+
public int Capacity => _buffer.Length;
51+
52+
public void Clear()
53+
{
54+
if (_disposed) throw new ObjectDisposedException(nameof(NetBuffer));
55+
Volatile.Write(ref _head, 0);
56+
Volatile.Write(ref _tail, 0);
57+
}
58+
59+
public void Dispose()
60+
{
61+
if (_disposed) return;
62+
ArrayPool<byte>.Shared.Return(_buffer);
63+
_disposed = true;
64+
}
65+
66+
// --- PRODUCER SIDE (only one thread may call these) ---
67+
68+
public MultiMemory<byte> GetWriteSegments()
69+
{
70+
if (_disposed)
71+
return MultiMemory<byte>.Empty;
72+
73+
int head = Volatile.Read(ref _head);
74+
int free = FreeSpace;
75+
if (free == 0)
76+
return MultiMemory<byte>.Empty;
77+
78+
// can write up to end of array, then wrap
79+
int firstLen = Math.Min(free, _buffer.Length - head);
80+
int secondLen = free - firstLen;
81+
82+
var first = new Memory<byte>(_buffer, head, firstLen);
83+
var second = secondLen > 0
84+
? new Memory<byte>(_buffer, 0, secondLen)
85+
: Memory<byte>.Empty;
86+
87+
return new MultiMemory<byte>(first, second);
88+
}
89+
90+
/// <summary>
91+
/// Get write segments as ArraySegment<byte> for direct SAEA BufferList usage
92+
/// This avoids MemoryMarshal overhead and provides zero-alloc access
93+
/// </summary>
94+
public (ArraySegment<byte> First, ArraySegment<byte> Second) GetWriteSegmentsAsArraySegments()
95+
{
96+
if (_disposed)
97+
return (ArraySegment<byte>.Empty, ArraySegment<byte>.Empty);
98+
99+
int head = Volatile.Read(ref _head);
100+
int free = FreeSpace;
101+
if (free == 0)
102+
return (ArraySegment<byte>.Empty, ArraySegment<byte>.Empty);
103+
104+
// can write up to end of array, then wrap
105+
int firstLen = Math.Min(free, _buffer.Length - head);
106+
int secondLen = free - firstLen;
107+
108+
var first = new ArraySegment<byte>(_buffer, head, firstLen);
109+
var second = secondLen > 0
110+
? new ArraySegment<byte>(_buffer, 0, secondLen)
111+
: ArraySegment<byte>.Empty;
112+
113+
return (first, second);
114+
}
115+
116+
public void AdvanceWrite(int count)
117+
{
118+
if (_disposed) throw new ObjectDisposedException(nameof(NetBuffer));
119+
if (count < 0)
120+
throw new ArgumentOutOfRangeException(nameof(count), "Count cannot be negative");
121+
122+
int free = FreeSpace;
123+
124+
// overwrite old data?
125+
if (count > free)
126+
{
127+
OnWarning?.Invoke(
128+
$"Buffer full: dropping entire incoming chunk of {count} bytes.");
129+
return;
130+
}
131+
132+
// advance head
133+
int head = Volatile.Read(ref _head);
134+
int newHead = head + count;
135+
if (newHead >= _buffer.Length) newHead -= _buffer.Length;
136+
Volatile.Write(ref _head, newHead);
137+
}
138+
139+
// --- CONSUMER SIDE (only one thread may call these) ---
140+
141+
public MultiMemory<byte> GetReadSegments()
142+
{
143+
if (_disposed)
144+
return MultiMemory<byte>.Empty;
145+
146+
int head = Volatile.Read(ref _head);
147+
int tail = Volatile.Read(ref _tail);
148+
int len = head >= tail
149+
? head - tail
150+
: head + _buffer.Length - tail;
151+
152+
if (len == 0)
153+
return MultiMemory<byte>.Empty;
154+
155+
int firstLen = Math.Min(len, _buffer.Length - tail);
156+
int secondLen = len - firstLen;
157+
158+
var first = new Memory<byte>(_buffer, tail, firstLen);
159+
var second = secondLen > 0
160+
? new Memory<byte>(_buffer, 0, secondLen)
161+
: Memory<byte>.Empty;
162+
163+
return new MultiMemory<byte>(first, second);
164+
}
165+
166+
public void AdvanceRead(int count)
167+
{
168+
if (_disposed) throw new ObjectDisposedException(nameof(NetBuffer));
169+
if (count < 0) throw new ArgumentOutOfRangeException(nameof(count));
170+
171+
int head = Volatile.Read(ref _head);
172+
int tail = Volatile.Read(ref _tail);
173+
int len = head >= tail
174+
? head - tail
175+
: head + _buffer.Length - tail;
176+
177+
if (count > len)
178+
{
179+
OnWarning?.Invoke(
180+
$"Requested to read {count}, but only {len} available. Clearing buffer.");
181+
// drop all
182+
Volatile.Write(ref _tail, head);
183+
return;
184+
}
185+
186+
int newTail = tail + count;
187+
if (newTail >= _buffer.Length) newTail -= _buffer.Length;
188+
Volatile.Write(ref _tail, newTail);
189+
}
190+
}

0 commit comments

Comments
 (0)