Aetheria/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs

229 lines
8 KiB
C#
Raw Normal View History

2026-06-25 18:09:33 +01:00
using System;
using System.Collections.Generic;
2026-06-25 18:09:33 +01:00
using System.IO;
using System.Threading.Tasks;
using GameCult.Eve.Surface;
using GameCult.Mesh;
2026-06-25 18:09:33 +01:00
#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;
2026-06-27 13:51:08 +01:00
private readonly AetheriaClientState _state;
2026-06-29 02:56:43 +01:00
private CultMeshReactiveDocument<AetheriaRuntimeDaemonFrameDocument>? _daemonFrame;
2026-06-25 18:09:33 +01:00
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();
2026-06-25 18:09:33 +01:00
}
public string StatePath => _verse.StatePath;
public string RuntimeId => _verse.RuntimeId;
public AetheriaControl Control => _control;
public AetheriaUi Ui => _ui;
2026-06-27 13:51:08 +01:00
public AetheriaClientState State => _state;
2026-06-25 18:09:33 +01:00
internal AetheriaRuntimeDaemonOperationsClient Operations => _operations;
public static async Task<AetheriaClient> 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<AetheriaClient> 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<int> RefreshRemoteAsync()
{
ThrowIfDisposed();
return _verse.RefreshRemoteAsync();
}
public IReadOnlyList<AetheriaRuntimeCommittedCommandFactDocument> CommittedCommandFacts()
{
ThrowIfDisposed();
return _verse.CommittedCommandFacts();
}
2026-06-25 18:09:33 +01:00
public static Task<AetheriaClient> 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(
2026-07-06 01:29:53 +01:00
$"Aetheria client requires a readable managed Verse target: {stateBoot.FailureMessage}");
2026-06-25 18:09:33 +01:00
}
return OpenAsync(
stateBoot.StateFilePath,
runtimeId,
sessionId,
startServer: false,
pullOnOpen: pullOnOpen);
}
internal Task<AetheriaRuntimeEveCommandEnvelope> 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<AetheriaRuntimeEveCommandEnvelope> SubmitLoadoutTemplateCommandAsync(
AetheriaRuntimeLoadoutTemplateCommit loadoutTemplate,
string? clientId = null,
bool flush = true)
{
ThrowIfDisposed();
return _verse.SubmitLoadoutTemplateCommandAsync(
loadoutTemplate,
string.IsNullOrWhiteSpace(clientId) ? _clientId : clientId!,
flush);
}
internal Task<AetheriaRuntimeEveCommandEnvelope> 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;
2026-06-29 02:56:43 +01:00
_daemonFrame?.Dispose();
2026-06-25 18:09:33 +01:00
_verse.Dispose();
}
private AetheriaRuntimeDaemonCommandEnvelope SendOperation(
2026-06-29 02:56:43 +01:00
Func<AetheriaRuntimeDaemonOperationClient, AetheriaRuntimeDaemonFrameDocument?, AetheriaRuntimeDaemonCommandEnvelope> submit)
2026-06-25 18:09:33 +01:00
{
ThrowIfDisposed();
if (submit == null) throw new ArgumentNullException(nameof(submit));
2026-06-29 02:56:43 +01:00
var frame = CurrentDaemonFrame();
2026-06-25 18:09:33 +01:00
var operationClient = new AetheriaRuntimeDaemonOperationClient(
StatePath,
_clientId,
2026-06-29 02:56:43 +01:00
frame?.SessionId ?? _sessionId,
2026-06-25 18:09:33 +01:00
command => _verse
.SubmitDaemonCommandAsync(command)
.GetAwaiter()
.GetResult());
2026-06-29 02:56:43 +01:00
return submit(operationClient, frame);
2026-06-25 18:09:33 +01:00
}
2026-06-29 02:56:43 +01:00
internal AetheriaRuntimeDaemonFrameDocument? CurrentDaemonFrame()
{
ThrowIfDisposed();
2026-06-29 02:56:43 +01:00
try
{
2026-06-30 19:14:49 +01:00
_daemonFrame ??= State.DaemonFrame.Reactive();
2026-06-29 02:56:43 +01:00
return _daemonFrame.Current;
}
catch
{
return null;
}
}
2026-06-25 18:09:33 +01:00
private void ThrowIfDisposed()
{
if (_disposed)
throw new ObjectDisposedException(nameof(AetheriaClient));
}
}
}