refactor(app): key live projections by runtime identity
Move materialized live-object sidecars and presentation worksets to exact RuntimeEntityKey ownership. Runtime remains the only GUID/incarnation/local-ID authority while hydration, animation, effects, lights, equipped children, renderer resources, visibility, liveness, and teardown resolve exact projection identities. Preserve synchronous callbacks, local-ID allocation order, and current rendering behavior.
This commit is contained in:
parent
e18df84437
commit
420e5eea70
73 changed files with 2939 additions and 1715 deletions
|
|
@ -196,7 +196,7 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.Single(spatial.Entities);
|
||||
Assert.True(runtime.WithdrawLiveEntityProjection(spawn.Guid));
|
||||
Assert.Empty(spatial.Entities);
|
||||
Assert.Empty(runtime.WorldEntities);
|
||||
Assert.Empty(runtime.VisibleRecords);
|
||||
Assert.Equal(1, resources.RegisterCount);
|
||||
Assert.Equal(0, resources.UnregisterCount);
|
||||
Assert.True(runtime.TryGetAnimationRuntime(entity!.Id, out _));
|
||||
|
|
@ -424,6 +424,7 @@ public sealed class LiveEntityRuntimeTests
|
|||
guid,
|
||||
0x01010001u,
|
||||
id => Entity(id, guid))!;
|
||||
Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord record));
|
||||
var animation = new AnimationRuntime(entity);
|
||||
var remote = new RemoteMotionRuntime();
|
||||
var projectile = new ProjectileRuntime(remote.Body);
|
||||
|
|
@ -431,9 +432,9 @@ public sealed class LiveEntityRuntimeTests
|
|||
runtime.SetRemoteMotionRuntime(guid, remote);
|
||||
runtime.SetProjectileRuntime(guid, projectile);
|
||||
|
||||
Assert.Same(animation, Assert.Single(runtime.SpatialAnimationRuntimes).Value);
|
||||
Assert.Same(remote, Assert.Single(runtime.SpatialRemoteMotionRuntimes).Value);
|
||||
Assert.Same(projectile, Assert.Single(runtime.SpatialProjectileRuntimes).Value);
|
||||
Assert.True(runtime.IsCurrentSpatialAnimation(record, animation));
|
||||
Assert.True(runtime.IsCurrentSpatialRemoteMotion(record, remote));
|
||||
Assert.True(runtime.IsCurrentSpatialProjectile(record, projectile));
|
||||
|
||||
// Hidden suppresses presentation, not the live CPhysicsObj workset.
|
||||
Assert.True(runtime.TryApplyState(new SetState.Parsed(
|
||||
|
|
@ -441,16 +442,16 @@ public sealed class LiveEntityRuntimeTests
|
|||
(uint)(PhysicsStateFlags.Hidden | PhysicsStateFlags.IgnoreCollisions),
|
||||
1,
|
||||
2), out _));
|
||||
Assert.Single(runtime.SpatialAnimationRuntimes);
|
||||
Assert.Single(runtime.SpatialRemoteMotionRuntimes);
|
||||
Assert.Single(runtime.SpatialProjectileRuntimes);
|
||||
Assert.Equal(1, runtime.SpatialAnimationRuntimeCount);
|
||||
Assert.Equal(1, runtime.SpatialRemoteMotionRuntimeCount);
|
||||
Assert.Equal(1, runtime.SpatialProjectileRuntimeCount);
|
||||
|
||||
// A pending projection retains every logical component but disappears
|
||||
// from all per-frame worksets.
|
||||
Assert.True(runtime.RebucketLiveEntity(guid, 0x02020001u));
|
||||
Assert.Empty(runtime.SpatialAnimationRuntimes);
|
||||
Assert.Empty(runtime.SpatialRemoteMotionRuntimes);
|
||||
Assert.Empty(runtime.SpatialProjectileRuntimes);
|
||||
Assert.Equal(0, runtime.SpatialAnimationRuntimeCount);
|
||||
Assert.Equal(0, runtime.SpatialRemoteMotionRuntimeCount);
|
||||
Assert.Equal(0, runtime.SpatialProjectileRuntimeCount);
|
||||
Assert.True(runtime.TryGetAnimationRuntime(entity.Id, out var retainedAnimation));
|
||||
Assert.Same(animation, retainedAnimation);
|
||||
Assert.True(runtime.TryGetRemoteMotionRuntime(guid, out var retainedRemote));
|
||||
|
|
@ -459,14 +460,14 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.Same(projectile, retainedProjectile);
|
||||
|
||||
spatial.AddLandblock(EmptyLandblock(0x0202FFFFu));
|
||||
Assert.Single(runtime.SpatialAnimationRuntimes);
|
||||
Assert.Single(runtime.SpatialRemoteMotionRuntimes);
|
||||
Assert.Single(runtime.SpatialProjectileRuntimes);
|
||||
Assert.Equal(1, runtime.SpatialAnimationRuntimeCount);
|
||||
Assert.Equal(1, runtime.SpatialRemoteMotionRuntimeCount);
|
||||
Assert.Equal(1, runtime.SpatialProjectileRuntimeCount);
|
||||
|
||||
Assert.True(runtime.WithdrawLiveEntityProjection(guid));
|
||||
Assert.Empty(runtime.SpatialAnimationRuntimes);
|
||||
Assert.Empty(runtime.SpatialRemoteMotionRuntimes);
|
||||
Assert.Empty(runtime.SpatialProjectileRuntimes);
|
||||
Assert.Equal(0, runtime.SpatialAnimationRuntimeCount);
|
||||
Assert.Equal(0, runtime.SpatialRemoteMotionRuntimeCount);
|
||||
Assert.Equal(0, runtime.SpatialProjectileRuntimeCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -484,22 +485,25 @@ public sealed class LiveEntityRuntimeTests
|
|||
var animation = new AnimationRuntime(entity);
|
||||
runtime.SetAnimationRuntime(guid, animation);
|
||||
var view = new LiveEntityAnimationRuntimeView<AnimationRuntime>(BoundSlot(runtime));
|
||||
var spatialIds = new HashSet<uint>();
|
||||
|
||||
Assert.Equal(1, view.Count);
|
||||
Assert.Equal(entity.Id, Assert.Single(view.Keys));
|
||||
view.CopySpatialIdsTo(spatialIds);
|
||||
Assert.Equal(entity.Id, Assert.Single(spatialIds));
|
||||
Assert.Same(animation, Assert.Single(view).Value);
|
||||
|
||||
Assert.True(runtime.RebucketLiveEntity(guid, 0x02020001u));
|
||||
Assert.Equal(0, view.Count);
|
||||
Assert.Empty(view.Keys);
|
||||
view.CopySpatialIdsTo(spatialIds);
|
||||
Assert.Empty(spatialIds);
|
||||
Assert.Empty(view);
|
||||
Assert.True(view.TryGetValue(entity.Id, out AnimationRuntime retained));
|
||||
Assert.Same(animation, retained);
|
||||
|
||||
Assert.True(view.Remove(entity.Id));
|
||||
Assert.False(view.TryGetValue(entity.Id, out _));
|
||||
Assert.Empty(runtime.AnimationRuntimes);
|
||||
Assert.Empty(runtime.SpatialAnimationRuntimes);
|
||||
Assert.Equal(0, runtime.AnimationRuntimeCount);
|
||||
Assert.Equal(0, runtime.SpatialAnimationRuntimeCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -531,8 +535,8 @@ public sealed class LiveEntityRuntimeTests
|
|||
}
|
||||
|
||||
Assert.Single(visited);
|
||||
Assert.Single(runtime.SpatialAnimationRuntimes);
|
||||
Assert.Equal(2, runtime.AnimationRuntimes.Count);
|
||||
Assert.Equal(1, runtime.SpatialAnimationRuntimeCount);
|
||||
Assert.Equal(2, runtime.AnimationRuntimeCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -601,10 +605,10 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.False(runtime.WithdrawLiveEntityProjection(guid));
|
||||
Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord current));
|
||||
Assert.Equal((ushort)2, current.Generation);
|
||||
KeyValuePair<uint, ILiveEntityAnimationRuntime> indexed =
|
||||
Assert.Single(runtime.SpatialAnimationRuntimes);
|
||||
Assert.Same(current.AnimationRuntime, indexed.Value);
|
||||
Assert.NotSame(oldAnimation, indexed.Value);
|
||||
Assert.True(runtime.IsCurrentSpatialAnimation(
|
||||
current,
|
||||
current.AnimationRuntime!));
|
||||
Assert.NotSame(oldAnimation, current.AnimationRuntime);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -670,13 +674,18 @@ public sealed class LiveEntityRuntimeTests
|
|||
spatial.AddLandblock(EmptyLandblock(0x0102FFFFu));
|
||||
var runtime = new LiveEntityRuntime(spatial, new RecordingResources());
|
||||
WorldSession.EntitySpawn spawn = Spawn(guid, 6, 1, 0x01010001u);
|
||||
runtime.RegisterLiveEntity(spawn);
|
||||
LiveEntityRegistrationResult registration =
|
||||
runtime.RegisterLiveEntity(spawn);
|
||||
RuntimeEntityRecord canonical = Assert.IsType<RuntimeEntityRecord>(
|
||||
registration.Canonical);
|
||||
var profile = new EffectProfile();
|
||||
runtime.SetEffectProfile(guid, profile);
|
||||
runtime.MaterializeLiveEntity(
|
||||
guid,
|
||||
canonical,
|
||||
spawn.Position!.Value.LandblockId,
|
||||
id => Entity(id, guid));
|
||||
id => Entity(id, guid),
|
||||
LiveEntityProjectionKind.World,
|
||||
initializeProjection: record => record.EffectProfile = profile,
|
||||
out _);
|
||||
|
||||
Assert.True(runtime.RebucketLiveEntity(guid, 0x01020001u));
|
||||
Assert.True(runtime.TryGetEffectProfile(guid, out var retained));
|
||||
|
|
@ -865,7 +874,7 @@ public sealed class LiveEntityRuntimeTests
|
|||
LiveEntityProjectionKind.Attached)!;
|
||||
|
||||
Assert.Single(spatial.Entities);
|
||||
Assert.Empty(runtime.WorldEntities);
|
||||
Assert.Empty(runtime.VisibleRecords);
|
||||
Assert.False(runtime.TryMarkWorldSpawnPublished(spawn.Guid));
|
||||
Assert.True(runtime.TryGetWorldEntity(spawn.Guid, out WorldEntity resolved));
|
||||
Assert.Same(attached, resolved);
|
||||
|
|
@ -880,7 +889,7 @@ public sealed class LiveEntityRuntimeTests
|
|||
|
||||
Assert.Same(attached, same);
|
||||
Assert.True(same.IsAncestorDrawVisible);
|
||||
Assert.Same(attached, Assert.Single(runtime.WorldEntities).Value);
|
||||
Assert.Same(attached, Assert.Single(runtime.VisibleRecords).WorldEntity);
|
||||
Assert.True(runtime.TryMarkWorldSpawnPublished(spawn.Guid));
|
||||
Assert.False(runtime.TryMarkWorldSpawnPublished(spawn.Guid));
|
||||
Assert.Equal(1, resources.RegisterCount);
|
||||
|
|
@ -902,12 +911,12 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.Empty(spatial.Entities);
|
||||
Assert.Equal(1, spatial.PendingLiveEntityCount);
|
||||
Assert.Equal(1, resources.RegisterCount);
|
||||
Assert.Empty(runtime.WorldEntities);
|
||||
Assert.Same(entity, Assert.Single(runtime.MaterializedWorldEntities).Value);
|
||||
Assert.Empty(runtime.VisibleRecords);
|
||||
Assert.Same(entity, Assert.Single(runtime.MaterializedRecords).WorldEntity);
|
||||
|
||||
spatial.AddLandblock(EmptyLandblock(0x0102FFFFu));
|
||||
Assert.Same(entity, Assert.Single(spatial.Entities));
|
||||
Assert.Same(entity, Assert.Single(runtime.WorldEntities).Value);
|
||||
Assert.Same(entity, Assert.Single(runtime.VisibleRecords).WorldEntity);
|
||||
Assert.True(runtime.RebucketLiveEntity(spawn.Guid, 0x01020001u));
|
||||
Assert.Same(entity, Assert.Single(spatial.Entities));
|
||||
Assert.Equal(1, resources.RegisterCount);
|
||||
|
|
@ -928,8 +937,8 @@ public sealed class LiveEntityRuntimeTests
|
|||
spawn.Position!.Value.LandblockId,
|
||||
id => Entity(id, guid))!;
|
||||
|
||||
Assert.Empty(runtime.WorldEntities);
|
||||
Assert.Same(entity, Assert.Single(runtime.MaterializedWorldEntities).Value);
|
||||
Assert.Empty(runtime.VisibleRecords);
|
||||
Assert.Same(entity, Assert.Single(runtime.MaterializedRecords).WorldEntity);
|
||||
Assert.True(runtime.TryApplyVector(
|
||||
new VectorUpdate.Parsed(
|
||||
guid,
|
||||
|
|
@ -960,8 +969,8 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.Equal(PositionTimestampDisposition.Apply, disposition);
|
||||
Assert.True(runtime.RebucketLiveEntity(guid, accepted.Position!.Value.LandblockId));
|
||||
|
||||
Assert.Empty(runtime.WorldEntities);
|
||||
Assert.Same(entity, Assert.Single(runtime.MaterializedWorldEntities).Value);
|
||||
Assert.Empty(runtime.VisibleRecords);
|
||||
Assert.Same(entity, Assert.Single(runtime.MaterializedRecords).WorldEntity);
|
||||
Assert.Equal(1, resources.RegisterCount);
|
||||
Assert.Equal(0, resources.UnregisterCount);
|
||||
Assert.Equal(1, spatial.PendingLiveEntityCount);
|
||||
|
|
@ -1097,11 +1106,7 @@ public sealed class LiveEntityRuntimeTests
|
|||
spatial.AddLandblock(EmptyLandblock(0x0102FFFFu));
|
||||
var runtime = new LiveEntityRuntime(spatial, new RecordingResources());
|
||||
WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u);
|
||||
LiveEntityRecord record = runtime.RegisterLiveEntity(spawn).Record!;
|
||||
runtime.MaterializeLiveEntity(
|
||||
guid,
|
||||
spawn.Position!.Value.LandblockId,
|
||||
id => Entity(id, guid));
|
||||
LiveEntityRecord record = RegisterProjection(runtime, spawn);
|
||||
RetailObjectQuantumClock clock = record.ObjectClock;
|
||||
Assert.Equal(0, clock.Advance(0.02).Count);
|
||||
|
||||
|
|
@ -1145,15 +1150,10 @@ public sealed class LiveEntityRuntimeTests
|
|||
1,
|
||||
0x01010001u,
|
||||
PhysicsStateFlags.Static);
|
||||
LiveEntityRecord record = runtime.RegisterLiveEntity(spawn).Record!;
|
||||
LiveEntityRecord record = RegisterProjection(runtime, spawn);
|
||||
var remote = new RemoteMotionRuntime();
|
||||
runtime.SetRemoteMotionRuntime(guid, remote);
|
||||
|
||||
runtime.MaterializeLiveEntity(
|
||||
guid,
|
||||
spawn.Position!.Value.LandblockId,
|
||||
id => Entity(id, guid));
|
||||
|
||||
Assert.False(record.ObjectClock.IsActive);
|
||||
Assert.Equal(0.0, record.ObjectClock.PendingSeconds, 8);
|
||||
Assert.False(remote.Body.TransientState.HasFlag(TransientStateFlags.Active));
|
||||
|
|
@ -1168,14 +1168,11 @@ public sealed class LiveEntityRuntimeTests
|
|||
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
|
||||
var runtime = new LiveEntityRuntime(spatial, new RecordingResources());
|
||||
|
||||
LiveEntityRecord ordinary = runtime.RegisterLiveEntity(
|
||||
Spawn(ordinaryGuid, 1, 1, 0x01010001u)).Record!;
|
||||
LiveEntityRecord ordinary = RegisterProjection(
|
||||
runtime,
|
||||
Spawn(ordinaryGuid, 1, 1, 0x01010001u));
|
||||
var ordinaryRemote = new RemoteMotionRuntime();
|
||||
runtime.SetRemoteMotionRuntime(ordinaryGuid, ordinaryRemote);
|
||||
runtime.MaterializeLiveEntity(
|
||||
ordinaryGuid,
|
||||
0x01010001u,
|
||||
id => Entity(id, ordinaryGuid));
|
||||
ordinary.ObjectClock.Deactivate();
|
||||
ordinaryRemote.Body.TransientState &= ~TransientStateFlags.Active;
|
||||
|
||||
|
|
@ -1183,14 +1180,16 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.True(ordinary.ObjectClock.IsActive);
|
||||
Assert.True(ordinaryRemote.Body.TransientState.HasFlag(TransientStateFlags.Active));
|
||||
|
||||
LiveEntityRecord staticRecord = runtime.RegisterLiveEntity(
|
||||
Spawn(staticGuid, 1, 1, 0x01010001u, PhysicsStateFlags.Static)).Record!;
|
||||
LiveEntityRecord staticRecord = RegisterProjection(
|
||||
runtime,
|
||||
Spawn(
|
||||
staticGuid,
|
||||
1,
|
||||
1,
|
||||
0x01010001u,
|
||||
PhysicsStateFlags.Static));
|
||||
var staticRemote = new RemoteMotionRuntime();
|
||||
runtime.SetRemoteMotionRuntime(staticGuid, staticRemote);
|
||||
runtime.MaterializeLiveEntity(
|
||||
staticGuid,
|
||||
0x01010001u,
|
||||
id => Entity(id, staticGuid));
|
||||
|
||||
Assert.False(runtime.TryActivateOrdinaryObject(staticGuid, staticRemote));
|
||||
Assert.False(staticRecord.ObjectClock.IsActive);
|
||||
|
|
@ -1207,14 +1206,11 @@ public sealed class LiveEntityRuntimeTests
|
|||
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
|
||||
var runtime = new LiveEntityRuntime(spatial, new RecordingResources());
|
||||
|
||||
LiveEntityRecord ordinary = runtime.RegisterLiveEntity(
|
||||
Spawn(ordinaryGuid, 1, 1, 0x01010001u)).Record!;
|
||||
LiveEntityRecord ordinary = RegisterProjection(
|
||||
runtime,
|
||||
Spawn(ordinaryGuid, 1, 1, 0x01010001u));
|
||||
var ordinaryRemote = new RemoteMotionRuntime();
|
||||
runtime.SetRemoteMotionRuntime(ordinaryGuid, ordinaryRemote);
|
||||
runtime.MaterializeLiveEntity(
|
||||
ordinaryGuid,
|
||||
0x01010001u,
|
||||
id => Entity(id, ordinaryGuid));
|
||||
ordinary.ObjectClock.Deactivate();
|
||||
ordinaryRemote.Body.TransientState &= ~TransientStateFlags.Active;
|
||||
ordinaryRemote.Body.LastUpdateTime = 1.0;
|
||||
|
|
@ -1234,14 +1230,11 @@ public sealed class LiveEntityRuntimeTests
|
|||
// Defensive split state: the retained object clock is already active,
|
||||
// but a body consumer cleared its legacy Active bit. set_velocity must
|
||||
// still rebase that body's compatibility timestamp.
|
||||
LiveEntityRecord defensive = runtime.RegisterLiveEntity(
|
||||
Spawn(defensiveGuid, 1, 1, 0x01010001u)).Record!;
|
||||
LiveEntityRecord defensive = RegisterProjection(
|
||||
runtime,
|
||||
Spawn(defensiveGuid, 1, 1, 0x01010001u));
|
||||
var defensiveRemote = new RemoteMotionRuntime();
|
||||
runtime.SetRemoteMotionRuntime(defensiveGuid, defensiveRemote);
|
||||
runtime.MaterializeLiveEntity(
|
||||
defensiveGuid,
|
||||
0x01010001u,
|
||||
id => Entity(id, defensiveGuid));
|
||||
Assert.True(defensive.ObjectClock.IsActive);
|
||||
defensiveRemote.Body.TransientState &= ~TransientStateFlags.Active;
|
||||
defensiveRemote.Body.LastUpdateTime = 2.0;
|
||||
|
|
@ -1255,19 +1248,16 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.True(defensiveRemote.Body.IsActive);
|
||||
Assert.Equal(9.0, defensiveRemote.Body.LastUpdateTime);
|
||||
|
||||
LiveEntityRecord staticRecord = runtime.RegisterLiveEntity(
|
||||
LiveEntityRecord staticRecord = RegisterProjection(
|
||||
runtime,
|
||||
Spawn(
|
||||
staticGuid,
|
||||
1,
|
||||
1,
|
||||
0x01010001u,
|
||||
PhysicsStateFlags.Static)).Record!;
|
||||
PhysicsStateFlags.Static));
|
||||
var staticRemote = new RemoteMotionRuntime();
|
||||
runtime.SetRemoteMotionRuntime(staticGuid, staticRemote);
|
||||
runtime.MaterializeLiveEntity(
|
||||
staticGuid,
|
||||
0x01010001u,
|
||||
id => Entity(id, staticGuid));
|
||||
staticRemote.Body.TransientState &= ~TransientStateFlags.Active;
|
||||
staticRemote.Body.LastUpdateTime = 3.0;
|
||||
|
||||
|
|
@ -1291,8 +1281,9 @@ public sealed class LiveEntityRuntimeTests
|
|||
const uint childGuid = 0x70000055u;
|
||||
var runtime = new LiveEntityRuntime(new GpuWorldState(), new RecordingResources());
|
||||
runtime.RegisterLiveEntity(Spawn(parentGuid, 1, 1, 0x01010001u));
|
||||
LiveEntityRecord child = runtime.RegisterLiveEntity(
|
||||
Spawn(childGuid, 1, 1, 0x01010001u)).Record!;
|
||||
LiveEntityRecord child = RegisterProjection(
|
||||
runtime,
|
||||
Spawn(childGuid, 1, 1, 0x01010001u));
|
||||
ulong initialPosition = child.PositionAuthorityVersion;
|
||||
ulong initialVelocity = child.VelocityAuthorityVersion;
|
||||
|
||||
|
|
@ -1325,11 +1316,7 @@ public sealed class LiveEntityRuntimeTests
|
|||
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
|
||||
var runtime = new LiveEntityRuntime(spatial, new RecordingResources());
|
||||
WorldSession.EntitySpawn firstSpawn = Spawn(guid, 1, 1, 0x01010001u);
|
||||
LiveEntityRecord first = runtime.RegisterLiveEntity(firstSpawn).Record!;
|
||||
runtime.MaterializeLiveEntity(
|
||||
guid,
|
||||
firstSpawn.Position!.Value.LandblockId,
|
||||
id => Entity(id, guid));
|
||||
LiveEntityRecord first = RegisterProjection(runtime, firstSpawn);
|
||||
RetailObjectQuantumClock firstClock = first.ObjectClock;
|
||||
Assert.Equal(0, firstClock.Advance(0.02).Count);
|
||||
|
||||
|
|
@ -1341,7 +1328,7 @@ public sealed class LiveEntityRuntimeTests
|
|||
new DeleteObject.Parsed(guid, InstanceSequence: 1),
|
||||
isLocalPlayer: false));
|
||||
WorldSession.EntitySpawn secondSpawn = Spawn(guid, 2, 2, 0x01010001u);
|
||||
LiveEntityRecord second = runtime.RegisterLiveEntity(secondSpawn).Record!;
|
||||
LiveEntityRecord second = RegisterProjection(runtime, secondSpawn);
|
||||
|
||||
Assert.NotSame(first, second);
|
||||
Assert.NotSame(firstClock, second.ObjectClock);
|
||||
|
|
@ -1364,9 +1351,17 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.True(runtime.TryApplyState(
|
||||
new SetState.Parsed(stateBeforeBindGuid, (uint)firstState, 1, 2),
|
||||
out _));
|
||||
runtime.MaterializeLiveEntity(
|
||||
stateBeforeBindGuid,
|
||||
0x01010001u,
|
||||
id => Entity(id, stateBeforeBindGuid));
|
||||
var lateBody = new RemoteMotionRuntime();
|
||||
runtime.SetRemoteMotionRuntime(stateBeforeBindGuid, lateBody);
|
||||
|
||||
runtime.MaterializeLiveEntity(
|
||||
bindBeforeStateGuid,
|
||||
0x01010001u,
|
||||
id => Entity(id, bindBeforeStateGuid));
|
||||
var earlyBody = new RemoteMotionRuntime();
|
||||
runtime.SetRemoteMotionRuntime(bindBeforeStateGuid, earlyBody);
|
||||
PhysicsStateFlags secondState = PhysicsStateFlags.Static
|
||||
|
|
@ -1388,6 +1383,10 @@ public sealed class LiveEntityRuntimeTests
|
|||
const uint guid = 0x70000039u;
|
||||
var runtime = new LiveEntityRuntime(new GpuWorldState(), new RecordingResources());
|
||||
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
|
||||
runtime.MaterializeLiveEntity(
|
||||
guid,
|
||||
0x01010001u,
|
||||
id => Entity(id, guid));
|
||||
var remote = new RemoteMotionRuntime();
|
||||
runtime.SetRemoteMotionRuntime(guid, remote);
|
||||
|
||||
|
|
@ -1404,9 +1403,10 @@ public sealed class LiveEntityRuntimeTests
|
|||
const uint guid = 0x70000040u;
|
||||
var runtime = new LiveEntityRuntime(new GpuWorldState(), new RecordingResources());
|
||||
|
||||
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
|
||||
LiveEntityRecord record = RegisterProjection(
|
||||
runtime,
|
||||
Spawn(guid, 1, 1, 0x01010001u));
|
||||
|
||||
Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord record));
|
||||
Assert.True(record.TryDequeueStateTransition(out RetailPhysicsStateTransition transition));
|
||||
Assert.Equal(RetailHiddenTransition.None, transition.HiddenTransition);
|
||||
Assert.Equal(PhysicsStateFlags.ReportCollisions, record.FinalPhysicsState);
|
||||
|
|
@ -1431,8 +1431,8 @@ public sealed class LiveEntityRuntimeTests
|
|||
id => Entity(id, guid))!;
|
||||
|
||||
Assert.False(entity.IsDrawVisible);
|
||||
Assert.Empty(runtime.WorldEntities);
|
||||
Assert.Same(entity, Assert.Single(runtime.MaterializedWorldEntities).Value);
|
||||
Assert.Empty(runtime.VisibleRecords);
|
||||
Assert.Same(entity, Assert.Single(runtime.MaterializedRecords).WorldEntity);
|
||||
Assert.Single(spatial.Entities);
|
||||
Assert.True(runtime.ShouldAdvanceRootRuntime(guid));
|
||||
Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord record));
|
||||
|
|
@ -1462,7 +1462,7 @@ public sealed class LiveEntityRuntimeTests
|
|||
2), out _, out RetailPhysicsStateTransition hidden));
|
||||
Assert.Equal(RetailHiddenTransition.BecameHidden, hidden.HiddenTransition);
|
||||
Assert.False(entity.IsDrawVisible);
|
||||
Assert.Empty(runtime.WorldEntities);
|
||||
Assert.Empty(runtime.VisibleRecords);
|
||||
Assert.False(runtime.TryGetInteractionEligibleEntity(guid, out _));
|
||||
Assert.False(runtime.TryGetInteractionEligibleRecord(guid, entity.Id, out _));
|
||||
|
||||
|
|
@ -1478,7 +1478,7 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.Same(entity, eligibleRecord.WorldEntity);
|
||||
Assert.False(runtime.TryGetInteractionEligibleRecord(guid, entity.Id + 1u, out _));
|
||||
Assert.Same(entity, eligible);
|
||||
Assert.Same(entity, Assert.Single(runtime.WorldEntities).Value);
|
||||
Assert.Same(entity, Assert.Single(runtime.VisibleRecords).WorldEntity);
|
||||
Assert.Same(entity, Assert.Single(spatial.Entities));
|
||||
}
|
||||
|
||||
|
|
@ -1573,7 +1573,7 @@ public sealed class LiveEntityRuntimeTests
|
|||
|
||||
spatial.RemoveLandblock(0x0101FFFFu);
|
||||
|
||||
Assert.Empty(runtime.WorldEntities);
|
||||
Assert.Empty(runtime.VisibleRecords);
|
||||
Assert.True(runtime.TryGetRecord(spawn.Guid, out LiveEntityRecord record));
|
||||
Assert.True(record.IsSpatiallyProjected);
|
||||
Assert.False(record.IsSpatiallyVisible);
|
||||
|
|
@ -1582,7 +1582,7 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.Equal(0, resources.UnregisterCount);
|
||||
|
||||
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
|
||||
Assert.Same(entity, Assert.Single(runtime.WorldEntities).Value);
|
||||
Assert.Same(entity, Assert.Single(runtime.VisibleRecords).WorldEntity);
|
||||
Assert.True(record.IsSpatiallyVisible);
|
||||
Assert.Equal(1, resources.RegisterCount);
|
||||
}
|
||||
|
|
@ -1632,7 +1632,7 @@ public sealed class LiveEntityRuntimeTests
|
|||
runtime.Clear();
|
||||
|
||||
Assert.Equal(0, runtime.Count);
|
||||
Assert.Empty(runtime.WorldEntities);
|
||||
Assert.Empty(runtime.VisibleRecords);
|
||||
Assert.Empty(spatial.Entities);
|
||||
Assert.Equal(1, resources.RegisterCount);
|
||||
Assert.Equal(1, resources.UnregisterCount);
|
||||
|
|
@ -1654,10 +1654,11 @@ public sealed class LiveEntityRuntimeTests
|
|||
spawn.Position!.Value.LandblockId,
|
||||
id => Entity(id, spawn.Guid)));
|
||||
|
||||
Assert.True(runtime.TryGetRecord(spawn.Guid, out LiveEntityRecord record));
|
||||
Assert.Null(record.WorldEntity);
|
||||
Assert.False(runtime.TryGetRecord(spawn.Guid, out _));
|
||||
RuntimeEntityRecord canonical = CurrentCanonical(runtime, spawn.Guid);
|
||||
Assert.Null(canonical.LocalEntityId);
|
||||
Assert.Equal(0, runtime.MaterializedCount);
|
||||
Assert.Empty(runtime.WorldEntities);
|
||||
Assert.Empty(runtime.VisibleRecords);
|
||||
Assert.Empty(spatial.Entities);
|
||||
Assert.Equal(1, resources.RegisterCount);
|
||||
Assert.Equal(1, resources.UnregisterCount);
|
||||
|
|
@ -1724,7 +1725,7 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.Equal(1, runtime.RetryPendingTeardowns());
|
||||
Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord current));
|
||||
Assert.Same(replacement, current.WorldEntity);
|
||||
Assert.Same(replacement, Assert.Single(runtime.MaterializedWorldEntities).Value);
|
||||
Assert.Same(replacement, Assert.Single(runtime.MaterializedRecords).WorldEntity);
|
||||
Assert.Equal(0, runtime.PendingTeardownCount);
|
||||
}
|
||||
|
||||
|
|
@ -1746,7 +1747,7 @@ public sealed class LiveEntityRuntimeTests
|
|||
LiveEntityRegistrationResult retransmit = runtime.RegisterLiveEntity(spawn);
|
||||
|
||||
Assert.False(retransmit.LogicalRegistrationCreated);
|
||||
Assert.Null(retransmit.Record);
|
||||
Assert.Null(retransmit.Projection);
|
||||
Assert.Equal(0, runtime.Count);
|
||||
Assert.Equal(1, runtime.PendingTeardownCount);
|
||||
|
||||
|
|
@ -1780,7 +1781,7 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.Equal(0, runtime.Count);
|
||||
Assert.Equal(1, runtime.PendingTeardownCount);
|
||||
Assert.Equal(1, runtime.MaterializedCount);
|
||||
Assert.Empty(runtime.WorldEntities);
|
||||
Assert.Empty(runtime.VisibleRecords);
|
||||
Assert.Empty(spatial.Entities);
|
||||
}
|
||||
|
||||
|
|
@ -1898,9 +1899,10 @@ public sealed class LiveEntityRuntimeTests
|
|||
Spawn(guid, 2, 1, 0x01010001u));
|
||||
|
||||
Assert.NotNull(replacement.PriorGenerationCleanupFailure);
|
||||
Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord record));
|
||||
Assert.Equal((ushort)2, record.Generation);
|
||||
Assert.Null(record.WorldEntity);
|
||||
RuntimeEntityRecord canonical = CurrentCanonical(runtime, guid);
|
||||
Assert.Equal((ushort)2, canonical.Generation);
|
||||
Assert.Null(canonical.LocalEntityId);
|
||||
Assert.False(runtime.TryGetRecord(guid, out _));
|
||||
WorldEntity installed = runtime.MaterializeLiveEntity(
|
||||
guid,
|
||||
0x01010001u,
|
||||
|
|
@ -1949,7 +1951,7 @@ public sealed class LiveEntityRuntimeTests
|
|||
runtime.RebucketLiveEntity(guid, 0x01020001u);
|
||||
|
||||
Assert.Same(attached, Assert.Single(spatial.Entities));
|
||||
Assert.Empty(runtime.WorldEntities);
|
||||
Assert.Empty(runtime.VisibleRecords);
|
||||
Assert.Equal(1, resources.RegisterCount);
|
||||
Assert.Equal(0, resources.UnregisterCount);
|
||||
var destination = Assert.Single(
|
||||
|
|
@ -2082,8 +2084,8 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.Equal(0x0202FFFFu, record.CanonicalLandblockId);
|
||||
Assert.True(record.IsSpatiallyProjected);
|
||||
Assert.False(record.IsSpatiallyVisible);
|
||||
Assert.Same(entity, Assert.Single(runtime.MaterializedWorldEntities).Value);
|
||||
Assert.Empty(runtime.WorldEntities);
|
||||
Assert.Same(entity, Assert.Single(runtime.MaterializedRecords).WorldEntity);
|
||||
Assert.Empty(runtime.VisibleRecords);
|
||||
Assert.Equal(1, spatial.PendingLiveEntityCount);
|
||||
Assert.Equal(0, spatial.PendingVisibilityTransitionCount);
|
||||
}
|
||||
|
|
@ -2112,8 +2114,8 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord record));
|
||||
Assert.False(record.IsSpatiallyProjected);
|
||||
Assert.False(record.IsSpatiallyVisible);
|
||||
Assert.Empty(runtime.MaterializedWorldEntities);
|
||||
Assert.Empty(runtime.WorldEntities);
|
||||
Assert.Single(runtime.MaterializedRecords);
|
||||
Assert.Empty(runtime.VisibleRecords);
|
||||
Assert.Empty(spatial.Entities);
|
||||
Assert.Equal(0, spatial.PendingLiveEntityCount);
|
||||
Assert.Equal(0, spatial.PendingVisibilityTransitionCount);
|
||||
|
|
@ -2148,8 +2150,8 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.Equal(0x01010011u, replacement.FullCellId);
|
||||
Assert.True(replacement.IsSpatiallyProjected);
|
||||
Assert.True(replacement.IsSpatiallyVisible);
|
||||
Assert.Same(replacement.WorldEntity, Assert.Single(runtime.MaterializedWorldEntities).Value);
|
||||
Assert.Same(replacement.WorldEntity, Assert.Single(runtime.WorldEntities).Value);
|
||||
Assert.Same(replacement.WorldEntity, Assert.Single(runtime.MaterializedRecords).WorldEntity);
|
||||
Assert.Same(replacement.WorldEntity, Assert.Single(runtime.VisibleRecords).WorldEntity);
|
||||
Assert.Same(replacement.WorldEntity, Assert.Single(spatial.Entities));
|
||||
}
|
||||
|
||||
|
|
@ -2161,11 +2163,14 @@ public sealed class LiveEntityRuntimeTests
|
|||
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
|
||||
var runtime = new LiveEntityRuntime(spatial, new RecordingResources());
|
||||
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
|
||||
runtime.MaterializeLiveEntity(guid, 0x01010001u, id => Entity(id, guid));
|
||||
WorldEntity original = runtime.MaterializeLiveEntity(
|
||||
guid,
|
||||
0x01010001u,
|
||||
id => Entity(id, guid))!;
|
||||
bool replaced = false;
|
||||
spatial.LiveProjectionVisibilityChanged += (edgeGuid, visible) =>
|
||||
spatial.LiveProjectionVisibilityChanged += (localEntityId, visible) =>
|
||||
{
|
||||
if (replaced || visible || edgeGuid != guid)
|
||||
if (replaced || visible || localEntityId != original.Id)
|
||||
return;
|
||||
replaced = true;
|
||||
Assert.True(runtime.UnregisterLiveEntity(
|
||||
|
|
@ -2183,8 +2188,8 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.Equal(0x0101FFFFu, replacement.CanonicalLandblockId);
|
||||
Assert.True(replacement.IsSpatiallyProjected);
|
||||
Assert.True(replacement.IsSpatiallyVisible);
|
||||
Assert.Same(replacement.WorldEntity, Assert.Single(runtime.MaterializedWorldEntities).Value);
|
||||
Assert.Same(replacement.WorldEntity, Assert.Single(runtime.WorldEntities).Value);
|
||||
Assert.Same(replacement.WorldEntity, Assert.Single(runtime.MaterializedRecords).WorldEntity);
|
||||
Assert.Same(replacement.WorldEntity, Assert.Single(runtime.VisibleRecords).WorldEntity);
|
||||
Assert.Same(replacement.WorldEntity, Assert.Single(spatial.Entities));
|
||||
Assert.Equal(0, spatial.PendingLiveEntityCount);
|
||||
}
|
||||
|
|
@ -2213,8 +2218,8 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.Equal(0x01010022u, record.FullCellId);
|
||||
Assert.True(record.IsSpatiallyProjected);
|
||||
Assert.True(record.IsSpatiallyVisible);
|
||||
Assert.Same(record.WorldEntity, Assert.Single(runtime.MaterializedWorldEntities).Value);
|
||||
Assert.Same(record.WorldEntity, Assert.Single(runtime.WorldEntities).Value);
|
||||
Assert.Same(record.WorldEntity, Assert.Single(runtime.MaterializedRecords).WorldEntity);
|
||||
Assert.Same(record.WorldEntity, Assert.Single(runtime.VisibleRecords).WorldEntity);
|
||||
Assert.Same(record.WorldEntity, Assert.Single(spatial.Entities));
|
||||
}
|
||||
|
||||
|
|
@ -2260,8 +2265,8 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.True(record.IsSpatiallyProjected);
|
||||
Assert.True(record.IsSpatiallyVisible);
|
||||
Assert.Same(pending, record.WorldEntity);
|
||||
Assert.Same(pending, Assert.Single(runtime.MaterializedWorldEntities).Value);
|
||||
Assert.Same(pending, Assert.Single(runtime.WorldEntities).Value);
|
||||
Assert.Same(pending, Assert.Single(runtime.MaterializedRecords).WorldEntity);
|
||||
Assert.Same(pending, Assert.Single(runtime.VisibleRecords).WorldEntity);
|
||||
Assert.Same(pending, Assert.Single(spatial.Entities));
|
||||
}
|
||||
|
||||
|
|
@ -2300,7 +2305,7 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord current));
|
||||
Assert.Equal((ushort)2, current.Generation);
|
||||
Assert.Same(replacementEntity, current.WorldEntity);
|
||||
Assert.Same(replacementEntity, Assert.Single(runtime.WorldEntities).Value);
|
||||
Assert.Same(replacementEntity, Assert.Single(runtime.VisibleRecords).WorldEntity);
|
||||
Assert.Equal(1, runtime.PendingTeardownCount);
|
||||
Assert.Equal(1, runtime.RetryPendingTeardowns());
|
||||
Assert.Equal(0, runtime.PendingTeardownCount);
|
||||
|
|
@ -2315,11 +2320,14 @@ public sealed class LiveEntityRuntimeTests
|
|||
spatial.AddLandblock(EmptyLandblock(0x0303FFFFu));
|
||||
var runtime = new LiveEntityRuntime(spatial, new RecordingResources());
|
||||
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
|
||||
runtime.MaterializeLiveEntity(guid, 0x01010001u, id => Entity(id, guid));
|
||||
WorldEntity original = runtime.MaterializeLiveEntity(
|
||||
guid,
|
||||
0x01010001u,
|
||||
id => Entity(id, guid))!;
|
||||
bool rebucketed = false;
|
||||
spatial.LiveProjectionVisibilityChanged += (edgeGuid, visible) =>
|
||||
spatial.LiveProjectionVisibilityChanged += (localEntityId, visible) =>
|
||||
{
|
||||
if (rebucketed || visible || edgeGuid != guid)
|
||||
if (rebucketed || visible || localEntityId != original.Id)
|
||||
return;
|
||||
rebucketed = true;
|
||||
Assert.True(runtime.RebucketLiveEntity(guid, 0x03030033u));
|
||||
|
|
@ -2332,8 +2340,8 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.Equal(0x0303FFFFu, record.CanonicalLandblockId);
|
||||
Assert.True(record.IsSpatiallyProjected);
|
||||
Assert.True(record.IsSpatiallyVisible);
|
||||
Assert.Same(record.WorldEntity, Assert.Single(runtime.MaterializedWorldEntities).Value);
|
||||
Assert.Same(record.WorldEntity, Assert.Single(runtime.WorldEntities).Value);
|
||||
Assert.Same(record.WorldEntity, Assert.Single(runtime.MaterializedRecords).WorldEntity);
|
||||
Assert.Same(record.WorldEntity, Assert.Single(runtime.VisibleRecords).WorldEntity);
|
||||
Assert.Same(record.WorldEntity, Assert.Single(spatial.Entities));
|
||||
Assert.Equal(0, spatial.PendingLiveEntityCount);
|
||||
}
|
||||
|
|
@ -2374,7 +2382,8 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord replacement));
|
||||
Assert.Equal((ushort)2, replacement.Generation);
|
||||
Assert.True(replacement.IsSpatiallyVisible);
|
||||
Assert.True(spatial.IsLiveEntityVisible(guid));
|
||||
Assert.True(spatial.IsLiveEntityVisible(
|
||||
replacement.WorldEntity!.Id));
|
||||
Assert.Equal(2, resources.RegisterCount);
|
||||
Assert.Equal(1, resources.UnregisterCount);
|
||||
}
|
||||
|
|
@ -2448,11 +2457,14 @@ public sealed class LiveEntityRuntimeTests
|
|||
error.Flatten().InnerExceptions,
|
||||
exception => exception is InvalidOperationException
|
||||
&& exception.Message.Contains("active logical-lifetime transition", StringComparison.Ordinal));
|
||||
Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord current));
|
||||
RuntimeEntityRecord current = CurrentCanonical(runtime, guid);
|
||||
Assert.Equal((ushort)2, current.Generation);
|
||||
Assert.Null(current.WorldEntity);
|
||||
Assert.Null(current.LocalEntityId);
|
||||
Assert.False(runtime.TryGetRecord(guid, out _));
|
||||
Assert.Empty(spatial.Entities);
|
||||
Assert.DoesNotContain(guid, runtime.WorldEntities.Keys);
|
||||
Assert.DoesNotContain(
|
||||
runtime.VisibleRecords,
|
||||
record => record.ServerGuid == guid);
|
||||
Assert.Equal(1, resources.RegisterCount);
|
||||
Assert.Equal(1, resources.UnregisterCount);
|
||||
Assert.False(runtime.TryGetServerGuid(old.Id, out _));
|
||||
|
|
@ -2479,9 +2491,10 @@ public sealed class LiveEntityRuntimeTests
|
|||
|
||||
Assert.Equal(CreateObjectTimestampDisposition.StaleGeneration, outer.Inbound.Disposition);
|
||||
Assert.False(outer.LogicalRegistrationCreated);
|
||||
Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord current));
|
||||
RuntimeEntityRecord current = CurrentCanonical(runtime, guid);
|
||||
Assert.Equal((ushort)3, current.Generation);
|
||||
Assert.Null(current.WorldEntity);
|
||||
Assert.Null(current.LocalEntityId);
|
||||
Assert.False(runtime.TryGetRecord(guid, out _));
|
||||
Assert.Empty(spatial.Entities);
|
||||
Assert.Equal(1, resources.RegisterCount);
|
||||
Assert.Equal(1, resources.UnregisterCount);
|
||||
|
|
@ -2509,9 +2522,12 @@ public sealed class LiveEntityRuntimeTests
|
|||
|
||||
Assert.Equal(CreateObjectTimestampDisposition.NewGeneration, outer.Inbound.Disposition);
|
||||
Assert.True(outer.LogicalRegistrationCreated);
|
||||
Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord replacement));
|
||||
RuntimeEntityRecord replacement = CurrentCanonical(runtime, guid);
|
||||
Assert.Equal((ushort)2, replacement.Generation);
|
||||
Assert.True(runtime.TryGetRecord(unrelatedGuid, out _));
|
||||
Assert.Null(replacement.LocalEntityId);
|
||||
Assert.Null(CurrentCanonical(runtime, unrelatedGuid).LocalEntityId);
|
||||
Assert.False(runtime.TryGetRecord(guid, out _));
|
||||
Assert.False(runtime.TryGetRecord(unrelatedGuid, out _));
|
||||
Assert.Equal(2, runtime.Count);
|
||||
}
|
||||
|
||||
|
|
@ -2569,9 +2585,10 @@ public sealed class LiveEntityRuntimeTests
|
|||
|
||||
Assert.Equal(CreateObjectTimestampDisposition.StaleGeneration, outer.Inbound.Disposition);
|
||||
Assert.False(outer.LogicalRegistrationCreated);
|
||||
Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord current));
|
||||
RuntimeEntityRecord current = CurrentCanonical(runtime, guid);
|
||||
Assert.Equal((ushort)3, current.Generation);
|
||||
Assert.Null(current.WorldEntity);
|
||||
Assert.Null(current.LocalEntityId);
|
||||
Assert.False(runtime.TryGetRecord(guid, out _));
|
||||
Assert.Equal(1, resources.RegisterCount);
|
||||
Assert.Equal(1, resources.UnregisterCount);
|
||||
}
|
||||
|
|
@ -2600,9 +2617,10 @@ public sealed class LiveEntityRuntimeTests
|
|||
error.Flatten().InnerExceptions,
|
||||
exception => exception is InvalidOperationException
|
||||
&& exception.Message == "old incarnation cleanup failed");
|
||||
Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord current));
|
||||
RuntimeEntityRecord current = CurrentCanonical(runtime, guid);
|
||||
Assert.Equal((ushort)3, current.Generation);
|
||||
Assert.Null(current.WorldEntity);
|
||||
Assert.Null(current.LocalEntityId);
|
||||
Assert.False(runtime.TryGetRecord(guid, out _));
|
||||
Assert.Equal(1, resources.RegisterCount);
|
||||
Assert.Equal(1, resources.UnregisterCount);
|
||||
}
|
||||
|
|
@ -2651,9 +2669,10 @@ public sealed class LiveEntityRuntimeTests
|
|||
runtime.MaterializeLiveEntity(guid, 0x01010001u, id => Entity(id, guid)));
|
||||
|
||||
Assert.Contains("atomic resource registration", error.Message, StringComparison.Ordinal);
|
||||
Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord current));
|
||||
RuntimeEntityRecord current = CurrentCanonical(runtime, guid);
|
||||
Assert.Equal((ushort)1, current.Generation);
|
||||
Assert.Null(current.WorldEntity);
|
||||
Assert.Null(current.LocalEntityId);
|
||||
Assert.False(runtime.TryGetRecord(guid, out _));
|
||||
Assert.Equal(1, resources.RegisterCount);
|
||||
Assert.Equal(1, resources.UnregisterCount);
|
||||
Assert.Equal(0, runtime.MaterializedCount);
|
||||
|
|
@ -2675,8 +2694,9 @@ public sealed class LiveEntityRuntimeTests
|
|||
runtime.MaterializeLiveEntity(guid, 0x01010001u, id => Entity(id, guid)));
|
||||
|
||||
Assert.Contains("atomic resource registration", error.Message, StringComparison.Ordinal);
|
||||
Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord current));
|
||||
Assert.Null(current.WorldEntity);
|
||||
RuntimeEntityRecord current = CurrentCanonical(runtime, guid);
|
||||
Assert.Null(current.LocalEntityId);
|
||||
Assert.False(runtime.TryGetRecord(guid, out _));
|
||||
Assert.Equal(1, resources.RegisterCount);
|
||||
Assert.Equal(1, resources.UnregisterCount);
|
||||
Assert.Equal(0, runtime.MaterializedCount);
|
||||
|
|
@ -2746,7 +2766,7 @@ public sealed class LiveEntityRuntimeTests
|
|||
Assert.Equal(0, runtime.Count);
|
||||
Assert.Equal(1, runtime.PendingTeardownCount);
|
||||
Assert.Equal(1, runtime.MaterializedCount);
|
||||
Assert.Empty(runtime.WorldEntities);
|
||||
Assert.Empty(runtime.VisibleRecords);
|
||||
Assert.Empty(spatial.Entities);
|
||||
Assert.Equal(0, spatial.PendingLiveEntityCount);
|
||||
Assert.Equal(1, resources.RegisterCount);
|
||||
|
|
@ -2755,7 +2775,7 @@ public sealed class LiveEntityRuntimeTests
|
|||
runtime.Clear();
|
||||
Assert.Equal(0, runtime.PendingTeardownCount);
|
||||
Assert.Equal(0, runtime.MaterializedCount);
|
||||
Assert.Empty(runtime.MaterializedWorldEntities);
|
||||
Assert.Empty(runtime.MaterializedRecords);
|
||||
Assert.Equal(2, resources.UnregisterCount);
|
||||
}
|
||||
|
||||
|
|
@ -2779,57 +2799,11 @@ public sealed class LiveEntityRuntimeTests
|
|||
beforeTeardown: () => throw new InvalidOperationException("fixture callback failure")));
|
||||
|
||||
Assert.Equal(0, runtime.Count);
|
||||
Assert.Empty(runtime.WorldEntities);
|
||||
Assert.Empty(runtime.VisibleRecords);
|
||||
Assert.Empty(spatial.Entities);
|
||||
Assert.Equal(1, resources.UnregisterCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IncarnationCleanup_OldTombstoneCannotMutateReplacementGuidState()
|
||||
{
|
||||
const uint guid = 0x70000035u;
|
||||
var oldRecord = new LiveEntityRecord(Spawn(guid, 1, 1, 0x01010001u));
|
||||
var replacement = new LiveEntityRecord(Spawn(guid, 2, 1, 0x01010001u));
|
||||
LiveEntityRecord? current = replacement;
|
||||
var cleanup = new LiveEntityIncarnationCleanup(oldRecord, _ => current);
|
||||
object oldHost = new();
|
||||
object newHost = new();
|
||||
var hosts = new Dictionary<uint, object> { [guid] = newHost };
|
||||
bool guidStateCleared = false;
|
||||
|
||||
var plan = new LiveEntityTeardownPlan(
|
||||
[
|
||||
() => cleanup.RunIfNoReplacement(() => guidStateCleared = true),
|
||||
() => cleanup.RemoveCaptured(hosts, oldHost),
|
||||
]);
|
||||
plan.Advance();
|
||||
|
||||
Assert.False(guidStateCleared);
|
||||
Assert.Same(newHost, hosts[guid]);
|
||||
Assert.True(plan.IsComplete);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IncarnationCleanup_RemovesOnlyCapturedOwnerAndResumesAfterReplacementEnds()
|
||||
{
|
||||
const uint guid = 0x70000036u;
|
||||
var oldRecord = new LiveEntityRecord(Spawn(guid, 1, 1, 0x01010001u));
|
||||
var replacement = new LiveEntityRecord(Spawn(guid, 2, 1, 0x01010001u));
|
||||
LiveEntityRecord? current = replacement;
|
||||
var cleanup = new LiveEntityIncarnationCleanup(oldRecord, _ => current);
|
||||
object oldHost = new();
|
||||
var hosts = new Dictionary<uint, object> { [guid] = oldHost };
|
||||
int guidCleanupCount = 0;
|
||||
|
||||
cleanup.RemoveCaptured(hosts, oldHost);
|
||||
cleanup.RunIfNoReplacement(() => guidCleanupCount++);
|
||||
current = null;
|
||||
cleanup.RunIfNoReplacement(() => guidCleanupCount++);
|
||||
|
||||
Assert.Empty(hosts);
|
||||
Assert.Equal(1, guidCleanupCount);
|
||||
}
|
||||
|
||||
private static LoadedLandblock EmptyLandblock(uint canonicalId) =>
|
||||
new(canonicalId, new LandBlock(), Array.Empty<WorldEntity>());
|
||||
|
||||
|
|
@ -2849,6 +2823,34 @@ public sealed class LiveEntityRuntimeTests
|
|||
: null,
|
||||
update => runtime.TryApplyParent(update, out _));
|
||||
|
||||
private static LiveEntityRecord RegisterProjection(
|
||||
LiveEntityRuntime runtime,
|
||||
WorldSession.EntitySpawn spawn)
|
||||
{
|
||||
LiveEntityRegistrationResult registration =
|
||||
runtime.RegisterLiveEntity(spawn);
|
||||
RuntimeEntityRecord canonical = Assert.IsType<RuntimeEntityRecord>(
|
||||
registration.Canonical);
|
||||
WorldEntity entity = Assert.IsType<WorldEntity>(
|
||||
runtime.MaterializeLiveEntity(
|
||||
canonical,
|
||||
spawn.Position?.LandblockId ?? 0u,
|
||||
id => Entity(id, spawn.Guid),
|
||||
LiveEntityProjectionKind.World,
|
||||
initializeProjection: null,
|
||||
out LiveEntityRecord? record));
|
||||
Assert.Equal(entity.Id, canonical.LocalEntityId);
|
||||
return Assert.IsType<LiveEntityRecord>(record);
|
||||
}
|
||||
|
||||
private static RuntimeEntityRecord CurrentCanonical(
|
||||
LiveEntityRuntime runtime,
|
||||
uint serverGuid)
|
||||
{
|
||||
Assert.True(runtime.TryGetCanonical(serverGuid, out RuntimeEntityRecord canonical));
|
||||
return canonical;
|
||||
}
|
||||
|
||||
private static WorldEntity Entity(uint id, uint guid) => new()
|
||||
{
|
||||
Id = id,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue