DotNext by .NET Foundation and Contributors

<PackageReference Include="DotNext" Version="4.14.0" />

 SparseBufferWriter<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; } } }