// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Buffers; using System.Collections.Generic; namespace EpicGames.Core { /// /// Class for building byte sequences, similar to StringBuilder. Allocates memory in chunks to avoid copying data. /// public class ByteArrayBuilder : IMemoryWriter { class Chunk { public readonly int RunningIndex; public readonly byte[] Data; public int Length; public Chunk(int runningIndex, int size) { RunningIndex = runningIndex; Data = new byte[size]; } public ReadOnlySpan WrittenSpan => Data.AsSpan(0, Length); public ReadOnlyMemory WrittenMemory => Data.AsMemory(0, Length); } readonly List _chunks = new List(); readonly int _chunkSize; Chunk _currentChunk; /// /// Length of the current sequence /// public int Length { get; private set; } /// /// Constructor /// /// public ByteArrayBuilder(int chunkSize) : this(chunkSize, chunkSize) { } /// /// Constructor /// /// Size of the initial chunk /// Default size for subsequent chunks public ByteArrayBuilder(int initialSize = 4096, int chunkSize = 4096) { _currentChunk = new Chunk(0, initialSize); _chunks.Add(_currentChunk); _chunkSize = chunkSize; } /// /// Appends a single byte to the sequence /// /// Byte to add public void WriteByte(byte value) { Span target = GetWritableSpan(1); target[0] = value; } /// /// Appends a span of bytes to the buffer /// /// Bytes to add public void Append(ReadOnlySpan span) { Span target = GetWritableSpan(span.Length); span.CopyTo(target); } /// /// Appends a sequence of bytes to the buffer /// /// Sequence to append public void Append(ReadOnlySequence sequence) { Span target = GetWritableSpan((int)sequence.Length); sequence.CopyTo(target); } /// public Memory GetMemory(int minSize) { if (_currentChunk.Length + minSize > _currentChunk.Data.Length) { _currentChunk = new Chunk(_currentChunk.RunningIndex + _currentChunk.Length, Math.Max(minSize, _chunkSize)); _chunks.Add(_currentChunk); } return _currentChunk.Data.AsMemory(_currentChunk.Length, minSize); } /// public void Advance(int length) { _currentChunk.Length += length; Length += length; } /// /// Gets a span of bytes to write to, and increases the length of the buffer /// /// Length of the buffer /// Writable span of bytes public Span GetWritableSpan(int length) { if (_currentChunk.Length + length > _currentChunk.Data.Length) { _currentChunk = new Chunk(_currentChunk.RunningIndex + _currentChunk.Length, Math.Max(length, _chunkSize)); _chunks.Add(_currentChunk); } Span span = _currentChunk.Data.AsSpan(_currentChunk.Length, length); _currentChunk.Length += length; Length += length; return span; } /// /// Appends data in this builder to the given sequence /// /// Sequence builder public void AppendTo(ReadOnlySequenceBuilder builder) { foreach (Chunk chunk in _chunks) { builder.Append(chunk.WrittenMemory); } } /// /// Gets a sequence representing the bytes that have been written so far /// /// Sequence of bytes public ReadOnlySequence AsSequence() { ReadOnlySequenceBuilder builder = new ReadOnlySequenceBuilder(); AppendTo(builder); return builder.Construct(); } /// /// Gets a sequence representing the bytes that have been written so far, starting at the given offset /// /// Offset to start from /// Sequence of bytes public ReadOnlySequence AsSequence(int offset) { // TODO: could do a binary search for offset and work forwards from there return AsSequence().Slice(offset); } /// /// Gets a sequence representing the bytes that have been written so far, starting at the given offset and /// /// Offset to start from /// Length of the sequence to return /// Sequence of bytes public ReadOnlySequence AsSequence(int offset, int length) { // TODO: could do a binary search for offset and work fowards from there return AsSequence().Slice(offset, length); } /// /// Copies the data to the given output span /// /// Span to write to public void CopyTo(Span span) { foreach (Chunk chunk in _chunks) { Span output = span.Slice(chunk.RunningIndex); chunk.WrittenSpan.CopyTo(output); } } /// /// Create a byte array from the sequence /// /// Byte array containing the current buffer data public byte[] ToByteArray() { byte[] data = new byte[Length]; CopyTo(data); return data; } } }