acdream/src/AcDream.Core/Physics/CellArray.cs

69 lines
2.7 KiB
C#

using System.Collections;
using System.Collections.Generic;
namespace AcDream.Core.Physics;
/// <summary>
/// Ordered, deduped cell-id collection — a faithful model of retail's CELLARRAY
/// (<c>CELLARRAY::add_cell</c> @ <c>acclient_2013_pseudo_c.txt:701036</c>: linear
/// dedup by cell_id, append at the END, insertion order preserved). The order is
/// load-bearing for <c>CObjCell::find_cell_list</c>'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
/// <see cref="HashSet{T}"/> the candidate build used to use.
///
/// <para>Implements <see cref="ICollection{T}"/> (so the candidate-building
/// helpers can take it where they used to take <c>HashSet&lt;uint&gt;</c>) and
/// <see cref="IReadOnlyCollection{T}"/> (so it satisfies the
/// <c>out IReadOnlyCollection&lt;uint&gt;</c> on <c>FindCellSet</c> and the
/// <c>CheckOtherCells</c> / diagnostics consumers). Enumeration is always
/// insertion order.</para>
/// </summary>
public sealed class CellArray : ICollection<uint>, IReadOnlyCollection<uint>
{
private readonly List<uint> _order = new();
private readonly HashSet<uint> _seen = new();
/// <summary>
/// Optional append-only union target used by one retained SetPosition
/// transaction. Clearing this CELLARRAY must not clear the target: retail
/// can rebuild the working array repeatedly while every probed cell still
/// contributes to the transaction's collision-world authority footprint.
/// </summary>
internal CellArray? UnionTarget { get; set; }
public int Count => _order.Count;
public bool IsReadOnly => false;
/// <summary>Ordered cell ids; index 0 is the cell added first (the current cell).</summary>
public IReadOnlyList<uint> OrderedIds => _order;
/// <summary>Append <paramref name="id"/> iff not already present (retail add_cell dedup).</summary>
public void Add(uint id)
{
if (UnionTarget is { } target
&& !ReferenceEquals(target, this))
{
target.Add(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<uint> GetEnumerator() => _order.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _order.GetEnumerator();
}