fix(physics): seal collision generations before activation

This commit is contained in:
Erik 2026-07-31 15:53:05 +02:00
parent be94bc9b06
commit d94145e6b8
15 changed files with 1556 additions and 410 deletions

View file

@ -182,12 +182,12 @@ public sealed class PhysicsEngine
return staging;
}
internal PreparedPhysicsEngineLandblock PrepareLandblockReplacement(
internal LandblockReplacementBuilder CreateLandblockReplacementBuilder(
PhysicsEngine staging,
uint landblockId,
ReadOnlySpan<uint> gfxObjectIds,
ReadOnlySpan<uint> setupIds,
IReadOnlyDictionary<uint, ulong> expectedDynamicVersions)
uint[] gfxObjectIds,
uint[] setupIds,
IReadOnlyDictionary<uint, ulong> expectedRetainedVersions)
{
ArgumentNullException.ThrowIfNull(staging);
uint canonical = (landblockId & 0xFFFF0000u) | 0xFFFFu;
@ -201,31 +201,25 @@ public sealed class PhysicsEngine
PhysicsDataCache stagingCache = staging.DataCache
?? throw new InvalidOperationException(
"Staging collision engine has no data cache.");
return new PreparedPhysicsEngineLandblock(
return new LandblockReplacementBuilder(
canonical,
landblock,
stagingCache.PrepareLandblockReplacement(
(DataCache ?? throw new InvalidOperationException(
"Active collision engine has no data cache."))
.CreateLandblockReplacementBuilder(
stagingCache,
canonical,
gfxObjectIds,
setupIds),
ShadowObjects.PrepareLandblockReplacement(
ShadowObjects.CreateLandblockReplacementBuilder(
staging.ShadowObjects,
canonical,
expectedDynamicVersions));
expectedRetainedVersions));
}
internal bool ValidateLandblockReplacement(
PreparedPhysicsEngineLandblock replacement) =>
ShadowObjects.ValidateLandblockReplacement(replacement.Shadows);
internal void CommitLandblockReplacement(
PreparedPhysicsEngineLandblock replacement)
{
if (!ValidateLandblockReplacement(replacement))
{
throw new InvalidOperationException(
"Collision generation changed after it was sealed.");
}
(DataCache ?? throw new InvalidOperationException(
"Active collision engine has no data cache."))
.CommitLandblockReplacement(replacement.DataCache);
@ -253,6 +247,64 @@ public sealed class PhysicsEngine
internal ShadowObjectRegistry.PreparedLandblockShadowReplacement Shadows { get; }
}
internal sealed class LandblockReplacementBuilder : IDisposable
{
private readonly uint _landblockId;
private readonly LandblockPhysics _landblock;
private readonly PhysicsDataCache.LandblockReplacementBuilder _data;
private readonly ShadowObjectRegistry.LandblockReplacementBuilder _shadows;
private int _phase;
internal LandblockReplacementBuilder(
uint landblockId,
LandblockPhysics landblock,
PhysicsDataCache.LandblockReplacementBuilder data,
ShadowObjectRegistry.LandblockReplacementBuilder shadows)
{
_landblockId = landblockId;
_landblock = landblock;
_data = data;
_shadows = shadows;
}
internal int WorkUnits => _data.WorkUnits + _shadows.WorkUnits;
internal bool IsStable => _shadows.IsStable;
internal PreparedPhysicsEngineLandblock? Prepared { get; private set; }
internal bool Advance()
{
if (_phase == 0)
{
if (!_data.Advance())
return false;
_phase++;
return false;
}
if (_phase == 1)
{
if (!_shadows.Advance())
return false;
if (IsStable && _data.Prepared is not null
&& _shadows.Prepared is not null)
{
Prepared = new PreparedPhysicsEngineLandblock(
_landblockId,
_landblock,
_data.Prepared,
_shadows.Prepared);
}
_phase++;
}
return true;
}
public void Dispose()
{
_data.Dispose();
_shadows.Dispose();
}
}
/// <summary>
/// Register a landblock with its terrain surface, indoor cells, portal
/// planes, and world-space origin offset.