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