using AcDream.App.Interaction;
using AcDream.App.Net;
using AcDream.Runtime;
using AcDream.Runtime.Session;
using AcDream.Runtime.World;
using AcDream.UI.Abstractions;
namespace AcDream.App.Runtime;
///
/// Synchronous graphical command adapter over one canonical
/// . It owns only its host lease and observer
/// subscriptions; every view and mutable owner is borrowed directly.
///
internal sealed class CurrentGameRuntimeAdapter
: IGameRuntimeView,
IGameRuntimeCommands,
IRuntimeEventSource,
IDisposable
{
private readonly GameRuntime _runtime;
private readonly CurrentGameRuntimeCommandAdapter _commands;
private readonly IDisposable _hostLease;
private readonly object _subscriptionGate = new();
private readonly HashSet _subscriptions = [];
private bool _disposed;
public CurrentGameRuntimeAdapter(
GameRuntime runtime,
LiveSessionHost sessionHost,
ICommandBus commands,
SelectionInteractionController selection)
{
_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;
}
}
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;
public IRuntimeCombatCommands Combat => _commands;
public IRuntimeMagicCommands Magic => _commands;
public IRuntimeMovementCommands MovementCommands => _commands;
IRuntimeMovementCommands IGameRuntimeCommands.Movement => _commands;
public IRuntimeChatCommands ChatCommands => _commands;
IRuntimeChatCommands IGameRuntimeCommands.Chat => _commands;
public IRuntimePortalCommands PortalCommands => _commands;
IRuntimePortalCommands IGameRuntimeCommands.Portal => _commands;
public IRuntimeInventoryStateCommands InventoryCommands => _commands;
IRuntimeInventoryStateCommands IGameRuntimeCommands.InventoryState =>
_commands;
public IRuntimeSpellbookCommands SpellbookCommands => _commands;
IRuntimeSpellbookCommands IGameRuntimeCommands.Spellbook => _commands;
public IRuntimeCharacterCommands CharacterCommands => _commands;
IRuntimeCharacterCommands IGameRuntimeCommands.Character => _commands;
public IRuntimeSocialCommands SocialCommands => _commands;
IRuntimeSocialCommands IGameRuntimeCommands.Social => _commands;
public RuntimeStateCheckpoint CaptureCheckpoint()
{
RuntimeStateCheckpoint checkpoint = _runtime.CaptureCheckpoint();
return checkpoint with { Lifecycle = Lifecycle.State };
}
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()
{
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);
}
}
}