refactor(runtime): publish canonical entity object deltas
Issue stable Runtime identities at canonical registration, publish entity and inventory commits through one generation-stamped synchronous stream, and make graphical adapters borrow the same direct views and events as a no-window host. Preserve exact projection teardown and retail mutation order while removing App-side event reconstruction. Make the hard-recenter ordering fixture independent of the production two-millisecond frame budget so its injected-failure gate is deterministic. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
d3e96ff912
commit
ce3ac310d9
23 changed files with 2352 additions and 666 deletions
151
src/AcDream.Runtime/Entities/RuntimeEntityObjectViews.cs
Normal file
151
src/AcDream.Runtime/Entities/RuntimeEntityObjectViews.cs
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
using AcDream.Core.Items;
|
||||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Core.Physics;
|
||||
using System.Numerics;
|
||||
|
||||
namespace AcDream.Runtime.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Allocation-free borrowed views over the canonical entity/object lifetime.
|
||||
/// These views contain no App projection or backend state.
|
||||
/// </summary>
|
||||
internal sealed class RuntimeEntityObjectViews
|
||||
{
|
||||
public RuntimeEntityObjectViews(
|
||||
RuntimeEntityDirectory entities,
|
||||
ClientObjectTable objects)
|
||||
{
|
||||
Entities = new EntityView(
|
||||
entities ?? throw new ArgumentNullException(nameof(entities)));
|
||||
Inventory = new InventoryView(
|
||||
entities,
|
||||
objects ?? throw new ArgumentNullException(nameof(objects)));
|
||||
}
|
||||
|
||||
public IRuntimeEntityView Entities { get; }
|
||||
public IRuntimeInventoryView Inventory { get; }
|
||||
|
||||
internal static RuntimeEntitySnapshot Snapshot(RuntimeEntityRecord record)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(record);
|
||||
uint localEntityId = record.LocalEntityId
|
||||
?? throw new InvalidOperationException(
|
||||
$"Canonical entity 0x{record.ServerGuid:X8}/{record.Incarnation} has no Runtime identity.");
|
||||
return new RuntimeEntitySnapshot(
|
||||
new RuntimeEntityIdentity(
|
||||
record.ServerGuid,
|
||||
localEntityId,
|
||||
record.Incarnation),
|
||||
record.FullCellId,
|
||||
(uint)record.FinalPhysicsState,
|
||||
ConvertPosition(record.Snapshot.Position));
|
||||
}
|
||||
|
||||
private static Position? ConvertPosition(
|
||||
CreateObject.ServerPosition? position) =>
|
||||
position is not { } value
|
||||
? null
|
||||
: new Position(
|
||||
value.LandblockId,
|
||||
new Vector3(
|
||||
value.PositionX,
|
||||
value.PositionY,
|
||||
value.PositionZ),
|
||||
new Quaternion(
|
||||
value.RotationX,
|
||||
value.RotationY,
|
||||
value.RotationZ,
|
||||
value.RotationW));
|
||||
|
||||
internal static RuntimeInventoryItemSnapshot Snapshot(
|
||||
ClientObject item,
|
||||
RuntimeEntityDirectory entities,
|
||||
ushort? exactGeneration = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
ushort incarnation = exactGeneration
|
||||
?? (entities.TryGetActive(
|
||||
item.ObjectId,
|
||||
out RuntimeEntityRecord canonical)
|
||||
? canonical.Incarnation
|
||||
: (ushort)0);
|
||||
return new RuntimeInventoryItemSnapshot(
|
||||
item.ObjectId,
|
||||
incarnation,
|
||||
item.Name,
|
||||
item.ContainerId,
|
||||
item.ContainerSlot,
|
||||
item.WielderId,
|
||||
(uint)item.CurrentlyEquippedLocation,
|
||||
item.StackSize,
|
||||
item.Value);
|
||||
}
|
||||
|
||||
private sealed class EntityView(RuntimeEntityDirectory owner)
|
||||
: IRuntimeEntityView
|
||||
{
|
||||
public int Count => owner.Count;
|
||||
|
||||
public bool TryGet(
|
||||
uint serverGuid,
|
||||
out RuntimeEntitySnapshot entity)
|
||||
{
|
||||
if (owner.TryGetActive(
|
||||
serverGuid,
|
||||
out RuntimeEntityRecord record))
|
||||
{
|
||||
entity = Snapshot(record);
|
||||
return true;
|
||||
}
|
||||
|
||||
entity = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Visit(IRuntimeEntityVisitor visitor)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(visitor);
|
||||
foreach (RuntimeEntityRecord record in owner.ActiveRecords)
|
||||
{
|
||||
RuntimeEntitySnapshot entity = Snapshot(record);
|
||||
visitor.Visit(in entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class InventoryView(
|
||||
RuntimeEntityDirectory entities,
|
||||
ClientObjectTable owner)
|
||||
: IRuntimeInventoryView
|
||||
{
|
||||
public int ObjectCount => owner.ObjectCount;
|
||||
public int ContainerCount => owner.ContainerCount;
|
||||
|
||||
public bool TryGet(
|
||||
uint objectId,
|
||||
out RuntimeInventoryItemSnapshot item)
|
||||
{
|
||||
ClientObject? found = owner.Get(objectId);
|
||||
if (found is not null)
|
||||
{
|
||||
item = Snapshot(found, entities);
|
||||
return true;
|
||||
}
|
||||
|
||||
item = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Visit(IRuntimeInventoryVisitor visitor)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(visitor);
|
||||
foreach (ClientObject item in owner.Objects)
|
||||
{
|
||||
RuntimeInventoryItemSnapshot snapshot = Snapshot(
|
||||
item,
|
||||
entities);
|
||||
visitor.Visit(in snapshot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue