Aetheria/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs
2026-07-01 15:26:48 +01:00

290 lines
9.9 KiB
C#

using System;
using System.Collections.Generic;
using GameCult.Aetheria.State.Verse;
using GameCult.Eve.Surface;
using GameCult.Mesh;
using UnityEngine;
using UnityEngine.UIElements;
using EveSurfaceDocument = GameCult.Eve.Surface.EveSurfaceDocument;
#nullable enable
namespace GameCult.Aetheria.EveRuntime
{
[DisallowMultipleComponent]
[RequireComponent(typeof(UIDocument))]
public sealed class AetheriaEveSurfacePresenter : MonoBehaviour
{
[SerializeField]
private string surfaceId = AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId;
[SerializeField]
private string stateFilePathOverride = "";
[SerializeField]
private bool mountOnEnable = true;
[SerializeField]
private bool refreshInUpdate = true;
[SerializeField]
private float refreshIntervalSeconds = 0.1f;
private UIDocument? _document;
private float _nextRefreshTime;
private string _mountedStatePath = "";
private string _mountedSurfaceId = "";
private long _mountedSurfaceVersion = -1;
private string _mountedSurfaceUpdatedAtUtc = "";
private CultMeshReactiveDocument<EveSurfaceDocument>? _reactiveSurfaceState;
private string _reactiveSurfaceStatePath = "";
private string _reactiveSurfaceId = "";
private static readonly AetheriaEveUnitySurfaceChrome RootOnlyChrome = new AetheriaEveUnitySurfaceChrome
{
UseShell = false,
RootPaddingTop = 0f,
RootAlignItems = Align.Stretch,
RootJustifyContent = Justify.FlexStart,
RootPickingMode = PickingMode.Position
};
public string SurfaceId
{
get => surfaceId;
set => surfaceId = value ?? "";
}
public string StateFilePathOverride
{
get => stateFilePathOverride;
set => stateFilePathOverride = value ?? "";
}
public bool RefreshInUpdate
{
get => refreshInUpdate;
set => refreshInUpdate = value;
}
public void Mount()
{
var document = ResolveDocument();
var root = document.rootVisualElement;
root.Clear();
var stateBoot = ResolveStateBoot();
if (!stateBoot.SupportsLocalStateFileRead)
{
root.Add(BuildError(stateBoot.FailureMessage));
return;
}
var statePath = stateBoot.StateFilePath;
if (!stateBoot.StateFileExists)
{
root.Add(BuildError($"Aetheria state file not found: {statePath}"));
return;
}
var surface = ReadDaemonSurface(stateBoot);
if (surface == null)
{
ClearMountedSurface();
return;
}
MountSurface(stateBoot, surface);
}
private void OnEnable()
{
if (mountOnEnable)
Mount();
}
private void OnDisable()
{
DisposeReactiveSurfaceState();
}
private void Update()
{
if (!refreshInUpdate || Time.unscaledTime < _nextRefreshTime)
return;
_nextRefreshTime = Time.unscaledTime + Mathf.Max(0.01f, refreshIntervalSeconds);
RefreshIfChanged();
}
private void RefreshIfChanged()
{
var stateBoot = ResolveStateBoot();
if (!stateBoot.SupportsLocalStateFileRead || !stateBoot.StateFileExists)
return;
var surface = ReadDaemonSurface(stateBoot);
if (surface == null || !ShouldMountSurface(stateBoot.StateFilePath, surface))
return;
MountSurface(stateBoot, surface);
}
private EveSurfaceDocument? ReadDaemonSurface(AetheriaRuntimeStateBootReport stateBoot)
{
var surface = ReadDaemonSurfaceState(stateBoot);
return surface == null
? null
: AetheriaRuntimeSurfaceDocuments.ToEveSurfaceDocument(
surface,
AetheriaEveRuntimeUnityHooks.TryCreateStateRefResolver(
stateBoot,
"unity-eve-surface-presenter"));
}
private EveSurfaceDocument? ReadDaemonSurfaceState(
AetheriaRuntimeStateBootReport stateBoot)
{
var reactive = ResolveReactiveDaemonSurfaceState(stateBoot);
return reactive?.Current;
}
private CultMeshReactiveDocument<EveSurfaceDocument>? ResolveReactiveDaemonSurfaceState(
AetheriaRuntimeStateBootReport stateBoot)
{
if (_reactiveSurfaceState != null &&
string.Equals(_reactiveSurfaceStatePath, stateBoot.StateFilePath, StringComparison.Ordinal) &&
string.Equals(_reactiveSurfaceId, surfaceId, StringComparison.Ordinal))
{
return _reactiveSurfaceState;
}
DisposeReactiveSurfaceState();
_reactiveSurfaceState = CreateReactiveDaemonSurfaceState(stateBoot);
if (_reactiveSurfaceState != null)
{
_reactiveSurfaceStatePath = stateBoot.StateFilePath;
_reactiveSurfaceId = surfaceId ?? "";
}
return _reactiveSurfaceState;
}
private CultMeshReactiveDocument<EveSurfaceDocument>? CreateReactiveDaemonSurfaceState(
AetheriaRuntimeStateBootReport stateBoot)
{
try
{
return AetheriaEveRuntimeUnityHooks
.RequireRuntimeState(stateBoot, "unity-eve-surface-presenter")
.EveSurfaceDocument(surfaceId)
?.Reactive();
}
catch (KeyNotFoundException)
{
return null;
}
}
private UIDocument ResolveDocument()
{
if (_document == null)
_document = GetComponent<UIDocument>();
return _document;
}
private AetheriaRuntimeStateBootReport ResolveStateBoot()
{
return AetheriaEveRuntimeUnityHooks.RequireStateBoot(stateFilePathOverride);
}
private static VisualElement BuildError(string message)
{
var container = new VisualElement();
container.AddToClassList("aetheria-eve-runtime-error");
var label = new Label(message);
label.AddToClassList("aetheria-eve-runtime-error-label");
container.Add(label);
return container;
}
private bool ShouldMountSurface(string statePath, EveSurfaceDocument surface)
{
return !string.Equals(_mountedStatePath, statePath, StringComparison.Ordinal) ||
!string.Equals(_mountedSurfaceId, surface.Surface.Id, StringComparison.Ordinal) ||
_mountedSurfaceVersion != surface.Version ||
!string.Equals(_mountedSurfaceUpdatedAtUtc, surface.UpdatedAtUtc, StringComparison.Ordinal);
}
private void ClearMountedSurface()
{
var document = ResolveDocument();
document.rootVisualElement.Clear();
_mountedStatePath = "";
_mountedSurfaceId = "";
_mountedSurfaceVersion = -1;
_mountedSurfaceUpdatedAtUtc = "";
}
private void MountSurface(AetheriaRuntimeStateBootReport stateBoot, EveSurfaceDocument surface)
{
var statePath = stateBoot.StateFilePath;
AetheriaEveUnitySurfaceHost.Render(
transform,
ResolveDocument(),
"Aetheria Eve Surface",
surface,
request => EmitCommand(stateBoot, request),
RootOnlyChrome,
AetheriaEveRuntimeUnityHooks.TryCreateStateRefResolver(
stateBoot,
"unity-eve-surface-presenter"));
_mountedStatePath = statePath;
_mountedSurfaceId = surface.Surface.Id;
_mountedSurfaceVersion = surface.Version;
_mountedSurfaceUpdatedAtUtc = surface.UpdatedAtUtc ?? "";
}
private void EmitCommand(AetheriaRuntimeStateBootReport stateBoot, EveSurfaceCommandRequest request)
{
var clientId = string.IsNullOrWhiteSpace(request.ClientId)
? "unity-eve-surface-presenter"
: request.ClientId;
var control = AetheriaEveRuntimeUnityHooks.RequireControl(
stateBoot,
clientId);
if (control.TrySubmitSurfaceCommand(request, out var daemonEnvelope))
{
Debug.Log(
$"Submitted Aetheria daemon operation from Eve surface: {daemonEnvelope!.Kind} {daemonEnvelope.CommandId}");
return;
}
try
{
var envelope = AetheriaEveRuntimeUnityHooks
.RequireUi(stateBoot, "unity-eve-surface-presenter")
.SurfaceCommandAsync(
request,
string.IsNullOrWhiteSpace(request.ClientId) ? "unity-uitoolkit" : request.ClientId)
.GetAwaiter()
.GetResult();
Debug.Log(
$"Submitted Eve operation for CultMesh bridge: {envelope.OperationId}");
}
catch (Exception ex)
{
Debug.LogWarning(
$"Ignored or failed Aetheria Eve command: {request.ProviderId}/{request.SurfaceId}/{request.Operation?.OperationId}: {ex}");
}
}
private void DisposeReactiveSurfaceState()
{
_reactiveSurfaceState?.Dispose();
_reactiveSurfaceState = null;
_reactiveSurfaceStatePath = "";
_reactiveSurfaceId = "";
}
}
}