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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue