22// Licensed under the Six Labors Split License.
33
44using System . Buffers ;
5- using System . Collections ;
65using System . Runtime . CompilerServices ;
76using System . Runtime . InteropServices ;
87using SixLabors . ImageSharp . Memory ;
@@ -14,15 +13,13 @@ namespace SixLabors.ImageSharp.IO;
1413/// Chunks are allocated by the <see cref="MemoryAllocator"/> assigned via the constructor
1514/// and is designed to take advantage of buffer pooling when available.
1615/// </summary>
17- public class ChunkedMemoryStream : Stream
16+ internal sealed class ChunkedMemoryStream : Stream
1817{
1918 private readonly MemoryChunkBuffer memoryChunkBuffer ;
20- private readonly byte [ ] singleByteBuffer = new byte [ 1 ] ;
21-
2219 private long length ;
2320 private long position ;
24- private int currentChunk ;
25- private int currentChunkIndex ;
21+ private int bufferIndex ;
22+ private int chunkIndex ;
2623 private bool isDisposed ;
2724
2825 /// <summary>
@@ -95,21 +92,13 @@ public override void SetLength(long value)
9592 /// <inheritdoc/>
9693 public override int ReadByte ( )
9794 {
98- this . EnsureNotDisposed ( ) ;
99- if ( this . position >= this . length )
100- {
101- return - 1 ;
102- }
103-
104- _ = this . Read ( this . singleByteBuffer , 0 , 1 ) ;
105- return MemoryMarshal . GetReference < byte > ( this . singleByteBuffer ) ;
95+ Unsafe . SkipInit ( out byte b ) ;
96+ return this . Read ( MemoryMarshal . CreateSpan ( ref b , 1 ) ) == 1 ? b : - 1 ;
10697 }
10798
10899 /// <inheritdoc/>
109100 public override int Read ( byte [ ] buffer , int offset , int count )
110101 {
111- this . EnsureNotDisposed ( ) ;
112-
113102 Guard . NotNull ( buffer , nameof ( buffer ) ) ;
114103 Guard . MustBeGreaterThanOrEqualTo ( offset , 0 , nameof ( offset ) ) ;
115104 Guard . MustBeGreaterThanOrEqualTo ( count , 0 , nameof ( count ) ) ;
@@ -135,39 +124,34 @@ public override int Read(Span<byte> buffer)
135124 return 0 ;
136125 }
137126
138- if ( remaining > count )
139- {
140- remaining = count ;
141- }
142-
143- int bytesToRead = ( int ) remaining ;
127+ int bytesToRead = count ;
144128 int bytesRead = 0 ;
145- while ( bytesToRead != 0 && this . currentChunk != this . memoryChunkBuffer . Length )
129+ while ( bytesToRead > 0 && this . bufferIndex != this . memoryChunkBuffer . Length )
146130 {
147131 bool moveToNextChunk = false ;
148- MemoryChunk chunk = this . memoryChunkBuffer [ this . currentChunk ] ;
132+ MemoryChunk chunk = this . memoryChunkBuffer [ this . bufferIndex ] ;
149133 int n = bytesToRead ;
150- int remainingBytesInCurrentChunk = chunk . Length - this . currentChunkIndex ;
134+ int remainingBytesInCurrentChunk = chunk . Length - this . chunkIndex ;
151135 if ( n >= remainingBytesInCurrentChunk )
152136 {
153137 n = remainingBytesInCurrentChunk ;
154138 moveToNextChunk = true ;
155139 }
156140
157141 // Read n bytes from the current chunk
158- chunk . Buffer . Memory . Span . Slice ( this . currentChunkIndex , n ) . CopyTo ( buffer . Slice ( offset , n ) ) ;
142+ chunk . Buffer . Memory . Span . Slice ( this . chunkIndex , n ) . CopyTo ( buffer . Slice ( offset , n ) ) ;
159143 bytesToRead -= n ;
160144 offset += n ;
161145 bytesRead += n ;
162146
163147 if ( moveToNextChunk )
164148 {
165- this . currentChunkIndex = 0 ;
166- this . currentChunk ++ ;
149+ this . chunkIndex = 0 ;
150+ this . bufferIndex ++ ;
167151 }
168152 else
169153 {
170- this . currentChunkIndex += n ;
154+ this . chunkIndex += n ;
171155 }
172156 }
173157
@@ -177,11 +161,7 @@ public override int Read(Span<byte> buffer)
177161
178162 /// <inheritdoc/>
179163 public override void WriteByte ( byte value )
180- {
181- this . EnsureNotDisposed ( ) ;
182- MemoryMarshal . Write ( this . singleByteBuffer , ref value ) ;
183- this . Write ( this . singleByteBuffer , 0 , 1 ) ;
184- }
164+ => this . Write ( MemoryMarshal . CreateSpan ( ref value , 1 ) ) ;
185165
186166 /// <inheritdoc/>
187167 public override void Write ( byte [ ] buffer , int offset , int count )
@@ -213,39 +193,34 @@ public override void Write(ReadOnlySpan<byte> buffer)
213193 remaining = this . memoryChunkBuffer . Length - this . position ;
214194 }
215195
216- if ( remaining > count )
217- {
218- remaining = count ;
219- }
220-
221- int bytesToWrite = ( int ) remaining ;
196+ int bytesToWrite = count ;
222197 int bytesWritten = 0 ;
223- while ( bytesToWrite != 0 && this . currentChunk != this . memoryChunkBuffer . Length )
198+ while ( bytesToWrite > 0 && this . bufferIndex != this . memoryChunkBuffer . Length )
224199 {
225200 bool moveToNextChunk = false ;
226- MemoryChunk chunk = this . memoryChunkBuffer [ this . currentChunk ] ;
201+ MemoryChunk chunk = this . memoryChunkBuffer [ this . bufferIndex ] ;
227202 int n = bytesToWrite ;
228- int remainingBytesInCurrentChunk = chunk . Length - this . currentChunkIndex ;
203+ int remainingBytesInCurrentChunk = chunk . Length - this . chunkIndex ;
229204 if ( n >= remainingBytesInCurrentChunk )
230205 {
231206 n = remainingBytesInCurrentChunk ;
232207 moveToNextChunk = true ;
233208 }
234209
235210 // Write n bytes to the current chunk
236- buffer . Slice ( offset , n ) . CopyTo ( chunk . Buffer . Slice ( this . currentChunkIndex , n ) ) ;
211+ buffer . Slice ( offset , n ) . CopyTo ( chunk . Buffer . Slice ( this . chunkIndex , n ) ) ;
237212 bytesToWrite -= n ;
238213 offset += n ;
239214 bytesWritten += n ;
240215
241216 if ( moveToNextChunk )
242217 {
243- this . currentChunkIndex = 0 ;
244- this . currentChunk ++ ;
218+ this . chunkIndex = 0 ;
219+ this . bufferIndex ++ ;
245220 }
246221 else
247222 {
248- this . currentChunkIndex += n ;
223+ this . chunkIndex += n ;
249224 }
250225 }
251226
@@ -275,31 +250,31 @@ public void WriteTo(Stream stream)
275250
276251 int bytesToRead = ( int ) remaining ;
277252 int bytesRead = 0 ;
278- while ( bytesToRead != 0 && this . currentChunk != this . memoryChunkBuffer . Length )
253+ while ( bytesToRead > 0 && this . bufferIndex != this . memoryChunkBuffer . Length )
279254 {
280255 bool moveToNextChunk = false ;
281- MemoryChunk chunk = this . memoryChunkBuffer [ this . currentChunk ] ;
256+ MemoryChunk chunk = this . memoryChunkBuffer [ this . bufferIndex ] ;
282257 int n = bytesToRead ;
283- int remainingBytesInCurrentChunk = chunk . Length - this . currentChunkIndex ;
258+ int remainingBytesInCurrentChunk = chunk . Length - this . chunkIndex ;
284259 if ( n >= remainingBytesInCurrentChunk )
285260 {
286261 n = remainingBytesInCurrentChunk ;
287262 moveToNextChunk = true ;
288263 }
289264
290265 // Read n bytes from the current chunk
291- stream . Write ( chunk . Buffer . Memory . Span . Slice ( this . currentChunkIndex , n ) ) ;
266+ stream . Write ( chunk . Buffer . Memory . Span . Slice ( this . chunkIndex , n ) ) ;
292267 bytesToRead -= n ;
293268 bytesRead += n ;
294269
295270 if ( moveToNextChunk )
296271 {
297- this . currentChunkIndex = 0 ;
298- this . currentChunk ++ ;
272+ this . chunkIndex = 0 ;
273+ this . bufferIndex ++ ;
299274 }
300275 else
301276 {
302- this . currentChunkIndex += n ;
277+ this . chunkIndex += n ;
303278 }
304279 }
305280
@@ -338,8 +313,8 @@ protected override void Dispose(bool disposing)
338313 this . memoryChunkBuffer . Dispose ( ) ;
339314 }
340315
341- this . currentChunk = 0 ;
342- this . currentChunkIndex = 0 ;
316+ this . bufferIndex = 0 ;
317+ this . chunkIndex = 0 ;
343318 this . position = 0 ;
344319 this . length = 0 ;
345320 }
@@ -366,8 +341,8 @@ private void SetPosition(long value)
366341 // If the new position is greater than the length of the stream, set the position to the end of the stream
367342 if ( offset > 0 && offset >= this . memoryChunkBuffer . Length )
368343 {
369- this . currentChunk = this . memoryChunkBuffer . ChunkCount - 1 ;
370- this . currentChunkIndex = this . memoryChunkBuffer [ this . currentChunk ] . Length - 1 ;
344+ this . bufferIndex = this . memoryChunkBuffer . ChunkCount - 1 ;
345+ this . chunkIndex = this . memoryChunkBuffer [ this . bufferIndex ] . Length - 1 ;
371346 return ;
372347 }
373348
@@ -386,10 +361,10 @@ private void SetPosition(long value)
386361 currentChunkIndex ++ ;
387362 }
388363
389- this . currentChunk = currentChunkIndex ;
364+ this . bufferIndex = currentChunkIndex ;
390365
391366 // Safe to cast here as we know the offset is less than the chunk length.
392- this . currentChunkIndex = ( int ) offset ;
367+ this . chunkIndex = ( int ) offset ;
393368 }
394369
395370 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
@@ -404,7 +379,7 @@ private void EnsureNotDisposed()
404379 [ MethodImpl ( MethodImplOptions . NoInlining ) ]
405380 private static void ThrowDisposed ( ) => throw new ObjectDisposedException ( nameof ( ChunkedMemoryStream ) , "The stream is closed." ) ;
406381
407- private sealed class MemoryChunkBuffer : IEnumerable < MemoryChunk > , IDisposable
382+ private sealed class MemoryChunkBuffer : IDisposable
408383 {
409384 private readonly List < MemoryChunk > memoryChunks = new ( ) ;
410385 private readonly MemoryAllocator allocator ;
@@ -439,15 +414,19 @@ public void Expand()
439414
440415 public void Dispose ( )
441416 {
442- this . Dispose ( true ) ;
443- GC . SuppressFinalize ( this ) ;
444- }
417+ if ( ! this . isDisposed )
418+ {
419+ foreach ( MemoryChunk chunk in this . memoryChunks )
420+ {
421+ chunk . Dispose ( ) ;
422+ }
445423
446- public IEnumerator < MemoryChunk > GetEnumerator ( )
447- => ( ( IEnumerable < MemoryChunk > ) this . memoryChunks ) . GetEnumerator ( ) ;
424+ this . memoryChunks . Clear ( ) ;
448425
449- IEnumerator IEnumerable . GetEnumerator ( )
450- => ( ( IEnumerable ) this . memoryChunks ) . GetEnumerator ( ) ;
426+ this . Length = 0 ;
427+ this . isDisposed = true ;
428+ }
429+ }
451430
452431 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
453432 private static int GetChunkSize ( int i )
@@ -459,25 +438,6 @@ private static int GetChunkSize(int i)
459438 const int b4M = 1 << 22 ;
460439 return i < 16 ? b128K * ( 1 << ( int ) ( ( uint ) i / 4 ) ) : b4M ;
461440 }
462-
463- private void Dispose ( bool disposing )
464- {
465- if ( ! this . isDisposed )
466- {
467- if ( disposing )
468- {
469- foreach ( MemoryChunk chunk in this . memoryChunks )
470- {
471- chunk . Dispose ( ) ;
472- }
473-
474- this . memoryChunks . Clear ( ) ;
475- }
476-
477- this . Length = 0 ;
478- this . isDisposed = true ;
479- }
480- }
481441 }
482442
483443 private sealed class MemoryChunk : IDisposable
@@ -490,23 +450,13 @@ private sealed class MemoryChunk : IDisposable
490450
491451 public int Length { get ; init ; }
492452
493- private void Dispose ( bool disposing )
453+ public void Dispose ( )
494454 {
495455 if ( ! this . isDisposed )
496456 {
497- if ( disposing )
498- {
499- this . Buffer . Dispose ( ) ;
500- }
501-
457+ this . Buffer . Dispose ( ) ;
502458 this . isDisposed = true ;
503459 }
504460 }
505-
506- public void Dispose ( )
507- {
508- this . Dispose ( disposing : true ) ;
509- GC . SuppressFinalize ( this ) ;
510- }
511461 }
512462}
0 commit comments