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
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue