Skip to content

Commit 6301662

Browse files
Feedback updates and massively expand write tests
1 parent b74d2e4 commit 6301662

File tree

2 files changed

+119
-117
lines changed

2 files changed

+119
-117
lines changed

src/ImageSharp/IO/ChunkedMemoryStream.cs

Lines changed: 49 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the Six Labors Split License.
33

44
using System.Buffers;
5-
using System.Collections;
65
using System.Runtime.CompilerServices;
76
using System.Runtime.InteropServices;
87
using 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

Comments
 (0)