// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace SixLabors.PolygonClipper {
///
/// Pool-backed list of clip vertices reused across clipping operations.
///
internal sealed class VertexPoolList : PooledList
{
///
/// Adds or reuses a clip vertex initialized with the given data.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SweepVertex Add(Vertex point, VertexFlags flags, SweepVertex? prev)
{
this.TryGrow();
SweepVertex poolVertex = this.Items[this.Size];
if (poolVertex == null)
{
poolVertex = new SweepVertex(point, flags, prev);
this.Items[this.Size] = poolVertex;
}
else
{
// Reset pooled state so linked lists are rebuilt safely.
poolVertex.Point = point;
poolVertex.Flags = flags;
poolVertex.Prev = prev;
poolVertex.Next = null;
}
this.Size++;
return poolVertex;
}
}
///
/// Pool-backed list of output points allocated during clipping.
///
internal sealed class OutputPointPoolList : PooledList
{
///
/// Adds or reuses an output point and increments the owning record count.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public OutputPoint Add(Vertex pt, OutputRecord outputRecord)
{
this.TryGrow();
OutputPoint pooledPoint = this.Items[this.Size];
if (pooledPoint == null)
{
pooledPoint = new OutputPoint(pt, outputRecord);
this.Items[this.Size] = pooledPoint;
}
else
{
pooledPoint.Point = pt;
pooledPoint.OutputRecord = outputRecord;
pooledPoint.Next = pooledPoint;
pooledPoint.Prev = pooledPoint;
pooledPoint.HorizontalSegment = null;
}
this.Size++;
outputRecord.OutputPointCount++;
return pooledPoint;
}
}
///
/// Pool-backed list of output records that preserves per-record state between runs.
///
internal sealed class OutputRecordPoolList : PooledList
{
private static readonly List Tombstone = [];
///
/// Adds or reuses an output record with cleared state.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public OutputRecord Add()
{
this.TryGrow();
OutputRecord outputRecord = this.Items[this.Size];
if (outputRecord == null)
{
outputRecord = new OutputRecord();
this.Items[this.Size] = outputRecord;
}
else
{
outputRecord.Index = 0;
outputRecord.OutputPointCount = 0;
outputRecord.Owner = null;
outputRecord.FrontEdge = null;
outputRecord.BackEdge = null;
outputRecord.Points = null;
outputRecord.Bounds = default;
outputRecord.Path.Clear();
outputRecord.Splits?.Clear();
outputRecord.RecursiveSplit = null;
}
this.Size++;
return outputRecord;
}
public override void Clear()
{
base.Clear();
for (int i = 0; i < this.Items.Length; i++)
{
OutputRecord outputRecord = this.Items[i];
if (outputRecord == null || outputRecord.Path == Tombstone)
{
break;
}
// Mark paths so pooled records are not accidentally reused without reset.
outputRecord.Path = Tombstone;
outputRecord.Owner = null;
outputRecord.FrontEdge = null;
outputRecord.BackEdge = null;
outputRecord.Points = null;
outputRecord.RecursiveSplit = null;
}
}
}
///
/// Pool-backed list of horizontal joins used during sweep processing.
///
internal sealed class HorizontalJoinPoolList : PooledList
{
///
/// Adds or reuses a horizontal join entry.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public HorizontalJoin Add(OutputPoint ltor, OutputPoint rtol)
{
this.TryGrow();
HorizontalJoin hJoin = this.Items[this.Size];
if (hJoin == null)
{
hJoin = new HorizontalJoin(ltor, rtol);
this.Items[this.Size] = hJoin;
}
else
{
hJoin.LeftToRight = ltor;
hJoin.RightToLeft = rtol;
}
this.Size++;
return hJoin;
}
}
///
/// Pool-backed list of sweep events reused between clipping runs.
///
internal sealed class SweepEventPoolList : PooledList
{
///
/// Adds a sweep event to the active range.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(SweepEvent sweepEvent)
{
this.TryGrow();
this.Items[this.Size] = sweepEvent;
this.Size++;
}
}
///
/// Base class for pool-backed lists with stable indexing and reuse.
///
///
/// These lists are append-only during a run and reset via to
/// reuse previously allocated storage and object instances. The internal array
/// can grow but never shrinks, so callers should treat
/// as a long-lived pool size. Elements are only valid in the range
/// [0, Count); indices remain stable for the lifetime of a run, which allows
/// pooled nodes to store indices instead of references when needed.
///
internal abstract class PooledList : IReadOnlyList
where T : class
{
private const int DefaultCapacity = 4;
///
/// Initializes a new instance of the class.
///
protected PooledList() => this.Items = [];
///
/// Gets the number of items that have been added during the current run.
///
public int Count => this.Size;
///
/// Gets the backing array used for pooled storage.
///
protected T[] Items { get; private set; }
///
/// Gets or sets the number of active items in the pool.
///
protected int Size { get; set; }
///
/// Gets the current capacity of the pooled storage.
///
public int Capacity
{
get => this.Items.Length;
private set
{
if (value <= this.Items.Length)
{
return;
}
int target = (int)BitOperations.RoundUpToPowerOf2((uint)value);
T[] newItems = new T[target];
if (this.Size > 0)
{
Array.Copy(this.Items, newItems, this.Size);
}
this.Items = newItems;
}
}
///
/// Gets the item at the specified index within the active range.
///
public T this[int index]
{
get
{
DebugGuard.MustBeLessThan((uint)index, (uint)this.Size, nameof(index));
return this.Items[index];
}
}
///
/// Ensures the pool can hold at least items.
///
public void EnsureCapacity(int capacity) => this.Capacity = capacity;
///
/// Resets the active count to zero without clearing the backing array.
///
public virtual void Clear() => this.Size = 0;
///
/// Gets a struct enumerator over the active items.
///
public PooledListEnumerator GetEnumerator() => new(this);
///
IEnumerator IEnumerable.GetEnumerator() => new PooledListEnumerator(this);
///
IEnumerator IEnumerable.GetEnumerator() => new PooledListEnumerator(this);
///
/// Grows the pool by at least one slot, doubling capacity when needed.
///
protected void TryGrow()
{
int newSize = this.Size + 1;
if (newSize <= this.Items.Length)
{
return;
}
int newCapacity = this.Items.Length == 0 ? DefaultCapacity : this.Items.Length * 2;
this.Capacity = newCapacity;
}
///
/// Struct enumerator for iterating active items without allocations.
///
internal struct PooledListEnumerator : IEnumerator
where TItem : class
{
private readonly PooledList list;
private int index;
private TItem? current;
public PooledListEnumerator(PooledList list)
{
this.list = list;
this.index = 0;
this.current = null;
}
public readonly TItem Current => this.current!;
readonly object IEnumerator.Current => this.current!;
public readonly void Dispose()
{
}
public bool MoveNext()
{
int count = this.list.Size;
if ((uint)this.index < (uint)count)
{
this.current = this.list[this.index];
this.index++;
return true;
}
return this.MoveNextRare(count);
}
private bool MoveNextRare(int count)
{
this.index = count + 1;
this.current = null;
return false;
}
public void Reset()
{
this.index = 0;
this.current = null;
}
}
}
}