fix(physics): make collision activation starvation-free
This commit is contained in:
parent
d94145e6b8
commit
6b28ff999c
14 changed files with 4637 additions and 474 deletions
|
|
@ -23,18 +23,20 @@ public sealed class PhysicsDataCache
|
|||
{
|
||||
private readonly bool _requirePreparedCollision;
|
||||
private PhysicsDataCache? _readFallback;
|
||||
private readonly CollisionWorldStateSlot _collisionWorld;
|
||||
private readonly ConcurrentDictionary<uint, GfxObjPhysics> _gfxObj = new();
|
||||
private readonly ConcurrentDictionary<uint, GfxObjVisualBounds> _visualBounds = new();
|
||||
private readonly ConcurrentDictionary<uint, SetupPhysics> _setup = new();
|
||||
private readonly ConcurrentDictionary<uint, CellPhysics> _cellStruct = new();
|
||||
private ConcurrentDictionary<uint, CellPhysics> _cellStruct =>
|
||||
_collisionWorld.Current.CellStruct;
|
||||
private readonly ConcurrentDictionary<uint, FlatGfxObjCollisionAsset>
|
||||
_flatGfxObj = new();
|
||||
private readonly ConcurrentDictionary<uint, FlatSetupCollision>
|
||||
_flatSetup = new();
|
||||
private readonly ConcurrentDictionary<uint, FlatCellStructureCollisionAsset>
|
||||
_flatCellStruct = new();
|
||||
private readonly ConcurrentDictionary<uint, FlatEnvCellTopology>
|
||||
_flatEnvCell = new();
|
||||
private ConcurrentDictionary<uint, FlatCellStructureCollisionAsset>
|
||||
_flatCellStruct => _collisionWorld.Current.FlatCellStruct;
|
||||
private ConcurrentDictionary<uint, FlatEnvCellTopology>
|
||||
_flatEnvCell => _collisionWorld.Current.FlatEnvCell;
|
||||
|
||||
public PhysicsDataCache()
|
||||
: this(requirePreparedCollision: false)
|
||||
|
|
@ -42,8 +44,18 @@ public sealed class PhysicsDataCache
|
|||
}
|
||||
|
||||
private PhysicsDataCache(bool requirePreparedCollision)
|
||||
: this(requirePreparedCollision, new CollisionWorldStateSlot())
|
||||
{
|
||||
}
|
||||
|
||||
private PhysicsDataCache(
|
||||
bool requirePreparedCollision,
|
||||
CollisionWorldStateSlot collisionWorld)
|
||||
{
|
||||
_requirePreparedCollision = requirePreparedCollision;
|
||||
_collisionWorld = collisionWorld
|
||||
?? throw new ArgumentNullException(nameof(collisionWorld));
|
||||
CellGraph = new UcgCellGraph(_collisionWorld);
|
||||
if (!requirePreparedCollision
|
||||
&& PhysicsDiagnostics.CollisionShadowSampleEvery > 0)
|
||||
{
|
||||
|
|
@ -67,6 +79,18 @@ public sealed class PhysicsDataCache
|
|||
return cache;
|
||||
}
|
||||
|
||||
internal static PhysicsDataCache CreateProduction(
|
||||
CollisionWorldStateSlot collisionWorld)
|
||||
{
|
||||
var cache = new PhysicsDataCache(
|
||||
requirePreparedCollision: true,
|
||||
collisionWorld)
|
||||
{
|
||||
CollisionTraversalMode = CollisionTraversalMode.Flat,
|
||||
};
|
||||
return cache;
|
||||
}
|
||||
|
||||
internal CollisionShadowVerifier? CollisionShadow { get; set; }
|
||||
|
||||
public CollisionShadowStats CollisionShadowStats =>
|
||||
|
|
@ -80,7 +104,8 @@ public sealed class PhysicsDataCache
|
|||
CollisionTraversalMode.Graph;
|
||||
|
||||
// ── Phase 2: building portal cache for outdoor→indoor entry ───────────
|
||||
private readonly ConcurrentDictionary<uint, BuildingPhysics> _buildings = new();
|
||||
private ConcurrentDictionary<uint, BuildingPhysics> _buildings =>
|
||||
_collisionWorld.Current.Buildings;
|
||||
|
||||
/// <summary>
|
||||
/// The unified cell graph (UCG): the active id->cell resolver and registry.
|
||||
|
|
@ -94,31 +119,23 @@ public sealed class PhysicsDataCache
|
|||
/// (<c>TryGetTerrainOrigin</c>, read by <c>CellTransit</c>'s pick + transit
|
||||
/// paths). No longer inert.
|
||||
/// </summary>
|
||||
public UcgCellGraph CellGraph { get; private set; } = new();
|
||||
public UcgCellGraph CellGraph { get; }
|
||||
|
||||
internal CollisionWorldStateSlot CollisionWorld => _collisionWorld;
|
||||
|
||||
/// <summary>
|
||||
/// Copies the currently committed immutable collision records into an
|
||||
/// off-side cache. Streaming may replace one landblock in this copy over
|
||||
/// many frames without exposing a partially withdrawn cell graph to live
|
||||
/// physics queries.
|
||||
/// Creates the empty off-side facade used by Runtime's metered root
|
||||
/// materializer. Global immutable catalogs fall through to this cache;
|
||||
/// mutable world topology is installed one leaf per host step.
|
||||
/// </summary>
|
||||
internal PhysicsDataCache CreateCollisionStagingCopy()
|
||||
internal PhysicsDataCache CreateEmptyCollisionStaging(
|
||||
CollisionWorldStateSlot collisionWorld)
|
||||
{
|
||||
var copy = new PhysicsDataCache(_requirePreparedCollision)
|
||||
return new PhysicsDataCache(_requirePreparedCollision, collisionWorld)
|
||||
{
|
||||
CollisionTraversalMode = CollisionTraversalMode,
|
||||
CellGraph = CellGraph.CreateCollisionStagingCopy(),
|
||||
_readFallback = this,
|
||||
};
|
||||
// Global immutable GfxObj/Setup records are not copied wholesale.
|
||||
// The accepted build's exact closure is staged cursor-by-cursor below;
|
||||
// copying the process-retained asset catalog here would turn every
|
||||
// landblock publication into an unbounded frame spike.
|
||||
CopyDictionary(_cellStruct, copy._cellStruct);
|
||||
CopyDictionary(_flatCellStruct, copy._flatCellStruct);
|
||||
CopyDictionary(_flatEnvCell, copy._flatEnvCell);
|
||||
CopyDictionary(_buildings, copy._buildings);
|
||||
return copy;
|
||||
}
|
||||
|
||||
internal LandblockReplacementBuilder CreateLandblockReplacementBuilder(
|
||||
|
|
@ -132,56 +149,6 @@ public sealed class PhysicsDataCache
|
|||
gfxObjectIds,
|
||||
setupIds);
|
||||
|
||||
internal void CommitLandblockReplacement(
|
||||
PreparedPhysicsDataCacheLandblock replacement)
|
||||
{
|
||||
RemoveEntries(_cellStruct, replacement.CellIdsToRemove);
|
||||
RemoveEntries(_flatCellStruct, replacement.FlatCellIdsToRemove);
|
||||
RemoveEntries(_flatEnvCell, replacement.FlatEnvCellIdsToRemove);
|
||||
RemoveEntries(_buildings, replacement.BuildingIdsToRemove);
|
||||
CommitEntries(_gfxObj, replacement.GfxObjects, replace: false);
|
||||
CommitEntries(_visualBounds, replacement.VisualBounds, replace: false);
|
||||
CommitEntries(_flatGfxObj, replacement.FlatGfxObjects, replace: false);
|
||||
CommitEntries(_setup, replacement.Setups, replace: false);
|
||||
CommitEntries(_flatSetup, replacement.FlatSetups, replace: false);
|
||||
CommitEntries(_cellStruct, replacement.Cells, replace: true);
|
||||
CommitEntries(_flatCellStruct, replacement.FlatCells, replace: true);
|
||||
CommitEntries(_flatEnvCell, replacement.FlatEnvCells, replace: true);
|
||||
CommitEntries(_buildings, replacement.Buildings, replace: true);
|
||||
CellGraph.CommitLandblockReplacement(replacement.CellGraph);
|
||||
}
|
||||
|
||||
private static void CopyDictionary<T>(
|
||||
ConcurrentDictionary<uint, T> source,
|
||||
ConcurrentDictionary<uint, T> destination)
|
||||
{
|
||||
foreach ((uint id, T value) in source)
|
||||
destination.TryAdd(id, value);
|
||||
}
|
||||
|
||||
private static void CommitEntries<T>(
|
||||
ConcurrentDictionary<uint, T> destination,
|
||||
IReadOnlyList<KeyValuePair<uint, T>> entries,
|
||||
bool replace)
|
||||
{
|
||||
for (int index = 0; index < entries.Count; index++)
|
||||
{
|
||||
(uint id, T value) = entries[index];
|
||||
if (replace)
|
||||
destination[id] = value;
|
||||
else
|
||||
destination.TryAdd(id, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemoveEntries<T>(
|
||||
ConcurrentDictionary<uint, T> destination,
|
||||
IReadOnlyList<uint> ids)
|
||||
{
|
||||
for (int index = 0; index < ids.Count; index++)
|
||||
destination.TryRemove(ids[index], out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract and cache the physics BSP + polygon data from a GfxObj,
|
||||
/// PLUS always cache a visual AABB from the vertex data regardless of
|
||||
|
|
@ -1085,6 +1052,9 @@ public sealed class PhysicsDataCache
|
|||
if (_cursor < _gfxIds.Length)
|
||||
{
|
||||
uint id = _gfxIds[_cursor++];
|
||||
Preinstall(_staging._gfxObj, _active._gfxObj, id);
|
||||
Preinstall(_staging._visualBounds, _active._visualBounds, id);
|
||||
Preinstall(_staging._flatGfxObj, _active._flatGfxObj, id);
|
||||
Capture(_staging._gfxObj, id, _gfx);
|
||||
Capture(_staging._visualBounds, id, _bounds);
|
||||
Capture(_staging._flatGfxObj, id, _flatGfx);
|
||||
|
|
@ -1098,6 +1068,8 @@ public sealed class PhysicsDataCache
|
|||
if (_cursor < _setupIds.Length)
|
||||
{
|
||||
uint id = _setupIds[_cursor++];
|
||||
Preinstall(_staging._setup, _active._setup, id);
|
||||
Preinstall(_staging._flatSetup, _active._flatSetup, id);
|
||||
Capture(_staging._setup, id, _setups);
|
||||
Capture(_staging._flatSetup, id, _flatSetups);
|
||||
WorkUnits++;
|
||||
|
|
@ -1229,6 +1201,15 @@ public sealed class PhysicsDataCache
|
|||
destination.Add(new KeyValuePair<uint, T>(id, value));
|
||||
}
|
||||
|
||||
private static void Preinstall<T>(
|
||||
ConcurrentDictionary<uint, T> source,
|
||||
ConcurrentDictionary<uint, T> destination,
|
||||
uint id)
|
||||
{
|
||||
if (source.TryGetValue(id, out T? value))
|
||||
destination.TryAdd(id, value);
|
||||
}
|
||||
|
||||
private static bool CapturePrefixOne<T>(
|
||||
IEnumerator<KeyValuePair<uint, T>> enumerator,
|
||||
uint prefix,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue