using System.Collections.Concurrent; using AcDream.Core.World.Cells; namespace AcDream.Core.Physics; /// /// Per-landblock installed-key ledger for one collision-world map. Slot lists /// mirror the shadow registry's prefix-owner-slot idiom: removal tombstones a /// slot (key 0) so an in-flight metered cursor retains its captured list /// reference and observes only tombstones, and an emptied container is /// reclaimed so a future install gets a fresh compact list. Maintained by the /// typed install/remove helpers; consumed by /// the landblock-replacement seal so capturing one prefix's keys never scans /// the whole resident world (O1 of the 2026-08-02 collision /// publication-throughput fix). /// internal sealed class PrefixKeyIndex { private readonly Dictionary> _slots = new(); private readonly Dictionary> _indices = new(); private readonly Dictionary> _freeSlots = new(); internal void Add(uint key) { uint prefix = key & 0xFFFF0000u; if (!_slots.TryGetValue(prefix, out List? slots)) { slots = new List(); _slots[prefix] = slots; _indices[prefix] = new Dictionary(); _freeSlots[prefix] = new Stack(); } Dictionary indices = _indices[prefix]; if (indices.ContainsKey(key)) return; if (_freeSlots[prefix].TryPop(out int freeIndex)) { slots[freeIndex] = key; indices[key] = freeIndex; return; } indices[key] = slots.Count; slots.Add(key); } internal void Remove(uint key) { uint prefix = key & 0xFFFF0000u; if (!_indices.TryGetValue(prefix, out Dictionary? indices) || !indices.Remove(key, out int slotIndex)) { return; } _slots[prefix][slotIndex] = 0u; _freeSlots[prefix].Push(slotIndex); if (indices.Count != 0) return; // An in-flight seal cursor retains its captured List reference and // observes only tombstones. A future install gets a fresh container. _slots.Remove(prefix); _indices.Remove(prefix); _freeSlots.Remove(prefix); } /// /// The live slot list for one landblock prefix, or null when no key is /// installed. Callers capture the reference plus Count once and /// iterate by index, skipping tombstone slots (key 0). /// internal List? SlotsForPrefix(uint prefix) => _slots.TryGetValue(prefix & 0xFFFF0000u, out List? slots) ? slots : null; internal int InstalledKeyCountForPrefix(uint prefix) => _indices.TryGetValue(prefix & 0xFFFF0000u, out var indices) ? indices.Count : 0; } /// /// One exclusive-by-ownership collision-world root. A preparation mutates only /// its private root; activation transfers the complete root through one volatile /// reference publication shared by every collision facade. /// internal sealed class CollisionWorldState { internal Dictionary Landblocks { get; } = new(); internal List LandblockSlots { get; } = new(); internal Dictionary LandblockIndices { get; } = new(); internal Stack LandblockFreeSlots { get; } = new(); internal ConcurrentDictionary CellStruct { get; } = new(); internal ConcurrentDictionary FlatCellStruct { get; } = new(); internal ConcurrentDictionary FlatEnvCell { get; } = new(); internal ConcurrentDictionary Buildings { get; } = new(); internal ConcurrentDictionary EnvCells { get; } = new(); internal ConcurrentDictionary Terrain { get; } = new(); internal ConcurrentDictionary OutdoorCells { get; } = new(); internal Dictionary> ShadowCells { get; } = new(); internal Dictionary> ShadowEntityCells { get; } = new(); internal HashSet SuspendedShadowEntities { get; } = new(); internal Dictionary> SuspendedShadowEntityCells { get; } = new(); internal Dictionary> WithdrawnPrefixesByOwner { get; } = new(); internal Dictionary> ShadowEntityShapes { get; } = new(); internal Dictionary ShadowEntityRegistrations { get; } = new(); internal Dictionary ShadowOwnerVersions { get; } = new(); internal Dictionary> ShadowOwnerPrefixes { get; } = new(); internal Dictionary> ShadowPrefixOwnerSlots { get; } = new(); internal Dictionary> ShadowPrefixOwnerIndices { get; } = new(); internal Dictionary> ShadowPrefixFreeSlots { get; } = new(); internal List ShadowOwnerSlots { get; } = new(); internal Dictionary ShadowOwnerIndices { get; } = new(); internal Stack ShadowOwnerFreeSlots { get; } = new(); // ── O1 per-prefix installed-key ledgers ──────────────────────────────── // Every mutation of the five landblock-scoped world maps goes through the // typed helpers below so these ledgers stay exact. The seal's landblock- // replacement builders enumerate one prefix's keys instead of scanning the // whole resident map, and the retirement/removal paths retire one prefix // in O(prefix keys). internal PrefixKeyIndex CellStructKeys { get; } = new(); internal PrefixKeyIndex FlatCellStructKeys { get; } = new(); internal PrefixKeyIndex FlatEnvCellKeys { get; } = new(); internal PrefixKeyIndex BuildingKeys { get; } = new(); internal PrefixKeyIndex EnvCellKeys { get; } = new(); internal void SetCellStruct(uint id, CellPhysics value) { CellStruct[id] = value; CellStructKeys.Add(id); } internal bool TryAddCellStruct(uint id, CellPhysics value) { if (!CellStruct.TryAdd(id, value)) return false; CellStructKeys.Add(id); return true; } internal bool RemoveCellStruct(uint id) { if (!CellStruct.TryRemove(id, out _)) return false; CellStructKeys.Remove(id); return true; } internal void SetFlatCellStruct(uint id, FlatCellStructureCollisionAsset value) { FlatCellStruct[id] = value; FlatCellStructKeys.Add(id); } internal bool TryAddFlatCellStruct(uint id, FlatCellStructureCollisionAsset value) { if (!FlatCellStruct.TryAdd(id, value)) return false; FlatCellStructKeys.Add(id); return true; } internal bool RemoveFlatCellStruct(uint id) { if (!FlatCellStruct.TryRemove(id, out _)) return false; FlatCellStructKeys.Remove(id); return true; } internal void SetFlatEnvCell(uint id, FlatEnvCellTopology value) { FlatEnvCell[id] = value; FlatEnvCellKeys.Add(id); } internal bool TryAddFlatEnvCell(uint id, FlatEnvCellTopology value) { if (!FlatEnvCell.TryAdd(id, value)) return false; FlatEnvCellKeys.Add(id); return true; } internal bool RemoveFlatEnvCell(uint id) { if (!FlatEnvCell.TryRemove(id, out _)) return false; FlatEnvCellKeys.Remove(id); return true; } internal void SetBuilding(uint id, BuildingPhysics value) { Buildings[id] = value; BuildingKeys.Add(id); } internal bool TryAddBuilding(uint id, BuildingPhysics value) { if (!Buildings.TryAdd(id, value)) return false; BuildingKeys.Add(id); return true; } internal bool RemoveBuilding(uint id) { if (!Buildings.TryRemove(id, out _)) return false; BuildingKeys.Remove(id); return true; } internal void SetEnvCell(uint id, EnvCell value) { EnvCells[id] = value; EnvCellKeys.Add(id); } internal bool TryAddEnvCell(uint id, EnvCell value) { if (!EnvCells.TryAdd(id, value)) return false; EnvCellKeys.Add(id); return true; } internal bool RemoveEnvCell(uint id) { if (!EnvCells.TryRemove(id, out _)) return false; EnvCellKeys.Remove(id); return true; } } /// /// Stable indirection shared by PhysicsEngine, PhysicsDataCache, CellGraph, /// and ShadowObjectRegistry. Readers observe either complete root, never a /// mixture assembled by several facade assignments. /// internal sealed class CollisionWorldStateSlot { private CollisionWorldState? _current = new(); private bool _revoked; internal CollisionWorldStateSlot() { } internal CollisionWorldStateSlot(CollisionWorldState current) { _current = current ?? throw new ArgumentNullException(nameof(current)); } internal CollisionWorldState Current { get { if (_revoked) throw new ObjectDisposedException("Transferred collision generation"); return Volatile.Read(ref _current) ?? throw new ObjectDisposedException("Transferred collision generation"); } } internal CollisionWorldState TransferTo(CollisionWorldStateSlot destination) { ArgumentNullException.ThrowIfNull(destination); if (_revoked) throw new ObjectDisposedException("Transferred collision generation"); CollisionWorldState transferred = _current ?? throw new ObjectDisposedException("Transferred collision generation"); _revoked = true; Volatile.Write(ref destination._current, transferred); _current = null; return transferred; } /// /// O2 (2026-08-02): terminally revokes a consumed staging root. The /// per-landblock delta commit installs the staged content into the active /// root instead of swapping roots, so the staging root no longer becomes /// the active root — but a committed preparation must still lose access to /// its private world exactly as the old transfer revoked it. /// internal void Revoke() { _revoked = true; _current = null; } internal CollisionWorldState Capture() => Current; }