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
|
|
@ -7,6 +7,7 @@ using AcDream.App.Net;
|
|||
using AcDream.App.Rendering;
|
||||
using AcDream.App.UI;
|
||||
using AcDream.App.UI.Layout;
|
||||
using AcDream.App.World;
|
||||
using AcDream.Core.Net;
|
||||
using AcDream.Core.World;
|
||||
using AcDream.UI.Abstractions;
|
||||
|
|
@ -91,7 +92,7 @@ public sealed class InteractionUiRuntimeSourcesTests
|
|||
|
||||
var provider = new RadarSnapshotProvider(
|
||||
new AcDream.Core.Items.ClientObjectTable(),
|
||||
static () => new Dictionary<uint, WorldEntity>(),
|
||||
EmptyRadarSource.Instance,
|
||||
static () => new Dictionary<uint, WorldSession.EntitySpawn>(),
|
||||
static () => 0u,
|
||||
static () => 0f,
|
||||
|
|
@ -253,6 +254,29 @@ public sealed class InteractionUiRuntimeSourcesTests
|
|||
}
|
||||
}
|
||||
|
||||
private sealed class EmptyRadarSource : ILiveEntityRadarSource
|
||||
{
|
||||
public static EmptyRadarSource Instance { get; } = new();
|
||||
|
||||
public bool TryGetMaterialized(
|
||||
uint serverGuid,
|
||||
out WorldEntity entity)
|
||||
{
|
||||
entity = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetVisible(uint serverGuid, out WorldEntity entity)
|
||||
{
|
||||
entity = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void CopyVisibleTo(
|
||||
List<KeyValuePair<uint, WorldEntity>> destination) =>
|
||||
destination.Clear();
|
||||
}
|
||||
|
||||
private static T Stub<T>() where T : class =>
|
||||
(T)RuntimeHelpers.GetUninitializedObject(typeof(T));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -501,13 +501,15 @@ public sealed class SelectionInteractionControllerTests
|
|||
public void OldRemovalClearsCapturedPickupButDoesNotClearReplacementSelection()
|
||||
{
|
||||
var h = new Harness();
|
||||
h.SetApproach(closeRange: true, localEntityId: 101u);
|
||||
var oldRecord = LiveEntityTestFixture.CreateExactProjectionRecord(
|
||||
Spawn(Target, instance: 1));
|
||||
uint localEntityId = oldRecord.LocalEntityId!.Value;
|
||||
h.SetApproach(closeRange: true, localEntityId: localEntityId);
|
||||
h.Selection.Select(Target, SelectionChangeSource.World);
|
||||
Assert.True(h.Items.PlaceWorldItemInBackpack(Target));
|
||||
var oldRecord = new LiveEntityRecord(Spawn(Target, instance: 1));
|
||||
oldRecord.WorldEntity = new WorldEntity
|
||||
{
|
||||
Id = 101u,
|
||||
Id = localEntityId,
|
||||
ServerGuid = Target,
|
||||
SourceGfxObjOrSetupId = 0x0200_0001u,
|
||||
Position = Vector3.Zero,
|
||||
|
|
|
|||
|
|
@ -33,10 +33,9 @@ public sealed class LiveEntityCollisionBuilderTests
|
|||
scale: 2f,
|
||||
itemType: (uint)ItemType.Creature,
|
||||
descriptionFlags: 0x28u);
|
||||
var record = new LiveEntityRecord(spawn)
|
||||
{
|
||||
FinalPhysicsState = PhysicsStateFlags.Hidden | PhysicsStateFlags.Static,
|
||||
};
|
||||
var record = LiveEntityTestFixture.CreateExactProjectionRecord(spawn);
|
||||
record.FinalPhysicsState =
|
||||
PhysicsStateFlags.Hidden | PhysicsStateFlags.Static;
|
||||
WorldEntity entity = Entity();
|
||||
record.WorldEntity = entity;
|
||||
var builder = Builder();
|
||||
|
|
@ -65,7 +64,7 @@ public sealed class LiveEntityCollisionBuilderTests
|
|||
var setup = new Setup();
|
||||
setup.Parts.Add(part);
|
||||
WorldSession.EntitySpawn spawn = Spawn(scale: 1.5f);
|
||||
var record = new LiveEntityRecord(spawn);
|
||||
var record = LiveEntityTestFixture.CreateExactProjectionRecord(spawn);
|
||||
WorldEntity entity = Entity();
|
||||
record.WorldEntity = entity;
|
||||
var builder = new LiveEntityCollisionBuilder(
|
||||
|
|
@ -90,7 +89,7 @@ public sealed class LiveEntityCollisionBuilderTests
|
|||
var setup = new Setup();
|
||||
setup.Parts.Add(basePart);
|
||||
WorldSession.EntitySpawn spawn = Spawn(scale: 1f);
|
||||
var record = new LiveEntityRecord(spawn);
|
||||
var record = LiveEntityTestFixture.CreateExactProjectionRecord(spawn);
|
||||
WorldEntity entity = Entity();
|
||||
record.WorldEntity = entity;
|
||||
var builder = new LiveEntityCollisionBuilder(
|
||||
|
|
@ -115,7 +114,7 @@ public sealed class LiveEntityCollisionBuilderTests
|
|||
var setup = new Setup();
|
||||
setup.Parts.Add(basePart);
|
||||
WorldSession.EntitySpawn spawn = Spawn(scale: 1f);
|
||||
var record = new LiveEntityRecord(spawn);
|
||||
var record = LiveEntityTestFixture.CreateExactProjectionRecord(spawn);
|
||||
WorldEntity entity = Entity();
|
||||
record.WorldEntity = entity;
|
||||
var builder = new LiveEntityCollisionBuilder(
|
||||
|
|
@ -145,7 +144,7 @@ public sealed class LiveEntityCollisionBuilderTests
|
|||
var setup = new Setup();
|
||||
setup.Parts.Add(basePart);
|
||||
WorldSession.EntitySpawn spawn = Spawn(scale: 1f);
|
||||
var record = new LiveEntityRecord(spawn);
|
||||
var record = LiveEntityTestFixture.CreateExactProjectionRecord(spawn);
|
||||
WorldEntity entity = Entity();
|
||||
record.WorldEntity = entity;
|
||||
var builder = new LiveEntityCollisionBuilder(
|
||||
|
|
@ -178,7 +177,7 @@ public sealed class LiveEntityCollisionBuilderTests
|
|||
var setup = new Setup();
|
||||
setup.Parts.Add(basePart);
|
||||
WorldSession.EntitySpawn spawn = Spawn(scale: 1f);
|
||||
var record = new LiveEntityRecord(spawn);
|
||||
var record = LiveEntityTestFixture.CreateExactProjectionRecord(spawn);
|
||||
WorldEntity entity = Entity();
|
||||
record.WorldEntity = entity;
|
||||
var builder = new LiveEntityCollisionBuilder(
|
||||
|
|
@ -243,7 +242,7 @@ public sealed class LiveEntityCollisionBuilderTests
|
|||
var setup = new Setup();
|
||||
setup.Parts.Add(basePart);
|
||||
WorldSession.EntitySpawn spawn = Spawn(scale: 1f);
|
||||
var record = new LiveEntityRecord(spawn);
|
||||
var record = LiveEntityTestFixture.CreateExactProjectionRecord(spawn);
|
||||
WorldEntity entity = Entity();
|
||||
record.WorldEntity = entity;
|
||||
var builder = new LiveEntityCollisionBuilder(
|
||||
|
|
@ -277,7 +276,7 @@ public sealed class LiveEntityCollisionBuilderTests
|
|||
public void ShapelessSetup_ProducesNoRegistration()
|
||||
{
|
||||
WorldSession.EntitySpawn spawn = Spawn(scale: 1f);
|
||||
var record = new LiveEntityRecord(spawn);
|
||||
var record = LiveEntityTestFixture.CreateExactProjectionRecord(spawn);
|
||||
WorldEntity entity = Entity();
|
||||
record.WorldEntity = entity;
|
||||
|
||||
|
|
@ -294,8 +293,9 @@ public sealed class LiveEntityCollisionBuilderTests
|
|||
public void Build_RejectsDifferentRecordIncarnation()
|
||||
{
|
||||
WorldSession.EntitySpawn spawn = Spawn(scale: 1f);
|
||||
var expected = new LiveEntityRecord(spawn);
|
||||
var other = new LiveEntityRecord(spawn with { InstanceSequence = 2 });
|
||||
var expected = LiveEntityTestFixture.CreateExactProjectionRecord(spawn);
|
||||
var other = LiveEntityTestFixture.CreateExactProjectionRecord(
|
||||
spawn with { InstanceSequence = 2 });
|
||||
WorldEntity entity = Entity();
|
||||
expected.WorldEntity = entity;
|
||||
other.WorldEntity = entity;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
|
|||
public void Motion_StrictFreshnessAndWrapAreOwnedBeforePresentation()
|
||||
{
|
||||
LiveEntityRuntime runtime = Runtime();
|
||||
runtime.RegisterLiveEntity(Spawn(Guid, instance: 7, movement: 0xFFFE));
|
||||
Register(runtime, Spawn(Guid, instance: 7, movement: 0xFFFE));
|
||||
var published = new List<AcceptedPhysicsTimestamps>();
|
||||
var gate = new LiveEntityInboundAuthorityGate(
|
||||
runtime,
|
||||
|
|
@ -52,7 +52,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
|
|||
public void AutonomousLocalMotion_ConsumesTimestampButDoesNotPublishPayload()
|
||||
{
|
||||
LiveEntityRuntime runtime = Runtime();
|
||||
runtime.RegisterLiveEntity(Spawn(Guid, instance: 1));
|
||||
Register(runtime, Spawn(Guid, instance: 1));
|
||||
int publishCount = 0;
|
||||
var gate = new LiveEntityInboundAuthorityGate(runtime, (_, _) => publishCount++);
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
|
|||
public void Vector_InvalidPayloadDoesNotConsumeSequenceAndDuplicateIsRejected()
|
||||
{
|
||||
LiveEntityRuntime runtime = Runtime();
|
||||
runtime.RegisterLiveEntity(Spawn(Guid, instance: 2));
|
||||
Register(runtime, Spawn(Guid, instance: 2));
|
||||
var gate = new LiveEntityInboundAuthorityGate(runtime, (_, _) => { });
|
||||
var update = new VectorUpdate.Parsed(
|
||||
Guid,
|
||||
|
|
@ -94,7 +94,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
|
|||
public void Vector_WrappedSequenceIsAccepted()
|
||||
{
|
||||
LiveEntityRuntime runtime = Runtime();
|
||||
runtime.RegisterLiveEntity(Spawn(Guid, instance: 2, vector: 0xFFFE));
|
||||
Register(runtime, Spawn(Guid, instance: 2, vector: 0xFFFE));
|
||||
var gate = new LiveEntityInboundAuthorityGate(runtime, (_, _) => { });
|
||||
|
||||
Assert.True(gate.TryAcceptVector(
|
||||
|
|
@ -112,7 +112,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
|
|||
public void State_EqualIsRejectedAndWrappedSequenceIsAccepted()
|
||||
{
|
||||
LiveEntityRuntime runtime = Runtime();
|
||||
runtime.RegisterLiveEntity(Spawn(Guid, instance: 3, state: 0xFFFE));
|
||||
Register(runtime, Spawn(Guid, instance: 3, state: 0xFFFE));
|
||||
var gate = new LiveEntityInboundAuthorityGate(runtime, (_, _) => { });
|
||||
|
||||
Assert.False(gate.TryAcceptState(
|
||||
|
|
@ -149,7 +149,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
|
|||
Assert.Equal(0xAABB0001u, gate.LastLivePlayerLandblockId);
|
||||
Assert.Equal(0, publishCount);
|
||||
|
||||
runtime.RegisterLiveEntity(Spawn(Guid, instance: 4, position: 1));
|
||||
Register(runtime, Spawn(Guid, instance: 4, position: 1));
|
||||
Assert.True(gate.TryAcceptPosition(
|
||||
update,
|
||||
Guid,
|
||||
|
|
@ -169,7 +169,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
|
|||
public void Position_InvalidPayloadDoesNotConsumeSequence()
|
||||
{
|
||||
LiveEntityRuntime runtime = Runtime();
|
||||
runtime.RegisterLiveEntity(Spawn(Guid, instance: 5, position: 1));
|
||||
Register(runtime, Spawn(Guid, instance: 5, position: 1));
|
||||
var gate = new LiveEntityInboundAuthorityGate(runtime, (_, _) => { });
|
||||
WorldSession.EntityPositionUpdate update = Position(Guid, 5, 2, 0x01010001u);
|
||||
|
||||
|
|
@ -185,7 +185,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
|
|||
public void Position_WrappedSequenceIsAccepted()
|
||||
{
|
||||
LiveEntityRuntime runtime = Runtime();
|
||||
runtime.RegisterLiveEntity(Spawn(Guid, instance: 5, position: 0xFFFE));
|
||||
Register(runtime, Spawn(Guid, instance: 5, position: 0xFFFE));
|
||||
var gate = new LiveEntityInboundAuthorityGate(runtime, (_, _) => { });
|
||||
|
||||
Assert.True(gate.TryAcceptPosition(
|
||||
|
|
@ -201,7 +201,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
|
|||
public void Vector_NewerSameIncarnationPacketInvalidatesCapturedAuthority()
|
||||
{
|
||||
LiveEntityRuntime runtime = Runtime();
|
||||
runtime.RegisterLiveEntity(Spawn(Guid, instance: 5, vector: 1));
|
||||
Register(runtime, Spawn(Guid, instance: 5, vector: 1));
|
||||
var gate = new LiveEntityInboundAuthorityGate(runtime, (_, _) => { });
|
||||
Assert.True(gate.TryAcceptVector(
|
||||
new VectorUpdate.Parsed(Guid, Vector3.One, Vector3.Zero, 5, 2),
|
||||
|
|
@ -220,7 +220,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
|
|||
public void State_NewerSameIncarnationPacketInvalidatesCapturedAuthority()
|
||||
{
|
||||
LiveEntityRuntime runtime = Runtime();
|
||||
runtime.RegisterLiveEntity(Spawn(Guid, instance: 5, state: 1));
|
||||
Register(runtime, Spawn(Guid, instance: 5, state: 1));
|
||||
var gate = new LiveEntityInboundAuthorityGate(runtime, (_, _) => { });
|
||||
Assert.True(gate.TryAcceptState(
|
||||
new SetState.Parsed(Guid, (uint)PhysicsStateFlags.Hidden, 5, 2),
|
||||
|
|
@ -238,7 +238,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
|
|||
public void Motion_PublisherReplacementInvalidatesCapturedIncarnation()
|
||||
{
|
||||
LiveEntityRuntime runtime = Runtime();
|
||||
runtime.RegisterLiveEntity(Spawn(Guid, instance: 6));
|
||||
Register(runtime, Spawn(Guid, instance: 6));
|
||||
var gate = new LiveEntityInboundAuthorityGate(
|
||||
runtime,
|
||||
(_, _) => runtime.RegisterLiveEntity(Spawn(Guid, instance: 7)));
|
||||
|
|
@ -249,15 +249,17 @@ public sealed class LiveEntityInboundAuthorityGateTests
|
|||
out _,
|
||||
out bool timestampAccepted));
|
||||
Assert.True(timestampAccepted);
|
||||
Assert.True(runtime.TryGetRecord(Guid, out LiveEntityRecord replacement));
|
||||
Assert.Equal((ushort)7, replacement.Generation);
|
||||
Assert.True(runtime.TryGetCanonical(Guid, out RuntimeEntityRecord replacement));
|
||||
Assert.Equal((ushort)7, replacement.Incarnation);
|
||||
Assert.Null(replacement.LocalEntityId);
|
||||
Assert.False(runtime.TryGetRecord(Guid, out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Position_PublisherNewerChannelInvalidatesCapturedAuthority()
|
||||
{
|
||||
LiveEntityRuntime runtime = Runtime();
|
||||
runtime.RegisterLiveEntity(Spawn(Guid, instance: 8, position: 1));
|
||||
Register(runtime, Spawn(Guid, instance: 8, position: 1));
|
||||
var nested = Position(Guid, 8, 3, 0x01010002u);
|
||||
LiveEntityInboundAuthorityGate? gate = null;
|
||||
gate = new LiveEntityInboundAuthorityGate(
|
||||
|
|
@ -286,7 +288,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
|
|||
public void Position_PublisherNewerVectorPreservesIndependentPositionAuthority()
|
||||
{
|
||||
LiveEntityRuntime runtime = Runtime();
|
||||
runtime.RegisterLiveEntity(Spawn(Guid, instance: 9, position: 1, vector: 1));
|
||||
Register(runtime, Spawn(Guid, instance: 9, position: 1, vector: 1));
|
||||
var gate = new LiveEntityInboundAuthorityGate(
|
||||
runtime,
|
||||
(_, _) => runtime.TryApplyVector(
|
||||
|
|
@ -317,6 +319,11 @@ public sealed class LiveEntityInboundAuthorityGateTests
|
|||
new GpuWorldState(),
|
||||
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
|
||||
|
||||
private static LiveEntityRecord Register(
|
||||
LiveEntityRuntime runtime,
|
||||
WorldSession.EntitySpawn spawn) =>
|
||||
runtime.RegisterAndMaterializeProjection(spawn);
|
||||
|
||||
private static WorldSession.EntityMotionUpdate Motion(
|
||||
ushort instance,
|
||||
ushort movement,
|
||||
|
|
|
|||
|
|
@ -26,10 +26,8 @@ public sealed class LiveEntityOrdinaryPhysicsUpdaterTests
|
|||
var live = new LiveEntityRuntime(
|
||||
spatial,
|
||||
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
|
||||
LiveEntityRecord record = live.RegisterLiveEntity(Spawn()).Record!;
|
||||
WorldEntity entity = live.MaterializeLiveEntity(
|
||||
Guid,
|
||||
SourceCell,
|
||||
LiveEntityRecord record = live.RegisterAndMaterializeProjection(
|
||||
Spawn(),
|
||||
id => new WorldEntity
|
||||
{
|
||||
Id = id,
|
||||
|
|
@ -39,7 +37,8 @@ public sealed class LiveEntityOrdinaryPhysicsUpdaterTests
|
|||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
ParentCellId = SourceCell,
|
||||
})!;
|
||||
});
|
||||
WorldEntity entity = Assert.IsType<WorldEntity>(record.WorldEntity);
|
||||
|
||||
var remote = new AcDream.App.Physics.RemoteMotion
|
||||
{
|
||||
|
|
@ -94,10 +93,8 @@ public sealed class LiveEntityOrdinaryPhysicsUpdaterTests
|
|||
var live = new LiveEntityRuntime(
|
||||
spatial,
|
||||
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
|
||||
LiveEntityRecord record = live.RegisterLiveEntity(Spawn()).Record!;
|
||||
WorldEntity entity = live.MaterializeLiveEntity(
|
||||
Guid,
|
||||
SourceCell,
|
||||
LiveEntityRecord record = live.RegisterAndMaterializeProjection(
|
||||
Spawn(),
|
||||
id => new WorldEntity
|
||||
{
|
||||
Id = id,
|
||||
|
|
@ -107,7 +104,8 @@ public sealed class LiveEntityOrdinaryPhysicsUpdaterTests
|
|||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
ParentCellId = SourceCell,
|
||||
})!;
|
||||
});
|
||||
WorldEntity entity = Assert.IsType<WorldEntity>(record.WorldEntity);
|
||||
var remote = new AcDream.App.Physics.RemoteMotion
|
||||
{
|
||||
CellId = SourceCell,
|
||||
|
|
|
|||
|
|
@ -243,18 +243,18 @@ public sealed class ProjectileControllerTests
|
|||
Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0, 1, 1));
|
||||
Assert.True(record.PhysicsBody!.InWorld);
|
||||
Assert.True(record.PhysicsBody.IsActive);
|
||||
Assert.Single(fixture.Live.SpatialProjectileRuntimes);
|
||||
Assert.Equal(1, fixture.Live.SpatialProjectileRuntimeCount);
|
||||
|
||||
fixture.Spatial.RemoveLandblock(0x0101FFFFu);
|
||||
|
||||
Assert.False(record.IsSpatiallyVisible);
|
||||
Assert.Empty(fixture.Live.SpatialProjectileRuntimes);
|
||||
Assert.Equal(0, fixture.Live.SpatialProjectileRuntimeCount);
|
||||
Assert.False(record.PhysicsBody.InWorld);
|
||||
Assert.False(record.PhysicsBody.IsActive);
|
||||
Assert.Equal(0, fixture.Engine.ShadowObjects.TotalRegistered);
|
||||
|
||||
fixture.Spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
|
||||
Assert.Single(fixture.Live.SpatialProjectileRuntimes);
|
||||
Assert.Equal(1, fixture.Live.SpatialProjectileRuntimeCount);
|
||||
Assert.True(record.PhysicsBody.InWorld);
|
||||
Assert.True(record.PhysicsBody.IsActive);
|
||||
Assert.Equal(1, fixture.Engine.ShadowObjects.TotalRegistered);
|
||||
|
|
@ -545,7 +545,8 @@ public sealed class ProjectileControllerTests
|
|||
Assert.Same(runtime, record.ProjectileRuntime);
|
||||
Assert.Same(body, record.PhysicsBody);
|
||||
Assert.True(body.IsActive); // State preserves an already-active body.
|
||||
Assert.Same(record.WorldEntity, fixture.Live.MaterializedWorldEntities[Guid]);
|
||||
Assert.True(fixture.Live.TryGetWorldEntity(Guid, out WorldEntity entity));
|
||||
Assert.Same(record.WorldEntity, entity);
|
||||
Assert.Equal(1, fixture.Resources.Registers);
|
||||
}
|
||||
|
||||
|
|
@ -1501,12 +1502,8 @@ public sealed class ProjectileControllerTests
|
|||
angularVelocity ?? Vector3.Zero,
|
||||
friction,
|
||||
elasticity);
|
||||
LiveEntityRegistrationResult registration = Live.RegisterLiveEntity(spawn);
|
||||
Assert.NotNull(registration.Record);
|
||||
registration.Record!.HasPartArray = true;
|
||||
WorldEntity? entity = Live.MaterializeLiveEntity(
|
||||
Guid,
|
||||
cellId,
|
||||
LiveEntityRecord record = Live.RegisterAndMaterializeProjection(
|
||||
spawn,
|
||||
localId => new WorldEntity
|
||||
{
|
||||
Id = localId,
|
||||
|
|
@ -1518,10 +1515,11 @@ public sealed class ProjectileControllerTests
|
|||
? new[] { new MeshRef(0x01000001u, Matrix4x4.Identity) }
|
||||
: Array.Empty<MeshRef>(),
|
||||
ParentCellId = cellId,
|
||||
});
|
||||
Assert.NotNull(entity);
|
||||
},
|
||||
initializeProjection: exact => exact.HasPartArray = true);
|
||||
WorldEntity entity = Assert.IsType<WorldEntity>(record.WorldEntity);
|
||||
Poses.PublishMeshRefs(entity);
|
||||
return registration.Record!;
|
||||
return record;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -697,7 +697,7 @@ public sealed class RemotePhysicsUpdaterTests
|
|||
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
|
||||
BindHiddenRemote(live, firstGuid);
|
||||
BindHiddenRemote(live, secondGuid);
|
||||
Assert.Equal(2, live.SpatialRemoteMotionRuntimes.Count);
|
||||
Assert.Equal(2, live.SpatialRemoteMotionRuntimeCount);
|
||||
|
||||
var updater = new RemotePhysicsUpdater(
|
||||
new PhysicsEngine(),
|
||||
|
|
@ -721,7 +721,7 @@ public sealed class RemotePhysicsUpdaterTests
|
|||
|
||||
Assert.Single(published);
|
||||
Assert.Equal(published, partPoseDirty);
|
||||
Assert.Single(live.SpatialRemoteMotionRuntimes);
|
||||
Assert.Equal(1, live.SpatialRemoteMotionRuntimeCount);
|
||||
}
|
||||
|
||||
private static PhysicsEngine BuildBoundaryEngine()
|
||||
|
|
@ -908,14 +908,8 @@ public sealed class RemotePhysicsUpdaterTests
|
|||
Vector3 position,
|
||||
bool enqueueDestination)
|
||||
{
|
||||
LiveEntityRegistrationResult registration = Live.RegisterLiveEntity(
|
||||
Spawn(instanceSequence, position));
|
||||
LiveEntityRecord record = Assert.IsType<LiveEntityRecord>(
|
||||
registration.Record);
|
||||
WorldEntity entity = Assert.IsType<WorldEntity>(
|
||||
Live.MaterializeLiveEntity(
|
||||
Guid,
|
||||
SourceCell,
|
||||
LiveEntityRecord record = Live.RegisterAndMaterializeProjection(
|
||||
Spawn(instanceSequence, position),
|
||||
id => new WorldEntity
|
||||
{
|
||||
Id = id,
|
||||
|
|
@ -925,7 +919,8 @@ public sealed class RemotePhysicsUpdaterTests
|
|||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
ParentCellId = SourceCell,
|
||||
}));
|
||||
});
|
||||
WorldEntity entity = Assert.IsType<WorldEntity>(record.WorldEntity);
|
||||
var remote = new AcDream.App.Physics.RemoteMotion();
|
||||
remote.Body.Position = position;
|
||||
remote.Body.Orientation = Quaternion.Identity;
|
||||
|
|
|
|||
|
|
@ -1089,13 +1089,14 @@ public sealed class RemoteTeleportControllerTests
|
|||
const uint sourceCell = 0xA9B40039u;
|
||||
const uint destinationCell = 0xAAB40001u;
|
||||
RemoteTeleportController? controller = null;
|
||||
LiveEntityRecord? owner = null;
|
||||
bool cleanupAfterFailureRan = false;
|
||||
var resources = new DelegateLiveEntityResourceLifecycle(
|
||||
_ => { },
|
||||
_ => LiveEntityTeardown.Run(
|
||||
[
|
||||
() => throw new InvalidOperationException("effect teardown failed"),
|
||||
() => controller!.Forget(guid),
|
||||
() => controller!.Forget(owner!),
|
||||
() => cleanupAfterFailureRan = true,
|
||||
]));
|
||||
var spatial = new GpuWorldState();
|
||||
|
|
@ -1117,6 +1118,7 @@ public sealed class RemoteTeleportControllerTests
|
|||
var remote = new AcDream.App.Physics.RemoteMotion();
|
||||
remote.Body.SnapToCell(sourceCell, entity.Position, entity.Position);
|
||||
live.SetRemoteMotionRuntime(guid, remote);
|
||||
Assert.True(live.TryGetRecord(guid, out owner));
|
||||
controller = new RemoteTeleportController(
|
||||
BuildEngine(includeDestination: false),
|
||||
live,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ public sealed class AttachmentUpdateOrderTests
|
|||
var calls = new List<uint>();
|
||||
|
||||
var children = insertionOrder.ToDictionary(id => id, id => parents[id]);
|
||||
new AttachmentUpdateOrder<uint?>().ForEachParentFirst(
|
||||
new AttachmentUpdateOrder<uint, uint?>().ForEachParentFirst(
|
||||
children,
|
||||
static parentId => parentId,
|
||||
id =>
|
||||
|
|
@ -44,7 +44,7 @@ public sealed class AttachmentUpdateOrderTests
|
|||
};
|
||||
var calls = new List<uint>();
|
||||
|
||||
IReadOnlyList<uint> failed = new AttachmentUpdateOrder<uint?>().ForEachParentFirst(
|
||||
IReadOnlyList<uint> failed = new AttachmentUpdateOrder<uint, uint?>().ForEachParentFirst(
|
||||
children,
|
||||
static parentId => parentId,
|
||||
id =>
|
||||
|
|
@ -128,7 +128,7 @@ public sealed class AttachmentUpdateOrderTests
|
|||
[20u] = 10u,
|
||||
[30u] = 20u,
|
||||
};
|
||||
var order = new AttachmentUpdateOrder<uint?>().CollectSubtreePostOrder(
|
||||
var order = new AttachmentUpdateOrder<uint, uint?>().CollectSubtreePostOrder(
|
||||
children,
|
||||
10u,
|
||||
static parentId => parentId);
|
||||
|
|
@ -146,7 +146,7 @@ public sealed class AttachmentUpdateOrderTests
|
|||
};
|
||||
var realized = new List<uint>();
|
||||
|
||||
new AttachmentUpdateOrder<uint?>().RealizeDescendants(
|
||||
new AttachmentUpdateOrder<uint, uint?>().RealizeDescendants(
|
||||
10u,
|
||||
parent => waitingByParent.TryGetValue(parent, out var waiting)
|
||||
? waiting
|
||||
|
|
@ -170,7 +170,7 @@ public sealed class AttachmentUpdateOrderTests
|
|||
};
|
||||
var attempted = new List<uint>();
|
||||
|
||||
new AttachmentUpdateOrder<uint?>().RealizeDescendants(
|
||||
new AttachmentUpdateOrder<uint, uint?>().RealizeDescendants(
|
||||
10u,
|
||||
parent => waitingByParent.TryGetValue(parent, out var waiting)
|
||||
? waiting
|
||||
|
|
|
|||
|
|
@ -106,8 +106,8 @@ public sealed class EquippedChildProjectionWithdrawalTests
|
|||
LiveEntityProjectionKind.Attached);
|
||||
fixture.InstallAttached(parent, oldChild);
|
||||
|
||||
LiveEntityRecord replacement = fixture.Live.RegisterLiveEntity(
|
||||
ControllerFixture.SpawnData(0x70000201u, generation: 2)).Record!;
|
||||
LiveEntityRecord replacement = fixture.Live.RegisterAndMaterializeProjection(
|
||||
ControllerFixture.SpawnData(0x70000201u, generation: 2));
|
||||
|
||||
Assert.Empty(fixture.Controller.AttachedEntityIds);
|
||||
Assert.True(fixture.Live.TryGetRecord(0x70000201u, out LiveEntityRecord current));
|
||||
|
|
@ -136,7 +136,7 @@ public sealed class EquippedChildProjectionWithdrawalTests
|
|||
fixture.Controller.ProjectionRemoved -= Throw;
|
||||
Assert.Equal(1, fixture.Live.RetryPendingTeardowns());
|
||||
|
||||
static void Throw(uint _) =>
|
||||
static void Throw(LiveEntityRecord _) =>
|
||||
throw new InvalidOperationException("injected projection observer failure");
|
||||
}
|
||||
|
||||
|
|
@ -424,11 +424,11 @@ public sealed class EquippedChildProjectionWithdrawalTests
|
|||
fixture.Controller.OnParentEvent(new ParentEvent.Parsed(
|
||||
invalidParent.ServerGuid, childGuid, 1, 0, 1, 2));
|
||||
|
||||
LiveEntityRecord child = fixture.RegisterOnly(
|
||||
RuntimeEntityRecord childIdentity = fixture.RegisterOnly(
|
||||
childGuid,
|
||||
generation: 1,
|
||||
hasPosition: true);
|
||||
fixture.Materialize(child);
|
||||
LiveEntityRecord child = fixture.Materialize(childIdentity);
|
||||
Assert.True(fixture.Live.TryGetSnapshot(
|
||||
childGuid,
|
||||
out WorldSession.EntitySpawn childSpawn));
|
||||
|
|
@ -455,7 +455,7 @@ public sealed class EquippedChildProjectionWithdrawalTests
|
|||
ExactProjectionWithdrawalDisposition.Completed,
|
||||
Failure: null));
|
||||
LiveEntityRecord parent = fixture.Spawn(0x70000254u, generation: 1);
|
||||
LiveEntityRecord child = fixture.RegisterOnly(
|
||||
RuntimeEntityRecord child = fixture.RegisterOnly(
|
||||
0x70000255u,
|
||||
generation: 1,
|
||||
hasPosition: false);
|
||||
|
|
@ -475,7 +475,7 @@ public sealed class EquippedChildProjectionWithdrawalTests
|
|||
out WorldSession.EntitySpawn snapshot));
|
||||
Assert.Equal(parent.ServerGuid, snapshot.ParentGuid);
|
||||
Assert.Null(snapshot.Position);
|
||||
Assert.Null(child.WorldEntity);
|
||||
Assert.False(fixture.Live.TryGetRecord(child.ServerGuid, out _));
|
||||
var relation = new ParentAttachmentRelation(
|
||||
parent.ServerGuid,
|
||||
child.ServerGuid,
|
||||
|
|
@ -490,9 +490,14 @@ public sealed class EquippedChildProjectionWithdrawalTests
|
|||
Array.Empty<Matrix4x4>());
|
||||
fixture.Controller.OnPosePublished(parent.ServerGuid);
|
||||
|
||||
Assert.NotNull(child.WorldEntity);
|
||||
Assert.True(child.IsSpatiallyProjected);
|
||||
Assert.Equal(LiveEntityProjectionKind.Attached, child.ProjectionKind);
|
||||
Assert.True(fixture.Live.TryGetRecord(
|
||||
child.ServerGuid,
|
||||
out LiveEntityRecord childProjection));
|
||||
Assert.NotNull(childProjection.WorldEntity);
|
||||
Assert.True(childProjection.IsSpatiallyProjected);
|
||||
Assert.Equal(
|
||||
LiveEntityProjectionKind.Attached,
|
||||
childProjection.ProjectionKind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -513,13 +518,12 @@ public sealed class EquippedChildProjectionWithdrawalTests
|
|||
PlacementId = null,
|
||||
PositionSequence = 0,
|
||||
};
|
||||
LiveEntityRecord child = fixture.Live.RegisterLiveEntity(childSpawn).Record!;
|
||||
|
||||
fixture.Live.RegisterLiveEntity(childSpawn);
|
||||
fixture.Controller.OnSpawn(childSpawn);
|
||||
|
||||
var expected = new ParentAttachmentRelation(
|
||||
parent.ServerGuid,
|
||||
child.ServerGuid,
|
||||
childSpawn.Guid,
|
||||
ParentLocation: 0,
|
||||
PlacementId: 0,
|
||||
ParentInstanceSequence: 0,
|
||||
|
|
@ -527,6 +531,9 @@ public sealed class EquippedChildProjectionWithdrawalTests
|
|||
Assert.True(fixture.Live.ParentAttachments.IsCommitted(expected));
|
||||
fixture.Poses.Publish(parent.WorldEntity!, Array.Empty<Matrix4x4>());
|
||||
fixture.Controller.OnPosePublished(parent.ServerGuid);
|
||||
Assert.True(fixture.Live.TryGetRecord(
|
||||
childSpawn.Guid,
|
||||
out LiveEntityRecord child));
|
||||
Assert.Equal(LiveEntityProjectionKind.Attached, child.ProjectionKind);
|
||||
Assert.NotNull(child.WorldEntity);
|
||||
}
|
||||
|
|
@ -560,10 +567,13 @@ public sealed class EquippedChildProjectionWithdrawalTests
|
|||
PlacementId = 0,
|
||||
PositionSequence = 0,
|
||||
};
|
||||
LiveEntityRecord child = fixture.Live.RegisterLiveEntity(childSpawn).Record!;
|
||||
fixture.Live.RegisterLiveEntity(childSpawn);
|
||||
fixture.Controller.OnSpawn(childSpawn);
|
||||
fixture.Poses.Publish(parent.WorldEntity!, Array.Empty<Matrix4x4>());
|
||||
fixture.Controller.OnPosePublished(parent.ServerGuid);
|
||||
Assert.True(fixture.Live.TryGetRecord(
|
||||
childSpawn.Guid,
|
||||
out LiveEntityRecord child));
|
||||
WorldEntity entity = child.WorldEntity!;
|
||||
Assert.Null(entity.PaletteOverride);
|
||||
WorldSession.EntitySpawn grandchildSpawn = ControllerFixture.SpawnData(
|
||||
|
|
@ -576,9 +586,11 @@ public sealed class EquippedChildProjectionWithdrawalTests
|
|||
PlacementId = 0,
|
||||
PositionSequence = 0,
|
||||
};
|
||||
LiveEntityRecord grandchild = fixture.Live.RegisterLiveEntity(
|
||||
grandchildSpawn).Record!;
|
||||
fixture.Live.RegisterLiveEntity(grandchildSpawn);
|
||||
fixture.Controller.OnSpawn(grandchildSpawn);
|
||||
Assert.True(fixture.Live.TryGetRecord(
|
||||
grandchildSpawn.Guid,
|
||||
out LiveEntityRecord grandchild));
|
||||
WorldEntity grandchildEntity = grandchild.WorldEntity!;
|
||||
Assert.Equal(
|
||||
LiveEntityProjectionKind.Attached,
|
||||
|
|
@ -642,12 +654,12 @@ public sealed class EquippedChildProjectionWithdrawalTests
|
|||
using (fixture)
|
||||
{
|
||||
LiveEntityRecord parent = fixture.Spawn(0x7000025Cu, generation: 1);
|
||||
LiveEntityRecord child = fixture.RegisterOnly(
|
||||
RuntimeEntityRecord childIdentity = fixture.RegisterOnly(
|
||||
0x7000025Du,
|
||||
generation: 1,
|
||||
hasPosition: true,
|
||||
hasSetup: false);
|
||||
fixture.Materialize(child);
|
||||
LiveEntityRecord child = fixture.Materialize(childIdentity);
|
||||
child.HasPartArray = false;
|
||||
var update = new ParentEvent.Parsed(
|
||||
parent.ServerGuid, child.ServerGuid, 0, 0, 1, 1);
|
||||
|
|
@ -763,7 +775,7 @@ public sealed class EquippedChildProjectionWithdrawalTests
|
|||
0x70000257u,
|
||||
generation: 1,
|
||||
LiveEntityProjectionKind.Attached);
|
||||
LiveEntityRecord waitingParent = fixture.RegisterOnly(
|
||||
RuntimeEntityRecord waitingParent = fixture.RegisterOnly(
|
||||
0x70000258u,
|
||||
generation: 1,
|
||||
hasPosition: true);
|
||||
|
|
@ -1117,7 +1129,8 @@ public sealed class EquippedChildProjectionWithdrawalTests
|
|||
}
|
||||
}
|
||||
|
||||
private static LiveEntityRecord ChildRecord() => new(
|
||||
private static LiveEntityRecord ChildRecord() =>
|
||||
LiveEntityTestFixture.CreateExactProjectionRecord(
|
||||
new WorldSession.EntitySpawn(
|
||||
0x70000100u,
|
||||
new CreateObject.ServerPosition(
|
||||
|
|
@ -1188,12 +1201,12 @@ public sealed class EquippedChildProjectionWithdrawalTests
|
|||
ushort generation,
|
||||
LiveEntityProjectionKind kind = LiveEntityProjectionKind.World)
|
||||
{
|
||||
LiveEntityRecord record = RegisterOnly(guid, generation, hasPosition: true);
|
||||
Materialize(record, kind);
|
||||
return record;
|
||||
RuntimeEntityRecord canonical =
|
||||
RegisterOnly(guid, generation, hasPosition: true);
|
||||
return Materialize(canonical, kind);
|
||||
}
|
||||
|
||||
internal LiveEntityRecord RegisterOnly(
|
||||
internal RuntimeEntityRecord RegisterOnly(
|
||||
uint guid,
|
||||
ushort generation,
|
||||
bool hasPosition,
|
||||
|
|
@ -1204,28 +1217,35 @@ public sealed class EquippedChildProjectionWithdrawalTests
|
|||
spawn = spawn with { Position = null };
|
||||
if (!hasSetup)
|
||||
spawn = spawn with { SetupTableId = null };
|
||||
return Live.RegisterLiveEntity(spawn).Record!;
|
||||
return Assert.IsType<RuntimeEntityRecord>(
|
||||
Live.RegisterLiveEntity(spawn).Canonical);
|
||||
}
|
||||
|
||||
internal void Materialize(
|
||||
LiveEntityRecord record,
|
||||
internal LiveEntityRecord Materialize(
|
||||
RuntimeEntityRecord canonical,
|
||||
LiveEntityProjectionKind kind = LiveEntityProjectionKind.World)
|
||||
{
|
||||
Live.MaterializeLiveEntity(
|
||||
record.ServerGuid,
|
||||
WorldEntity? entity = Live.MaterializeLiveEntity(
|
||||
canonical,
|
||||
Cell,
|
||||
id => new WorldEntity
|
||||
{
|
||||
Id = id,
|
||||
ServerGuid = record.ServerGuid,
|
||||
ServerGuid = canonical.ServerGuid,
|
||||
SourceGfxObjOrSetupId = 0x02000001u,
|
||||
Position = Vector3.Zero,
|
||||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
ParentCellId = Cell,
|
||||
},
|
||||
kind);
|
||||
kind,
|
||||
initializeProjection: null,
|
||||
out LiveEntityRecord? projected);
|
||||
Assert.NotNull(entity);
|
||||
LiveEntityRecord record =
|
||||
Assert.IsType<LiveEntityRecord>(projected);
|
||||
record.HasPartArray = true;
|
||||
return record;
|
||||
}
|
||||
|
||||
internal static WorldSession.EntitySpawn SpawnData(uint guid, ushort generation) => new(
|
||||
|
|
@ -1277,7 +1297,7 @@ public sealed class EquippedChildProjectionWithdrawalTests
|
|||
"_attachedByChild",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic)!;
|
||||
var map = (IDictionary)mapField.GetValue(Controller)!;
|
||||
map.Add(child.ServerGuid, attached);
|
||||
map.Add(child.ProjectionKey!.Value, attached);
|
||||
}
|
||||
|
||||
internal void CommitRenderedRelation(ParentAttachmentRelation relation)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ public sealed class LiveAppearanceAnimationTests
|
|||
var runtime = new AcDream.App.World.LiveEntityRuntime(
|
||||
new AcDream.App.Streaming.GpuWorldState(),
|
||||
new AcDream.App.World.DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
|
||||
var record = runtime.RegisterLiveEntity(new AcDream.Core.Net.WorldSession.EntitySpawn(
|
||||
var spawn = new AcDream.Core.Net.WorldSession.EntitySpawn(
|
||||
guid,
|
||||
Position: null,
|
||||
SetupTableId: null,
|
||||
|
|
@ -33,8 +33,11 @@ public sealed class LiveAppearanceAnimationTests
|
|||
ItemType: null,
|
||||
MotionState: null,
|
||||
MotionTableId: null,
|
||||
InstanceSequence: 1)).Record!;
|
||||
WorldEntity entity = Entity(0x70000002u, 0x01000001u);
|
||||
InstanceSequence: 1);
|
||||
LiveEntityRecord record = runtime.RegisterAndMaterializeProjection(
|
||||
spawn,
|
||||
id => Entity(id, 0x01000001u, guid));
|
||||
WorldEntity entity = Assert.IsType<WorldEntity>(record.WorldEntity);
|
||||
var state = new LiveEntityAnimationState
|
||||
{
|
||||
Entity = entity,
|
||||
|
|
@ -47,7 +50,6 @@ public sealed class LiveAppearanceAnimationTests
|
|||
PartTemplate = Array.Empty<LiveAnimationPartTemplate>(),
|
||||
PartAvailability = Array.Empty<bool>(),
|
||||
};
|
||||
record.WorldEntity = entity;
|
||||
record.AnimationRuntime = state;
|
||||
|
||||
LiveEntityAppearanceUpdateState captured = Assert.IsType<LiveEntityAppearanceUpdateState>(
|
||||
|
|
@ -188,10 +190,13 @@ public sealed class LiveAppearanceAnimationTests
|
|||
MotionTableId: null,
|
||||
InstanceSequence: instance);
|
||||
|
||||
private static WorldEntity Entity(uint id, uint gfxObjId) => new()
|
||||
private static WorldEntity Entity(
|
||||
uint id,
|
||||
uint gfxObjId,
|
||||
uint serverGuid = 0x50000001u) => new()
|
||||
{
|
||||
Id = id,
|
||||
ServerGuid = 0x50000001u,
|
||||
ServerGuid = serverGuid,
|
||||
SourceGfxObjOrSetupId = 0x02000001u,
|
||||
Position = Vector3.Zero,
|
||||
Rotation = Quaternion.Identity,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using AcDream.Core.Net;
|
|||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.World;
|
||||
using AcDream.Runtime.Entities;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Types;
|
||||
|
||||
|
|
@ -64,9 +65,9 @@ public sealed class LiveEntityAnimationPresenterTests
|
|||
old.Live.SetAnimationRuntime(Guid, replacement);
|
||||
var presenter = Presenter(old.Live, new EntityEffectPoseRegistry(), new Context());
|
||||
|
||||
presenter.Present(new Dictionary<uint, LiveEntityAnimationSchedule>
|
||||
presenter.Present(new Dictionary<RuntimeEntityKey, LiveEntityAnimationSchedule>
|
||||
{
|
||||
[old.Entity.Id] = stale,
|
||||
[old.Record.ProjectionKey!.Value] = stale,
|
||||
});
|
||||
|
||||
Assert.Empty(replacement.MeshRefsScratch);
|
||||
|
|
@ -178,9 +179,9 @@ public sealed class LiveEntityAnimationPresenterTests
|
|||
[true]);
|
||||
var presenter = Presenter(fixture.Live, new EntityEffectPoseRegistry(), new Context());
|
||||
|
||||
presenter.Present(new Dictionary<uint, LiveEntityAnimationSchedule>
|
||||
presenter.Present(new Dictionary<RuntimeEntityKey, LiveEntityAnimationSchedule>
|
||||
{
|
||||
[fixture.Entity.Id] = stale,
|
||||
[fixture.Record.ProjectionKey!.Value] = stale,
|
||||
});
|
||||
|
||||
Assert.Empty(fixture.State.MeshRefsScratch);
|
||||
|
|
@ -252,11 +253,11 @@ public sealed class LiveEntityAnimationPresenterTests
|
|||
Assert.Equal(2, nested.Count);
|
||||
};
|
||||
var presenter = Presenter(first.Live, poses, new Context());
|
||||
var schedules = new Dictionary<uint, LiveEntityAnimationSchedule>
|
||||
var schedules = new Dictionary<RuntimeEntityKey, LiveEntityAnimationSchedule>
|
||||
{
|
||||
[first.Entity.Id] = Schedule(first,
|
||||
[first.Record.ProjectionKey!.Value] = Schedule(first,
|
||||
[new PartTransform(Vector3.UnitX, Quaternion.Identity)]),
|
||||
[second.Entity.Id] = Schedule(second,
|
||||
[second.Record.ProjectionKey!.Value] = Schedule(second,
|
||||
[new PartTransform(Vector3.UnitY, Quaternion.Identity)]),
|
||||
};
|
||||
|
||||
|
|
@ -283,11 +284,11 @@ public sealed class LiveEntityAnimationPresenterTests
|
|||
first.Live.SetAnimationRuntime(Guid + 1, replacement);
|
||||
};
|
||||
var presenter = Presenter(first.Live, poses, new Context());
|
||||
var schedules = new Dictionary<uint, LiveEntityAnimationSchedule>
|
||||
var schedules = new Dictionary<RuntimeEntityKey, LiveEntityAnimationSchedule>
|
||||
{
|
||||
[first.Entity.Id] = Schedule(first,
|
||||
[first.Record.ProjectionKey!.Value] = Schedule(first,
|
||||
[new PartTransform(Vector3.UnitX, Quaternion.Identity)]),
|
||||
[second.Entity.Id] = Schedule(second,
|
||||
[second.Record.ProjectionKey!.Value] = Schedule(second,
|
||||
[new PartTransform(new Vector3(99f, 0f, 0f), Quaternion.Identity)]),
|
||||
};
|
||||
|
||||
|
|
@ -305,11 +306,11 @@ public sealed class LiveEntityAnimationPresenterTests
|
|||
var second = Add(first.Live, Guid + 1, partCount: 1);
|
||||
var poses = new EntityEffectPoseRegistry();
|
||||
var presenter = Presenter(first.Live, poses, new Context());
|
||||
var schedules = new Dictionary<uint, LiveEntityAnimationSchedule>
|
||||
var schedules = new Dictionary<RuntimeEntityKey, LiveEntityAnimationSchedule>
|
||||
{
|
||||
[first.Entity.Id] = Schedule(first,
|
||||
[first.Record.ProjectionKey!.Value] = Schedule(first,
|
||||
[new PartTransform(Vector3.UnitX, Quaternion.Identity)]),
|
||||
[second.Entity.Id] = Schedule(second,
|
||||
[second.Record.ProjectionKey!.Value] = Schedule(second,
|
||||
[new PartTransform(Vector3.UnitY, Quaternion.Identity)]),
|
||||
};
|
||||
bool recursed = false;
|
||||
|
|
@ -359,9 +360,9 @@ public sealed class LiveEntityAnimationPresenterTests
|
|||
fixture.Record.ObjectClockEpoch,
|
||||
fixture.Record.ProjectionMutationVersion,
|
||||
fixture.State.PresentationRevision);
|
||||
var schedules = new Dictionary<uint, LiveEntityAnimationSchedule>
|
||||
var schedules = new Dictionary<RuntimeEntityKey, LiveEntityAnimationSchedule>
|
||||
{
|
||||
[fixture.Entity.Id] = schedule,
|
||||
[fixture.Record.ProjectionKey!.Value] = schedule,
|
||||
};
|
||||
var poses = new EntityEffectPoseRegistry();
|
||||
var presenter = Presenter(fixture.Live, poses, new Context());
|
||||
|
|
@ -407,12 +408,12 @@ public sealed class LiveEntityAnimationPresenterTests
|
|||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<uint, LiveEntityAnimationSchedule> Schedules(
|
||||
private static IReadOnlyDictionary<RuntimeEntityKey, LiveEntityAnimationSchedule> Schedules(
|
||||
Fixture fixture,
|
||||
IReadOnlyList<PartTransform> frames) =>
|
||||
new Dictionary<uint, LiveEntityAnimationSchedule>
|
||||
new Dictionary<RuntimeEntityKey, LiveEntityAnimationSchedule>
|
||||
{
|
||||
[fixture.Entity.Id] = Schedule(fixture, frames),
|
||||
[fixture.Record.ProjectionKey!.Value] = Schedule(fixture, frames),
|
||||
};
|
||||
|
||||
private static LiveEntityAnimationSchedule Schedule(
|
||||
|
|
@ -452,10 +453,8 @@ public sealed class LiveEntityAnimationPresenterTests
|
|||
float scale = 1f,
|
||||
bool withSequencer = true)
|
||||
{
|
||||
LiveEntityRecord record = live.RegisterLiveEntity(Spawn(guid)).Record!;
|
||||
WorldEntity entity = live.MaterializeLiveEntity(
|
||||
guid,
|
||||
Cell,
|
||||
LiveEntityRecord record = live.RegisterAndMaterializeProjection(
|
||||
Spawn(guid),
|
||||
id => new WorldEntity
|
||||
{
|
||||
Id = id,
|
||||
|
|
@ -465,7 +464,8 @@ public sealed class LiveEntityAnimationPresenterTests
|
|||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
ParentCellId = Cell,
|
||||
})!;
|
||||
});
|
||||
WorldEntity entity = Assert.IsType<WorldEntity>(record.WorldEntity);
|
||||
LiveEntityAnimationState state = State(entity, partCount, scale, withSequencer);
|
||||
entity.SetIndexedPartPoses(
|
||||
Enumerable.Repeat(Matrix4x4.Identity, partCount).ToArray(),
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ using AcDream.Core.Net.Messages;
|
|||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.World;
|
||||
using AcDream.Core.Vfx;
|
||||
using AcDream.Runtime.Entities;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Enums;
|
||||
using DatReaderWriter.Types;
|
||||
|
|
@ -36,14 +37,14 @@ public sealed class LiveEntityAnimationSchedulerTests
|
|||
localHiddenPartPoseDirty: true,
|
||||
liveCenterX: 0,
|
||||
liveCenterY: 0)
|
||||
.TryGetValue(animation.Entity.Id, out LiveEntityAnimationSchedule marked));
|
||||
.TryGetValue(record.ProjectionKey!.Value, out LiveEntityAnimationSchedule marked));
|
||||
bool repeated = scheduler.Tick(
|
||||
PhysicsBody.MaxQuantum,
|
||||
animation.Entity.Position,
|
||||
localHiddenPartPoseDirty: false,
|
||||
liveCenterX: 0,
|
||||
liveCenterY: 0)
|
||||
.TryGetValue(animation.Entity.Id, out LiveEntityAnimationSchedule consumed);
|
||||
.TryGetValue(record.ProjectionKey!.Value, out LiveEntityAnimationSchedule consumed);
|
||||
|
||||
Assert.True(marked.ComposeParts);
|
||||
Assert.NotNull(marked.SequenceFrames);
|
||||
|
|
@ -73,14 +74,14 @@ public sealed class LiveEntityAnimationSchedulerTests
|
|||
localHiddenPartPoseDirty: false,
|
||||
liveCenterX: 0,
|
||||
liveCenterY: 0)
|
||||
.TryGetValue(animation.Entity.Id, out LiveEntityAnimationSchedule marked));
|
||||
.TryGetValue(record.ProjectionKey!.Value, out LiveEntityAnimationSchedule marked));
|
||||
bool repeated = scheduler.Tick(
|
||||
elapsedSeconds: 0f,
|
||||
playerPosition: null,
|
||||
localHiddenPartPoseDirty: false,
|
||||
liveCenterX: 0,
|
||||
liveCenterY: 0)
|
||||
.TryGetValue(animation.Entity.Id, out LiveEntityAnimationSchedule consumed);
|
||||
.TryGetValue(record.ProjectionKey!.Value, out LiveEntityAnimationSchedule consumed);
|
||||
|
||||
Assert.True(marked.ComposeParts);
|
||||
Assert.NotNull(marked.SequenceFrames);
|
||||
|
|
@ -91,7 +92,7 @@ public sealed class LiveEntityAnimationSchedulerTests
|
|||
[Fact]
|
||||
public void VisibleAnimationWithoutRemoteMotion_AppliesCompleteRootFrame()
|
||||
{
|
||||
var (live, _, animation) = BuildVisibleAnimatedWithPosFrames(RemoteGuid);
|
||||
var (live, record, animation) = BuildVisibleAnimatedWithPosFrames(RemoteGuid);
|
||||
var retainedBodyOwner = BuildRemote(animation.Entity);
|
||||
retainedBodyOwner.Body.SnapToCell(
|
||||
Cell,
|
||||
|
|
@ -108,7 +109,7 @@ public sealed class LiveEntityAnimationSchedulerTests
|
|||
localHiddenPartPoseDirty: false,
|
||||
liveCenterX: 0,
|
||||
liveCenterY: 0)
|
||||
.ContainsKey(animation.Entity.Id));
|
||||
.ContainsKey(record.ProjectionKey!.Value));
|
||||
|
||||
Assert.True(animation.Entity.Position.X > startX + 1f);
|
||||
}
|
||||
|
|
@ -116,14 +117,14 @@ public sealed class LiveEntityAnimationSchedulerTests
|
|||
[Fact]
|
||||
public void ScheduleOwnsPartFramesAcrossLaterSequencerSampling()
|
||||
{
|
||||
var (live, _, animation) = BuildVisibleAnimatedWithPosFrames(RemoteGuid);
|
||||
var (live, record, animation) = BuildVisibleAnimatedWithPosFrames(RemoteGuid);
|
||||
LiveEntityAnimationScheduler scheduler = BuildScheduler(live, LocalGuid);
|
||||
LiveEntityAnimationSchedule schedule = scheduler.Tick(
|
||||
0.1f,
|
||||
animation.Entity.Position,
|
||||
localHiddenPartPoseDirty: false,
|
||||
liveCenterX: 0,
|
||||
liveCenterY: 0)[animation.Entity.Id];
|
||||
liveCenterY: 0)[record.ProjectionKey!.Value];
|
||||
PartTransform retained = schedule.SequenceFrames![0];
|
||||
animation.CaptureSequenceFrames(
|
||||
[new PartTransform(new Vector3(77f, 76f, 75f), Quaternion.Identity)]);
|
||||
|
|
@ -194,7 +195,7 @@ public sealed class LiveEntityAnimationSchedulerTests
|
|||
liveCenterY: 0);
|
||||
|
||||
Assert.True(entity.Position.X > startX);
|
||||
Assert.Same(record, live.SpatialRootObjects[RemoteGuid]);
|
||||
Assert.True(live.IsCurrentSpatialRootObject(record));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -224,7 +225,7 @@ public sealed class LiveEntityAnimationSchedulerTests
|
|||
|
||||
Assert.True(entity.Position.X > startX + 0.5f);
|
||||
Assert.True(remote.Interp.IsActive || entity.Position.X >= startX + 0.99f);
|
||||
Assert.Same(record, live.SpatialRootObjects[RemoteGuid]);
|
||||
Assert.True(live.IsCurrentSpatialRootObject(record));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -343,14 +344,14 @@ public sealed class LiveEntityAnimationSchedulerTests
|
|||
physics: physics,
|
||||
projectiles: projectiles);
|
||||
|
||||
IReadOnlyDictionary<uint, LiveEntityAnimationSchedule> schedules = scheduler.Tick(
|
||||
IReadOnlyDictionary<RuntimeEntityKey, LiveEntityAnimationSchedule> schedules = scheduler.Tick(
|
||||
0.1f,
|
||||
animation.Entity.Position,
|
||||
localHiddenPartPoseDirty: false,
|
||||
liveCenterX: 1,
|
||||
liveCenterY: 1);
|
||||
|
||||
Assert.Contains(animation.Entity.Id, schedules.Keys);
|
||||
Assert.Contains(record.ProjectionKey!.Value, schedules.Keys);
|
||||
Assert.Equal(1, rootPublishes);
|
||||
float distance = animation.Entity.Position.X - startX;
|
||||
Assert.InRange(distance, 0.79f, 0.81f);
|
||||
|
|
@ -374,7 +375,7 @@ public sealed class LiveEntityAnimationSchedulerTests
|
|||
isLocalPlayer: false);
|
||||
});
|
||||
|
||||
IReadOnlyDictionary<uint, LiveEntityAnimationSchedule> schedules = scheduler.Tick(
|
||||
IReadOnlyDictionary<RuntimeEntityKey, LiveEntityAnimationSchedule> schedules = scheduler.Tick(
|
||||
PhysicsBody.MaxQuantum * 2.5f,
|
||||
animation.Entity.Position,
|
||||
localHiddenPartPoseDirty: false,
|
||||
|
|
@ -407,7 +408,7 @@ public sealed class LiveEntityAnimationSchedulerTests
|
|||
},
|
||||
_ => rootPublishes++);
|
||||
|
||||
IReadOnlyDictionary<uint, LiveEntityAnimationSchedule> schedules = scheduler.Tick(
|
||||
IReadOnlyDictionary<RuntimeEntityKey, LiveEntityAnimationSchedule> schedules = scheduler.Tick(
|
||||
PhysicsBody.MaxQuantum * 2.5f,
|
||||
animation.Entity.Position,
|
||||
localHiddenPartPoseDirty: false,
|
||||
|
|
@ -441,7 +442,7 @@ public sealed class LiveEntityAnimationSchedulerTests
|
|||
},
|
||||
_ => rootPublishes++);
|
||||
|
||||
IReadOnlyDictionary<uint, LiveEntityAnimationSchedule> schedules = scheduler.Tick(
|
||||
IReadOnlyDictionary<RuntimeEntityKey, LiveEntityAnimationSchedule> schedules = scheduler.Tick(
|
||||
PhysicsBody.MaxQuantum * 2.5f,
|
||||
animation.Entity.Position,
|
||||
localHiddenPartPoseDirty: false,
|
||||
|
|
@ -472,7 +473,7 @@ public sealed class LiveEntityAnimationSchedulerTests
|
|||
},
|
||||
_ => rootPublishes++);
|
||||
|
||||
IReadOnlyDictionary<uint, LiveEntityAnimationSchedule> schedules = scheduler.Tick(
|
||||
IReadOnlyDictionary<RuntimeEntityKey, LiveEntityAnimationSchedule> schedules = scheduler.Tick(
|
||||
PhysicsBody.MaxQuantum * 2.5f,
|
||||
animation.Entity.Position,
|
||||
localHiddenPartPoseDirty: false,
|
||||
|
|
@ -509,7 +510,7 @@ public sealed class LiveEntityAnimationSchedulerTests
|
|||
},
|
||||
_ => rootPublishes++);
|
||||
|
||||
IReadOnlyDictionary<uint, LiveEntityAnimationSchedule> schedules = scheduler.Tick(
|
||||
IReadOnlyDictionary<RuntimeEntityKey, LiveEntityAnimationSchedule> schedules = scheduler.Tick(
|
||||
PhysicsBody.MaxQuantum * 2.5f,
|
||||
animation.Entity.Position,
|
||||
localHiddenPartPoseDirty: false,
|
||||
|
|
@ -572,7 +573,7 @@ public sealed class LiveEntityAnimationSchedulerTests
|
|||
_ => throw new InvalidOperationException(),
|
||||
LiveEntityProjectionKind.Attached));
|
||||
Assert.False(record.ObjectClock.IsActive);
|
||||
Assert.DoesNotContain(RemoteGuid, live.SpatialRootObjects.Keys);
|
||||
Assert.False(live.IsCurrentSpatialRootObject(record));
|
||||
|
||||
scheduler.Tick(
|
||||
PhysicsBody.MaxQuantum,
|
||||
|
|
@ -590,7 +591,7 @@ public sealed class LiveEntityAnimationSchedulerTests
|
|||
_ => throw new InvalidOperationException(),
|
||||
LiveEntityProjectionKind.World));
|
||||
Assert.True(record.ObjectClock.IsActive);
|
||||
Assert.Contains(RemoteGuid, live.SpatialRootObjects.Keys);
|
||||
Assert.True(live.IsCurrentSpatialRootObject(record));
|
||||
|
||||
scheduler.Tick(
|
||||
PhysicsBody.MaxQuantum,
|
||||
|
|
@ -634,8 +635,8 @@ public sealed class LiveEntityAnimationSchedulerTests
|
|||
(_, _) => (0.48f, 1.835f));
|
||||
var identity = new LocalPlayerIdentityState { ServerGuid = localPlayerGuid };
|
||||
var poses = new EntityEffectPoseRegistry();
|
||||
foreach (WorldEntity entity in live.MaterializedWorldEntities.Values)
|
||||
poses.PublishMeshRefs(entity);
|
||||
foreach (LiveEntityRecord record in live.MaterializedRecords)
|
||||
poses.PublishMeshRefs(record.WorldEntity!);
|
||||
IAnimationHookCaptureSink animationHooks = captureHooks is null
|
||||
? new AnimationHookCaptureSink(new AnimationHookFrameQueue(
|
||||
new AnimationHookRouter(),
|
||||
|
|
@ -681,10 +682,8 @@ public sealed class LiveEntityAnimationSchedulerTests
|
|||
var live = new LiveEntityRuntime(
|
||||
spatial,
|
||||
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
|
||||
LiveEntityRecord record = live.RegisterLiveEntity(Spawn(guid)).Record!;
|
||||
WorldEntity entity = live.MaterializeLiveEntity(
|
||||
guid,
|
||||
Cell,
|
||||
LiveEntityRecord record = live.RegisterAndMaterializeProjection(
|
||||
Spawn(guid),
|
||||
id => new WorldEntity
|
||||
{
|
||||
Id = id,
|
||||
|
|
@ -694,7 +693,8 @@ public sealed class LiveEntityAnimationSchedulerTests
|
|||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
ParentCellId = Cell,
|
||||
})!;
|
||||
});
|
||||
WorldEntity entity = Assert.IsType<WorldEntity>(record.WorldEntity);
|
||||
var setup = new Setup();
|
||||
var animation = new LiveEntityAnimationState
|
||||
{
|
||||
|
|
@ -803,10 +803,8 @@ public sealed class LiveEntityAnimationSchedulerTests
|
|||
var live = new LiveEntityRuntime(
|
||||
spatial,
|
||||
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
|
||||
LiveEntityRecord record = live.RegisterLiveEntity(Spawn(guid, state)).Record!;
|
||||
WorldEntity entity = live.MaterializeLiveEntity(
|
||||
guid,
|
||||
Cell,
|
||||
LiveEntityRecord record = live.RegisterAndMaterializeProjection(
|
||||
Spawn(guid, state),
|
||||
id => new WorldEntity
|
||||
{
|
||||
Id = id,
|
||||
|
|
@ -816,7 +814,8 @@ public sealed class LiveEntityAnimationSchedulerTests
|
|||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
ParentCellId = Cell,
|
||||
})!;
|
||||
});
|
||||
WorldEntity entity = Assert.IsType<WorldEntity>(record.WorldEntity);
|
||||
return (live, record, entity);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -294,10 +294,8 @@ public sealed class LiveEntityCreateSupersessionRecoveryTests
|
|||
|
||||
private static LiveEntityRecord RegisterAndMaterialize(LiveEntityRuntime runtime)
|
||||
{
|
||||
LiveEntityRecord record = runtime.RegisterLiveEntity(Spawn()).Record!;
|
||||
runtime.MaterializeLiveEntity(
|
||||
Guid,
|
||||
Cell,
|
||||
LiveEntityRecord record = runtime.RegisterAndMaterializeProjection(
|
||||
Spawn(),
|
||||
id => new WorldEntity
|
||||
{
|
||||
Id = id,
|
||||
|
|
|
|||
|
|
@ -147,8 +147,8 @@ public sealed class LiveRenderProjectionJournalTests
|
|||
incarnation,
|
||||
harness.Journal.Pending[0].Record.OwnerIncarnation);
|
||||
Assert.True(harness.Runtime.WithdrawLiveEntityProjection(Guid));
|
||||
harness.Projections.OnProjectionRemoved(record.WorldEntity.Id);
|
||||
harness.Projections.OnProjectionRemoved(record.WorldEntity.Id);
|
||||
harness.Projections.OnProjectionRemoved(record);
|
||||
harness.Projections.OnProjectionRemoved(record);
|
||||
Assert.Equal(2, harness.Journal.Count);
|
||||
Assert.Equal(
|
||||
RenderProjectionDeltaKind.UpdateFlags,
|
||||
|
|
@ -166,13 +166,10 @@ public sealed class LiveRenderProjectionJournalTests
|
|||
uint firstLocalId = first.WorldEntity!.Id;
|
||||
harness.Journal.DrainTo(harness.Scene);
|
||||
|
||||
LiveEntityRegistrationResult replacement =
|
||||
harness.Runtime.RegisterLiveEntity(Spawn(Guid, instance: 5));
|
||||
LiveEntityRecord second = replacement.Record!;
|
||||
WorldEntity secondEntity = harness.Runtime.MaterializeLiveEntity(
|
||||
Guid,
|
||||
CellId,
|
||||
localId => Entity(localId, Guid))!;
|
||||
LiveEntityRecord second = harness.Runtime.RegisterAndMaterializeProjection(
|
||||
Spawn(Guid, instance: 5),
|
||||
localId => Entity(localId, Guid));
|
||||
WorldEntity secondEntity = Assert.IsType<WorldEntity>(second.WorldEntity);
|
||||
second.InitialHydrationCompleted = true;
|
||||
LiveEntityReadyCandidate current = LiveEntityReadyCandidate.Capture(second);
|
||||
Assert.True(harness.Projections.OnEntityReady(current));
|
||||
|
|
@ -209,7 +206,7 @@ public sealed class LiveRenderProjectionJournalTests
|
|||
|
||||
LiveEntityRegistrationResult duplicate =
|
||||
harness.Runtime.RegisterLiveEntity(Spawn(Guid, instance: 5));
|
||||
Assert.Same(record, duplicate.Record);
|
||||
Assert.Same(record, duplicate.Projection);
|
||||
Assert.True(harness.Projections.OnEntityReady(
|
||||
LiveEntityReadyCandidate.Capture(record)));
|
||||
|
||||
|
|
@ -285,7 +282,7 @@ public sealed class LiveRenderProjectionJournalTests
|
|||
LiveEntityReadyCandidate.Capture(record)));
|
||||
harness.Journal.DrainTo(harness.Scene);
|
||||
Assert.True(harness.Runtime.WithdrawLiveEntityProjection(Guid));
|
||||
harness.Projections.OnProjectionRemoved(record.WorldEntity!.Id);
|
||||
harness.Projections.OnProjectionRemoved(record);
|
||||
harness.Journal.DrainTo(harness.Scene);
|
||||
|
||||
harness.Projections.SynchronizeActiveSources();
|
||||
|
|
@ -311,7 +308,7 @@ public sealed class LiveRenderProjectionJournalTests
|
|||
LiveEntityReadyCandidate.Capture(record)));
|
||||
harness.Journal.DrainTo(harness.Scene);
|
||||
Assert.True(harness.Runtime.WithdrawLiveEntityProjection(Guid));
|
||||
harness.Projections.OnProjectionRemoved(record.WorldEntity!.Id);
|
||||
harness.Projections.OnProjectionRemoved(record);
|
||||
harness.Journal.DrainTo(harness.Scene);
|
||||
Assert.True(harness.Runtime.RebucketLiveEntity(Guid, CellId));
|
||||
|
||||
|
|
@ -337,7 +334,7 @@ public sealed class LiveRenderProjectionJournalTests
|
|||
LiveEntityReadyCandidate.Capture(record)));
|
||||
harness.Journal.DrainTo(harness.Scene);
|
||||
Assert.True(harness.Runtime.WithdrawLiveEntityProjection(Guid));
|
||||
harness.Projections.OnProjectionRemoved(record.WorldEntity!.Id);
|
||||
harness.Projections.OnProjectionRemoved(record);
|
||||
harness.Journal.DrainTo(harness.Scene);
|
||||
|
||||
Assert.True(harness.Runtime.UnregisterLiveEntity(
|
||||
|
|
@ -383,14 +380,11 @@ public sealed class LiveRenderProjectionJournalTests
|
|||
LiveEntityProjectionKind projectionKind =
|
||||
LiveEntityProjectionKind.World)
|
||||
{
|
||||
LiveEntityRecord record =
|
||||
harness.Runtime.RegisterLiveEntity(Spawn(guid, instance)).Record!;
|
||||
WorldEntity? entity = harness.Runtime.MaterializeLiveEntity(
|
||||
guid,
|
||||
CellId,
|
||||
LiveEntityRecord record = harness.Runtime.RegisterAndMaterializeProjection(
|
||||
Spawn(guid, instance),
|
||||
localId => Entity(localId, guid),
|
||||
projectionKind);
|
||||
Assert.NotNull(entity);
|
||||
Assert.NotNull(record.WorldEntity);
|
||||
record.InitialHydrationCompleted = true;
|
||||
return record;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -635,11 +635,10 @@ public sealed class RetailStaticAnimatingObjectSchedulerTests
|
|||
body.SnapToCell(0x01010001u, entity.Position, Vector3.Zero);
|
||||
LiveEntityAnimationState state = LiveState(entity, setup, sequencer);
|
||||
Assert.True(scheduler.BindLiveOwner(entity, state, body));
|
||||
var record = new LiveEntityRecord(Spawn(serverGuid))
|
||||
{
|
||||
WorldEntity = entity,
|
||||
AnimationRuntime = state,
|
||||
};
|
||||
var record = LiveEntityTestFixture.CreateExactProjectionRecord(
|
||||
Spawn(serverGuid));
|
||||
record.WorldEntity = entity;
|
||||
record.AnimationRuntime = state;
|
||||
int motionDone = 0;
|
||||
sequencer.MotionDoneTarget = (_, success) =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -119,12 +119,12 @@ public sealed class EntityEffectControllerTests
|
|||
EntityEffectProfile? profile = null)
|
||||
{
|
||||
WorldSession.EntitySpawn spawn = Spawn(guid, generation);
|
||||
Runtime.RegisterLiveEntity(spawn);
|
||||
Runtime.SetEffectProfile(guid, profile ?? LiveProfile());
|
||||
WorldEntity entity = Runtime.MaterializeLiveEntity(
|
||||
guid,
|
||||
spawn.Position!.Value.LandblockId,
|
||||
id => Entity(id, guid))!;
|
||||
LiveEntityRecord record = Runtime.RegisterAndMaterializeProjection(
|
||||
spawn,
|
||||
id => Entity(id, guid),
|
||||
initializeProjection: exact =>
|
||||
exact.EffectProfile = profile ?? LiveProfile());
|
||||
WorldEntity entity = Assert.IsType<WorldEntity>(record.WorldEntity);
|
||||
Assert.True(Controller.OnLiveEntityReady(guid));
|
||||
return entity;
|
||||
}
|
||||
|
|
@ -172,12 +172,11 @@ public sealed class EntityEffectControllerTests
|
|||
var fixture = new Fixture();
|
||||
fixture.Controller.HandleDirect(new PlayPhysicsScript(Guid, DirectDid));
|
||||
WorldSession.EntitySpawn spawn = Spawn(Guid, 1);
|
||||
fixture.Runtime.RegisterLiveEntity(spawn);
|
||||
fixture.Runtime.SetEffectProfile(Guid, Fixture.LiveProfile());
|
||||
fixture.Runtime.MaterializeLiveEntity(
|
||||
Guid,
|
||||
spawn.Position!.Value.LandblockId,
|
||||
id => Entity(id, Guid));
|
||||
fixture.Runtime.RegisterAndMaterializeProjection(
|
||||
spawn,
|
||||
id => Entity(id, Guid),
|
||||
initializeProjection: record =>
|
||||
record.EffectProfile = Fixture.LiveProfile());
|
||||
|
||||
Assert.True(fixture.Controller.PrepareLiveEntityOwner(Guid));
|
||||
Assert.Equal(1, fixture.Controller.PendingPacketCount);
|
||||
|
|
@ -279,12 +278,13 @@ public sealed class EntityEffectControllerTests
|
|||
var fixture = new Fixture();
|
||||
fixture.Controller.HandleDirect(new PlayPhysicsScript(Guid, DirectDid));
|
||||
WorldSession.EntitySpawn spawn = Spawn(Guid, 1);
|
||||
fixture.Runtime.RegisterLiveEntity(spawn);
|
||||
fixture.Runtime.SetEffectProfile(Guid, Fixture.LiveProfile());
|
||||
WorldEntity entity = fixture.Runtime.MaterializeLiveEntity(
|
||||
Guid,
|
||||
0x02020001u,
|
||||
id => Entity(id, Guid))!;
|
||||
LiveEntityRecord record = fixture.Runtime.RegisterAndMaterializeProjection(
|
||||
spawn,
|
||||
id => Entity(id, Guid),
|
||||
initializeProjection: exact =>
|
||||
exact.EffectProfile = Fixture.LiveProfile());
|
||||
WorldEntity entity = Assert.IsType<WorldEntity>(record.WorldEntity);
|
||||
fixture.Runtime.RebucketLiveEntity(Guid, 0x02020001u);
|
||||
|
||||
Assert.True(fixture.Controller.OnLiveEntityReady(Guid));
|
||||
Assert.Equal(1, fixture.Controller.PendingPacketCount);
|
||||
|
|
@ -360,6 +360,7 @@ public sealed class EntityEffectControllerTests
|
|||
WorldSession.EntitySpawn first = Spawn(Guid, generation: 1);
|
||||
fixture.Runtime.RegisterLiveEntity(first);
|
||||
|
||||
fixture.Controller.ForgetUnknownOwner(Guid);
|
||||
Assert.True(fixture.Runtime.UnregisterLiveEntity(
|
||||
new DeleteObject.Parsed(Guid, 1),
|
||||
isLocalPlayer: false));
|
||||
|
|
@ -383,11 +384,11 @@ public sealed class EntityEffectControllerTests
|
|||
isLocalPlayer: false));
|
||||
Assert.Equal(1, fixture.Controller.PendingPacketCount);
|
||||
|
||||
fixture.Runtime.SetEffectProfile(Guid, Fixture.LiveProfile());
|
||||
fixture.Runtime.MaterializeLiveEntity(
|
||||
Guid,
|
||||
current.Position!.Value.LandblockId,
|
||||
id => Entity(id, Guid));
|
||||
fixture.Runtime.RegisterAndMaterializeProjection(
|
||||
current,
|
||||
id => Entity(id, Guid),
|
||||
initializeProjection: record =>
|
||||
record.EffectProfile = Fixture.LiveProfile());
|
||||
Assert.True(fixture.Controller.OnLiveEntityReady(Guid));
|
||||
fixture.Runner.Tick(0.0);
|
||||
Assert.Equal([1u], EmitterIds(fixture.Sink));
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ public sealed class EntitySpawnAdapterLifetimeTests
|
|||
var adapter = new EntitySpawnAdapter(textures, _ => MakeSequencer(), meshes);
|
||||
WorldEntity oldEntity = MakeEntity(61u, guid);
|
||||
oldEntity.MeshRefs = [new MeshRef(0x01000100u, Matrix4x4.Identity)];
|
||||
WorldEntity replacement = MakeEntity(62u, guid);
|
||||
WorldEntity replacement = MakeEntity(61u, guid);
|
||||
replacement.MeshRefs = [new MeshRef(0x01000200u, Matrix4x4.Identity)];
|
||||
|
||||
adapter.OnCreate(oldEntity);
|
||||
|
|
@ -280,7 +280,7 @@ public sealed class EntitySpawnAdapterLifetimeTests
|
|||
var adapter = new EntitySpawnAdapter(textures, _ => MakeSequencer(), meshes);
|
||||
WorldEntity oldEntity = MakeEntity(101u, guid);
|
||||
oldEntity.MeshRefs = [new MeshRef((uint)oldMeshId, Matrix4x4.Identity)];
|
||||
WorldEntity replacement = MakeEntity(102u, guid);
|
||||
WorldEntity replacement = MakeEntity(101u, guid);
|
||||
AnimatedEntityState oldState = Assert.IsType<AnimatedEntityState>(adapter.OnCreate(oldEntity));
|
||||
Assert.True(adapter.SetPresentationResident(oldEntity, resident: true));
|
||||
|
||||
|
|
@ -682,7 +682,7 @@ public sealed class EntitySpawnAdapterLifetimeTests
|
|||
adapter.OnCreate(oldEntity);
|
||||
Assert.True(adapter.SetPresentationResident(oldEntity, resident: true));
|
||||
|
||||
WorldEntity replacement = MakeEntity(202u, guid);
|
||||
WorldEntity replacement = MakeEntity(201u, guid);
|
||||
replacement.MeshRefs = [new MeshRef((uint)replacementMesh, Matrix4x4.Identity)];
|
||||
adapter.OnCreate(replacement);
|
||||
Assert.True(adapter.SetPresentationResident(replacement, resident: true));
|
||||
|
|
|
|||
|
|
@ -40,12 +40,12 @@ public sealed class CurrentGameRuntimeAdapterTests
|
|||
Assert.Equal(Harness.PlayerGuid, harness.Identity.ServerGuid);
|
||||
Assert.Equal(RuntimeLifecycleState.InWorld, harness.Runtime.Lifecycle.State);
|
||||
|
||||
LiveEntityRegistrationResult registration =
|
||||
harness.Entities.RegisterLiveEntity(Spawn(
|
||||
LiveEntityRecord liveRecord =
|
||||
harness.Entities.RegisterAndMaterializeProjection(Spawn(
|
||||
Harness.TargetGuid,
|
||||
instance: 3,
|
||||
cell: 0x12340001u));
|
||||
Assert.NotNull(registration.Record);
|
||||
Assert.NotNull(liveRecord.WorldEntity);
|
||||
|
||||
var item = new ClientObject
|
||||
{
|
||||
|
|
|
|||
|
|
@ -227,14 +227,15 @@ public sealed class GpuWorldStateVisibilityTests
|
|||
Array.Empty<WorldEntity>()));
|
||||
WorldEntity entity = Entity(1u, 0x73600001u);
|
||||
state.PlaceLiveEntityProjection(landblock, entity);
|
||||
var edges = new List<(uint Guid, bool Visible)>();
|
||||
state.LiveProjectionVisibilityChanged += (guid, visible) => edges.Add((guid, visible));
|
||||
var edges = new List<(uint LocalEntityId, bool Visible)>();
|
||||
state.LiveProjectionVisibilityChanged += (localEntityId, visible) =>
|
||||
edges.Add((localEntityId, visible));
|
||||
|
||||
state.RemoveLandblock(landblock);
|
||||
|
||||
Assert.Empty(state.Entities);
|
||||
Assert.Equal(1, state.PendingLiveEntityCount);
|
||||
Assert.False(state.IsLiveEntityVisible(entity.ServerGuid));
|
||||
Assert.False(state.IsLiveEntityVisible(entity.Id));
|
||||
|
||||
state.AddLandblock(new LoadedLandblock(
|
||||
landblock,
|
||||
|
|
@ -244,9 +245,9 @@ public sealed class GpuWorldStateVisibilityTests
|
|||
Assert.Same(entity, Assert.Single(state.Entities));
|
||||
Assert.True(state.TryGetLandblock(landblock, out LoadedLandblock? reloaded));
|
||||
Assert.Same(entity, Assert.Single(reloaded!.Entities));
|
||||
Assert.True(state.IsLiveEntityVisible(entity.ServerGuid));
|
||||
Assert.True(state.IsLiveEntityVisible(entity.Id));
|
||||
Assert.Equal(
|
||||
[(entity.ServerGuid, false), (entity.ServerGuid, true)],
|
||||
[(entity.Id, false), (entity.Id, true)],
|
||||
edges);
|
||||
|
||||
state.RemoveLiveEntityProjection(entity);
|
||||
|
|
@ -279,9 +280,9 @@ public sealed class GpuWorldStateVisibilityTests
|
|||
state.MarkPersistent(playerGuid);
|
||||
state.PlaceLiveEntityProjection(secondLandblock, player);
|
||||
state.PlaceLiveEntityProjection(pendingLandblock, pending);
|
||||
var edges = new List<(uint Guid, bool Visible)>();
|
||||
state.LiveProjectionVisibilityChanged += (guid, visible) =>
|
||||
edges.Add((guid, visible));
|
||||
var edges = new List<(uint LocalEntityId, bool Visible)>();
|
||||
state.LiveProjectionVisibilityChanged += (localEntityId, visible) =>
|
||||
edges.Add((localEntityId, visible));
|
||||
|
||||
GpuWorldRecenterRetirement result =
|
||||
state.DetachAllForOriginRecenter();
|
||||
|
|
@ -316,7 +317,7 @@ public sealed class GpuWorldStateVisibilityTests
|
|||
Assert.Equal(2, state.PendingLiveEntityCount);
|
||||
Assert.Same(player, Assert.Single(state.DrainRescued()));
|
||||
Assert.Equal(
|
||||
[(remote.ServerGuid, false), (player.ServerGuid, false)],
|
||||
[(remote.Id, false), (player.Id, false)],
|
||||
edges);
|
||||
|
||||
state.AddLandblock(new LoadedLandblock(
|
||||
|
|
@ -378,12 +379,12 @@ public sealed class GpuWorldStateVisibilityTests
|
|||
Array.Empty<WorldEntity>()));
|
||||
WorldEntity entity = Entity(1u, 0x73700001u);
|
||||
int callbacks = 0;
|
||||
state.LiveProjectionVisibilityChanged += (guid, visible) =>
|
||||
state.LiveProjectionVisibilityChanged += (localEntityId, visible) =>
|
||||
{
|
||||
callbacks++;
|
||||
Assert.Equal(entity.ServerGuid, guid);
|
||||
Assert.Equal(entity.Id, localEntityId);
|
||||
Assert.True(visible);
|
||||
Assert.True(state.IsLiveEntityVisible(guid));
|
||||
Assert.True(state.IsLiveEntityVisible(localEntityId));
|
||||
Assert.Same(entity, Assert.Single(state.Entities));
|
||||
Assert.True(state.TryGetLandblock(landblock, out LoadedLandblock? loaded));
|
||||
Assert.Same(entity, Assert.Single(loaded!.Entities));
|
||||
|
|
@ -414,13 +415,14 @@ public sealed class GpuWorldStateVisibilityTests
|
|||
Array.Empty<WorldEntity>()));
|
||||
WorldEntity entity = Entity(1u, 0x73800001u);
|
||||
state.PlaceLiveEntityProjection(sourceLandblock, entity);
|
||||
var edges = new List<(uint Guid, bool Visible)>();
|
||||
state.LiveProjectionVisibilityChanged += (guid, visible) => edges.Add((guid, visible));
|
||||
var edges = new List<(uint LocalEntityId, bool Visible)>();
|
||||
state.LiveProjectionVisibilityChanged += (localEntityId, visible) =>
|
||||
edges.Add((localEntityId, visible));
|
||||
|
||||
state.RebucketLiveEntity(entity, targetLandblock);
|
||||
|
||||
Assert.Empty(edges);
|
||||
Assert.True(state.IsLiveEntityVisible(entity.ServerGuid));
|
||||
Assert.True(state.IsLiveEntityVisible(entity.Id));
|
||||
Assert.True(state.TryGetLandblock(sourceLandblock, out LoadedLandblock? source));
|
||||
Assert.Empty(source!.Entities);
|
||||
Assert.True(state.TryGetLandblock(targetLandblock, out LoadedLandblock? target));
|
||||
|
|
@ -429,7 +431,7 @@ public sealed class GpuWorldStateVisibilityTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void SameGuidOverlap_ExactRemovalPromotesSecondaryWithoutVisibilityPulse()
|
||||
public void SameGuidOverlap_TracksExactProjectionVisibilityIndependently()
|
||||
{
|
||||
const uint landblock = 0x0101FFFFu;
|
||||
const uint guid = 0x73900001u;
|
||||
|
|
@ -442,20 +444,21 @@ public sealed class GpuWorldStateVisibilityTests
|
|||
WorldEntity second = Entity(2u, guid);
|
||||
state.PlaceLiveEntityProjection(landblock, first);
|
||||
state.PlaceLiveEntityProjection(landblock, second);
|
||||
var edges = new List<(uint Guid, bool Visible)>();
|
||||
state.LiveProjectionVisibilityChanged += (edgeGuid, visible) =>
|
||||
edges.Add((edgeGuid, visible));
|
||||
var edges = new List<(uint LocalEntityId, bool Visible)>();
|
||||
state.LiveProjectionVisibilityChanged += (localEntityId, visible) =>
|
||||
edges.Add((localEntityId, visible));
|
||||
|
||||
state.RemoveLiveEntityProjection(first);
|
||||
|
||||
Assert.Empty(edges);
|
||||
Assert.True(state.IsLiveEntityVisible(guid));
|
||||
Assert.Equal([(first.Id, false)], edges);
|
||||
Assert.False(state.IsLiveEntityVisible(first.Id));
|
||||
Assert.True(state.IsLiveEntityVisible(second.Id));
|
||||
Assert.Same(second, Assert.Single(state.Entities));
|
||||
|
||||
state.RemoveLiveEntityProjection(guid);
|
||||
|
||||
Assert.Equal([(guid, false)], edges);
|
||||
Assert.False(state.IsLiveEntityVisible(guid));
|
||||
Assert.Equal([(first.Id, false), (second.Id, false)], edges);
|
||||
Assert.False(state.IsLiveEntityVisible(second.Id));
|
||||
Assert.Empty(state.Entities);
|
||||
}
|
||||
|
||||
|
|
@ -488,7 +491,7 @@ public sealed class GpuWorldStateVisibilityTests
|
|||
var state = new GpuWorldState();
|
||||
state.PlaceLiveEntityProjection(landblock, first);
|
||||
state.PlaceLiveEntityProjection(landblock, second);
|
||||
var observed = new List<(uint Guid, bool Visible)>();
|
||||
var observed = new List<(uint LocalEntityId, bool Visible)>();
|
||||
state.LiveProjectionVisibilityChanged += (_, _) =>
|
||||
throw new InvalidOperationException("fixture observer failure");
|
||||
state.LiveProjectionVisibilityChanged += (guid, visible) =>
|
||||
|
|
@ -502,10 +505,10 @@ public sealed class GpuWorldStateVisibilityTests
|
|||
|
||||
Assert.Equal(2, error.InnerExceptions.Count);
|
||||
Assert.Equal(
|
||||
[(first.ServerGuid, true), (second.ServerGuid, true)],
|
||||
observed.OrderBy(edge => edge.Guid).ToArray());
|
||||
Assert.True(state.IsLiveEntityVisible(first.ServerGuid));
|
||||
Assert.True(state.IsLiveEntityVisible(second.ServerGuid));
|
||||
[(first.Id, true), (second.Id, true)],
|
||||
observed.OrderBy(edge => edge.LocalEntityId).ToArray());
|
||||
Assert.True(state.IsLiveEntityVisible(first.Id));
|
||||
Assert.True(state.IsLiveEntityVisible(second.Id));
|
||||
Assert.Equal(0, state.PendingVisibilityTransitionCount);
|
||||
Assert.Equal(2, state.Entities.Count);
|
||||
}
|
||||
|
|
@ -574,14 +577,14 @@ public sealed class GpuWorldStateVisibilityTests
|
|||
cell,
|
||||
id => Entity(id, guid));
|
||||
|
||||
Assert.False(state.IsLiveEntityVisible(guid));
|
||||
Assert.True(state.IsLiveEntityProjectionResident(guid));
|
||||
Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord record));
|
||||
Assert.False(state.IsLiveEntityVisible(record.WorldEntity!.Id));
|
||||
Assert.True(state.IsLiveEntityProjectionResident(record.WorldEntity.Id));
|
||||
Assert.True(record.IsSpatiallyVisible);
|
||||
Assert.Equal([true], edges);
|
||||
|
||||
Assert.True(availability.End(7));
|
||||
Assert.True(state.IsLiveEntityVisible(guid));
|
||||
Assert.True(state.IsLiveEntityVisible(record.WorldEntity.Id));
|
||||
Assert.True(record.IsSpatiallyVisible);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public sealed class WorldGenerationQuiescenceTests
|
|||
world.SetLandblockAabb(LandblockId, Vector3.Zero, Vector3.One);
|
||||
var nearby = new List<KeyValuePair<uint, WorldEntity>>();
|
||||
|
||||
Assert.True(world.IsLiveEntityVisible(ServerGuid));
|
||||
Assert.True(world.IsLiveEntityVisible(entity.Id));
|
||||
Assert.Single(world.LandblockEntries);
|
||||
Assert.Single(world.LandblockBounds);
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ public sealed class WorldGenerationQuiescenceTests
|
|||
world.CopyLiveEntitiesNearLandblock(LandblockId, 0, nearby);
|
||||
|
||||
Assert.False(availability.IsWorldAvailable);
|
||||
Assert.False(world.IsLiveEntityVisible(ServerGuid));
|
||||
Assert.False(world.IsLiveEntityVisible(entity.Id));
|
||||
Assert.Empty(world.LandblockEntries);
|
||||
Assert.Empty(world.LandblockBounds);
|
||||
Assert.Empty(nearby);
|
||||
|
|
@ -44,7 +44,7 @@ public sealed class WorldGenerationQuiescenceTests
|
|||
Assert.False(availability.End(16));
|
||||
Assert.False(availability.IsWorldAvailable);
|
||||
Assert.True(availability.End(17));
|
||||
Assert.True(world.IsLiveEntityVisible(ServerGuid));
|
||||
Assert.True(world.IsLiveEntityVisible(entity.Id));
|
||||
Assert.Same(entity, Assert.Single(world.LandblockEntries).Entities.Single());
|
||||
}
|
||||
|
||||
|
|
@ -57,7 +57,8 @@ public sealed class WorldGenerationQuiescenceTests
|
|||
LandblockId,
|
||||
new LandBlock(),
|
||||
Array.Empty<WorldEntity>()));
|
||||
world.PlaceLiveEntityProjection(LandblockId, Entity());
|
||||
WorldEntity entity = Entity();
|
||||
world.PlaceLiveEntityProjection(LandblockId, entity);
|
||||
var selection = new SelectionState();
|
||||
selection.Select(ServerGuid, SelectionChangeSource.World);
|
||||
var audio = new RecordingAudioQuiescence();
|
||||
|
|
@ -65,6 +66,7 @@ public sealed class WorldGenerationQuiescenceTests
|
|||
availability,
|
||||
selection,
|
||||
world,
|
||||
guid => guid == ServerGuid ? entity.Id : null,
|
||||
audio);
|
||||
|
||||
quiescence.Begin(1);
|
||||
|
|
@ -95,6 +97,7 @@ public sealed class WorldGenerationQuiescenceTests
|
|||
availability,
|
||||
selection,
|
||||
world,
|
||||
_ => null,
|
||||
audio: null);
|
||||
|
||||
quiescence.Begin(3);
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ public sealed class WorldRevealCoordinatorTests
|
|||
availability,
|
||||
new SelectionState(),
|
||||
world,
|
||||
_ => null,
|
||||
audio);
|
||||
var coordinator = new WorldRevealCoordinator(
|
||||
isRenderNeighborhoodReady: (_, _) => true,
|
||||
|
|
@ -165,6 +166,7 @@ public sealed class WorldRevealCoordinatorTests
|
|||
availability,
|
||||
new SelectionState(),
|
||||
world,
|
||||
_ => null,
|
||||
audio);
|
||||
var scheduler = new RecordingDestinationScheduler();
|
||||
var coordinator = new WorldRevealCoordinator(
|
||||
|
|
@ -215,6 +217,7 @@ public sealed class WorldRevealCoordinatorTests
|
|||
availability,
|
||||
new SelectionState(),
|
||||
world,
|
||||
_ => null,
|
||||
audio: null);
|
||||
var scheduler = new RecordingDestinationScheduler();
|
||||
var coordinator = new WorldRevealCoordinator(
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ public sealed class RadarSnapshotProviderTests
|
|||
|
||||
uint? selected = monster;
|
||||
var provider = new RadarSnapshotProvider(
|
||||
objects, () => entities, () => spawns,
|
||||
objects, new RadarEntities(() => entities), () => spawns,
|
||||
playerGuid: () => player,
|
||||
playerYawRadians: () => MathF.PI / 2f, // retail heading 0 degrees
|
||||
playerCellId: () => 0xA9B40001u,
|
||||
|
|
@ -83,7 +83,7 @@ public sealed class RadarSnapshotProviderTests
|
|||
[hidden] = Spawn(hidden),
|
||||
};
|
||||
var provider = new RadarSnapshotProvider(
|
||||
objects, () => entities, () => spawns,
|
||||
objects, new RadarEntities(() => entities), () => spawns,
|
||||
playerGuid: () => player,
|
||||
playerYawRadians: () => 0f,
|
||||
playerCellId: () => 0xA9B40100u, // EnvCell: no landscape coordinate
|
||||
|
|
@ -122,15 +122,16 @@ public sealed class RadarSnapshotProviderTests
|
|||
};
|
||||
var provider = new RadarSnapshotProvider(
|
||||
objects,
|
||||
() => interactionVisible,
|
||||
new RadarEntities(
|
||||
() => interactionVisible,
|
||||
() => canonical),
|
||||
() => spawns,
|
||||
playerGuid: () => player,
|
||||
playerYawRadians: () => 0f,
|
||||
playerCellId: () => 0xA9B40001u,
|
||||
selectedGuid: () => hiddenTarget,
|
||||
coordinatesOnRadar: () => true,
|
||||
uiLocked: () => false,
|
||||
playerEntities: () => canonical);
|
||||
uiLocked: () => false);
|
||||
|
||||
var snapshot = provider.BuildSnapshot();
|
||||
|
||||
|
|
@ -158,15 +159,14 @@ public sealed class RadarSnapshotProviderTests
|
|||
new Dictionary<uint, WorldSession.EntitySpawn>();
|
||||
var provider = new RadarSnapshotProvider(
|
||||
objects,
|
||||
() => visible,
|
||||
new RadarEntities(() => visible, () => canonical),
|
||||
() => spawns,
|
||||
playerGuid: () => player,
|
||||
playerYawRadians: () => MathF.PI / 2f,
|
||||
playerCellId: () => 0xA9B40001u,
|
||||
selectedGuid: () => null,
|
||||
coordinatesOnRadar: () => true,
|
||||
uiLocked: () => false,
|
||||
playerEntities: () => canonical);
|
||||
uiLocked: () => false);
|
||||
|
||||
Assert.Null(provider.BuildSnapshot().CoordinatesText);
|
||||
|
||||
|
|
@ -215,7 +215,7 @@ public sealed class RadarSnapshotProviderTests
|
|||
new KeyValuePair<uint, WorldEntity>(nearby, entities[nearby]));
|
||||
var provider = new RadarSnapshotProvider(
|
||||
objects,
|
||||
() => entities,
|
||||
new RadarEntities(() => entities),
|
||||
() => spawns,
|
||||
playerGuid: () => player,
|
||||
playerYawRadians: () => 0f,
|
||||
|
|
@ -252,7 +252,7 @@ public sealed class RadarSnapshotProviderTests
|
|||
ILiveEntitySpatialQuery currentSpatialQuery = new RecordingSpatialQuery();
|
||||
var provider = new RadarSnapshotProvider(
|
||||
objects,
|
||||
() => entities,
|
||||
new RadarEntities(() => entities),
|
||||
() => spawns,
|
||||
playerGuid: () => player,
|
||||
playerYawRadians: () => 0f,
|
||||
|
|
@ -287,6 +287,35 @@ public sealed class RadarSnapshotProviderTests
|
|||
}
|
||||
}
|
||||
|
||||
private sealed class RadarEntities(
|
||||
Func<IReadOnlyDictionary<uint, WorldEntity>> visible,
|
||||
Func<IReadOnlyDictionary<uint, WorldEntity>>? materialized = null)
|
||||
: ILiveEntityRadarSource
|
||||
{
|
||||
private readonly Func<IReadOnlyDictionary<uint, WorldEntity>> _visible =
|
||||
visible;
|
||||
private readonly Func<IReadOnlyDictionary<uint, WorldEntity>> _materialized =
|
||||
materialized ?? visible;
|
||||
|
||||
public bool TryGetMaterialized(
|
||||
uint serverGuid,
|
||||
out WorldEntity entity) =>
|
||||
_materialized().TryGetValue(serverGuid, out entity!);
|
||||
|
||||
public bool TryGetVisible(
|
||||
uint serverGuid,
|
||||
out WorldEntity entity) =>
|
||||
_visible().TryGetValue(serverGuid, out entity!);
|
||||
|
||||
public void CopyVisibleTo(
|
||||
List<KeyValuePair<uint, WorldEntity>> destination)
|
||||
{
|
||||
destination.Clear();
|
||||
foreach (KeyValuePair<uint, WorldEntity> pair in _visible())
|
||||
destination.Add(pair);
|
||||
}
|
||||
}
|
||||
|
||||
private static WorldEntity Entity(uint guid, Vector3 position, Quaternion rotation) => new()
|
||||
{
|
||||
Id = guid,
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ public sealed class DeferredLiveEntityRuntimeComponentLifecycleTests
|
|||
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }),
|
||||
bridge);
|
||||
WorldSession.EntitySpawn spawn = CreateSpawn(guid);
|
||||
LiveEntityRecord record = runtime.RegisterLiveEntity(spawn).Record!;
|
||||
LiveEntityRecord record = runtime.RegisterAndMaterializeProjection(spawn);
|
||||
var delete = new DeleteObject.Parsed(guid, InstanceSequence: 1);
|
||||
|
||||
Assert.Throws<AggregateException>(() =>
|
||||
|
|
@ -139,7 +139,8 @@ public sealed class DeferredLiveEntityRuntimeComponentLifecycleTests
|
|||
var runtime = new LiveEntityRuntime(
|
||||
new GpuWorldState(),
|
||||
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
|
||||
return runtime.RegisterLiveEntity(CreateSpawn(0x70000001u)).Record!;
|
||||
return runtime.RegisterAndMaterializeProjection(
|
||||
CreateSpawn(0x70000001u));
|
||||
}
|
||||
|
||||
private static WorldSession.EntitySpawn CreateSpawn(uint guid) =>
|
||||
|
|
|
|||
|
|
@ -90,8 +90,18 @@ public sealed class GameWindowLiveEntityCompositionTests
|
|||
foreach (FieldInfo field in helperType.GetFields(
|
||||
BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic))
|
||||
{
|
||||
Assert.False(typeof(IDictionary).IsAssignableFrom(field.FieldType),
|
||||
$"{helperType.Name}.{field.Name} must resolve identity through LiveEntityRuntime.");
|
||||
bool isExactSynchronousProjectionContext =
|
||||
helperType == typeof(LiveEntityHydrationController)
|
||||
&& field.Name == "_projectionOperations"
|
||||
&& field.FieldType.IsGenericType
|
||||
&& field.FieldType.GetGenericArguments()[0]
|
||||
== typeof(AcDream.Runtime.Entities.RuntimeEntityRecord);
|
||||
if (!isExactSynchronousProjectionContext)
|
||||
{
|
||||
Assert.False(
|
||||
typeof(IDictionary).IsAssignableFrom(field.FieldType),
|
||||
$"{helperType.Name}.{field.Name} must resolve identity through LiveEntityRuntime.");
|
||||
}
|
||||
string typeName = field.FieldType.FullName ?? field.FieldType.Name;
|
||||
Assert.DoesNotContain("Silk.NET", typeName, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("OpenGL", typeName, StringComparison.OrdinalIgnoreCase);
|
||||
|
|
|
|||
|
|
@ -78,14 +78,16 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
|
||||
fixture.Controller.OnCreate(Spawn(Generation: 1, PositionSequence: 1));
|
||||
|
||||
Assert.True(fixture.Runtime.TryGetRecord(Guid, out LiveEntityRecord record));
|
||||
Assert.Null(record.WorldEntity);
|
||||
RuntimeEntityRecord canonical = fixture.Canonical;
|
||||
Assert.False(fixture.Runtime.TryGetRecord(Guid, out _));
|
||||
Assert.Null(canonical.LocalEntityId);
|
||||
Assert.Equal(0, fixture.Resources.RegisterCount);
|
||||
Assert.Empty(fixture.Materializer.Calls);
|
||||
|
||||
fixture.Origin.IsKnownValue = true;
|
||||
fixture.Controller.OnLandblockLoaded(Cell);
|
||||
|
||||
LiveEntityRecord record = fixture.Record;
|
||||
Assert.NotNull(record.WorldEntity);
|
||||
Assert.Single(fixture.Materializer.Calls);
|
||||
Assert.Equal(LiveProjectionPurpose.SpatialRecovery, fixture.Materializer.Calls[0].Purpose);
|
||||
|
|
@ -130,11 +132,13 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
fixture.Materializer.SkipMaterializationCount = 1;
|
||||
WorldSession.EntitySpawn spawn = Spawn(Generation: 1, PositionSequence: 1);
|
||||
fixture.Controller.OnCreate(spawn);
|
||||
LiveEntityRecord record = fixture.Record;
|
||||
Assert.Null(record.WorldEntity);
|
||||
RuntimeEntityRecord canonical = fixture.Canonical;
|
||||
Assert.False(fixture.Runtime.TryGetRecord(Guid, out _));
|
||||
Assert.Null(canonical.LocalEntityId);
|
||||
|
||||
Assert.True(fixture.Controller.OnAppearance(ObjDesc(2, 0x04000022u)));
|
||||
|
||||
LiveEntityRecord record = fixture.Record;
|
||||
Assert.NotNull(record.WorldEntity);
|
||||
Assert.Equal(2, fixture.Materializer.Calls.Count);
|
||||
Assert.Equal(LiveProjectionPurpose.SpatialRecovery, fixture.Materializer.Calls[1].Purpose);
|
||||
|
|
@ -516,13 +520,15 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
Assert.Throws<InvalidOperationException>(() =>
|
||||
fixture.Controller.OnCreate(Spawn(Generation: 1, PositionSequence: 1)));
|
||||
|
||||
Assert.True(fixture.Runtime.TryGetRecord(Guid, out LiveEntityRecord record));
|
||||
Assert.Null(record.WorldEntity);
|
||||
RuntimeEntityRecord canonical = fixture.Canonical;
|
||||
Assert.False(fixture.Runtime.TryGetRecord(Guid, out _));
|
||||
Assert.Null(canonical.LocalEntityId);
|
||||
Assert.Equal(1, resources.RegisterCount);
|
||||
Assert.Equal(1, resources.UnregisterCount);
|
||||
|
||||
fixture.Controller.OnLandblockLoaded(Cell);
|
||||
|
||||
LiveEntityRecord record = fixture.Record;
|
||||
Assert.NotNull(record.WorldEntity);
|
||||
Assert.Equal(2, resources.RegisterCount);
|
||||
Assert.Equal(1, resources.UnregisterCount);
|
||||
|
|
@ -644,7 +650,9 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
ClientObject retainedObject = fixture.Objects.Get(Guid)!;
|
||||
|
||||
Assert.True(fixture.Controller.OnPrune(
|
||||
new LiveEntityPruneCandidate(Guid, Generation: 1)));
|
||||
new LiveEntityPruneCandidate(
|
||||
fixture.Record.ProjectionKey!.Value,
|
||||
Guid)));
|
||||
|
||||
Assert.False(fixture.Runtime.TryGetRecord(Guid, out _));
|
||||
Assert.Same(retainedObject, fixture.Objects.Get(Guid));
|
||||
|
|
@ -658,7 +666,9 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
using var fixture = new Fixture(originKnown: true);
|
||||
fixture.Controller.OnCreate(Spawn(Generation: 1, PositionSequence: 1));
|
||||
Assert.True(fixture.Controller.OnPrune(
|
||||
new LiveEntityPruneCandidate(Guid, Generation: 1)));
|
||||
new LiveEntityPruneCandidate(
|
||||
fixture.Record.ProjectionKey!.Value,
|
||||
Guid)));
|
||||
|
||||
fixture.Controller.OnLandblockLoaded(Cell);
|
||||
|
||||
|
|
@ -677,7 +687,9 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
using var fixture = new Fixture(originKnown: true);
|
||||
fixture.Controller.OnCreate(Spawn(Generation: 1, PositionSequence: 1));
|
||||
Assert.True(fixture.Controller.OnPrune(
|
||||
new LiveEntityPruneCandidate(Guid, Generation: 1)));
|
||||
new LiveEntityPruneCandidate(
|
||||
fixture.Record.ProjectionKey!.Value,
|
||||
Guid)));
|
||||
|
||||
Assert.True(fixture.Controller.OnDelete(
|
||||
new DeleteObject.Parsed(Guid, InstanceSequence: 1)));
|
||||
|
|
@ -694,7 +706,9 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
using var fixture = new Fixture(originKnown: true);
|
||||
fixture.Controller.OnCreate(Spawn(Generation: 2, PositionSequence: 1));
|
||||
Assert.True(fixture.Controller.OnPrune(
|
||||
new LiveEntityPruneCandidate(Guid, Generation: 2)));
|
||||
new LiveEntityPruneCandidate(
|
||||
fixture.Record.ProjectionKey!.Value,
|
||||
Guid)));
|
||||
|
||||
fixture.Controller.OnCreate(Spawn(
|
||||
Generation: 1,
|
||||
|
|
@ -723,7 +737,9 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
PositionSequence: 5);
|
||||
fixture.Controller.OnCreate(accepted);
|
||||
Assert.True(fixture.Controller.OnPrune(
|
||||
new LiveEntityPruneCandidate(Guid, Generation: 1)));
|
||||
new LiveEntityPruneCandidate(
|
||||
fixture.Record.ProjectionKey!.Value,
|
||||
Guid)));
|
||||
|
||||
CreateObject.ServerPosition stalePosition =
|
||||
accepted.Position!.Value with
|
||||
|
|
@ -760,7 +776,9 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
Value = 123,
|
||||
});
|
||||
Assert.True(fixture.Controller.OnPrune(
|
||||
new LiveEntityPruneCandidate(Guid, Generation: 1)));
|
||||
new LiveEntityPruneCandidate(
|
||||
fixture.Record.ProjectionKey!.Value,
|
||||
Guid)));
|
||||
|
||||
fixture.Controller.OnCreate(
|
||||
Spawn(Generation: 2, PositionSequence: 1) with
|
||||
|
|
@ -831,10 +849,11 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
Assert.True(fixture.Controller.OnDelete(
|
||||
new DeleteObject.Parsed(Guid, InstanceSequence: 1)));
|
||||
|
||||
Assert.True(fixture.Runtime.TryGetRecord(Guid, out LiveEntityRecord replacement));
|
||||
Assert.NotSame(old, replacement);
|
||||
RuntimeEntityRecord replacement = fixture.Canonical;
|
||||
Assert.Equal((ushort)2, replacement.Generation);
|
||||
Assert.Equal("replacement", replacement.Snapshot.Name);
|
||||
Assert.Null(replacement.LocalEntityId);
|
||||
Assert.False(fixture.Runtime.TryGetRecord(Guid, out _));
|
||||
Assert.Equal(0, fixture.Runtime.PendingTeardownCount);
|
||||
}
|
||||
|
||||
|
|
@ -1348,10 +1367,9 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
|
||||
fixture.Controller.OnCreate(Spawn(Generation: 1, PositionSequence: 1));
|
||||
|
||||
LiveEntityRecord record = fixture.Record;
|
||||
Assert.False(record.CreateProjectionSynchronizationPending);
|
||||
Assert.False(record.InitialHydrationCompleted);
|
||||
Assert.Null(record.WorldEntity);
|
||||
RuntimeEntityRecord canonical = fixture.Canonical;
|
||||
Assert.Null(canonical.LocalEntityId);
|
||||
Assert.False(fixture.Runtime.TryGetRecord(Guid, out _));
|
||||
Assert.Equal(0, fixture.Resources.RegisterCount);
|
||||
|
||||
fixture.Materializer.BeforeMaterialize = null;
|
||||
|
|
@ -1371,6 +1389,7 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
};
|
||||
fixture.Controller.OnCreate(Spawn(Generation: 1, PositionSequence: 3));
|
||||
|
||||
LiveEntityRecord record = fixture.Record;
|
||||
Assert.True(record.InitialHydrationCompleted);
|
||||
Assert.NotNull(record.WorldEntity);
|
||||
Assert.Equal(1, fixture.Resources.RegisterCount);
|
||||
|
|
@ -1481,12 +1500,14 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
};
|
||||
fixture.Relationships.OnSpawnAction = _ =>
|
||||
{
|
||||
LiveEntityRecord record = fixture.Record;
|
||||
if (record.WorldEntity is not null)
|
||||
RuntimeEntityRecord canonical = fixture.Canonical;
|
||||
if (fixture.Runtime.TryGetRecord(
|
||||
Guid,
|
||||
out LiveEntityRecord _))
|
||||
return;
|
||||
|
||||
WorldEntity? attached = fixture.Runtime.MaterializeLiveEntity(
|
||||
Guid,
|
||||
canonical,
|
||||
Cell,
|
||||
id => new WorldEntity
|
||||
{
|
||||
|
|
@ -1498,10 +1519,13 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
MeshRefs = [],
|
||||
ParentCellId = Cell,
|
||||
},
|
||||
LiveEntityProjectionKind.Attached);
|
||||
LiveEntityProjectionKind.Attached,
|
||||
initializeProjection: null,
|
||||
out LiveEntityRecord? record);
|
||||
Assert.NotNull(attached);
|
||||
Assert.NotNull(record);
|
||||
fixture.Controller.OnEntityReady(
|
||||
LiveEntityReadyCandidate.Capture(record));
|
||||
LiveEntityReadyCandidate.Capture(record!));
|
||||
};
|
||||
|
||||
fixture.Controller.OnCreate(CelllessSpawn(
|
||||
|
|
@ -1757,7 +1781,8 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
record,
|
||||
staleVersion,
|
||||
accepted));
|
||||
Assert.Empty(fixture.Runtime.MaterializedWorldEntities);
|
||||
Assert.Single(fixture.Runtime.MaterializedRecords);
|
||||
Assert.Empty(fixture.Runtime.VisibleRecords);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1890,6 +1915,17 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
}
|
||||
}
|
||||
|
||||
public RuntimeEntityRecord Canonical
|
||||
{
|
||||
get
|
||||
{
|
||||
Assert.True(Runtime.TryGetCanonical(
|
||||
Guid,
|
||||
out RuntimeEntityRecord canonical));
|
||||
return canonical;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
|
|
@ -1991,24 +2027,24 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
public readonly List<(LiveProjectionPurpose Purpose, ushort Generation)> Calls = [];
|
||||
public readonly List<ushort> PositionSequences = [];
|
||||
public ulong? InstalledCreateIntegrationVersion { get; private set; }
|
||||
public Action<LiveEntityRecord, WorldSession.EntitySpawn>? BeforeMaterialize { get; set; }
|
||||
public Action<LiveEntityRecord, WorldSession.EntitySpawn>? AfterMaterialize { get; set; }
|
||||
public Action<RuntimeEntityRecord, WorldSession.EntitySpawn>? BeforeMaterialize { get; set; }
|
||||
public Action<RuntimeEntityRecord, WorldSession.EntitySpawn>? AfterMaterialize { get; set; }
|
||||
public LiveProjectionPurpose? ThrowAfterMaterializePurposeOnce { get; set; }
|
||||
public int SkipMaterializationCount { get; set; }
|
||||
|
||||
public bool TryMaterialize(
|
||||
LiveEntityRecord expectedRecord,
|
||||
RuntimeEntityRecord expectedCanonical,
|
||||
WorldSession.EntitySpawn canonicalSpawn,
|
||||
LiveProjectionPurpose purpose,
|
||||
ulong expectedCreateIntegrationVersion,
|
||||
AcDream.App.Rendering.LiveEntityAppearanceUpdateState? appearanceUpdate = null)
|
||||
{
|
||||
Calls.Add((purpose, expectedRecord.Generation));
|
||||
Calls.Add((purpose, expectedCanonical.Generation));
|
||||
PositionSequences.Add(canonicalSpawn.PositionSequence);
|
||||
operations.Add($"materialize:{purpose}:{expectedRecord.Generation}");
|
||||
BeforeMaterialize?.Invoke(expectedRecord, canonicalSpawn);
|
||||
operations.Add($"materialize:{purpose}:{expectedCanonical.Generation}");
|
||||
BeforeMaterialize?.Invoke(expectedCanonical, canonicalSpawn);
|
||||
if (!runtime.IsCurrentCreateIntegration(
|
||||
expectedRecord,
|
||||
expectedCanonical,
|
||||
expectedCreateIntegrationVersion))
|
||||
{
|
||||
return false;
|
||||
|
|
@ -2030,7 +2066,7 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
}
|
||||
|
||||
WorldEntity? entity = runtime.MaterializeLiveEntity(
|
||||
canonicalSpawn.Guid,
|
||||
expectedCanonical,
|
||||
position.LandblockId,
|
||||
id => new WorldEntity
|
||||
{
|
||||
|
|
@ -2041,15 +2077,19 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = [],
|
||||
ParentCellId = position.LandblockId,
|
||||
});
|
||||
},
|
||||
LiveEntityProjectionKind.World,
|
||||
initializeProjection: null,
|
||||
out LiveEntityRecord? expectedRecord);
|
||||
if (ThrowAfterMaterializePurposeOnce == purpose)
|
||||
{
|
||||
ThrowAfterMaterializePurposeOnce = null;
|
||||
throw new InvalidOperationException(
|
||||
"fixture supersession projection failure");
|
||||
}
|
||||
AfterMaterialize?.Invoke(expectedRecord, canonicalSpawn);
|
||||
AfterMaterialize?.Invoke(expectedCanonical, canonicalSpawn);
|
||||
return entity is not null
|
||||
&& expectedRecord is not null
|
||||
&& runtime.IsCurrentRecord(expectedRecord);
|
||||
}
|
||||
|
||||
|
|
@ -2137,7 +2177,7 @@ public sealed class LiveEntityHydrationControllerTests
|
|||
{
|
||||
}
|
||||
|
||||
public void OnProjectionRemoved(uint localEntityId)
|
||||
public void OnProjectionRemoved(LiveEntityRecord record)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -355,7 +355,7 @@ public sealed class LiveEntityLifecycleStressTests
|
|||
if (record.WorldEntity is { } entity)
|
||||
{
|
||||
cleanups.Add(() => Engine.ShadowObjects.Deregister(entity.Id));
|
||||
cleanups.Add(() => _liveLights?.Forget(entity.Id));
|
||||
cleanups.Add(() => _liveLights?.Forget(record));
|
||||
}
|
||||
LiveEntityTeardown.Run(cleanups);
|
||||
});
|
||||
|
|
@ -402,14 +402,8 @@ public sealed class LiveEntityLifecycleStressTests
|
|||
8f + (ordinal / 12) * 6f,
|
||||
10f);
|
||||
WorldSession.EntitySpawn spawn = MissileSpawn(guid, generation, position);
|
||||
LiveEntityRegistrationResult registration = Runtime.RegisterLiveEntity(spawn);
|
||||
LiveEntityRecord record = Assert.IsType<LiveEntityRecord>(registration.Record);
|
||||
Runtime.SetEffectProfile(
|
||||
guid,
|
||||
EntityEffectProfile.CreateLive(new Setup(), spawn.Physics!.Value));
|
||||
WorldEntity entity = Assert.IsType<WorldEntity>(Runtime.MaterializeLiveEntity(
|
||||
guid,
|
||||
CellA,
|
||||
LiveEntityRecord record = Runtime.RegisterAndMaterializeProjection(
|
||||
spawn,
|
||||
localId => new WorldEntity
|
||||
{
|
||||
Id = localId,
|
||||
|
|
@ -419,7 +413,12 @@ public sealed class LiveEntityLifecycleStressTests
|
|||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
ParentCellId = CellA,
|
||||
}));
|
||||
},
|
||||
initializeProjection: exact =>
|
||||
exact.EffectProfile = EntityEffectProfile.CreateLive(
|
||||
new Setup(),
|
||||
spawn.Physics!.Value));
|
||||
WorldEntity entity = Assert.IsType<WorldEntity>(record.WorldEntity);
|
||||
Assert.True(Effects.OnLiveEntityReady(guid));
|
||||
Assert.True(Projectiles.TryBind(record, Setup, 0.0, 1, 1));
|
||||
|
||||
|
|
@ -520,13 +519,8 @@ public sealed class LiveEntityLifecycleStressTests
|
|||
Router.Register(Effects);
|
||||
|
||||
WorldSession.EntitySpawn spawn = RecallSpawn(Guid, CellOne);
|
||||
Runtime.RegisterLiveEntity(spawn);
|
||||
Runtime.SetEffectProfile(
|
||||
Guid,
|
||||
EntityEffectProfile.CreateLive(new Setup(), spawn.Physics!.Value));
|
||||
Entity = Runtime.MaterializeLiveEntity(
|
||||
Guid,
|
||||
CellOne,
|
||||
LiveEntityRecord record = Runtime.RegisterAndMaterializeProjection(
|
||||
spawn,
|
||||
localId => new WorldEntity
|
||||
{
|
||||
Id = localId,
|
||||
|
|
@ -536,7 +530,12 @@ public sealed class LiveEntityLifecycleStressTests
|
|||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
ParentCellId = CellOne,
|
||||
})!;
|
||||
},
|
||||
initializeProjection: exact =>
|
||||
exact.EffectProfile = EntityEffectProfile.CreateLive(
|
||||
new Setup(),
|
||||
spawn.Physics!.Value));
|
||||
Entity = Assert.IsType<WorldEntity>(record.WorldEntity);
|
||||
Assert.True(Effects.OnLiveEntityReady(Guid));
|
||||
|
||||
_remote.Body.SnapToCell(CellOne, Entity.Position, Entity.Position);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using AcDream.App.World;
|
||||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Runtime.Entities;
|
||||
|
||||
namespace AcDream.App.Tests.World;
|
||||
|
||||
|
|
@ -14,7 +15,9 @@ public sealed class LiveEntityLivenessControllerTests
|
|||
Assert.Empty(tracker.Tick(10.0, samples));
|
||||
Assert.Empty(tracker.Tick(34.999, samples));
|
||||
Assert.Equal(
|
||||
new LiveEntityPruneCandidate(0x7000_0001u, 4),
|
||||
new LiveEntityPruneCandidate(
|
||||
new RuntimeEntityKey(0x7000_0001u, 4),
|
||||
0x7000_0001u),
|
||||
Assert.Single(tracker.Tick(35.0, samples)));
|
||||
Assert.Equal(0, tracker.DeadlineCount);
|
||||
}
|
||||
|
|
@ -47,7 +50,7 @@ public sealed class LiveEntityLivenessControllerTests
|
|||
Assert.Empty(tracker.Tick(24.0, [Sample(1, 2, visible: false)]));
|
||||
Assert.Empty(tracker.Tick(25.0, [Sample(1, 2, visible: false)]));
|
||||
Assert.Equal(
|
||||
new LiveEntityPruneCandidate(1, 2),
|
||||
new LiveEntityPruneCandidate(new RuntimeEntityKey(1, 2), 1),
|
||||
Assert.Single(tracker.Tick(49.0, [Sample(1, 2, visible: false)])));
|
||||
}
|
||||
|
||||
|
|
@ -80,7 +83,11 @@ public sealed class LiveEntityLivenessControllerTests
|
|||
ushort generation,
|
||||
bool visible,
|
||||
bool retained = false) =>
|
||||
new(guid, generation, visible, retained);
|
||||
new(
|
||||
new RuntimeEntityKey(guid, generation),
|
||||
guid,
|
||||
visible,
|
||||
retained);
|
||||
|
||||
private static CreateObject.ServerPosition Position(
|
||||
uint cell,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using AcDream.Core.Net;
|
|||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.Physics.Motion;
|
||||
using AcDream.Core.World;
|
||||
|
||||
namespace AcDream.App.Tests.World;
|
||||
|
||||
|
|
@ -16,7 +17,7 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
{
|
||||
const uint guid = 0x70000010u;
|
||||
var runtime = Runtime();
|
||||
LiveEntityRecord first = runtime.RegisterLiveEntity(Spawn(guid, 1)).Record!;
|
||||
LiveEntityRecord first = runtime.RegisterAndMaterializeProjection(Spawn(guid, 1));
|
||||
var firstMotion = new HostConsumerMotion();
|
||||
runtime.SetRemoteMotionRuntime(guid, firstMotion);
|
||||
EntityPhysicsHost firstHost = Host(guid);
|
||||
|
|
@ -26,7 +27,7 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
Assert.True(runtime.TryGetPhysicsHost(guid, out IPhysicsObjHost resolved));
|
||||
Assert.Same(firstHost, resolved);
|
||||
|
||||
LiveEntityRecord second = runtime.RegisterLiveEntity(Spawn(guid, 2)).Record!;
|
||||
LiveEntityRecord second = runtime.RegisterAndMaterializeProjection(Spawn(guid, 2));
|
||||
var secondMotion = new HostConsumerMotion();
|
||||
runtime.SetRemoteMotionRuntime(guid, secondMotion);
|
||||
EntityPhysicsHost replacement = Host(guid);
|
||||
|
|
@ -42,7 +43,7 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
{
|
||||
const uint guid = 0x70000011u;
|
||||
var runtime = Runtime();
|
||||
LiveEntityRecord first = runtime.RegisterLiveEntity(Spawn(guid, 1)).Record!;
|
||||
LiveEntityRecord first = runtime.RegisterAndMaterializeProjection(Spawn(guid, 1));
|
||||
EntityPhysicsHost original = Host(guid);
|
||||
runtime.InstallPhysicsHost(first, original);
|
||||
|
||||
|
|
@ -51,7 +52,7 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
Assert.Throws<InvalidOperationException>(() =>
|
||||
runtime.InstallPhysicsHost(first, Host(guid)));
|
||||
|
||||
LiveEntityRecord second = runtime.RegisterLiveEntity(Spawn(guid, 2)).Record!;
|
||||
LiveEntityRecord second = runtime.RegisterAndMaterializeProjection(Spawn(guid, 2));
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
runtime.InstallPhysicsHost(first, Host(guid)));
|
||||
Assert.Null(second.PhysicsHost);
|
||||
|
|
@ -63,8 +64,8 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
const uint watcherGuid = 0x70000020u;
|
||||
const uint targetGuid = 0x70000021u;
|
||||
var runtime = Runtime();
|
||||
LiveEntityRecord watcherRecord = runtime.RegisterLiveEntity(Spawn(watcherGuid, 1)).Record!;
|
||||
LiveEntityRecord targetRecord = runtime.RegisterLiveEntity(Spawn(targetGuid, 1)).Record!;
|
||||
LiveEntityRecord watcherRecord = runtime.RegisterAndMaterializeProjection(Spawn(watcherGuid, 1));
|
||||
LiveEntityRecord targetRecord = runtime.RegisterAndMaterializeProjection(Spawn(targetGuid, 1));
|
||||
IPhysicsObjHost? Resolve(uint id) =>
|
||||
runtime.TryGetPhysicsHost(id, out IPhysicsObjHost host)
|
||||
? host
|
||||
|
|
@ -129,7 +130,7 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
{
|
||||
const uint guid = 0x70000025u;
|
||||
var runtime = Runtime();
|
||||
LiveEntityRecord record = runtime.RegisterLiveEntity(Spawn(guid, 1)).Record!;
|
||||
LiveEntityRecord record = runtime.RegisterAndMaterializeProjection(Spawn(guid, 1));
|
||||
EntityPhysicsHost original = Host(
|
||||
guid,
|
||||
position: new Vector3(1f, 0f, 0f));
|
||||
|
|
@ -163,7 +164,7 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
{
|
||||
const uint guid = 0x70000022u;
|
||||
var runtime = Runtime();
|
||||
LiveEntityRecord first = runtime.RegisterLiveEntity(Spawn(guid, 1)).Record!;
|
||||
LiveEntityRecord first = runtime.RegisterAndMaterializeProjection(Spawn(guid, 1));
|
||||
int firstUpdates = 0;
|
||||
int firstInterrupts = 0;
|
||||
var firstHost = CallbackHost(
|
||||
|
|
@ -172,7 +173,7 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
() => firstInterrupts++);
|
||||
runtime.InstallPhysicsHost(first, firstHost);
|
||||
|
||||
LiveEntityRecord replacement = runtime.RegisterLiveEntity(Spawn(guid, 2)).Record!;
|
||||
LiveEntityRecord replacement = runtime.RegisterAndMaterializeProjection(Spawn(guid, 2));
|
||||
int replacementUpdates = 0;
|
||||
int replacementInterrupts = 0;
|
||||
var replacementHost = CallbackHost(
|
||||
|
|
@ -195,7 +196,7 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
{
|
||||
const uint guid = 0x70000030u;
|
||||
var runtime = Runtime();
|
||||
LiveEntityRecord record = runtime.RegisterLiveEntity(Spawn(guid, 1)).Record!;
|
||||
LiveEntityRecord record = runtime.RegisterAndMaterializeProjection(Spawn(guid, 1));
|
||||
var motion = new RemoteMotion(new PhysicsBody());
|
||||
runtime.SetRemoteMotionRuntime(guid, motion);
|
||||
EntityPhysicsHost minimal = Host(guid);
|
||||
|
|
@ -221,7 +222,7 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
{
|
||||
const uint guid = 0x70000031u;
|
||||
var runtime = Runtime();
|
||||
LiveEntityRecord record = runtime.RegisterLiveEntity(Spawn(guid, 1)).Record!;
|
||||
LiveEntityRecord record = runtime.RegisterAndMaterializeProjection(Spawn(guid, 1));
|
||||
var failing = new ThrowingCellConsumerMotion();
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
|
|
@ -240,11 +241,11 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
{
|
||||
const uint guid = 0x70000032u;
|
||||
var runtime = Runtime();
|
||||
runtime.RegisterLiveEntity(Spawn(guid, 1));
|
||||
runtime.RegisterAndMaterializeProjection(Spawn(guid, 1));
|
||||
var stale = new RemoteMotion(new PhysicsBody());
|
||||
runtime.SetRemoteMotionRuntime(guid, stale);
|
||||
|
||||
LiveEntityRecord replacement = runtime.RegisterLiveEntity(Spawn(guid, 2)).Record!;
|
||||
LiveEntityRecord replacement = runtime.RegisterAndMaterializeProjection(Spawn(guid, 2));
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
runtime.SetRemoteMotionRuntime(guid, stale));
|
||||
Assert.Null(replacement.RemoteMotionRuntime);
|
||||
|
|
@ -261,7 +262,7 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
{
|
||||
const uint guid = 0x70000033u;
|
||||
var runtime = Runtime();
|
||||
LiveEntityRecord record = runtime.RegisterLiveEntity(Spawn(guid, 1)).Record!;
|
||||
LiveEntityRecord record = runtime.RegisterAndMaterializeProjection(Spawn(guid, 1));
|
||||
var firstBody = new PhysicsBody();
|
||||
var secondBody = new PhysicsBody();
|
||||
var alternating = new AlternatingBodyMotion(firstBody, secondBody);
|
||||
|
|
@ -282,7 +283,7 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
{
|
||||
const uint guid = 0x70000036u;
|
||||
var runtime = Runtime();
|
||||
LiveEntityRecord record = runtime.RegisterLiveEntity(Spawn(guid, 1)).Record!;
|
||||
LiveEntityRecord record = runtime.RegisterAndMaterializeProjection(Spawn(guid, 1));
|
||||
var canonical = new PhysicsBody();
|
||||
var unrelated = new PhysicsBody();
|
||||
PhysicsStateFlags unrelatedInitialState = unrelated.State;
|
||||
|
|
@ -302,17 +303,18 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
{
|
||||
const uint guid = 0x70000034u;
|
||||
var runtime = Runtime();
|
||||
LiveEntityRecord retired = runtime.RegisterLiveEntity(Spawn(guid, 1)).Record!;
|
||||
LiveEntityRecord retired = runtime.RegisterAndMaterializeProjection(Spawn(guid, 1));
|
||||
var motion = new CallbackCanonicalMotion(() =>
|
||||
runtime.RegisterLiveEntity(Spawn(guid, 2)));
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
runtime.SetRemoteMotionRuntime(guid, motion));
|
||||
|
||||
Assert.True(runtime.TryGetRecord(guid, out LiveEntityRecord replacement));
|
||||
Assert.Equal((ushort)2, replacement.Generation);
|
||||
Assert.Null(replacement.RemoteMotionRuntime);
|
||||
Assert.True(runtime.TryGetCanonical(guid, out var replacement));
|
||||
Assert.Equal((ushort)2, replacement.Incarnation);
|
||||
Assert.Null(replacement.LocalEntityId);
|
||||
Assert.Null(replacement.PhysicsBody);
|
||||
Assert.False(runtime.TryGetRecord(guid, out _));
|
||||
Assert.Null(retired.RemoteMotionRuntime);
|
||||
Assert.Null(retired.PhysicsBody);
|
||||
Assert.False(runtime.TryGetRemoteMotionRuntime(guid, out _));
|
||||
|
|
@ -323,7 +325,7 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
{
|
||||
const uint guid = 0x70000035u;
|
||||
var runtime = Runtime();
|
||||
LiveEntityRecord retired = runtime.RegisterLiveEntity(Spawn(guid, 1)).Record!;
|
||||
LiveEntityRecord retired = runtime.RegisterAndMaterializeProjection(Spawn(guid, 1));
|
||||
var motion = new CallbackCanonicalMotion(runtime.Clear);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
|
|
@ -343,8 +345,8 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
const uint rightGuid = 0x70000041u;
|
||||
LiveEntityRuntime runtime = null!;
|
||||
runtime = Runtime(record => CleanPhysicsHost(record));
|
||||
LiveEntityRecord leftRecord = runtime.RegisterLiveEntity(Spawn(leftGuid, 1)).Record!;
|
||||
LiveEntityRecord rightRecord = runtime.RegisterLiveEntity(Spawn(rightGuid, 1)).Record!;
|
||||
LiveEntityRecord leftRecord = runtime.RegisterAndMaterializeProjection(Spawn(leftGuid, 1));
|
||||
LiveEntityRecord rightRecord = runtime.RegisterAndMaterializeProjection(Spawn(rightGuid, 1));
|
||||
IPhysicsObjHost? Resolve(uint id) =>
|
||||
runtime.TryGetPhysicsHost(id, out IPhysicsObjHost host)
|
||||
? host
|
||||
|
|
@ -374,8 +376,8 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
const uint rightGuid = 0x70000043u;
|
||||
LiveEntityRuntime runtime = null!;
|
||||
runtime = Runtime(record => CleanPhysicsHost(record));
|
||||
LiveEntityRecord leftRecord = runtime.RegisterLiveEntity(Spawn(leftGuid, 1)).Record!;
|
||||
LiveEntityRecord rightRecord = runtime.RegisterLiveEntity(Spawn(rightGuid, 1)).Record!;
|
||||
LiveEntityRecord leftRecord = runtime.RegisterAndMaterializeProjection(Spawn(leftGuid, 1));
|
||||
LiveEntityRecord rightRecord = runtime.RegisterAndMaterializeProjection(Spawn(rightGuid, 1));
|
||||
IPhysicsObjHost? Resolve(uint id) =>
|
||||
runtime.TryGetPhysicsHost(id, out IPhysicsObjHost host)
|
||||
? host
|
||||
|
|
@ -410,21 +412,14 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
if (failOnce)
|
||||
{
|
||||
failOnce = false;
|
||||
replacementRecord = runtime.RegisterLiveEntity(Spawn(guid, 2)).Record!;
|
||||
IPhysicsObjHost? Resolve(uint id) =>
|
||||
runtime.TryGetPhysicsHost(id, out IPhysicsObjHost host)
|
||||
? host
|
||||
: null;
|
||||
replacementHost = Host(guid, Resolve);
|
||||
runtime.InstallPhysicsHost(replacementRecord, replacementHost);
|
||||
replacementHost.PositionManager.StickTo(targetGuid, 0.5f, 1f);
|
||||
runtime.RegisterLiveEntity(Spawn(guid, 2));
|
||||
throw new InvalidOperationException("fixture failure before old host cleanup");
|
||||
}
|
||||
|
||||
CleanPhysicsHost(record);
|
||||
});
|
||||
LiveEntityRecord oldRecord = runtime.RegisterLiveEntity(Spawn(guid, 1)).Record!;
|
||||
LiveEntityRecord targetRecord = runtime.RegisterLiveEntity(Spawn(targetGuid, 1)).Record!;
|
||||
LiveEntityRecord oldRecord = runtime.RegisterAndMaterializeProjection(Spawn(guid, 1));
|
||||
LiveEntityRecord targetRecord = runtime.RegisterAndMaterializeProjection(Spawn(targetGuid, 1));
|
||||
IPhysicsObjHost? InitialResolve(uint id) =>
|
||||
runtime.TryGetPhysicsHost(id, out IPhysicsObjHost host)
|
||||
? host
|
||||
|
|
@ -438,6 +433,14 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
Assert.Throws<AggregateException>(() => runtime.UnregisterLiveEntity(
|
||||
new DeleteObject.Parsed(guid, InstanceSequence: 1),
|
||||
isLocalPlayer: false));
|
||||
replacementRecord = runtime.RegisterAndMaterializeProjection(Spawn(guid, 2));
|
||||
IPhysicsObjHost? ResolveReplacement(uint id) =>
|
||||
runtime.TryGetPhysicsHost(id, out IPhysicsObjHost host)
|
||||
? host
|
||||
: null;
|
||||
replacementHost = Host(guid, ResolveReplacement);
|
||||
runtime.InstallPhysicsHost(replacementRecord, replacementHost);
|
||||
replacementHost.PositionManager.StickTo(targetGuid, 0.5f, 1f);
|
||||
Assert.Same(oldHost, oldRecord.PhysicsHost);
|
||||
Assert.Same(replacementHost, replacementRecord!.PhysicsHost);
|
||||
Assert.Equal(targetGuid, replacementHost!.PositionManager.GetStickyObjectId());
|
||||
|
|
@ -466,8 +469,8 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
throw new InvalidOperationException("fixture post-exit failure");
|
||||
}
|
||||
});
|
||||
LiveEntityRecord retiredRecord = runtime.RegisterLiveEntity(Spawn(retiredGuid, 1)).Record!;
|
||||
LiveEntityRecord watcherRecord = runtime.RegisterLiveEntity(Spawn(watcherGuid, 1)).Record!;
|
||||
LiveEntityRecord retiredRecord = runtime.RegisterAndMaterializeProjection(Spawn(retiredGuid, 1));
|
||||
LiveEntityRecord watcherRecord = runtime.RegisterAndMaterializeProjection(Spawn(watcherGuid, 1));
|
||||
IPhysicsObjHost? Resolve(uint id) =>
|
||||
runtime.TryGetPhysicsHost(id, out IPhysicsObjHost host)
|
||||
? host
|
||||
|
|
@ -493,7 +496,7 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
{
|
||||
const uint guid = 0x70000048u;
|
||||
var runtime = Runtime();
|
||||
LiveEntityRecord record = runtime.RegisterLiveEntity(Spawn(guid, 1)).Record!;
|
||||
LiveEntityRecord record = runtime.RegisterAndMaterializeProjection(Spawn(guid, 1));
|
||||
record.FullCellId = 0x01010123u;
|
||||
record.WorldEntity = new AcDream.Core.World.WorldEntity
|
||||
{
|
||||
|
|
@ -521,32 +524,31 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
runtime = Runtime(record =>
|
||||
{
|
||||
if (record.ServerGuid == departingGuid)
|
||||
{
|
||||
LiveEntityRecord replacement = runtime.RegisterLiveEntity(
|
||||
Spawn(departingGuid, 2)).Record!;
|
||||
replacement.FullCellId = 0x02020202u;
|
||||
replacement.WorldEntity = Entity(
|
||||
departingGuid,
|
||||
new Vector3(999f, 999f, 999f),
|
||||
Quaternion.Identity);
|
||||
runtime.InstallPhysicsHost(replacement, Host(departingGuid));
|
||||
}
|
||||
runtime.RegisterLiveEntity(Spawn(departingGuid, 2));
|
||||
CleanPhysicsHost(record);
|
||||
});
|
||||
LiveEntityRecord departing = runtime.RegisterLiveEntity(Spawn(departingGuid, 1)).Record!;
|
||||
LiveEntityRecord watcherRecord = runtime.RegisterLiveEntity(Spawn(watcherGuid, 1)).Record!;
|
||||
departing.FullCellId = 0x01010123u;
|
||||
Quaternion departingRotation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, 0.75f);
|
||||
departing.WorldEntity = Entity(
|
||||
departingGuid,
|
||||
new Vector3(12f, 34f, 56f),
|
||||
departingRotation);
|
||||
watcherRecord.WorldEntity = Entity(watcherGuid, Vector3.Zero, Quaternion.Identity);
|
||||
LiveEntityRecord departing = runtime.RegisterAndMaterializeProjection(
|
||||
Spawn(departingGuid, 1),
|
||||
localId => Entity(
|
||||
localId,
|
||||
departingGuid,
|
||||
new Vector3(12f, 34f, 56f),
|
||||
departingRotation));
|
||||
departing.FullCellId = 0x01010123u;
|
||||
WorldEntity departingEntity = Assert.IsType<WorldEntity>(departing.WorldEntity);
|
||||
LiveEntityRecord watcherRecord = runtime.RegisterAndMaterializeProjection(
|
||||
Spawn(watcherGuid, 1),
|
||||
localId => Entity(
|
||||
localId,
|
||||
watcherGuid,
|
||||
Vector3.Zero,
|
||||
Quaternion.Identity));
|
||||
IPhysicsObjHost? Resolve(uint id) =>
|
||||
runtime.TryGetPhysicsHost(id, out IPhysicsObjHost host) ? host : null;
|
||||
var body = new PhysicsBody
|
||||
{
|
||||
Position = departing.WorldEntity.Position,
|
||||
Position = departingEntity.Position,
|
||||
Orientation = departingRotation,
|
||||
};
|
||||
var remote = new RemoteMotion(body);
|
||||
|
|
@ -586,6 +588,14 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
Assert.True(runtime.UnregisterLiveEntity(
|
||||
new DeleteObject.Parsed(departingGuid, InstanceSequence: 1),
|
||||
isLocalPlayer: false));
|
||||
LiveEntityRecord replacement = runtime.RegisterAndMaterializeProjection(
|
||||
Spawn(departingGuid, 2));
|
||||
replacement.FullCellId = 0x02020202u;
|
||||
runtime.InstallPhysicsHost(
|
||||
replacement,
|
||||
Host(
|
||||
departingGuid,
|
||||
position: new Vector3(999f, 999f, 999f)));
|
||||
|
||||
TargetInfo exit = Assert.Single(delivered);
|
||||
Assert.Equal(TargetStatus.ExitWorld, exit.Status);
|
||||
|
|
@ -600,11 +610,11 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
{
|
||||
const uint guid = 0x7000004Bu;
|
||||
var runtime = Runtime();
|
||||
LiveEntityRecord record = runtime.RegisterLiveEntity(Spawn(guid, 1)).Record!;
|
||||
LiveEntityRecord record = runtime.RegisterAndMaterializeProjection(Spawn(guid, 1));
|
||||
EntityPhysicsHost host = EntityPhysicsHost.CreateMinimal(record, _ => null, () => 0);
|
||||
runtime.InstallPhysicsHost(record, host);
|
||||
|
||||
Assert.Empty(runtime.WorldEntities);
|
||||
Assert.Empty(runtime.VisibleRecords);
|
||||
Assert.True(runtime.TryGetPhysicsHost(guid, out IPhysicsObjHost resolved));
|
||||
Assert.Same(host, resolved);
|
||||
}
|
||||
|
|
@ -645,12 +655,13 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
InstanceSequence: instance);
|
||||
|
||||
private static AcDream.Core.World.WorldEntity Entity(
|
||||
uint localId,
|
||||
uint guid,
|
||||
Vector3 position,
|
||||
Quaternion rotation) =>
|
||||
new()
|
||||
{
|
||||
Id = 1_000_000u + (guid & 0xFFFFu),
|
||||
Id = localId,
|
||||
ServerGuid = guid,
|
||||
SourceGfxObjOrSetupId = 0x02000001u,
|
||||
Position = position,
|
||||
|
|
|
|||
|
|
@ -73,7 +73,9 @@ public sealed class LiveEntityPresentationControllerTests
|
|||
fixture.PresentationOrder);
|
||||
Assert.Equal(1, fixture.Shadows.TotalRegistered);
|
||||
Assert.True(fixture.Entity.IsDrawVisible);
|
||||
Assert.Same(fixture.Entity, Assert.Single(fixture.Runtime.WorldEntities).Value);
|
||||
Assert.Same(
|
||||
fixture.Entity,
|
||||
Assert.Single(fixture.Runtime.VisibleRecords).WorldEntity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -289,7 +291,9 @@ public sealed class LiveEntityPresentationControllerTests
|
|||
Assert.Equal(0, fixture.Shadows.TotalRegistered);
|
||||
|
||||
Assert.True(fixture.Runtime.RebucketLiveEntity(Fixture.Guid, 0x02020001u));
|
||||
Assert.False(fixture.Runtime.WorldEntities.ContainsKey(Fixture.Guid));
|
||||
Assert.False(fixture.Runtime.TryGetInteractionEligibleEntity(
|
||||
Fixture.Guid,
|
||||
out _));
|
||||
|
||||
Assert.True(fixture.Runtime.TryApplyState(
|
||||
new SetState.Parsed(Fixture.Guid, 0u, 1, 3),
|
||||
|
|
@ -303,7 +307,9 @@ public sealed class LiveEntityPresentationControllerTests
|
|||
new LandBlock(),
|
||||
Array.Empty<WorldEntity>()));
|
||||
|
||||
Assert.True(fixture.Runtime.WorldEntities.ContainsKey(Fixture.Guid));
|
||||
Assert.True(fixture.Runtime.TryGetInteractionEligibleEntity(
|
||||
Fixture.Guid,
|
||||
out _));
|
||||
Assert.Equal(1, fixture.Shadows.TotalRegistered);
|
||||
}
|
||||
|
||||
|
|
@ -318,7 +324,9 @@ public sealed class LiveEntityPresentationControllerTests
|
|||
Fixture.Guid,
|
||||
0x02020001u));
|
||||
|
||||
Assert.False(fixture.Runtime.WorldEntities.ContainsKey(Fixture.Guid));
|
||||
Assert.False(fixture.Runtime.TryGetInteractionEligibleEntity(
|
||||
Fixture.Guid,
|
||||
out _));
|
||||
Assert.Equal(0, fixture.Shadows.TotalRegistered);
|
||||
Assert.Equal(1, fixture.Shadows.RetainedRegistrationCount);
|
||||
Assert.Equal(1, fixture.Shadows.SuspendedRegistrationCount);
|
||||
|
|
@ -331,7 +339,7 @@ public sealed class LiveEntityPresentationControllerTests
|
|||
|
||||
Assert.Same(
|
||||
fixture.Entity,
|
||||
Assert.Single(fixture.Runtime.WorldEntities).Value);
|
||||
Assert.Single(fixture.Runtime.VisibleRecords).WorldEntity);
|
||||
Assert.Equal(1, fixture.Shadows.TotalRegistered);
|
||||
Assert.Equal(0, fixture.Shadows.SuspendedRegistrationCount);
|
||||
Assert.False(fixture.Controller.HasDeferredShadowRestore(Fixture.Guid));
|
||||
|
|
@ -467,7 +475,7 @@ public sealed class LiveEntityPresentationControllerTests
|
|||
new LandBlock(),
|
||||
Array.Empty<WorldEntity>()));
|
||||
|
||||
Assert.Same(entity, Assert.Single(runtime.WorldEntities).Value);
|
||||
Assert.Same(entity, Assert.Single(runtime.VisibleRecords).WorldEntity);
|
||||
Assert.Equal(1, shadows.TotalRegistered);
|
||||
Assert.Equal(0, shadows.SuspendedRegistrationCount);
|
||||
Assert.False(controller.HasDeferredShadowRestore(Fixture.Guid));
|
||||
|
|
|
|||
|
|
@ -247,10 +247,8 @@ public sealed class LiveEntityProjectionWithdrawalControllerTests
|
|||
MotionState: null,
|
||||
MotionTableId: null,
|
||||
InstanceSequence: instance);
|
||||
LiveEntityRecord record = Live.RegisterLiveEntity(spawn).Record!;
|
||||
WorldEntity entity = Live.MaterializeLiveEntity(
|
||||
Guid,
|
||||
Cell,
|
||||
LiveEntityRecord record = Live.RegisterAndMaterializeProjection(
|
||||
spawn,
|
||||
id => new WorldEntity
|
||||
{
|
||||
Id = id,
|
||||
|
|
@ -260,7 +258,8 @@ public sealed class LiveEntityProjectionWithdrawalControllerTests
|
|||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
ParentCellId = Cell,
|
||||
})!;
|
||||
});
|
||||
WorldEntity entity = Assert.IsType<WorldEntity>(record.WorldEntity);
|
||||
var snapshot = new WorldEntitySnapshot(
|
||||
entity.Id,
|
||||
entity.SourceGfxObjOrSetupId,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ public sealed class LiveEntityRuntimeTeardownControllerTests
|
|||
[Fact]
|
||||
public void Retry_ReusesPlanAndDoesNotReplayCompletedOwners()
|
||||
{
|
||||
LiveEntityRecord record = new(Spawn());
|
||||
LiveEntityRecord record =
|
||||
LiveEntityTestFixture.CreateExactProjectionRecord(Spawn());
|
||||
int factoryCalls = 0;
|
||||
int completedOwnerCalls = 0;
|
||||
int retryingOwnerCalls = 0;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
87
tests/AcDream.App.Tests/World/LiveEntityTestFixture.cs
Normal file
87
tests/AcDream.App.Tests/World/LiveEntityTestFixture.cs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Core.Net;
|
||||
using AcDream.Core.World;
|
||||
using AcDream.Runtime.Entities;
|
||||
|
||||
namespace AcDream.App.World;
|
||||
|
||||
/// <summary>
|
||||
/// Test-only construction seam for exact Runtime identities and App
|
||||
/// projections. Component fixtures must not manufacture detached
|
||||
/// <see cref="LiveEntityRecord"/> instances because that bypasses the same
|
||||
/// local-ID/incarnation key used by production.
|
||||
/// </summary>
|
||||
internal static class LiveEntityTestFixture
|
||||
{
|
||||
public static LiveEntityRecord CreateExactProjectionRecord(
|
||||
WorldSession.EntitySpawn spawn)
|
||||
{
|
||||
var directory = new RuntimeEntityDirectory();
|
||||
RuntimeEntityRecord canonical = directory.AddActive(spawn);
|
||||
directory.ClaimLocalId(canonical);
|
||||
var projections = new LiveEntityProjectionStore(directory);
|
||||
return projections.AddMaterializing(canonical);
|
||||
}
|
||||
|
||||
public static LiveEntityRecord RegisterAndMaterializeProjection(
|
||||
this LiveEntityRuntime runtime,
|
||||
WorldSession.EntitySpawn spawn,
|
||||
Func<uint, WorldEntity>? factory = null,
|
||||
LiveEntityProjectionKind projectionKind = LiveEntityProjectionKind.World,
|
||||
Action<LiveEntityRecord>? initializeProjection = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(runtime);
|
||||
|
||||
LiveEntityRegistrationResult registration =
|
||||
runtime.RegisterLiveEntity(spawn);
|
||||
RuntimeEntityRecord canonical = registration.Canonical
|
||||
?? throw new InvalidOperationException(
|
||||
"The fixture cannot materialize a stale CreateObject.");
|
||||
if (registration.Projection is { } existing)
|
||||
return existing;
|
||||
|
||||
WorldEntity? entity = runtime.MaterializeLiveEntity(
|
||||
canonical,
|
||||
spawn.Position?.LandblockId ?? canonical.FullCellId,
|
||||
factory ?? (id => CreateWorldEntity(id, spawn)),
|
||||
projectionKind,
|
||||
initializeProjection,
|
||||
out LiveEntityRecord? record);
|
||||
if (entity is null || record is null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The fixture failed to materialize the exact Runtime incarnation.");
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
private static WorldEntity CreateWorldEntity(
|
||||
uint localEntityId,
|
||||
WorldSession.EntitySpawn spawn)
|
||||
{
|
||||
var position = spawn.Position;
|
||||
return new WorldEntity
|
||||
{
|
||||
Id = localEntityId,
|
||||
ServerGuid = spawn.Guid,
|
||||
SourceGfxObjOrSetupId = spawn.SetupTableId ?? 0u,
|
||||
Position = position is { } placed
|
||||
? new Vector3(
|
||||
placed.PositionX,
|
||||
placed.PositionY,
|
||||
placed.PositionZ)
|
||||
: Vector3.Zero,
|
||||
Rotation = position is { } oriented
|
||||
? new Quaternion(
|
||||
oriented.RotationX,
|
||||
oriented.RotationY,
|
||||
oriented.RotationZ,
|
||||
oriented.RotationW)
|
||||
: Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
ParentCellId = position?.LandblockId,
|
||||
EffectCellId = position?.LandblockId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,9 @@
|
|||
using System.Reflection;
|
||||
using AcDream.App.Physics;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Rendering.Scene;
|
||||
using AcDream.App.Rendering.Vfx;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.App.World;
|
||||
using AcDream.Runtime.Entities;
|
||||
|
||||
|
|
@ -14,28 +19,118 @@ public sealed class RuntimeEntityOwnershipTests
|
|||
BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
?? throw new InvalidOperationException("Missing canonical entity directory.");
|
||||
FieldInfo sidecars = typeof(LiveEntityRuntime).GetField(
|
||||
"_activeRecords",
|
||||
"_projections",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
?? throw new InvalidOperationException("Missing App projection sidecar store.");
|
||||
|
||||
Assert.Equal(typeof(RuntimeEntityDirectory), directory.FieldType);
|
||||
Assert.Equal(typeof(RuntimeEntityRecord), sidecars.FieldType.GetGenericArguments()[0]);
|
||||
Assert.Equal(typeof(LiveEntityRecord), sidecars.FieldType.GetGenericArguments()[1]);
|
||||
Assert.Equal(typeof(LiveEntityProjectionStore), sidecars.FieldType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AppCompatibilityLookup_IsNotASecondGuidDictionary()
|
||||
public void AppProjectionOwners_UseExactRuntimeKeysAndNoGuidDictionary()
|
||||
{
|
||||
FieldInfo compatibilityView = typeof(LiveEntityRuntime).GetField(
|
||||
"_recordsByGuid",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
?? throw new InvalidOperationException("Missing migration compatibility view.");
|
||||
FieldInfo[] runtimeFields = typeof(LiveEntityRuntime).GetFields(
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.DoesNotContain(runtimeFields, IsGuidDictionary);
|
||||
|
||||
Assert.False(compatibilityView.FieldType.IsGenericType);
|
||||
Assert.DoesNotContain(
|
||||
compatibilityView.FieldType.GetInterfaces(),
|
||||
type => type.IsGenericType
|
||||
&& type.GetGenericTypeDefinition() == typeof(IDictionary<,>));
|
||||
FieldInfo[] storeFields = typeof(LiveEntityProjectionStore).GetFields(
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
FieldInfo[] exactStores = storeFields
|
||||
.Where(field => IsDictionaryWithKey(field, typeof(RuntimeEntityKey)))
|
||||
.ToArray();
|
||||
Assert.NotEmpty(exactStores);
|
||||
Assert.All(
|
||||
exactStores,
|
||||
field => Assert.Equal(
|
||||
typeof(LiveEntityRecord),
|
||||
field.FieldType.GetGenericArguments()[1]));
|
||||
Assert.DoesNotContain(storeFields, IsGuidDictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MaterializedPresentationWorksets_AreExactKeyed()
|
||||
{
|
||||
AssertExactKeyFields(
|
||||
typeof(LiveEntityPresentationController),
|
||||
"_readyOwners",
|
||||
"_suspendedShadowOwners",
|
||||
"_activePlacementOwners");
|
||||
AssertExactKeyFields(typeof(RemoteTeleportController), "_pending");
|
||||
AssertExactKeyFields(typeof(LiveRenderProjectionJournal), "_byKey");
|
||||
AssertExactKeyFields(
|
||||
typeof(EntityEffectController),
|
||||
"_liveProfiles",
|
||||
"_readyLiveOwners");
|
||||
AssertExactKeyFields(
|
||||
typeof(LiveEntityLightController),
|
||||
"_trackedOwners",
|
||||
"_presentOwners");
|
||||
AssertExactKeyFields(
|
||||
typeof(EquippedChildRenderController),
|
||||
"_attachedByChild",
|
||||
"_pendingUnparentByChild",
|
||||
"_pendingOrdinaryRemovalByRoot",
|
||||
"_pendingDetachedRemovalByChild",
|
||||
"_pendingReparentRemovalByChild",
|
||||
"_pendingPoseLossRemovalByChild",
|
||||
"_pendingOrphanRemovalByChild");
|
||||
AssertExactKeyFields(typeof(LiveEntityAnimationScheduler), "_schedules");
|
||||
AssertExactKeyFields(typeof(EntitySpawnAdapter), "_ownersByKey");
|
||||
AssertExactKeyFields(
|
||||
typeof(LiveEntityLivenessTracker),
|
||||
"_deadlines",
|
||||
"_present");
|
||||
AssertExactKeyFields(
|
||||
typeof(RemoteMovementObservationTracker),
|
||||
"_lastMove");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AppAssembly_HasNoGuidKeyedLiveEntityRecordDictionary()
|
||||
{
|
||||
FieldInfo[] forbidden = typeof(LiveEntityRuntime).Assembly
|
||||
.GetTypes()
|
||||
.SelectMany(type => type.GetFields(
|
||||
BindingFlags.Instance
|
||||
| BindingFlags.Static
|
||||
| BindingFlags.Public
|
||||
| BindingFlags.NonPublic))
|
||||
.Where(field =>
|
||||
IsDictionaryWithKey(field, typeof(uint))
|
||||
&& field.FieldType.GetGenericArguments()[1]
|
||||
== typeof(LiveEntityRecord))
|
||||
.ToArray();
|
||||
|
||||
Assert.Empty(forbidden);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExactOwners_HaveNoRetainedGuidIndex()
|
||||
{
|
||||
Type[] exactOwnerTypes =
|
||||
[
|
||||
typeof(LiveEntityProjectionStore),
|
||||
typeof(LiveEntityPresentationController),
|
||||
typeof(RemoteTeleportController),
|
||||
typeof(LiveRenderProjectionJournal),
|
||||
typeof(LiveEntityLightController),
|
||||
typeof(EquippedChildRenderController),
|
||||
typeof(LiveEntityAnimationScheduler),
|
||||
typeof(EntitySpawnAdapter),
|
||||
typeof(LiveEntityLivenessTracker),
|
||||
typeof(RemoteMovementObservationTracker),
|
||||
];
|
||||
|
||||
FieldInfo[] forbidden = exactOwnerTypes
|
||||
.SelectMany(type => type.GetFields(
|
||||
BindingFlags.Instance
|
||||
| BindingFlags.NonPublic))
|
||||
.Where(field =>
|
||||
field.Name.Contains("Guid", StringComparison.OrdinalIgnoreCase))
|
||||
.ToArray();
|
||||
|
||||
Assert.Empty(forbidden);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -73,4 +168,42 @@ public sealed class RuntimeEntityOwnershipTests
|
|||
|| ns?.StartsWith("Silk.NET", StringComparison.Ordinal) == true
|
||||
|| ns?.StartsWith("ImGuiNET", StringComparison.Ordinal) == true;
|
||||
}
|
||||
|
||||
private static bool IsGuidDictionary(FieldInfo field) =>
|
||||
IsDictionaryWithKey(field, typeof(uint));
|
||||
|
||||
private static void AssertExactKeyFields(
|
||||
Type owner,
|
||||
params string[] fieldNames)
|
||||
{
|
||||
foreach (string fieldName in fieldNames)
|
||||
{
|
||||
FieldInfo field = owner.GetField(
|
||||
fieldName,
|
||||
BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
?? throw new InvalidOperationException(
|
||||
$"{owner.Name}.{fieldName} is missing.");
|
||||
|
||||
Assert.True(
|
||||
IsCollectionWithKey(field, typeof(RuntimeEntityKey)),
|
||||
$"{owner.Name}.{fieldName} must be keyed by RuntimeEntityKey, "
|
||||
+ $"but was {field.FieldType}.");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsCollectionWithKey(FieldInfo field, Type keyType)
|
||||
{
|
||||
if (!field.FieldType.IsGenericType)
|
||||
return false;
|
||||
|
||||
Type generic = field.FieldType.GetGenericTypeDefinition();
|
||||
return (generic == typeof(Dictionary<,>)
|
||||
|| generic == typeof(HashSet<>))
|
||||
&& field.FieldType.GetGenericArguments()[0] == keyType;
|
||||
}
|
||||
|
||||
private static bool IsDictionaryWithKey(FieldInfo field, Type keyType) =>
|
||||
field.FieldType.IsGenericType
|
||||
&& field.FieldType.GetGenericTypeDefinition() == typeof(Dictionary<,>)
|
||||
&& field.FieldType.GetGenericArguments()[0] == keyType;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue