using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using GameCult.Eve.Surface; using GameCult.Mesh; #nullable enable namespace GameCult.Aetheria.State.Verse { public sealed class AetheriaClient : IDisposable { private readonly AetheriaRuntimeVerseClient _verse; private readonly string _clientId; private readonly string _sessionId; private readonly AetheriaRuntimeDaemonOperationsClient _operations; private readonly AetheriaControl _control; private readonly AetheriaUi _ui; private readonly AetheriaClientState _state; private CultMeshReactiveDocument? _daemonFrame; private bool _disposed; private AetheriaClient(AetheriaRuntimeVerseClient verse, string clientId, string sessionId) { _verse = verse ?? throw new ArgumentNullException(nameof(verse)); _clientId = string.IsNullOrWhiteSpace(clientId) ? AetheriaRuntimeVerseClient.DefaultRuntimeId : clientId; _sessionId = string.IsNullOrWhiteSpace(sessionId) ? "local" : sessionId; _operations = new AetheriaRuntimeDaemonOperationsClient(SendOperation); _control = new AetheriaControl(_operations); _ui = new AetheriaUi(this); _state = _verse.Aetheria(); } public string StatePath => _verse.StatePath; public string RuntimeId => _verse.RuntimeId; public AetheriaControl Control => _control; public AetheriaUi Ui => _ui; public AetheriaClientState State => _state; internal AetheriaRuntimeDaemonOperationsClient Operations => _operations; public static async Task OpenAsync( string statePath, string runtimeId = AetheriaRuntimeVerseClient.DefaultRuntimeId, string sessionId = "local", bool startServer = false, bool pullOnOpen = true) { var verse = await AetheriaRuntimeVerseClient .OpenAsync(statePath, runtimeId, startServer, pullOnOpen) .ConfigureAwait(false); return new AetheriaClient(verse, runtimeId, sessionId); } public static async Task OpenRemoteAsync( string replicaStatePath, string endpoint, string runtimeId = AetheriaRuntimeVerseClient.DefaultRuntimeId, string sessionId = "remote", bool synchronizeOnOpen = true) { var verse = await AetheriaRuntimeVerseClient .OpenRemoteAsync( replicaStatePath, endpoint, runtimeId, pullOnOpen: false, synchronizeOnOpen: synchronizeOnOpen) .ConfigureAwait(false); return new AetheriaClient(verse, runtimeId, sessionId); } public Task RefreshRemoteAsync() { ThrowIfDisposed(); return _verse.RefreshRemoteAsync(); } public IReadOnlyList CommittedCommandFacts() { ThrowIfDisposed(); return _verse.CommittedCommandFacts(); } public static Task OpenLocalAsync( DirectoryInfo gameDataDirectory, string runtimeId, string sessionId = "local", bool pullOnOpen = true) { if (gameDataDirectory == null) throw new ArgumentNullException(nameof(gameDataDirectory)); var stateBoot = AetheriaRuntimeStateBoot.Inspect(gameDataDirectory); if (!stateBoot.SupportsLocalStateFileRead || !stateBoot.StateFileExists) { throw new InvalidOperationException( $"Aetheria client requires a readable managed Verse target: {stateBoot.FailureMessage}"); } return OpenAsync( stateBoot.StateFilePath, runtimeId, sessionId, startServer: false, pullOnOpen: pullOnOpen); } internal Task SubmitInputSettingsCommandAsync( AetheriaRuntimeEveCommandKind command, AetheriaRuntimeInputSettingsCommandBody body, string? clientId = null, bool flush = true) { ThrowIfDisposed(); return _verse.SubmitInputSettingsCommandAsync( command, body, string.IsNullOrWhiteSpace(clientId) ? _clientId : clientId!, flush); } internal Task SubmitLoadoutTemplateCommandAsync( AetheriaRuntimeLoadoutTemplateCommit loadoutTemplate, string? clientId = null, bool flush = true) { ThrowIfDisposed(); return _verse.SubmitLoadoutTemplateCommandAsync( loadoutTemplate, string.IsNullOrWhiteSpace(clientId) ? _clientId : clientId!, flush); } internal Task SubmitKnownSurfaceCommandAsync( EveSurfaceCommandRequest request, string? clientId = null, bool flush = true) { ThrowIfDisposed(); return _verse.SubmitKnownSurfaceCommandAsync( request, string.IsNullOrWhiteSpace(clientId) ? _clientId : clientId!, flush); } public AetheriaRuntimeDaemonCommandEnvelope SetMoveVector( double directionX, double directionY, double scalarValue = 1.0) { return Control.SetMoveVector(directionX, directionY, scalarValue); } public AetheriaRuntimeDaemonCommandEnvelope SetTarget(string targetEntityKey) { return Control.SetTarget(targetEntityKey); } internal AetheriaRuntimeDaemonCommandEnvelope SubmitDaemonCommandDocument( AetheriaRuntimeDaemonCommandDocument command) { ThrowIfDisposed(); return _verse .SubmitDaemonCommandAsync(command) .GetAwaiter() .GetResult(); } internal AetheriaRuntimeEveCommandEnvelope SubmitEveCommandDocument( AetheriaRuntimeEveCommandDocument command) { ThrowIfDisposed(); return _verse .SubmitEveCommandAsync(command) .GetAwaiter() .GetResult(); } public void Dispose() { if (_disposed) return; _disposed = true; _daemonFrame?.Dispose(); _verse.Dispose(); } private AetheriaRuntimeDaemonCommandEnvelope SendOperation( Func submit) { ThrowIfDisposed(); if (submit == null) throw new ArgumentNullException(nameof(submit)); var frame = CurrentDaemonFrame(); var operationClient = new AetheriaRuntimeDaemonOperationClient( StatePath, _clientId, frame?.SessionId ?? _sessionId, command => _verse .SubmitDaemonCommandAsync(command) .GetAwaiter() .GetResult()); return submit(operationClient, frame); } internal AetheriaRuntimeDaemonFrameDocument? CurrentDaemonFrame() { ThrowIfDisposed(); try { _daemonFrame ??= State.DaemonFrame.Reactive(); return _daemonFrame.Current; } catch { return null; } } private void ThrowIfDisposed() { if (_disposed) throw new ObjectDisposedException(nameof(AetheriaClient)); } } }