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