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