using System.Collections; using System.Collections.Generic; namespace AcDream.Core.Physics; /// /// Ordered, deduped cell-id collection — a faithful model of retail's CELLARRAY /// (CELLARRAY::add_cell @ acclient_2013_pseudo_c.txt:701036: linear /// dedup by cell_id, append at the END, insertion order preserved). The order is /// load-bearing for CObjCell::find_cell_list's current-cell-first, /// interior-wins pick (pc:308742) — the current cell is added at index 0 and the /// pick iterates in order, so the current cell wins a boundary straddle and the /// membership does not ping-pong (the R1 flap fix). Replaces the unordered /// the candidate build used to use. /// /// Implements (so the candidate-building /// helpers can take it where they used to take HashSet<uint>) and /// (so it satisfies the /// out IReadOnlyCollection<uint> on FindCellSet and the /// CheckOtherCells / diagnostics consumers). Enumeration is always /// insertion order. /// public sealed class CellArray : ICollection, IReadOnlyCollection { private readonly List _order = new(); private readonly HashSet _seen = new(); public int Count => _order.Count; public bool IsReadOnly => false; /// Ordered cell ids; index 0 is the cell added first (the current cell). public IReadOnlyList OrderedIds => _order; /// Append iff not already present (retail add_cell dedup). public void Add(uint id) { if (_seen.Add(id)) _order.Add(id); } public bool Contains(uint id) => _seen.Contains(id); public void Clear() { _order.Clear(); _seen.Clear(); } public bool Remove(uint id) { if (!_seen.Remove(id)) return false; _order.Remove(id); return true; } public void CopyTo(uint[] array, int arrayIndex) => _order.CopyTo(array, arrayIndex); public IEnumerator GetEnumerator() => _order.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => _order.GetEnumerator(); }