// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.Fonts {
///
/// A helper type for avoiding allocations while building arrays.
///
/// The type of item contained in the array.
internal struct ArrayBuilder
where T : struct
{
private const int DefaultCapacity = 4;
private const int MaxCoreClrArrayLength = 0x7FeFFFFF;
// Starts out null, initialized on first Add.
private T[]? data;
private int size;
///
/// Initializes a new instance of the struct.
///
/// The initial capacity of the array.
public ArrayBuilder(int capacity)
: this()
{
Guard.MustBeGreaterThanOrEqualTo(capacity, 0, nameof(capacity));
this.data = new T[capacity];
}
///
/// Gets or sets the number of items in the array.
///
public int Length
{
readonly get => this.size;
set
{
if (value != this.size)
{
if (value > 0)
{
this.EnsureCapacity(value);
this.size = value;
}
else
{
this.size = 0;
}
}
}
}
///
/// Returns a reference to specified element of the array.
///
/// The index of the element to return.
/// The .
///
/// Thrown when index less than 0 or index greater than or equal to .
///
public readonly ref T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
DebugGuard.MustBeBetweenOrEqualTo(index, 0, this.size, nameof(index));
return ref this.data![index];
}
}
///
/// Adds the given item to the array.
///
/// The item to add.
public void Add(T item)
{
int position = this.size;
// Expand the array.
this.Length++;
this.data![position] = item;
}
///
/// Appends a given number of empty items to the array returning
/// the items as a slice.
///
/// The number of items in the slice.
/// Whether to clear the new slice, Defaults to .
/// The .
public ArraySlice Add(int length, bool clear = true)
{
int position = this.size;
// Expand the array.
this.Length += length;
ArraySlice slice = this.AsSlice(position, this.Length - position);
if (clear)
{
slice.Span.Clear();
}
return slice;
}
///
/// Appends the slice to the array copying the data across.
///
/// The array slice.
/// The .
public ArraySlice Add(in ReadOnlyArraySlice value)
{
int position = this.size;
// Expand the array.
this.Length += value.Length;
ArraySlice slice = this.AsSlice(position, this.Length - position);
value.CopyTo(slice);
return slice;
}
///
/// Clears the array.
/// Allocated memory is left intact for future usage.
///
public void Clear() =>
// No need to actually clear since we're not allowing reference types.
this.size = 0;
private void EnsureCapacity(int min)
{
int length = this.data?.Length ?? 0;
if (length < min)
{
// Same expansion algorithm as List.
uint newCapacity = length == 0 ? DefaultCapacity : (uint)length * 2u;
if (newCapacity > MaxCoreClrArrayLength)
{
newCapacity = MaxCoreClrArrayLength;
}
if (newCapacity < min)
{
newCapacity = (uint)min;
}
var array = new T[newCapacity];
if (this.size > 0)
{
Array.Copy(this.data!, array, this.size);
}
this.data = array;
}
}
///
/// Returns the current state of the array as a slice.
///
/// The .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArraySlice AsSlice() => this.AsSlice(this.Length);
///
/// Returns the current state of the array as a slice.
///
/// The number of items in the slice.
/// The .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly ArraySlice AsSlice(int length)
=> new(this.data!, 0, length);
///
/// Returns the current state of the array as a slice.
///
/// The index at which to begin the slice.
/// The number of items in the slice.
/// The .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly ArraySlice AsSlice(int start, int length)
=> new(this.data!, start, length);
}
}