SparseBufferWriter<T>
public class SparseBufferWriter<T> : Disposable, IGrowableBuffer<T>, IReadOnlySpanConsumer<T>, ISupplier<ReadOnlyMemory<T>, CancellationToken, ValueTask>, IFunctional<Func<ReadOnlyMemory<T>, CancellationToken, ValueTask>>, IDisposable, IResettable, ISupplier<ReadOnlySequence<T>>, IFunctional<Func<ReadOnlySequence<T>>>, IEnumerable<ReadOnlyMemory<T>>, IEnumerable, IBufferWriter<T>
Represents builder of the sparse memory buffer.
using DotNext.Collections.Generic;
using DotNext.Runtime.CompilerServices;
using System;
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace DotNext.Buffers
{
[System.Runtime.CompilerServices.NullableContext(1)]
[System.Runtime.CompilerServices.Nullable(0)]
[DebuggerDisplay("WrittenCount = {WrittenCount}, FragmentedBytes = {FragmentedBytes}")]
public class SparseBufferWriter<[System.Runtime.CompilerServices.Nullable(2)] T> : Disposable, IGrowableBuffer<T>, IReadOnlySpanConsumer<T>, ISupplier<ReadOnlyMemory<T>, CancellationToken, ValueTask>, IFunctional<Func<ReadOnlyMemory<T>, CancellationToken, ValueTask>>, IDisposable, IResettable, ISupplier<ReadOnlySequence<T>>, IFunctional<Func<ReadOnlySequence<T>>>, IEnumerable<ReadOnlyMemory<T>>, IEnumerable, IBufferWriter<T>
{
internal abstract class MemoryChunk : Disposable
{
internal abstract int FreeCapacity { get; }
internal abstract Memory<T> FreeMemory { get; }
internal abstract ReadOnlyMemory<T> WrittenMemory { get; }
internal MemoryChunk Next { get; set; }
internal SequencePosition StartOfNextChunk => new SequencePosition(Next, 0);
internal SequencePosition EndOfChunk => new SequencePosition(this, WrittenMemory.Length);
private protected MemoryChunk(MemoryChunk previous)
{
if (previous != null)
previous.Next = this;
}
internal abstract int Write(ReadOnlySpan<T> input);
protected override void Dispose(bool disposing)
{
if (disposing)
Next = null;
base.Dispose(disposing);
}
}
private sealed class ImportedMemoryChunk : MemoryChunk
{
internal override ReadOnlyMemory<T> WrittenMemory { get; }
internal override int FreeCapacity => 0;
internal override Memory<T> FreeMemory => Memory<T>.Empty;
internal ImportedMemoryChunk(ReadOnlyMemory<T> memory, MemoryChunk previous = null)
: base(previous)
{
WrittenMemory = memory;
}
internal override int Write(ReadOnlySpan<T> input)
{
return 0;
}
}
private sealed class PooledMemoryChunk : MemoryChunk
{
private MemoryOwner<T> owner;
private int writtenCount;
internal bool IsUnused => writtenCount == 0;
internal override Memory<T> FreeMemory => owner.Memory.Slice(writtenCount);
internal override int FreeCapacity => owner.Length - writtenCount;
internal override ReadOnlyMemory<T> WrittenMemory => owner.Memory.Slice(0, writtenCount);
internal PooledMemoryChunk(MemoryAllocator<T> allocator, int length, MemoryChunk previous = null)
: base(previous)
{
owner = MemoryAllocator.Invoke<T>(allocator, length, false);
}
internal override int Write(ReadOnlySpan<T> input)
{
Span.CopyTo<T>(input, FreeMemory.Span, out int num);
writtenCount += num;
return num;
}
internal void Advance(int count)
{
int num = writtenCount + count;
if ((uint)num > (uint)owner.Length)
throw new ArgumentOutOfRangeException("count");
writtenCount = num;
}
internal void Realloc(MemoryAllocator<T> allocator, int length)
{
owner.Dispose();
owner = MemoryAllocator.Invoke<T>(allocator, length, false);
}
protected override void Dispose(bool disposing)
{
if (disposing)
owner.Dispose();
writtenCount = 0;
base.Dispose(disposing);
}
}
[StructLayout(LayoutKind.Auto)]
[System.Runtime.CompilerServices.NullableContext(0)]
public struct Enumerator : IEnumerator<ReadOnlyMemory<T>>, IEnumerator, IDisposable
{
private MemoryChunk current;
private bool initialized;
[System.Runtime.CompilerServices.Nullable(new byte[] {
0,
1
})]
public ReadOnlyMemory<T> Current {
[IsReadOnly]
[return: System.Runtime.CompilerServices.Nullable(new byte[] {
0,
1
})]
get {
if (current != null)
return current.WrittenMemory;
return ReadOnlyMemory<T>.Empty;
}
}
[System.Runtime.CompilerServices.Nullable(1)]
object IEnumerator.Current {
[IsReadOnly]
get {
return Current;
}
}
internal Enumerator(MemoryChunk head)
{
current = head;
initialized = false;
}
public bool MoveNext()
{
if (initialized) {
MemoryChunk memoryChunk = current;
current = ((memoryChunk != null) ? memoryChunk.Next : null);
} else
initialized = true;
return current != null;
}
[IsReadOnly]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
void IDisposable.Dispose()
{
this = default(Enumerator);
}
}
private readonly int chunkSize;
private readonly MemoryAllocator<T> allocator;
private readonly IntPtr growth;
private int chunkIndex;
private MemoryChunk first;
private MemoryChunk last;
private long length;
[System.Runtime.CompilerServices.Nullable(new byte[] {
2,
0
})]
internal MemoryChunk FirstChunk {
get {
return first;
}
}
public long WrittenCount {
get {
ThrowIfDisposed();
return length;
}
}
public bool IsSingleSegment => first == last;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
[ExcludeFromCodeCoverage]
private long FragmentedBytes {
get {
long num = 0;
MemoryChunk next;
for (MemoryChunk memoryChunk = first; memoryChunk != null; memoryChunk = next) {
next = memoryChunk.Next;
if (next != null && next.WrittenMemory.Length > 0)
num += memoryChunk.FreeCapacity;
}
return num;
}
}
public SequencePosition End {
get {
MemoryChunk memoryChunk = last;
if (memoryChunk == null)
return default(SequencePosition);
return memoryChunk.EndOfChunk;
}
}
public SequencePosition Start => new SequencePosition(first, 0);
public unsafe SparseBufferWriter(int chunkSize, SparseBufferGrowth growth = SparseBufferGrowth.None, [System.Runtime.CompilerServices.Nullable(new byte[] {
2,
1
})] MemoryAllocator<T> allocator = null)
{
if (chunkSize <= 0)
throw new ArgumentOutOfRangeException("chunkSize");
this.chunkSize = chunkSize;
this.allocator = allocator;
IntPtr intPtr;
switch (growth) {
case SparseBufferGrowth.Linear:
intPtr = (IntPtr)(void*);
break;
case SparseBufferGrowth.Exponential:
intPtr = (IntPtr)(void*);
break;
default:
intPtr = (IntPtr)(void*);
break;
}
this.growth = intPtr;
}
public unsafe SparseBufferWriter(MemoryPool<T> pool)
{
chunkSize = -1;
allocator = MemoryAllocator.ToAllocator<T>(pool);
growth = (IntPtr)(void*);
}
public SparseBufferWriter()
: this(MemoryPool<T>.Shared)
{
}
public bool TryGetWrittenContent([System.Runtime.CompilerServices.Nullable(new byte[] {
0,
1
})] out ReadOnlyMemory<T> segment)
{
ThrowIfDisposed();
if (IsSingleSegment) {
segment = ((first == null) ? ReadOnlyMemory<T>.Empty : first.WrittenMemory);
return true;
}
segment = default(ReadOnlyMemory<T>);
return false;
}
public void Write([System.Runtime.CompilerServices.Nullable(new byte[] {
0,
1
})] ReadOnlySpan<T> input)
{
ThrowIfDisposed();
if (last == null)
first = (last = new PooledMemoryChunk(allocator, chunkSize, null));
while (!input.IsEmpty) {
int num = last.Write(input);
if (num == 0) {
MemoryAllocator<T> memoryAllocator = allocator;
IntPtr intPtr = growth;
last = new PooledMemoryChunk(memoryAllocator, (int), last);
} else
input = input.Slice(num);
length += num;
}
}
public void Write([System.Runtime.CompilerServices.Nullable(new byte[] {
0,
1
})] ReadOnlyMemory<T> input, bool copyMemory = true)
{
ThrowIfDisposed();
if (!input.IsEmpty) {
if (copyMemory)
Write(input.Span);
else if (last == null) {
first = (last = new ImportedMemoryChunk(input, null));
length += input.Length;
} else {
last = new ImportedMemoryChunk(input, last);
length += input.Length;
}
}
}
public void Add(T item)
{
Write(MemoryMarshal.CreateReadOnlySpan<T>(ref item, 1));
}
void IGrowableBuffer<T>.Write(T value)
{
Add(value);
}
public void Write([In] [IsReadOnly] [System.Runtime.CompilerServices.Nullable(new byte[] {
0,
1
})] ref ReadOnlySequence<T> sequence, bool copyMemory = true)
{
ReadOnlySequence<T>.Enumerator enumerator = sequence.GetEnumerator();
while (enumerator.MoveNext()) {
ReadOnlyMemory<T> current = enumerator.Current;
Write(current, copyMemory);
}
}
public void CopyTo<TConsumer>(TConsumer consumer) where TConsumer : IReadOnlySpanConsumer<T>
{
ThrowIfDisposed();
for (MemoryChunk next = this.first; next != null; next = next.Next) {
((IReadOnlySpanConsumer<T>)consumer).Invoke(next.WrittenMemory.Span);
}
}
[AsyncStateMachine(typeof(<DotNext-Buffers-IGrowableBuffer<T>-CopyToAsync>d__25<>))]
ValueTask IGrowableBuffer<T>.CopyToAsync<TConsumer>(TConsumer consumer, CancellationToken token)
{
<DotNext-Buffers-IGrowableBuffer<T>-CopyToAsync>d__25<TConsumer> stateMachine = default(<DotNext-Buffers-IGrowableBuffer<T>-CopyToAsync>d__25<TConsumer>);
stateMachine.<>t__builder = AsyncValueTaskMethodBuilder.Create();
stateMachine.<>4__this = this;
stateMachine.consumer = consumer;
stateMachine.token = token;
stateMachine.<>1__state = -1;
stateMachine.<>t__builder.Start<<DotNext-Buffers-IGrowableBuffer<T>-CopyToAsync>d__25<TConsumer>>(ref stateMachine);
return stateMachine.<>t__builder.Task;
}
public void CopyTo<[System.Runtime.CompilerServices.Nullable(2)] TArg>(ReadOnlySpanAction<T, TArg> writer, TArg arg)
{
CopyTo(new DelegatingReadOnlySpanConsumer<T, TArg>(writer, arg));
}
public int CopyTo([System.Runtime.CompilerServices.Nullable(new byte[] {
0,
1
})] Span<T> output)
{
ThrowIfDisposed();
int num = 0;
MemoryChunk next = first;
while (next != null && !output.IsEmpty) {
Span.CopyTo<T>(next.WrittenMemory.Span, output, out int writtenCount);
output = output.Slice(writtenCount);
num += writtenCount;
next = next.Next;
}
return num;
}
public void Clear()
{
ThrowIfDisposed();
ReleaseChunks();
length = 0;
}
ReadOnlySequence<T> ISupplier<ReadOnlySequence<T>>.Invoke()
{
if (!TryGetWrittenContent(out ReadOnlyMemory<T> segment))
return BufferHelpers.ToReadOnlySequence<T>((IEnumerable<ReadOnlyMemory<T>>)this);
return new ReadOnlySequence<T>(segment);
}
private void ReleaseChunks()
{
MemoryChunk next;
for (MemoryChunk memoryChunk = first; memoryChunk != null; memoryChunk = next) {
next = memoryChunk.Next;
memoryChunk.Dispose();
}
first = (last = null);
}
protected override void Dispose(bool disposing)
{
if (disposing)
ReleaseChunks();
base.Dispose(disposing);
}
public override string ToString()
{
if (!(typeof(T) == typeof(char)) || length > 2147483647)
return GetType().ToString();
return <ToString>g__BuildString|32_1(first, (int)length);
}
public SequencePosition GetPosition(long offset, SequencePosition origin = default(SequencePosition))
{
ThrowIfDisposed();
if (offset == 0)
return origin;
NormalizePosition(ref origin);
while (offset > 0) {
MemoryChunk memoryChunk = origin.GetObject() as MemoryChunk;
if (memoryChunk == null)
break;
long num = memoryChunk.WrittenMemory.Length - origin.GetInteger();
if (offset <= num)
return new SequencePosition(memoryChunk, (int)offset);
offset -= num;
origin = new SequencePosition(memoryChunk.Next, 0);
}
throw new ArgumentOutOfRangeException("offset");
}
private static ReadOnlyMemory<T> Read(ref SequencePosition position, ref long count)
{
MemoryChunk memoryChunk = position.GetObject() as MemoryChunk;
if (memoryChunk == null || count == 0)
return ReadOnlyMemory<T>.Empty;
int integer = position.GetInteger();
ReadOnlyMemory<T> readOnlyMemory = memoryChunk.WrittenMemory.Slice(integer);
if (readOnlyMemory.IsEmpty) {
if (memoryChunk.Next == null)
return ReadOnlyMemory<T>.Empty;
position = memoryChunk.StartOfNextChunk;
return Read(ref position, ref count);
}
if (readOnlyMemory.Length < count) {
position = ((memoryChunk.Next == null) ? memoryChunk.EndOfChunk : memoryChunk.StartOfNextChunk);
count -= readOnlyMemory.Length;
return readOnlyMemory;
}
ReadOnlyMemory<T> result = readOnlyMemory;
result = result.Slice(0, (int)count);
count = 0;
position = new SequencePosition(memoryChunk, integer + result.Length);
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void NormalizePosition([LifetimeAnnotation(true, false)] ref SequencePosition position)
{
if (!(position.GetObject() is MemoryChunk))
position = new SequencePosition(first, position.GetInteger());
}
[return: System.Runtime.CompilerServices.Nullable(new byte[] {
0,
1
})]
public ReadOnlySequence<T> Read(ref SequencePosition position, long count)
{
ThrowIfDisposed();
if (count < 0)
throw new ArgumentOutOfRangeException("count");
NormalizePosition(ref position);
Chunk<T> chunk = null;
Chunk<T> chunk2 = null;
while (true) {
ReadOnlyMemory<T> segment = Read(ref position, ref count);
if (segment.IsEmpty)
break;
Chunk<T>.AddChunk(ref segment, ref chunk, ref chunk2);
}
if (count > 0)
throw new InvalidOperationException(ExceptionMessages.EndOfBuffer(count));
if (chunk == null || chunk2 == null)
return ReadOnlySequence<T>.Empty;
if (chunk == chunk2)
return new ReadOnlySequence<T>(chunk.Memory);
return Chunk<T>.CreateSequence(chunk, chunk2);
}
public int CopyTo([LifetimeAnnotation(false, true)] [System.Runtime.CompilerServices.Nullable(new byte[] {
0,
1
})] Span<T> output, [LifetimeAnnotation(true, false)] ref SequencePosition position)
{
ThrowIfDisposed();
NormalizePosition(ref position);
int num = 0;
long count = output.Length;
while (true) {
ReadOnlyMemory<T> readOnlyMemory = Read(ref position, ref count);
if (readOnlyMemory.IsEmpty)
break;
readOnlyMemory.Span.CopyTo(output);
num += readOnlyMemory.Length;
output = output.Slice(readOnlyMemory.Length);
}
return num;
}
public void CopyTo<TConsumer>(TConsumer consumer, SequencePosition start) where TConsumer : IReadOnlySpanConsumer<T>
{
ThrowIfDisposed();
this.NormalizePosition(ref start);
long count = this.length;
while (true) {
ReadOnlyMemory<T> readOnlyMemory = SparseBufferWriter<T>.Read(ref start, ref count);
if (readOnlyMemory.IsEmpty)
break;
((IReadOnlySpanConsumer<T>)consumer).Invoke(readOnlyMemory.Span);
}
}
public long CopyTo<TConsumer>(TConsumer consumer, [LifetimeAnnotation(true, false)] ref SequencePosition position, long count) where TConsumer : IReadOnlySpanConsumer<T>
{
ThrowIfDisposed();
if (count < 0)
throw new ArgumentOutOfRangeException("count");
this.NormalizePosition(ref position);
long num = 0;
while (true) {
ReadOnlyMemory<T> readOnlyMemory = SparseBufferWriter<T>.Read(ref position, ref count);
if (readOnlyMemory.IsEmpty)
break;
((IReadOnlySpanConsumer<T>)consumer).Invoke(readOnlyMemory.Span);
num += readOnlyMemory.Length;
}
return num;
}
[System.Runtime.CompilerServices.NullableContext(0)]
public Enumerator GetEnumerator()
{
ThrowIfDisposed();
return new Enumerator(first);
}
IEnumerator<ReadOnlyMemory<T>> IEnumerable<ReadOnlyMemory<T>>.GetEnumerator()
{
if (first != null)
return GetEnumerator();
return Sequence.GetEmptyEnumerator<ReadOnlyMemory<T>>();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private Memory<T> GetMemory()
{
MemoryChunk memoryChunk = last;
if (memoryChunk != null) {
if (memoryChunk.FreeCapacity == 0) {
MemoryAllocator<T> memoryAllocator = allocator;
IntPtr intPtr = growth;
last = new PooledMemoryChunk(memoryAllocator, (int), last);
}
} else
first = (last = new PooledMemoryChunk(allocator, chunkSize, null));
return last.FreeMemory;
}
private Memory<T> GetMemory(int sizeHint)
{
ThrowIfDisposed();
if (sizeHint < 0)
throw new ArgumentOutOfRangeException("sizeHint");
if (sizeHint == 0)
return GetMemory();
if (last == null)
first = (last = new PooledMemoryChunk(allocator, sizeHint, null));
else if (last.FreeCapacity == 0) {
last = new PooledMemoryChunk(allocator, sizeHint, last);
} else if (last.FreeCapacity < sizeHint) {
PooledMemoryChunk pooledMemoryChunk = last as PooledMemoryChunk;
if (pooledMemoryChunk != null && pooledMemoryChunk.IsUnused)
pooledMemoryChunk.Realloc(allocator, sizeHint);
else
last = new PooledMemoryChunk(allocator, sizeHint, last);
}
return last.FreeMemory;
}
Memory<T> IBufferWriter<T>.GetMemory(int sizeHint)
{
return GetMemory(sizeHint);
}
Span<T> IBufferWriter<T>.GetSpan(int sizeHint)
{
return GetMemory(sizeHint).Span;
}
void IBufferWriter<T>.Advance(int count)
{
ThrowIfDisposed();
PooledMemoryChunk obj = last as PooledMemoryChunk;
if (obj == null)
throw new InvalidOperationException();
obj.Advance(count);
length += count;
}
}
}