feat(runtime): define borrowed views commands and ordered events
Establish the J1 presentation-independent contract with instance-scoped clocks and generations, immutable borrowed views, typed generation-gated commands, normalized ordered diagnostics, and teardown acknowledgements. Route graphical startup plus press-time selection, movement, and combat through focused App adapters over the exact existing owners without adding a queue or mirrored world. Validated by the Release solution build, 13 Runtime tests, 3,838 App tests with three existing skips, and the complete 8,424-test Release suite with five existing skips. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
afebbe3eca
commit
854d9e9cd1
21 changed files with 2594 additions and 44 deletions
271
src/AcDream.App/Runtime/CurrentGameRuntimeViewAdapter.cs
Normal file
271
src/AcDream.App/Runtime/CurrentGameRuntimeViewAdapter.cs
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
using AcDream.App.Input;
|
||||
using AcDream.App.Net;
|
||||
using AcDream.App.Streaming;
|
||||
using AcDream.App.World;
|
||||
using AcDream.Core.Chat;
|
||||
using AcDream.Core.Items;
|
||||
using AcDream.Runtime;
|
||||
|
||||
namespace AcDream.App.Runtime;
|
||||
|
||||
/// <summary>
|
||||
/// Read-only borrowed projections over the current App owners. Enumeration is
|
||||
/// synchronous and allocation-free; no mutable collection is copied or owned.
|
||||
/// </summary>
|
||||
internal sealed class CurrentGameRuntimeViewAdapter : IGameRuntimeView
|
||||
{
|
||||
private readonly LiveSessionController _session;
|
||||
private readonly LocalPlayerIdentityState _playerIdentity;
|
||||
private readonly LiveEntityRuntime _entities;
|
||||
private readonly ClientObjectTable _objects;
|
||||
private readonly ChatLog _chat;
|
||||
private readonly IGameRuntimeClock _clock;
|
||||
private readonly EntityView _entityView;
|
||||
private readonly InventoryView _inventoryView;
|
||||
private readonly ChatView _chatView;
|
||||
private readonly MovementView _movementView;
|
||||
private readonly PortalView _portalView;
|
||||
private bool _active = true;
|
||||
|
||||
public CurrentGameRuntimeViewAdapter(
|
||||
LiveSessionController session,
|
||||
LocalPlayerIdentityState playerIdentity,
|
||||
LiveEntityRuntime entities,
|
||||
ClientObjectTable objects,
|
||||
ChatLog chat,
|
||||
ILocalPlayerControllerSource playerController,
|
||||
WorldRevealCoordinator worldReveal,
|
||||
IGameRuntimeClock clock)
|
||||
{
|
||||
_session = session ?? throw new ArgumentNullException(nameof(session));
|
||||
_playerIdentity = playerIdentity
|
||||
?? throw new ArgumentNullException(nameof(playerIdentity));
|
||||
_entities = entities ?? throw new ArgumentNullException(nameof(entities));
|
||||
_objects = objects ?? throw new ArgumentNullException(nameof(objects));
|
||||
_chat = chat ?? throw new ArgumentNullException(nameof(chat));
|
||||
_clock = clock ?? throw new ArgumentNullException(nameof(clock));
|
||||
|
||||
_entityView = new EntityView(_entities);
|
||||
_inventoryView = new InventoryView(_objects, _entities);
|
||||
_chatView = new ChatView(_chat);
|
||||
_movementView = new MovementView(
|
||||
playerController
|
||||
?? throw new ArgumentNullException(nameof(playerController)),
|
||||
_clock);
|
||||
_portalView = new PortalView(
|
||||
worldReveal ?? throw new ArgumentNullException(nameof(worldReveal)));
|
||||
}
|
||||
|
||||
internal bool IsActive => _active && !_session.IsDisposalComplete;
|
||||
|
||||
public RuntimeGenerationToken Generation =>
|
||||
new(_session.SessionGeneration);
|
||||
|
||||
public RuntimeLifecycleSnapshot Lifecycle
|
||||
{
|
||||
get
|
||||
{
|
||||
RuntimeLifecycleState state;
|
||||
if (!IsActive)
|
||||
state = RuntimeLifecycleState.Disposed;
|
||||
else if (_session.IsInWorld)
|
||||
state = RuntimeLifecycleState.InWorld;
|
||||
else if (_session.CurrentSession is not null)
|
||||
state = RuntimeLifecycleState.Starting;
|
||||
else if (_session.SessionGeneration == 0UL)
|
||||
state = RuntimeLifecycleState.Constructed;
|
||||
else
|
||||
state = RuntimeLifecycleState.Stopped;
|
||||
|
||||
return new RuntimeLifecycleSnapshot(
|
||||
Generation,
|
||||
state,
|
||||
_playerIdentity.ServerGuid,
|
||||
_session.CurrentSession is not null);
|
||||
}
|
||||
}
|
||||
|
||||
public IGameRuntimeClock Clock => _clock;
|
||||
public IRuntimeEntityView Entities => _entityView;
|
||||
public IRuntimeInventoryView Inventory => _inventoryView;
|
||||
public IRuntimeChatView Chat => _chatView;
|
||||
public IRuntimeMovementView Movement => _movementView;
|
||||
public IRuntimePortalView Portal => _portalView;
|
||||
|
||||
public RuntimeStateCheckpoint CaptureCheckpoint() =>
|
||||
new(
|
||||
Generation,
|
||||
Lifecycle.State,
|
||||
_clock.FrameNumber,
|
||||
_entities.Count,
|
||||
_entities.MaterializedCount,
|
||||
_objects.ObjectCount,
|
||||
_objects.ContainerCount,
|
||||
_chat.Revision,
|
||||
_chat.Count,
|
||||
_movementView.Snapshot,
|
||||
_portalView.Snapshot);
|
||||
|
||||
internal void Deactivate() => _active = false;
|
||||
|
||||
internal static RuntimeEntitySnapshot Snapshot(LiveEntityRecord record) =>
|
||||
new(
|
||||
new RuntimeEntityIdentity(
|
||||
record.ServerGuid,
|
||||
record.LocalEntityId ?? 0u,
|
||||
record.Generation),
|
||||
record.FullCellId,
|
||||
(uint)record.FinalPhysicsState,
|
||||
record.PhysicsBody?.CellPosition,
|
||||
record.LocalEntityId.HasValue,
|
||||
record.IsSpatiallyVisible,
|
||||
record.InitialHydrationCompleted);
|
||||
|
||||
internal static RuntimeInventoryItemSnapshot Snapshot(
|
||||
ClientObject item,
|
||||
LiveEntityRuntime entities,
|
||||
ushort? exactGeneration = null)
|
||||
{
|
||||
ushort generation = exactGeneration
|
||||
?? (entities.TryGetRecord(item.ObjectId, out LiveEntityRecord record)
|
||||
? record.Generation
|
||||
: (ushort)0);
|
||||
return new RuntimeInventoryItemSnapshot(
|
||||
item.ObjectId,
|
||||
generation,
|
||||
item.Name,
|
||||
item.ContainerId,
|
||||
item.ContainerSlot,
|
||||
item.WielderId,
|
||||
(uint)item.CurrentlyEquippedLocation,
|
||||
item.StackSize,
|
||||
item.Value);
|
||||
}
|
||||
|
||||
private sealed class EntityView(LiveEntityRuntime owner)
|
||||
: IRuntimeEntityView
|
||||
{
|
||||
public int Count => owner.Count;
|
||||
public int MaterializedCount => owner.MaterializedCount;
|
||||
|
||||
public bool TryGet(uint serverGuid, out RuntimeEntitySnapshot entity)
|
||||
{
|
||||
if (owner.TryGetRecord(serverGuid, out LiveEntityRecord record))
|
||||
{
|
||||
entity = Snapshot(record);
|
||||
return true;
|
||||
}
|
||||
|
||||
entity = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Visit(IRuntimeEntityVisitor visitor)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(visitor);
|
||||
foreach (LiveEntityRecord record in owner.Records)
|
||||
{
|
||||
RuntimeEntitySnapshot entity = Snapshot(record);
|
||||
visitor.Visit(in entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class InventoryView(
|
||||
ClientObjectTable owner,
|
||||
LiveEntityRuntime entities)
|
||||
: 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ChatView(ChatLog owner) : IRuntimeChatView
|
||||
{
|
||||
public long Revision => owner.Revision;
|
||||
public int Count => owner.Count;
|
||||
}
|
||||
|
||||
private sealed class MovementView(
|
||||
ILocalPlayerControllerSource owner,
|
||||
IGameRuntimeClock clock)
|
||||
: IRuntimeMovementView
|
||||
{
|
||||
public RuntimeMovementSnapshot Snapshot
|
||||
{
|
||||
get
|
||||
{
|
||||
PlayerMovementController? controller = owner.Controller;
|
||||
return controller is null
|
||||
? new RuntimeMovementSnapshot(
|
||||
false,
|
||||
0u,
|
||||
default,
|
||||
default,
|
||||
false,
|
||||
clock.SimulationTimeSeconds)
|
||||
: new RuntimeMovementSnapshot(
|
||||
true,
|
||||
controller.LocalEntityId,
|
||||
controller.CellPosition,
|
||||
controller.BodyVelocity,
|
||||
controller.IsAirborne,
|
||||
clock.SimulationTimeSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class PortalView(WorldRevealCoordinator owner)
|
||||
: IRuntimePortalView
|
||||
{
|
||||
public RuntimePortalSnapshot Snapshot
|
||||
{
|
||||
get
|
||||
{
|
||||
WorldRevealLifecycleSnapshot snapshot = owner.Snapshot;
|
||||
RuntimePortalKind kind = snapshot.Generation == 0
|
||||
? RuntimePortalKind.None
|
||||
: snapshot.Kind switch
|
||||
{
|
||||
WorldRevealKind.Login => RuntimePortalKind.Login,
|
||||
WorldRevealKind.Portal => RuntimePortalKind.Portal,
|
||||
_ => RuntimePortalKind.None,
|
||||
};
|
||||
return new RuntimePortalSnapshot(
|
||||
snapshot.Generation,
|
||||
kind,
|
||||
snapshot.Readiness.DestinationCell,
|
||||
snapshot.IsReady,
|
||||
snapshot.Materialized,
|
||||
snapshot.Completed,
|
||||
snapshot.Cancelled,
|
||||
snapshot.WorldViewportObserved);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue