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