fix(physics): seal collision generations before activation
This commit is contained in:
parent
be94bc9b06
commit
d94145e6b8
15 changed files with 1556 additions and 410 deletions
|
|
@ -68,7 +68,7 @@ public readonly record struct RuntimeCollisionAcknowledgement(
|
|||
|
||||
public readonly record struct RuntimeCollisionGenerationCommit(
|
||||
RuntimeCollisionAcknowledgement Acknowledgement,
|
||||
uint[] DirtyDynamicOwnerIds)
|
||||
uint[] DirtyRetainedOwnerIds)
|
||||
{
|
||||
public bool Committed => Acknowledgement.Ready;
|
||||
}
|
||||
|
|
@ -87,7 +87,11 @@ internal sealed class PreparedLandblockCollisionGeneration : IDisposable
|
|||
{
|
||||
private readonly RuntimePhysicsState _owner;
|
||||
private readonly RuntimeCollisionAdmission _admission;
|
||||
private readonly Dictionary<uint, ulong> _dynamicOwnerVersions = new();
|
||||
private readonly Dictionary<uint, ulong> _retainedOwnerVersions = new();
|
||||
private readonly List<uint> _retainedOwnerIds = new();
|
||||
private ShadowObjectRegistry.RetainedRefloodOwnerScan? _retainedOwnerScan;
|
||||
private PhysicsEngine.LandblockReplacementBuilder? _sealBuilder;
|
||||
private PhysicsEngine.PreparedPhysicsEngineLandblock? _sealedReplacement;
|
||||
private bool _disposed;
|
||||
|
||||
internal PreparedLandblockCollisionGeneration(
|
||||
|
|
@ -106,9 +110,13 @@ internal sealed class PreparedLandblockCollisionGeneration : IDisposable
|
|||
internal PhysicsEngine Engine { get; }
|
||||
internal uint[] GfxObjectIds { get; private set; } = Array.Empty<uint>();
|
||||
internal uint[] SetupIds { get; private set; } = Array.Empty<uint>();
|
||||
internal IReadOnlyDictionary<uint, ulong> DynamicOwnerVersions =>
|
||||
_dynamicOwnerVersions;
|
||||
internal IReadOnlyDictionary<uint, ulong> RetainedOwnerVersions =>
|
||||
_retainedOwnerVersions;
|
||||
internal bool IsDisposed => _disposed;
|
||||
internal bool RetainedOwnerCaptureComplete { get; private set; }
|
||||
internal ulong RetainedOwnerCaptureMutationVersion { get; private set; }
|
||||
internal bool IsSealed => _sealedReplacement is not null;
|
||||
internal ulong SealedShadowMutationVersion { get; private set; }
|
||||
|
||||
internal bool Matches(
|
||||
RuntimePhysicsState owner,
|
||||
|
|
@ -123,26 +131,155 @@ internal sealed class PreparedLandblockCollisionGeneration : IDisposable
|
|||
SetupIds = setupIds ?? throw new ArgumentNullException(nameof(setupIds));
|
||||
}
|
||||
|
||||
internal void RefreshDynamicOwner(uint ownerId)
|
||||
internal void RefreshRetainedOwner(uint ownerId)
|
||||
{
|
||||
EnsureUsable();
|
||||
bool retained = Engine.ShadowObjects.RefreshDynamicOwnerFrom(
|
||||
bool retained = Engine.ShadowObjects.RefreshRetainedOwnerFrom(
|
||||
_owner.Engine.ShadowObjects,
|
||||
ownerId,
|
||||
_admission.LandblockId,
|
||||
out ulong version);
|
||||
if (retained)
|
||||
_dynamicOwnerVersions[ownerId] = version;
|
||||
_retainedOwnerVersions[ownerId] = version;
|
||||
else
|
||||
_dynamicOwnerVersions.Remove(ownerId);
|
||||
_retainedOwnerVersions.Remove(ownerId);
|
||||
}
|
||||
|
||||
internal uint[] FindDirtyDynamicOwners()
|
||||
internal RuntimeCollisionOwnerCaptureStep AdvanceRetainedOwnerCapture()
|
||||
{
|
||||
EnsureUsable();
|
||||
return _owner.Engine.ShadowObjects.FindDirtyDynamicOwners(
|
||||
if (RetainedOwnerCaptureComplete)
|
||||
{
|
||||
return new RuntimeCollisionOwnerCaptureStep(
|
||||
Completed: true,
|
||||
Restarted: false,
|
||||
HasOwner: false,
|
||||
OwnerId: 0u);
|
||||
}
|
||||
_retainedOwnerScan ??= _owner.Engine.ShadowObjects
|
||||
.CreateRetainedRefloodOwnerScan(_admission.LandblockId);
|
||||
ShadowObjectRegistry.RetainedRefloodOwnerScanStep step =
|
||||
_retainedOwnerScan.Advance();
|
||||
if (step.Completed && !step.Stable)
|
||||
{
|
||||
ResetRetainedOwnerCapture();
|
||||
return new RuntimeCollisionOwnerCaptureStep(
|
||||
Completed: false,
|
||||
Restarted: true,
|
||||
HasOwner: false,
|
||||
OwnerId: 0u);
|
||||
}
|
||||
if (step.HasOwner)
|
||||
_retainedOwnerIds.Add(step.OwnerId);
|
||||
if (step.Completed)
|
||||
{
|
||||
_retainedOwnerScan.Dispose();
|
||||
_retainedOwnerScan = null;
|
||||
RetainedOwnerCaptureMutationVersion = step.SourceMutationVersion;
|
||||
RetainedOwnerCaptureComplete = true;
|
||||
}
|
||||
return new RuntimeCollisionOwnerCaptureStep(
|
||||
RetainedOwnerCaptureComplete,
|
||||
Restarted: false,
|
||||
step.HasOwner,
|
||||
step.OwnerId);
|
||||
}
|
||||
|
||||
internal IReadOnlyList<uint> RetainedOwnerIds
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureUsable();
|
||||
if (!RetainedOwnerCaptureComplete)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Retained collision-owner capture is incomplete.");
|
||||
}
|
||||
return _retainedOwnerIds;
|
||||
}
|
||||
}
|
||||
|
||||
internal void ResetRetainedOwnerCapture()
|
||||
{
|
||||
EnsureUsable();
|
||||
_retainedOwnerScan?.Dispose();
|
||||
_retainedOwnerScan = null;
|
||||
_retainedOwnerIds.Clear();
|
||||
_retainedOwnerVersions.Clear();
|
||||
RetainedOwnerCaptureComplete = false;
|
||||
RetainedOwnerCaptureMutationVersion = 0UL;
|
||||
_sealedReplacement = null;
|
||||
_sealBuilder?.Dispose();
|
||||
_sealBuilder = null;
|
||||
SealedShadowMutationVersion = 0UL;
|
||||
}
|
||||
|
||||
internal RuntimeCollisionSealStep AdvanceSeal()
|
||||
{
|
||||
EnsureUsable();
|
||||
if (!RetainedOwnerCaptureComplete)
|
||||
{
|
||||
return new RuntimeCollisionSealStep(
|
||||
Completed: false,
|
||||
Restarted: true,
|
||||
WorkUnits: 0);
|
||||
}
|
||||
if (_owner.Engine.ShadowObjects.MutationVersion
|
||||
!= RetainedOwnerCaptureMutationVersion)
|
||||
{
|
||||
ResetRetainedOwnerCapture();
|
||||
return new RuntimeCollisionSealStep(
|
||||
Completed: false,
|
||||
Restarted: true,
|
||||
WorkUnits: 0);
|
||||
}
|
||||
if (_retainedOwnerVersions.Count != _retainedOwnerIds.Count)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Every retained collision owner must refresh before sealing.");
|
||||
}
|
||||
|
||||
_sealBuilder ??= _owner.Engine.CreateLandblockReplacementBuilder(
|
||||
Engine,
|
||||
_admission.LandblockId,
|
||||
_dynamicOwnerVersions);
|
||||
GfxObjectIds,
|
||||
SetupIds,
|
||||
_retainedOwnerVersions);
|
||||
int before = _sealBuilder.WorkUnits;
|
||||
bool completed = _sealBuilder.Advance();
|
||||
int workUnits = _sealBuilder.WorkUnits - before;
|
||||
if (!completed)
|
||||
{
|
||||
return new RuntimeCollisionSealStep(
|
||||
Completed: false,
|
||||
Restarted: false,
|
||||
workUnits);
|
||||
}
|
||||
if (!_sealBuilder.IsStable || _sealBuilder.Prepared is null)
|
||||
{
|
||||
ResetRetainedOwnerCapture();
|
||||
return new RuntimeCollisionSealStep(
|
||||
Completed: false,
|
||||
Restarted: true,
|
||||
workUnits);
|
||||
}
|
||||
_sealedReplacement = _sealBuilder.Prepared;
|
||||
_sealBuilder.Dispose();
|
||||
_sealBuilder = null;
|
||||
SealedShadowMutationVersion =
|
||||
_owner.Engine.ShadowObjects.MutationVersion;
|
||||
return new RuntimeCollisionSealStep(
|
||||
Completed: true,
|
||||
Restarted: false,
|
||||
workUnits);
|
||||
}
|
||||
|
||||
internal PhysicsEngine.PreparedPhysicsEngineLandblock TakeSealedReplacement()
|
||||
{
|
||||
EnsureUsable();
|
||||
return _sealedReplacement
|
||||
?? throw new InvalidOperationException(
|
||||
"Collision generation must be sealed before activation.");
|
||||
}
|
||||
|
||||
internal void MarkCommitted()
|
||||
|
|
@ -156,7 +293,14 @@ internal sealed class PreparedLandblockCollisionGeneration : IDisposable
|
|||
if (_disposed)
|
||||
return;
|
||||
Engine.Clear();
|
||||
_dynamicOwnerVersions.Clear();
|
||||
_retainedOwnerScan?.Dispose();
|
||||
_retainedOwnerScan = null;
|
||||
_retainedOwnerIds.Clear();
|
||||
_retainedOwnerVersions.Clear();
|
||||
_sealBuilder?.Dispose();
|
||||
_sealBuilder = null;
|
||||
_sealedReplacement = null;
|
||||
SealedShadowMutationVersion = 0UL;
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
|
|
@ -167,6 +311,17 @@ internal sealed class PreparedLandblockCollisionGeneration : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
internal readonly record struct RuntimeCollisionOwnerCaptureStep(
|
||||
bool Completed,
|
||||
bool Restarted,
|
||||
bool HasOwner,
|
||||
uint OwnerId);
|
||||
|
||||
internal readonly record struct RuntimeCollisionSealStep(
|
||||
bool Completed,
|
||||
bool Restarted,
|
||||
int WorkUnits);
|
||||
|
||||
/// <summary>
|
||||
/// Presentation-free mutable physics world for one Runtime/session owner.
|
||||
/// Immutable prepared collision inputs may be supplied by a graphical or
|
||||
|
|
@ -1001,6 +1156,43 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
stagingEngine);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels only the named unpublished generation. The currently active
|
||||
/// collision world is never withdrawn. A stale receipt may dispose its
|
||||
/// own staging storage but cannot invalidate a newer admission.
|
||||
/// </summary>
|
||||
internal void CancelCollisionGeneration(
|
||||
RuntimeCollisionAdmission admission,
|
||||
PreparedLandblockCollisionGeneration? prepared = null)
|
||||
{
|
||||
EnsureNotDisposed();
|
||||
EnsureCollisionMutationThread();
|
||||
ArgumentNullException.ThrowIfNull(admission);
|
||||
if (!ReferenceEquals(admission.Owner, this))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Collision admission belongs to another Runtime.",
|
||||
nameof(admission));
|
||||
}
|
||||
if (prepared is not null && !prepared.Matches(this, admission))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Prepared collision generation belongs to another admission.",
|
||||
nameof(prepared));
|
||||
}
|
||||
|
||||
prepared?.Dispose();
|
||||
if (_collisionAdmissions.TryGetValue(
|
||||
admission.LandblockId,
|
||||
out RuntimeCollisionAdmission? current)
|
||||
&& ReferenceEquals(current, admission))
|
||||
{
|
||||
_collisionAdmissions.Remove(admission.LandblockId);
|
||||
_collisionGenerations[admission.LandblockId] = checked(
|
||||
admission.Generation + 1UL);
|
||||
}
|
||||
}
|
||||
|
||||
internal void StageCollisionAssets(
|
||||
RuntimeCollisionAdmission admission,
|
||||
PreparedLandblockCollisionGeneration prepared,
|
||||
|
|
@ -1043,18 +1235,17 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
admission.AssetsPrepared = true;
|
||||
}
|
||||
|
||||
internal uint[] CaptureCollisionDynamicOwners(
|
||||
internal RuntimeCollisionOwnerCaptureStep AdvanceCollisionRetainedOwnerCapture(
|
||||
RuntimeCollisionAdmission admission,
|
||||
PreparedLandblockCollisionGeneration prepared)
|
||||
{
|
||||
ValidateAdmission(admission);
|
||||
EnsureCollisionMutationThread();
|
||||
ValidatePreparedGeneration(admission, prepared);
|
||||
return Engine.ShadowObjects.CaptureDynamicRefloodOwnersForLandblock(
|
||||
admission.LandblockId);
|
||||
return prepared.AdvanceRetainedOwnerCapture();
|
||||
}
|
||||
|
||||
internal void RefreshCollisionDynamicOwner(
|
||||
internal void RefreshCollisionRetainedOwner(
|
||||
RuntimeCollisionAdmission admission,
|
||||
PreparedLandblockCollisionGeneration prepared,
|
||||
uint ownerId)
|
||||
|
|
@ -1062,7 +1253,32 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
ValidateAdmission(admission);
|
||||
EnsureCollisionMutationThread();
|
||||
ValidatePreparedGeneration(admission, prepared);
|
||||
prepared.RefreshDynamicOwner(ownerId);
|
||||
prepared.RefreshRetainedOwner(ownerId);
|
||||
}
|
||||
|
||||
internal void RestartCollisionRetainedOwnerCapture(
|
||||
RuntimeCollisionAdmission admission,
|
||||
PreparedLandblockCollisionGeneration prepared)
|
||||
{
|
||||
ValidateAdmission(admission);
|
||||
EnsureCollisionMutationThread();
|
||||
ValidatePreparedGeneration(admission, prepared);
|
||||
prepared.ResetRetainedOwnerCapture();
|
||||
}
|
||||
|
||||
internal RuntimeCollisionSealStep AdvanceCollisionGenerationSeal(
|
||||
RuntimeCollisionAdmission admission,
|
||||
PreparedLandblockCollisionGeneration prepared)
|
||||
{
|
||||
ValidateAdmission(admission);
|
||||
EnsureCollisionMutationThread();
|
||||
ValidatePreparedGeneration(admission, prepared);
|
||||
if (!admission.AssetsPrepared)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Collision generation cannot seal before its assets are prepared.");
|
||||
}
|
||||
return prepared.AdvanceSeal();
|
||||
}
|
||||
|
||||
internal RuntimeCollisionGenerationCommit CommitCollisionGeneration(
|
||||
|
|
@ -1083,38 +1299,25 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
"Collision generation has already completed.");
|
||||
}
|
||||
|
||||
uint[] dirtyOwners = prepared.FindDirtyDynamicOwners();
|
||||
if (dirtyOwners.Length != 0)
|
||||
if (!prepared.IsSealed)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Collision generation cannot activate before sealing.");
|
||||
}
|
||||
if (Engine.ShadowObjects.MutationVersion
|
||||
!= prepared.SealedShadowMutationVersion)
|
||||
{
|
||||
prepared.ResetRetainedOwnerCapture();
|
||||
return new RuntimeCollisionGenerationCommit(
|
||||
new RuntimeCollisionAcknowledgement(
|
||||
admission.LandblockId,
|
||||
admission.Generation,
|
||||
Engine.IsLandblockTerrainResident(admission.LandblockId),
|
||||
Ready: false),
|
||||
dirtyOwners);
|
||||
Array.Empty<uint>());
|
||||
}
|
||||
|
||||
PhysicsEngine.PreparedPhysicsEngineLandblock replacement =
|
||||
Engine.PrepareLandblockReplacement(
|
||||
prepared.Engine,
|
||||
admission.LandblockId,
|
||||
prepared.GfxObjectIds,
|
||||
prepared.SetupIds,
|
||||
prepared.DynamicOwnerVersions);
|
||||
if (!Engine.ValidateLandblockReplacement(replacement))
|
||||
{
|
||||
dirtyOwners = prepared.FindDirtyDynamicOwners();
|
||||
return new RuntimeCollisionGenerationCommit(
|
||||
new RuntimeCollisionAcknowledgement(
|
||||
admission.LandblockId,
|
||||
admission.Generation,
|
||||
Engine.IsLandblockTerrainResident(admission.LandblockId),
|
||||
Ready: false),
|
||||
dirtyOwners);
|
||||
}
|
||||
|
||||
Engine.CommitLandblockReplacement(replacement);
|
||||
Engine.CommitLandblockReplacement(prepared.TakeSealedReplacement());
|
||||
admission.Completed = true;
|
||||
_collisionAdmissions.Remove(admission.LandblockId);
|
||||
prepared.MarkCommitted();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue