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