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