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