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:
Erik 2026-07-25 21:50:58 +02:00
parent e18df84437
commit 420e5eea70
73 changed files with 2939 additions and 1715 deletions

View file

@ -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(),