fix(physics): activate collision generations atomically
This commit is contained in:
parent
3e0f3b6206
commit
be94bc9b06
18 changed files with 1402 additions and 80 deletions
|
|
@ -6,6 +6,7 @@ using DatReaderWriter.Types;
|
|||
using Plane = System.Numerics.Plane;
|
||||
using UcgEnvCell = AcDream.Core.World.Cells.EnvCell;
|
||||
using UcgCellGraph = AcDream.Core.World.Cells.CellGraph;
|
||||
using PreparedCellGraphLandblock = AcDream.Core.World.Cells.PreparedCellGraphLandblock;
|
||||
|
||||
namespace AcDream.Core.Physics;
|
||||
|
||||
|
|
@ -21,6 +22,7 @@ namespace AcDream.Core.Physics;
|
|||
public sealed class PhysicsDataCache
|
||||
{
|
||||
private readonly bool _requirePreparedCollision;
|
||||
private PhysicsDataCache? _readFallback;
|
||||
private readonly ConcurrentDictionary<uint, GfxObjPhysics> _gfxObj = new();
|
||||
private readonly ConcurrentDictionary<uint, GfxObjVisualBounds> _visualBounds = new();
|
||||
private readonly ConcurrentDictionary<uint, SetupPhysics> _setup = new();
|
||||
|
|
@ -92,7 +94,112 @@ public sealed class PhysicsDataCache
|
|||
/// (<c>TryGetTerrainOrigin</c>, read by <c>CellTransit</c>'s pick + transit
|
||||
/// paths). No longer inert.
|
||||
/// </summary>
|
||||
public UcgCellGraph CellGraph { get; } = new();
|
||||
public UcgCellGraph CellGraph { get; private set; } = new();
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
internal PhysicsDataCache CreateCollisionStagingCopy()
|
||||
{
|
||||
var copy = new PhysicsDataCache(_requirePreparedCollision)
|
||||
{
|
||||
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 PreparedPhysicsDataCacheLandblock PrepareLandblockReplacement(
|
||||
uint landblockId,
|
||||
ReadOnlySpan<uint> gfxObjectIds,
|
||||
ReadOnlySpan<uint> setupIds)
|
||||
{
|
||||
uint prefix = landblockId & 0xFFFF0000u;
|
||||
return new PreparedPhysicsDataCacheLandblock(
|
||||
prefix,
|
||||
CaptureRequested(_gfxObj, gfxObjectIds),
|
||||
CaptureRequested(_visualBounds, gfxObjectIds),
|
||||
CaptureRequested(_flatGfxObj, gfxObjectIds),
|
||||
CaptureRequested(_setup, setupIds),
|
||||
CaptureRequested(_flatSetup, setupIds),
|
||||
CapturePrefix(_cellStruct, prefix),
|
||||
CapturePrefix(_flatCellStruct, prefix),
|
||||
CapturePrefix(_flatEnvCell, prefix),
|
||||
CapturePrefix(_buildings, prefix),
|
||||
CellGraph.PrepareLandblockReplacement(prefix));
|
||||
}
|
||||
|
||||
internal void CommitLandblockReplacement(
|
||||
PreparedPhysicsDataCacheLandblock replacement)
|
||||
{
|
||||
RemoveCellsForLandblock(replacement.LandblockPrefix);
|
||||
RemoveBuildingsForLandblock(replacement.LandblockPrefix);
|
||||
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 KeyValuePair<uint, T>[] CaptureRequested<T>(
|
||||
ConcurrentDictionary<uint, T> source,
|
||||
ReadOnlySpan<uint> ids)
|
||||
{
|
||||
var result = new List<KeyValuePair<uint, T>>(ids.Length);
|
||||
for (int index = 0; index < ids.Length; index++)
|
||||
{
|
||||
uint id = ids[index];
|
||||
if (source.TryGetValue(id, out T? value))
|
||||
result.Add(new KeyValuePair<uint, T>(id, value));
|
||||
}
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
private static KeyValuePair<uint, T>[] CapturePrefix<T>(
|
||||
ConcurrentDictionary<uint, T> source,
|
||||
uint prefix) => source
|
||||
.Where(pair => (pair.Key & 0xFFFF0000u) == prefix)
|
||||
.OrderBy(static pair => pair.Key)
|
||||
.ToArray();
|
||||
|
||||
private static void CommitEntries<T>(
|
||||
ConcurrentDictionary<uint, T> destination,
|
||||
KeyValuePair<uint, T>[] entries,
|
||||
bool replace)
|
||||
{
|
||||
foreach ((uint id, T value) in entries)
|
||||
{
|
||||
if (replace)
|
||||
destination[id] = value;
|
||||
else
|
||||
destination.TryAdd(id, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract and cache the physics BSP + polygon data from a GfxObj,
|
||||
|
|
@ -237,7 +344,9 @@ public sealed class PhysicsDataCache
|
|||
/// Get the cached visual AABB for a GfxObj, or null if not cached.
|
||||
/// </summary>
|
||||
public GfxObjVisualBounds? GetVisualBounds(uint gfxObjId) =>
|
||||
_visualBounds.TryGetValue(gfxObjId, out var vb) ? vb : null;
|
||||
_visualBounds.TryGetValue(gfxObjId, out var vb)
|
||||
? vb
|
||||
: _readFallback?.GetVisualBounds(gfxObjId);
|
||||
|
||||
/// <summary>
|
||||
/// Compute a tight axis-aligned bounding box over all vertices in the mesh.
|
||||
|
|
@ -756,14 +865,24 @@ public sealed class PhysicsDataCache
|
|||
$"Production {kind} 0x{sourceId:X8} has no prepared collision asset. " +
|
||||
"Gameplay must not extract or fall back to a parsed DAT graph.");
|
||||
|
||||
public GfxObjPhysics? GetGfxObj(uint id) => _gfxObj.TryGetValue(id, out var p) ? p : null;
|
||||
public GfxObjPhysics? GetGfxObj(uint id) =>
|
||||
_gfxObj.TryGetValue(id, out var p)
|
||||
? p
|
||||
: _readFallback?.GetGfxObj(id);
|
||||
|
||||
public SetupPhysics? GetSetup(uint id) => _setup.TryGetValue(id, out var p) ? p : null;
|
||||
public SetupPhysics? GetSetup(uint id) =>
|
||||
_setup.TryGetValue(id, out var p)
|
||||
? p
|
||||
: _readFallback?.GetSetup(id);
|
||||
public CellPhysics? GetCellStruct(uint id) => _cellStruct.TryGetValue(id, out var p) ? p : null;
|
||||
public FlatGfxObjCollisionAsset? GetFlatGfxObj(uint id) =>
|
||||
_flatGfxObj.TryGetValue(id, out var value) ? value : null;
|
||||
_flatGfxObj.TryGetValue(id, out var value)
|
||||
? value
|
||||
: _readFallback?.GetFlatGfxObj(id);
|
||||
public FlatSetupCollision? GetFlatSetup(uint id) =>
|
||||
_flatSetup.TryGetValue(id, out var value) ? value : null;
|
||||
_flatSetup.TryGetValue(id, out var value)
|
||||
? value
|
||||
: _readFallback?.GetFlatSetup(id);
|
||||
public FlatCellStructureCollisionAsset? GetFlatCellStruct(uint id) =>
|
||||
_flatCellStruct.TryGetValue(id, out var value) ? value : null;
|
||||
public FlatEnvCellTopology? GetFlatEnvCell(uint id) =>
|
||||
|
|
@ -926,6 +1045,19 @@ public sealed class PhysicsDataCache
|
|||
public void RegisterBuildingForTest(uint landcellId, BuildingPhysics b) => _buildings[landcellId] = b;
|
||||
}
|
||||
|
||||
internal sealed record PreparedPhysicsDataCacheLandblock(
|
||||
uint LandblockPrefix,
|
||||
KeyValuePair<uint, GfxObjPhysics>[] GfxObjects,
|
||||
KeyValuePair<uint, GfxObjVisualBounds>[] VisualBounds,
|
||||
KeyValuePair<uint, FlatGfxObjCollisionAsset>[] FlatGfxObjects,
|
||||
KeyValuePair<uint, SetupPhysics>[] Setups,
|
||||
KeyValuePair<uint, FlatSetupCollision>[] FlatSetups,
|
||||
KeyValuePair<uint, CellPhysics>[] Cells,
|
||||
KeyValuePair<uint, FlatCellStructureCollisionAsset>[] FlatCells,
|
||||
KeyValuePair<uint, FlatEnvCellTopology>[] FlatEnvCells,
|
||||
KeyValuePair<uint, BuildingPhysics>[] Buildings,
|
||||
PreparedCellGraphLandblock CellGraph);
|
||||
|
||||
/// <summary>
|
||||
/// Visual AABB of a GfxObj mesh — populated for every cached GfxObj regardless
|
||||
/// of whether it has physics data. Used as a collision fallback shape for
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue