refactor(runtime): cut graphical host over to canonical root
Make every App composition phase borrow one GameRuntime, retire the duplicate view/event adapters, and dispose the root only after its graphical borrowers release. This preserves synchronous UI commands while giving shutdown one exact ownership ledger. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
ecb9f79444
commit
ce41efb9e5
29 changed files with 613 additions and 778 deletions
|
|
@ -1,10 +1,6 @@
|
|||
using AcDream.App.Input;
|
||||
using AcDream.App.Interaction;
|
||||
using AcDream.App.Net;
|
||||
using AcDream.App.World;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.Entities;
|
||||
using AcDream.Runtime.Gameplay;
|
||||
using AcDream.Runtime.Session;
|
||||
using AcDream.Runtime.World;
|
||||
using AcDream.UI.Abstractions;
|
||||
|
|
@ -12,9 +8,9 @@ using AcDream.UI.Abstractions;
|
|||
namespace AcDream.App.Runtime;
|
||||
|
||||
/// <summary>
|
||||
/// App composition root for Slice J's borrowed runtime boundary. Focused
|
||||
/// adapters project the existing canonical owners; this root owns only those
|
||||
/// adapters and never owns or copies gameplay state.
|
||||
/// Synchronous graphical command adapter over one canonical
|
||||
/// <see cref="GameRuntime"/>. It owns only its host lease and observer
|
||||
/// subscriptions; every view and mutable owner is borrowed directly.
|
||||
/// </summary>
|
||||
internal sealed class CurrentGameRuntimeAdapter
|
||||
: IGameRuntimeView,
|
||||
|
|
@ -22,72 +18,80 @@ internal sealed class CurrentGameRuntimeAdapter
|
|||
IRuntimeEventSource,
|
||||
IDisposable
|
||||
{
|
||||
private readonly CurrentGameRuntimeViewAdapter _view;
|
||||
private readonly CurrentGameRuntimeEventAdapter _events;
|
||||
private readonly GameRuntime _runtime;
|
||||
private readonly CurrentGameRuntimeCommandAdapter _commands;
|
||||
private readonly IDisposable _hostLease;
|
||||
private readonly object _subscriptionGate = new();
|
||||
private readonly HashSet<AdapterSubscription> _subscriptions = [];
|
||||
private bool _disposed;
|
||||
|
||||
public CurrentGameRuntimeAdapter(
|
||||
LiveSessionController session,
|
||||
GameRuntime runtime,
|
||||
LiveSessionHost sessionHost,
|
||||
ICommandBus commands,
|
||||
LocalPlayerIdentityState playerIdentity,
|
||||
RuntimeEntityObjectLifetime entityObjects,
|
||||
RuntimeInventoryState inventory,
|
||||
RuntimeCharacterState character,
|
||||
RuntimeCommunicationState communication,
|
||||
RuntimeActionState actions,
|
||||
RuntimeLocalPlayerMovementState movement,
|
||||
RuntimeWorldEnvironmentState environment,
|
||||
RuntimeWorldTransitState worldTransit,
|
||||
IGameRuntimeClock clock,
|
||||
SelectionInteractionController selection)
|
||||
{
|
||||
_view = new CurrentGameRuntimeViewAdapter(
|
||||
session,
|
||||
playerIdentity,
|
||||
entityObjects,
|
||||
inventory,
|
||||
character,
|
||||
communication,
|
||||
actions,
|
||||
movement,
|
||||
environment,
|
||||
worldTransit,
|
||||
clock);
|
||||
entityObjects.BindEventContext(
|
||||
() => _view.Generation,
|
||||
() => clock.FrameNumber);
|
||||
_events = new CurrentGameRuntimeEventAdapter(
|
||||
_view,
|
||||
entityObjects,
|
||||
communication);
|
||||
_commands = new CurrentGameRuntimeCommandAdapter(
|
||||
session,
|
||||
sessionHost,
|
||||
commands,
|
||||
_view,
|
||||
inventory,
|
||||
character,
|
||||
actions,
|
||||
movement,
|
||||
selection,
|
||||
_events);
|
||||
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
|
||||
ArgumentNullException.ThrowIfNull(sessionHost);
|
||||
ArgumentNullException.ThrowIfNull(commands);
|
||||
ArgumentNullException.ThrowIfNull(selection);
|
||||
|
||||
_hostLease = runtime.AcquireHostLease(
|
||||
"graphical game-runtime command adapter");
|
||||
try
|
||||
{
|
||||
_commands = new CurrentGameRuntimeCommandAdapter(
|
||||
runtime.Session,
|
||||
sessionHost,
|
||||
commands,
|
||||
this,
|
||||
runtime.InventoryOwner,
|
||||
runtime.CharacterOwner,
|
||||
runtime.ActionOwner,
|
||||
runtime.MovementOwner,
|
||||
selection,
|
||||
runtime.EventSink);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_hostLease.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public RuntimeGenerationToken Generation => _view.Generation;
|
||||
public RuntimeLifecycleSnapshot Lifecycle => _view.Lifecycle;
|
||||
public IGameRuntimeClock Clock => _view.Clock;
|
||||
public IRuntimeEntityView Entities => _view.Entities;
|
||||
public IRuntimeInventoryView Inventory => _view.Inventory;
|
||||
public IRuntimeInventoryStateView InventoryState => _view.InventoryState;
|
||||
public IRuntimeCharacterView Character => _view.Character;
|
||||
public IRuntimeSocialView Social => _view.Social;
|
||||
public IRuntimeChatView Chat => _view.Chat;
|
||||
public IRuntimeActionView Actions => _view.Actions;
|
||||
public IRuntimeMovementView Movement => _view.Movement;
|
||||
public IRuntimeWorldEnvironmentView Environment => _view.Environment;
|
||||
public IRuntimePortalView Portal => _view.Portal;
|
||||
private bool IsActive =>
|
||||
!_disposed
|
||||
&& !_runtime.Session.IsDisposalComplete;
|
||||
|
||||
public RuntimeGenerationToken Generation => _runtime.Generation;
|
||||
|
||||
public RuntimeLifecycleSnapshot Lifecycle
|
||||
{
|
||||
get
|
||||
{
|
||||
RuntimeLifecycleSnapshot current = _runtime.Lifecycle;
|
||||
return IsActive
|
||||
? current
|
||||
: current with
|
||||
{
|
||||
State = RuntimeLifecycleState.Disposed,
|
||||
HasTransport = false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public IGameRuntimeClock Clock => _runtime.Clock;
|
||||
public IRuntimeEntityView Entities => _runtime.Entities;
|
||||
public IRuntimeInventoryView Inventory => _runtime.Inventory;
|
||||
public IRuntimeInventoryStateView InventoryState =>
|
||||
_runtime.InventoryState;
|
||||
public IRuntimeCharacterView Character => _runtime.Character;
|
||||
public IRuntimeSocialView Social => _runtime.Social;
|
||||
public IRuntimeChatView Chat => _runtime.Chat;
|
||||
public IRuntimeActionView Actions => _runtime.Actions;
|
||||
public IRuntimeMovementView Movement => _runtime.Movement;
|
||||
public IRuntimeWorldEnvironmentView Environment => _runtime.Environment;
|
||||
public IRuntimePortalView Portal => _runtime.Portal;
|
||||
|
||||
public IRuntimeSessionCommands Session => _commands;
|
||||
public IRuntimeSelectionCommands Selection => _commands;
|
||||
|
|
@ -109,18 +113,64 @@ internal sealed class CurrentGameRuntimeAdapter
|
|||
public IRuntimeSocialCommands SocialCommands => _commands;
|
||||
IRuntimeSocialCommands IGameRuntimeCommands.Social => _commands;
|
||||
|
||||
public RuntimeStateCheckpoint CaptureCheckpoint() =>
|
||||
_view.CaptureCheckpoint();
|
||||
public RuntimeStateCheckpoint CaptureCheckpoint()
|
||||
{
|
||||
RuntimeStateCheckpoint checkpoint = _runtime.CaptureCheckpoint();
|
||||
return checkpoint with { Lifecycle = Lifecycle.State };
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IRuntimeEventObserver observer) =>
|
||||
_events.Subscribe(observer);
|
||||
public IDisposable Subscribe(IRuntimeEventObserver observer)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(observer);
|
||||
lock (_subscriptionGate)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
IDisposable runtimeSubscription = _runtime.Subscribe(observer);
|
||||
var subscription = new AdapterSubscription(
|
||||
this,
|
||||
runtimeSubscription);
|
||||
_subscriptions.Add(subscription);
|
||||
return subscription;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
_disposed = true;
|
||||
_view.Deactivate();
|
||||
_events.Dispose();
|
||||
lock (_subscriptionGate)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
_disposed = true;
|
||||
while (_subscriptions.Count != 0)
|
||||
_subscriptions.First().DisposeCore();
|
||||
}
|
||||
_hostLease.Dispose();
|
||||
}
|
||||
|
||||
private void Remove(AdapterSubscription subscription)
|
||||
{
|
||||
lock (_subscriptionGate)
|
||||
_subscriptions.Remove(subscription);
|
||||
}
|
||||
|
||||
private sealed class AdapterSubscription(
|
||||
CurrentGameRuntimeAdapter owner,
|
||||
IDisposable runtimeSubscription) : IDisposable
|
||||
{
|
||||
private CurrentGameRuntimeAdapter? _owner = owner;
|
||||
private IDisposable? _runtimeSubscription = runtimeSubscription;
|
||||
|
||||
public void Dispose() => DisposeCore();
|
||||
|
||||
public void DisposeCore()
|
||||
{
|
||||
IDisposable? runtime = Interlocked.Exchange(
|
||||
ref _runtimeSubscription,
|
||||
null);
|
||||
if (runtime is null)
|
||||
return;
|
||||
runtime.Dispose();
|
||||
Interlocked.Exchange(ref _owner, null)?.Remove(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,25 +30,25 @@ internal sealed class CurrentGameRuntimeCommandAdapter
|
|||
private readonly LiveSessionController _session;
|
||||
private readonly LiveSessionHost _sessionHost;
|
||||
private readonly ICommandBus _commands;
|
||||
private readonly CurrentGameRuntimeViewAdapter _view;
|
||||
private readonly IGameRuntimeView _view;
|
||||
private readonly RuntimeInventoryState _inventory;
|
||||
private readonly RuntimeCharacterState _character;
|
||||
private readonly RuntimeActionState _actions;
|
||||
private readonly RuntimeLocalPlayerMovementState _movement;
|
||||
private readonly SelectionInteractionController _selection;
|
||||
private readonly ICurrentGameRuntimeEventSink _events;
|
||||
private readonly IGameRuntimeEventSink _events;
|
||||
|
||||
public CurrentGameRuntimeCommandAdapter(
|
||||
LiveSessionController session,
|
||||
LiveSessionHost sessionHost,
|
||||
ICommandBus commands,
|
||||
CurrentGameRuntimeViewAdapter view,
|
||||
IGameRuntimeView view,
|
||||
RuntimeInventoryState inventory,
|
||||
RuntimeCharacterState character,
|
||||
RuntimeActionState actions,
|
||||
RuntimeLocalPlayerMovementState movement,
|
||||
SelectionInteractionController selection,
|
||||
ICurrentGameRuntimeEventSink events)
|
||||
IGameRuntimeEventSink events)
|
||||
{
|
||||
_session = session ?? throw new ArgumentNullException(nameof(session));
|
||||
_sessionHost = sessionHost ?? throw new ArgumentNullException(nameof(sessionHost));
|
||||
|
|
@ -80,7 +80,7 @@ internal sealed class CurrentGameRuntimeCommandAdapter
|
|||
ToCommandStatus(result.Status),
|
||||
result.CharacterId,
|
||||
result.CharacterName);
|
||||
_events.EmitLifecycle(previous);
|
||||
_events.EmitLifecycle(previous, _view.Lifecycle.State);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -100,7 +100,7 @@ internal sealed class CurrentGameRuntimeCommandAdapter
|
|||
ToCommandStatus(result.Status),
|
||||
result.CharacterId,
|
||||
result.CharacterName);
|
||||
_events.EmitLifecycle(previous);
|
||||
_events.EmitLifecycle(previous, _view.Lifecycle.State);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -125,7 +125,7 @@ internal sealed class CurrentGameRuntimeCommandAdapter
|
|||
operation: 2,
|
||||
acknowledgement.Status,
|
||||
text: acknowledgement.Error?.GetType().Name ?? string.Empty);
|
||||
_events.EmitLifecycle(previous);
|
||||
_events.EmitLifecycle(previous, _view.Lifecycle.State);
|
||||
return acknowledgement;
|
||||
}
|
||||
|
||||
|
|
@ -674,7 +674,7 @@ internal sealed class CurrentGameRuntimeCommandAdapter
|
|||
RuntimeGenerationToken expectedGeneration,
|
||||
bool requireWorld)
|
||||
{
|
||||
if (!_view.IsActive)
|
||||
if (_view.Lifecycle.State == RuntimeLifecycleState.Disposed)
|
||||
return RuntimeCommandStatus.Inactive;
|
||||
if (expectedGeneration != _view.Generation)
|
||||
return RuntimeCommandStatus.StaleGeneration;
|
||||
|
|
|
|||
|
|
@ -1,275 +0,0 @@
|
|||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.Entities;
|
||||
using AcDream.Runtime.Gameplay;
|
||||
|
||||
namespace AcDream.App.Runtime;
|
||||
|
||||
internal interface ICurrentGameRuntimeEventSink
|
||||
{
|
||||
void EmitCommand(
|
||||
RuntimeCommandDomain domain,
|
||||
int operation,
|
||||
RuntimeCommandStatus status,
|
||||
uint primaryObjectId = 0u,
|
||||
string? text = null);
|
||||
|
||||
void EmitLifecycle(RuntimeLifecycleState previous);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lazily projects existing owner notifications into one ordered diagnostic
|
||||
/// stream. With no subscribers it attaches no callbacks and adds no per-event
|
||||
/// production work.
|
||||
/// </summary>
|
||||
internal sealed class CurrentGameRuntimeEventAdapter
|
||||
: IRuntimeEventSource,
|
||||
ICurrentGameRuntimeEventSink,
|
||||
IRuntimeEntityObjectObserver,
|
||||
IRuntimeCommunicationObserver,
|
||||
IDisposable
|
||||
{
|
||||
private readonly CurrentGameRuntimeViewAdapter _view;
|
||||
private readonly RuntimeEntityObjectLifetime _entityObjects;
|
||||
private readonly RuntimeCommunicationState _communication;
|
||||
private readonly object _observerGate = new();
|
||||
private IRuntimeEventObserver[] _observers = [];
|
||||
private IDisposable? _entityObjectSubscription;
|
||||
private IDisposable? _communicationSubscription;
|
||||
private bool _ownerEventsAttached;
|
||||
private bool _disposed;
|
||||
|
||||
internal long DispatchFailureCount { get; private set; }
|
||||
internal Exception? LastDispatchFailure { get; private set; }
|
||||
|
||||
public CurrentGameRuntimeEventAdapter(
|
||||
CurrentGameRuntimeViewAdapter view,
|
||||
RuntimeEntityObjectLifetime entityObjects,
|
||||
RuntimeCommunicationState communication)
|
||||
{
|
||||
_view = view ?? throw new ArgumentNullException(nameof(view));
|
||||
_entityObjects = entityObjects
|
||||
?? throw new ArgumentNullException(nameof(entityObjects));
|
||||
_communication = communication
|
||||
?? throw new ArgumentNullException(nameof(communication));
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IRuntimeEventObserver observer)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(observer);
|
||||
lock (_observerGate)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
IRuntimeEventObserver[] current = _observers;
|
||||
if (Array.IndexOf(current, observer) >= 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The runtime observer is already subscribed.");
|
||||
}
|
||||
|
||||
var replacement = new IRuntimeEventObserver[current.Length + 1];
|
||||
Array.Copy(current, replacement, current.Length);
|
||||
replacement[^1] = observer;
|
||||
Volatile.Write(ref _observers, replacement);
|
||||
if (!_ownerEventsAttached)
|
||||
AttachOwnerEvents();
|
||||
}
|
||||
|
||||
return new ObserverSubscription(this, observer);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_observerGate)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
_disposed = true;
|
||||
Volatile.Write(ref _observers, []);
|
||||
DetachOwnerEvents();
|
||||
}
|
||||
}
|
||||
|
||||
public void EmitCommand(
|
||||
RuntimeCommandDomain domain,
|
||||
int operation,
|
||||
RuntimeCommandStatus status,
|
||||
uint primaryObjectId = 0u,
|
||||
string? text = null)
|
||||
{
|
||||
if (!TryObservers(out IRuntimeEventObserver[] observers))
|
||||
return;
|
||||
var delta = new RuntimeCommandDelta(
|
||||
NextStamp(),
|
||||
domain,
|
||||
operation,
|
||||
status,
|
||||
primaryObjectId,
|
||||
text);
|
||||
foreach (IRuntimeEventObserver observer in observers)
|
||||
{
|
||||
try
|
||||
{
|
||||
observer.OnCommand(in delta);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
RecordDispatchFailure(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void EmitLifecycle(RuntimeLifecycleState previous)
|
||||
{
|
||||
RuntimeLifecycleState current = _view.Lifecycle.State;
|
||||
if (current == previous
|
||||
|| !TryObservers(out IRuntimeEventObserver[] observers))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var delta = new RuntimeLifecycleDelta(
|
||||
NextStamp(),
|
||||
previous,
|
||||
current);
|
||||
foreach (IRuntimeEventObserver observer in observers)
|
||||
{
|
||||
try
|
||||
{
|
||||
observer.OnLifecycle(in delta);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
RecordDispatchFailure(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Unsubscribe(IRuntimeEventObserver observer)
|
||||
{
|
||||
lock (_observerGate)
|
||||
{
|
||||
IRuntimeEventObserver[] current = _observers;
|
||||
int index = Array.IndexOf(current, observer);
|
||||
if (index < 0)
|
||||
return;
|
||||
if (current.Length == 1)
|
||||
{
|
||||
Volatile.Write(ref _observers, []);
|
||||
DetachOwnerEvents();
|
||||
return;
|
||||
}
|
||||
|
||||
var replacement = new IRuntimeEventObserver[current.Length - 1];
|
||||
if (index != 0)
|
||||
Array.Copy(current, 0, replacement, 0, index);
|
||||
if (index != current.Length - 1)
|
||||
{
|
||||
Array.Copy(
|
||||
current,
|
||||
index + 1,
|
||||
replacement,
|
||||
index,
|
||||
current.Length - index - 1);
|
||||
}
|
||||
Volatile.Write(ref _observers, replacement);
|
||||
}
|
||||
}
|
||||
|
||||
private void AttachOwnerEvents()
|
||||
{
|
||||
_entityObjectSubscription = _entityObjects.Events.Subscribe(this);
|
||||
_communicationSubscription = _communication.Events.Subscribe(this);
|
||||
_ownerEventsAttached = true;
|
||||
}
|
||||
|
||||
private void DetachOwnerEvents()
|
||||
{
|
||||
if (!_ownerEventsAttached)
|
||||
return;
|
||||
_communicationSubscription?.Dispose();
|
||||
_communicationSubscription = null;
|
||||
_entityObjectSubscription?.Dispose();
|
||||
_entityObjectSubscription = null;
|
||||
_ownerEventsAttached = false;
|
||||
}
|
||||
|
||||
public void OnEntity(in RuntimeEntityDelta delta)
|
||||
{
|
||||
if (!TryObservers(out IRuntimeEventObserver[] observers))
|
||||
return;
|
||||
foreach (IRuntimeEventObserver observer in observers)
|
||||
{
|
||||
try
|
||||
{
|
||||
observer.OnEntity(in delta);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
RecordDispatchFailure(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnInventory(in RuntimeInventoryDelta delta)
|
||||
{
|
||||
if (!TryObservers(out IRuntimeEventObserver[] observers))
|
||||
return;
|
||||
foreach (IRuntimeEventObserver observer in observers)
|
||||
{
|
||||
try
|
||||
{
|
||||
observer.OnInventory(in delta);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
RecordDispatchFailure(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnChat(in RuntimeCommunicationEvent committed)
|
||||
{
|
||||
if (!TryObservers(out IRuntimeEventObserver[] observers))
|
||||
return;
|
||||
var delta = new RuntimeChatDelta(
|
||||
NextStamp(),
|
||||
committed.Entry);
|
||||
foreach (IRuntimeEventObserver observer in observers)
|
||||
{
|
||||
try
|
||||
{
|
||||
observer.OnChat(in delta);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
RecordDispatchFailure(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RuntimeEventStamp NextStamp() =>
|
||||
_entityObjects.Events.NextStamp();
|
||||
|
||||
private bool TryObservers(out IRuntimeEventObserver[] observers)
|
||||
{
|
||||
observers = Volatile.Read(ref _observers);
|
||||
return observers.Length != 0;
|
||||
}
|
||||
|
||||
private void RecordDispatchFailure(Exception error)
|
||||
{
|
||||
DispatchFailureCount++;
|
||||
LastDispatchFailure = error;
|
||||
}
|
||||
|
||||
private sealed class ObserverSubscription(
|
||||
CurrentGameRuntimeEventAdapter owner,
|
||||
IRuntimeEventObserver observer)
|
||||
: IDisposable
|
||||
{
|
||||
private CurrentGameRuntimeEventAdapter? _owner = owner;
|
||||
|
||||
public void Dispose() =>
|
||||
Interlocked.Exchange(ref _owner, null)?.Unsubscribe(observer);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,134 +0,0 @@
|
|||
using AcDream.App.Input;
|
||||
using AcDream.App.Net;
|
||||
using AcDream.Core.Chat;
|
||||
using AcDream.Core.Items;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.Entities;
|
||||
using AcDream.Runtime.Gameplay;
|
||||
using AcDream.Runtime.Session;
|
||||
using AcDream.Runtime.World;
|
||||
|
||||
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 IGameRuntimeClock _clock;
|
||||
private readonly IRuntimeEntityView _entityView;
|
||||
private readonly IRuntimeInventoryView _inventoryView;
|
||||
private readonly IRuntimeInventoryStateView _inventoryStateView;
|
||||
private readonly IRuntimeCharacterView _characterView;
|
||||
private readonly IRuntimeSocialView _socialView;
|
||||
private readonly IRuntimeChatView _chatView;
|
||||
private readonly IRuntimeActionView _actionView;
|
||||
private readonly IRuntimeMovementView _movementView;
|
||||
private readonly IRuntimeWorldEnvironmentView _environmentView;
|
||||
private readonly IRuntimePortalView _portalView;
|
||||
private bool _active = true;
|
||||
|
||||
public CurrentGameRuntimeViewAdapter(
|
||||
LiveSessionController session,
|
||||
LocalPlayerIdentityState playerIdentity,
|
||||
RuntimeEntityObjectLifetime entityObjects,
|
||||
RuntimeInventoryState inventory,
|
||||
RuntimeCharacterState character,
|
||||
RuntimeCommunicationState communication,
|
||||
RuntimeActionState actions,
|
||||
RuntimeLocalPlayerMovementState movement,
|
||||
RuntimeWorldEnvironmentState environment,
|
||||
RuntimeWorldTransitState worldTransit,
|
||||
IGameRuntimeClock clock)
|
||||
{
|
||||
_session = session ?? throw new ArgumentNullException(nameof(session));
|
||||
_playerIdentity = playerIdentity
|
||||
?? throw new ArgumentNullException(nameof(playerIdentity));
|
||||
ArgumentNullException.ThrowIfNull(entityObjects);
|
||||
ArgumentNullException.ThrowIfNull(communication);
|
||||
_clock = clock ?? throw new ArgumentNullException(nameof(clock));
|
||||
|
||||
_entityView = entityObjects.EntityView;
|
||||
_inventoryView = entityObjects.InventoryView;
|
||||
_inventoryStateView = (
|
||||
inventory ?? throw new ArgumentNullException(nameof(inventory))).View;
|
||||
_characterView = (
|
||||
character ?? throw new ArgumentNullException(nameof(character))).View;
|
||||
_chatView = communication.View;
|
||||
_socialView = communication.SocialView;
|
||||
_actionView = (
|
||||
actions ?? throw new ArgumentNullException(nameof(actions))).View;
|
||||
_movementView = (
|
||||
movement ?? throw new ArgumentNullException(nameof(movement))).View;
|
||||
_environmentView = environment
|
||||
?? throw new ArgumentNullException(nameof(environment));
|
||||
_portalView = worldTransit
|
||||
?? throw new ArgumentNullException(nameof(worldTransit));
|
||||
}
|
||||
|
||||
internal bool IsActive => _active && !_session.IsDisposalComplete;
|
||||
|
||||
public RuntimeGenerationToken Generation => _session.Generation;
|
||||
|
||||
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 IRuntimeInventoryStateView InventoryState => _inventoryStateView;
|
||||
public IRuntimeCharacterView Character => _characterView;
|
||||
public IRuntimeSocialView Social => _socialView;
|
||||
public IRuntimeChatView Chat => _chatView;
|
||||
public IRuntimeActionView Actions => _actionView;
|
||||
public IRuntimeMovementView Movement => _movementView;
|
||||
public IRuntimeWorldEnvironmentView Environment => _environmentView;
|
||||
public IRuntimePortalView Portal => _portalView;
|
||||
|
||||
public RuntimeStateCheckpoint CaptureCheckpoint() =>
|
||||
new(
|
||||
Generation,
|
||||
Lifecycle.State,
|
||||
_clock.FrameNumber,
|
||||
_entityView.Count,
|
||||
_entityView.MaterializedCount,
|
||||
_inventoryView.ObjectCount,
|
||||
_inventoryView.ContainerCount,
|
||||
_inventoryStateView.Snapshot,
|
||||
_characterView.Snapshot,
|
||||
_socialView.Snapshot,
|
||||
_chatView.Revision,
|
||||
_chatView.Count,
|
||||
_actionView.Snapshot,
|
||||
_movementView.Snapshot,
|
||||
_environmentView.Snapshot,
|
||||
_environmentView.Ownership,
|
||||
_portalView.Snapshot,
|
||||
_portalView.Ownership);
|
||||
|
||||
internal void Deactivate() => _active = false;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue