diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index 6ace3f084..9d4fb2c08 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -6347,6 +6347,8 @@ static void RequireClientTargetBootAuthority(string root) { "CurrentStateBoot()", "LatestDaemonFrame(AetheriaRuntimeStateBootReport stateBoot)", + "AetheriaRuntimeVerseClient", + "GetLatestAuthoritativeRunFrameAsync()", "LatestVerseHostSettings(AetheriaRuntimeStateBootReport stateBoot)", "AetheriaState.At(ActionGameManager.GameDataDirectory)", ".ClientTarget", @@ -8117,6 +8119,8 @@ static void RequireAetheriaRuntimeVerseClientContract(string root) "WatchDaemonGameTuiSurfaces()", "WatchDaemonEditorTuiSurfaces()", "AetheriaRuntimeVerseDocument", + "GetLatestAuthoritativeRunFrameAsync()", + "GetObservedDaemonStateAsync()", "SubmitDaemonCommandAsync(", "AetheriaRuntimeVerseRecordKeys.DaemonCommand(command.CommandId)", "DaemonGameTuiSurface { get; }", @@ -8708,9 +8712,8 @@ static void RequireMainMenuContinueRunState(string root) var requiredMenuSymbols = new[] { "LatestDaemonFrame", - "AetheriaRuntimeStateReader", - "TryReadDaemonFrame(stateBoot.StateFilePath, out var frame)", - "frame.IsAuthoritative", + "AetheriaRuntimeVerseClient", + "GetLatestAuthoritativeRunFrameAsync()", "ContinueGame()", "ActionGameManager.ObservedGalaxy = Galaxy.ProjectObservedDaemonRun(", "SceneManager.LoadScene(\"ARPG\")" @@ -9691,8 +9694,8 @@ static void RequireUnityObserverDoesNotTickLocalSimulation(string root) var requiredMenuSymbols = new[] { "TryStartDaemonObservedGame", - "TryReadDaemonFrame(stateBoot.StateFilePath, out var frame)", - "frame.IsAuthoritative", + "LatestDaemonFrame(stateBoot)", + "GetLatestAuthoritativeRunFrameAsync()", "ActionGameManager.ObservedGalaxy = Galaxy.ProjectObservedDaemonRun(", "frame.Run", "SceneManager.LoadScene(\"ARPG\")" @@ -10335,11 +10338,17 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) "AetheriaDaemonObserver still spelunks observed daemon state through the runtime file reader instead of the Verse client."); } - if (!mainMenu.Contains("AetheriaRuntimeStateReader", StringComparison.Ordinal) || - !mainMenu.Contains("TryReadDaemonFrame(stateBoot.StateFilePath, out var frame)", StringComparison.Ordinal)) + if (!mainMenu.Contains("AetheriaRuntimeVerseClient", StringComparison.Ordinal) || + !mainMenu.Contains("GetLatestAuthoritativeRunFrameAsync()", StringComparison.Ordinal)) { throw new InvalidOperationException( - "MainMenu no longer routes daemon-frame lookup through the shared runtime state reader."); + "MainMenu no longer routes daemon-frame lookup through the shared runtime Verse client."); + } + + if (mainMenu.Contains("AetheriaRuntimeStateReader.TryReadDaemonFrame", StringComparison.Ordinal)) + { + throw new InvalidOperationException( + "MainMenu still reads daemon frames through the runtime file reader instead of the Verse client."); } if (!eveSurfacePresenter.Contains("AetheriaRuntimeVerseClient.OpenAsync(", StringComparison.Ordinal) || diff --git a/Assets/Scripts/UI/MainMenu.cs b/Assets/Scripts/UI/MainMenu.cs index e9d90bf1a..b0b7a4ef7 100644 --- a/Assets/Scripts/UI/MainMenu.cs +++ b/Assets/Scripts/UI/MainMenu.cs @@ -17,6 +17,8 @@ public class MainMenu : MonoBehaviour public bool InGame; private UIDocument _menuSurfaceDocument; + private AetheriaRuntimeVerseClient _verseClient; + private string _verseClientStatePath; private readonly AetheriaEveUnitySurfaceChrome _menuSurfaceChrome = new AetheriaEveUnitySurfaceChrome { RootAlignItems = Align.Center, @@ -108,14 +110,10 @@ public class MainMenu : MonoBehaviour private bool TryStartDaemonObservedGame(string title) { var stateBoot = CurrentStateBoot(); - if (!AetheriaRuntimeStateReader.TryReadDaemonFrame(stateBoot.StateFilePath, out var frame) || - frame == null || - !frame.IsAuthoritative || - frame.Run == null || - frame.Run.Zones == null || - frame.Run.Zones.Count == 0) + var frame = LatestDaemonFrame(stateBoot); + if (frame == null) { - Debug.LogWarning($"Cannot start Aetheria observer scene without an authoritative daemon frame at {AetheriaRuntimeDaemonFrameStore.GetFramePath(stateBoot.StateFilePath)}."); + Debug.LogWarning($"Cannot start Aetheria observer scene without an authoritative daemon frame in {stateBoot.StateFilePath}."); return false; } @@ -146,19 +144,25 @@ public class MainMenu : MonoBehaviour return AetheriaRuntimeStateBoot.Inspect(ActionGameManager.GameDataDirectory); } - private static AetheriaRuntimeDaemonFrameDocument LatestDaemonFrame(AetheriaRuntimeStateBootReport stateBoot) + private AetheriaRuntimeDaemonFrameDocument LatestDaemonFrame(AetheriaRuntimeStateBootReport stateBoot) { - if (!AetheriaRuntimeStateReader.TryReadDaemonFrame(stateBoot.StateFilePath, out var frame) || - frame == null || - !frame.IsAuthoritative || - frame.Run == null || - frame.Run.Zones == null || - frame.Run.Zones.Count == 0) + if (!stateBoot.SupportsLocalStateFileRead || !stateBoot.StateFileExists) { return null; } - return frame; + try + { + return ResolveVerseClient(stateBoot) + .GetLatestAuthoritativeRunFrameAsync() + .GetAwaiter() + .GetResult(); + } + catch (Exception ex) + { + Debug.LogError($"Failed to read authoritative Aetheria daemon frame for the main menu: {ex}"); + return null; + } } private static AetheriaRuntimePlayerSettingsSnapshot LatestPlayerSettings(AetheriaRuntimeStateBootReport stateBoot) @@ -454,8 +458,33 @@ public class MainMenu : MonoBehaviour return true; } + private AetheriaRuntimeVerseClient ResolveVerseClient(AetheriaRuntimeStateBootReport stateBoot) + { + var statePath = stateBoot.StateFilePath; + if (_verseClient != null && string.Equals(_verseClientStatePath, statePath, StringComparison.Ordinal)) + { + return _verseClient; + } + + DisposeVerseClient(); + _verseClient = AetheriaRuntimeVerseClient + .OpenAsync(statePath, "unity-main-menu", startServer: false, pullOnOpen: true) + .GetAwaiter() + .GetResult(); + _verseClientStatePath = statePath; + return _verseClient; + } + + private void DisposeVerseClient() + { + _verseClient?.Dispose(); + _verseClient = null; + _verseClientStatePath = null; + } + private void OnDestroy() { + DisposeVerseClient(); if (_menuSurfaceDocument != null) { Destroy(_menuSurfaceDocument.gameObject); diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs index 88b3d2ee4..bb36f39b9 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs @@ -265,6 +265,23 @@ namespace GameCult.Aetheria.State.Verse AetheriaRuntimeDaemonSoaViewStore.GetViewPath(StatePath)); } + public async Task GetLatestAuthoritativeRunFrameAsync() + { + ThrowIfDisposed(); + + var frame = await GetLatestFrameAsync().ConfigureAwait(false); + if (frame == null || + !frame.IsAuthoritative || + frame.Run == null || + frame.Run.Zones == null || + frame.Run.Zones.Count == 0) + { + return null; + } + + return frame; + } + public Task GetDaemonGameSurfaceAsync() { ThrowIfDisposed();