// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace SixLabors.PolygonClipper { /// /// 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; // 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() { if (capacity > 0) { 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 > 0) { this.EnsureCapacity(value); this.size = value; } else { this.size = 0; } } } /// /// Gets the backing buffer capacity. /// public readonly int Capacity => this.data?.Length ?? 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; T[]? array = this.data; if (array != null && (uint)position < (uint)array.Length) { this.size = position + 1; array[position] = item; } else { this.AddWithResize(item); } } // Non-inline from Add to improve its code quality as uncommon path [MethodImpl(MethodImplOptions.NoInlining)] private void AddWithResize(T item) { int size = this.size; this.Grow(size + 1); this.size = size + 1; this.data[size] = item; } /// /// Remove the last item from the array. /// public void RemoveLast() { DebugGuard.MustBeGreaterThan(this.size, 0, nameof(this.size)); this.size--; } /// /// 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; /// /// Sorts the active range of items using the specified comparer. /// /// The comparer to use, or null for the default comparer. [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly void Sort(IComparer? comparer = null) { if (this.size <= 1 || this.data == null) { return; } Array.Sort(this.data, 0, this.size, comparer); } private void EnsureCapacity(int min) { int length = this.data?.Length ?? 0; if (length < min) { this.Grow(min); } } [MemberNotNull(nameof(data))] private void Grow(int capacity) { // Same expansion algorithm as List. int length = this.data?.Length ?? 0; int newCapacity = length == 0 ? DefaultCapacity : length * 2; if ((uint)newCapacity > Array.MaxLength) { newCapacity = Array.MaxLength; } if (newCapacity < capacity) { newCapacity = capacity; } T[] array = new T[newCapacity]; if (this.size > 0) { Array.Copy(this.data!, array, this.size); } this.data = array; } } }