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); } } }