diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index d3649dcdc..34249e6f7 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -2556,13 +2556,13 @@ static void RequireDaemonRenderQueryAuthority(string root) var requiredSchematicHudSymbols = new[] { - "private CultMeshReactiveDocument _currentEntity;", + "private AetheriaRuntimePlayerHudSession _playerHud;", + "ResolvePlayerHud()", "ResolveCurrentEntityHudStatus()", "AetheriaUnityRuntimeClientProvider.ResolveClient(", - ".Current", - ".ReactiveEntity()", - "_currentEntity?.Current?.Hud", - "_currentEntity?.Dispose()", + "ResolveClient().State.ObservePlayerHud()", + "return ResolvePlayerHud()?.Hud ?? new AetheriaRuntimeCurrentEntityHudStatus();", + "_playerHud?.Dispose()", "hud.OverrideShutdown", "hud.HeatsinksEnabled", "hud.Heatstroke", @@ -7266,16 +7266,11 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) "SchematicDisplay.cs")); var requiredSchematicDisplaySymbols = new[] { - "CultMeshReactiveDocument _catalog", - "CultMeshReactiveDocument _playerSettings", - "ResolveClient().State.ReactiveCatalog()", - ".Settings", - ".ReactivePlayer()", - "_catalog?.Dispose()", - "_playerSettings?.Dispose()", - "_catalog?.Current", - "ResolveCatalog()?.FindItem(item, x => x.ItemKey)", - "_playerSettings?.Current", + "AetheriaRuntimePlayerHudSession _playerHud", + "ResolveClient().State.ObservePlayerHud()", + "_playerHud?.Dispose()", + "ResolvePlayerHud()?.Catalog?.FindItem(item, x => x.ItemKey)", + "ResolvePlayerHud()?.PlayerSettings", "private void OnDestroy()" }; var missingSchematicDisplaySymbols = requiredSchematicDisplaySymbols @@ -7284,10 +7279,29 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) if (missingSchematicDisplaySymbols.Length > 0) { throw new InvalidOperationException( - "SchematicDisplay should bind shared catalog/settings through managed reactive Aetheria documents with HUD lifetime disposal: " + + "SchematicDisplay should bind shared catalog/settings/current-entity HUD through one managed Aetheria player HUD session: " + string.Join(", ", missingSchematicDisplaySymbols)); } + var forbiddenSchematicDisplaySymbols = new[] + { + "CultMeshReactiveDocument _catalog", + "CultMeshReactiveDocument _playerSettings", + "CultMeshReactiveDocument _currentEntity", + ".State.ReactiveCatalog()", + ".ReactivePlayer()", + ".ReactiveEntity()" + }; + var schematicDisplayHits = forbiddenSchematicDisplaySymbols + .Where(symbol => schematicDisplay.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (schematicDisplayHits.Length > 0) + { + throw new InvalidOperationException( + "SchematicDisplay still owns raw player HUD CultMesh documents instead of AetheriaRuntimePlayerHudSession: " + + string.Join(", ", schematicDisplayHits)); + } + var mapRenderer = File.ReadAllText(Path.Combine( root, "Assets", @@ -7725,6 +7739,7 @@ static void RequireAetheriaManagedStateAccessorsCoverDomainDocuments(string root "public CultMeshReactiveDocument ReactiveDaemonSoaView(", "public AetheriaRuntimeObservedDaemonState? LatestObservedDaemon()", "public AetheriaRuntimeObservedDockingState? CurrentDocking(", + "public AetheriaRuntimePlayerHudSession ObservePlayerHud(", "public AetheriaRuntimeLoadoutTemplatesDocument LatestLoadoutTemplates()", "public AetheriaRuntimeStationRefitDocument LatestStationRefit()", "public AetheriaRuntimeZoneRenderDocument LatestZoneRender()", diff --git a/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs b/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs index fe4f5752a..e1869d69a 100644 --- a/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs +++ b/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs @@ -205,6 +205,7 @@ public class DaemonRuntimeDocumentTests .Reactive(); var observed = client.State.LatestObservedDaemon(); var observedAuthoritativeFrame = client.State.LatestDaemonFrame(); + using var playerHud = client.State.ObservePlayerHud(); var gameSurface = client.State.LatestGameSurface(); var gameTuiSurface = client.State.LatestGameTuiSurface(); var editorSurface = client.State.LatestEditorSurface(); @@ -289,6 +290,9 @@ public class DaemonRuntimeDocumentTests Assert.AreEqual("zone.0.entity.0", currentEntity.EntityKey); Assert.AreEqual(0, currentEntity.EntityIndex); Assert.AreEqual("Player", currentEntity.Entity?.DisplayName); + Assert.AreEqual(currentEntity.EntityKey, playerHud.CurrentEntity.EntityKey); + Assert.AreEqual(AetheriaRuntimePlayerSettingsDocument.SchemaId, playerHud.PlayerSettings.Schema); + Assert.AreSame(playerHud.Hud, playerHud.CurrentEntity.Hud); Assert.AreEqual(currentEntity.EntityKey, currentEntityReactive.Current.EntityKey); Assert.AreEqual(AetheriaRuntimeDaemonSchemas.ZoneRender, zoneRenderReactive.Current.Schema); Assert.AreEqual(currentEntity.EntityKey, currentEntityByType.EntityKey); diff --git a/Assets/Scripts/UI/HUD/SchematicDisplay.cs b/Assets/Scripts/UI/HUD/SchematicDisplay.cs index 51673a4bb..dadbd6d3c 100644 --- a/Assets/Scripts/UI/HUD/SchematicDisplay.cs +++ b/Assets/Scripts/UI/HUD/SchematicDisplay.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using GameCult.Aetheria.State.Verse; -using GameCult.Mesh; using TMPro; using UnityEngine; using UnityEngine.UI; @@ -70,9 +69,7 @@ public class SchematicDisplay : MonoBehaviour private SchematicDisplayItem[] _schematicItems; private AetherDrive _aetherDrive; private string _clientStatePath = ""; - private CultMeshReactiveDocument _catalog; - private CultMeshReactiveDocument _playerSettings; - private CultMeshReactiveDocument _currentEntity; + private AetheriaRuntimePlayerHudSession _playerHud; private AetheriaRuntimeDaemonRenderSettings? _renderSettings; private bool _enemy; @@ -316,64 +313,34 @@ public class SchematicDisplay : MonoBehaviour private AetheriaRuntimeCatalogItem FindTypedItem(ItemInstance item) { - return ResolveCatalog()?.FindItem(item, x => x.ItemKey); + return ResolvePlayerHud()?.Catalog?.FindItem(item, x => x.ItemKey); } - private AetheriaRuntimeCatalogSnapshot ResolveCatalog() + private AetheriaRuntimePlayerHudSession ResolvePlayerHud() { - if (_catalog != null) - return _catalog.Current; + if (_playerHud != null) + return _playerHud; try { - _catalog = ResolveClient().State.ReactiveCatalog(); + _playerHud = ResolveClient().State.ObservePlayerHud(); } catch (Exception ex) { - Debug.LogWarning($"Failed to bind Aetheria runtime catalog for schematic display: {ex.Message}"); + Debug.LogWarning($"Failed to bind Aetheria player HUD state for schematic display: {ex.Message}"); } - return _catalog?.Current; + return _playerHud; } private AetheriaRuntimePlayerSettingsDocument ResolvePlayerSettings() { - if (_playerSettings != null) - return _playerSettings.Current; - - try - { - _playerSettings = ResolveClient() - .State - .Settings - .ReactivePlayer(); - } - catch (Exception ex) - { - Debug.LogWarning($"Failed to bind Aetheria player settings for schematic display: {ex.Message}"); - } - - return _playerSettings?.Current; + return ResolvePlayerHud()?.PlayerSettings; } private AetheriaRuntimeCurrentEntityHudStatus ResolveCurrentEntityHudStatus() { - if (_currentEntity == null) - { - try - { - _currentEntity = ResolveClient() - .State - .Current - .ReactiveEntity(); - } - catch (Exception ex) - { - Debug.LogWarning($"Failed to bind Aetheria current entity HUD status: {ex.Message}"); - } - } - - return _currentEntity?.Current?.Hud ?? new AetheriaRuntimeCurrentEntityHudStatus(); + return ResolvePlayerHud()?.Hud ?? new AetheriaRuntimeCurrentEntityHudStatus(); } private AetheriaClient ResolveClient() @@ -418,12 +385,8 @@ public class SchematicDisplay : MonoBehaviour private void ClearClientCaches() { - _catalog?.Dispose(); - _playerSettings?.Dispose(); - _currentEntity?.Dispose(); - _catalog = null; - _playerSettings = null; - _currentEntity = null; + _playerHud?.Dispose(); + _playerHud = null; } private void OnDestroy() diff --git a/GameCult.Aetheria.State.Unity.csproj b/GameCult.Aetheria.State.Unity.csproj index 624df2d16..292bba4fb 100644 --- a/GameCult.Aetheria.State.Unity.csproj +++ b/GameCult.Aetheria.State.Unity.csproj @@ -75,6 +75,7 @@ + diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs index 20cf12152..8e3fe414d 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs @@ -299,6 +299,27 @@ namespace GameCult.Aetheria.State.Verse : null; } + public AetheriaRuntimePlayerHudSession ObservePlayerHud( + CultMeshReactiveDocumentOptions? options = null) + { + var catalog = ReactiveCatalog(options); + CultMeshReactiveDocument? playerSettings = null; + CultMeshReactiveDocument? currentEntity = null; + try + { + playerSettings = Settings.ReactivePlayer(options); + currentEntity = Current.ReactiveEntity(options); + return new AetheriaRuntimePlayerHudSession(catalog, playerSettings, currentEntity); + } + catch + { + catalog.Dispose(); + playerSettings?.Dispose(); + currentEntity?.Dispose(); + throw; + } + } + private CultMeshReactiveDocument? TryReactiveDaemonSoaView( CultMeshReactiveDocumentOptions? options) { diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerHudState.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerHudState.cs new file mode 100644 index 000000000..90f7d9694 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerHudState.cs @@ -0,0 +1,37 @@ +using System; +using GameCult.Mesh; + +#nullable enable + +namespace GameCult.Aetheria.State.Verse +{ + public sealed class AetheriaRuntimePlayerHudSession : IDisposable + { + private readonly CultMeshReactiveDocument _catalog; + private readonly CultMeshReactiveDocument _playerSettings; + private readonly CultMeshReactiveDocument _currentEntity; + + public AetheriaRuntimePlayerHudSession( + CultMeshReactiveDocument catalog, + CultMeshReactiveDocument playerSettings, + CultMeshReactiveDocument currentEntity) + { + _catalog = catalog ?? throw new ArgumentNullException(nameof(catalog)); + _playerSettings = playerSettings ?? throw new ArgumentNullException(nameof(playerSettings)); + _currentEntity = currentEntity ?? throw new ArgumentNullException(nameof(currentEntity)); + } + + public AetheriaRuntimeCatalogSnapshot? Catalog => _catalog.Current; + public AetheriaRuntimePlayerSettingsDocument? PlayerSettings => _playerSettings.Current; + public AetheriaRuntimeCurrentEntityDocument? CurrentEntity => _currentEntity.Current; + public AetheriaRuntimeCurrentEntityHudStatus Hud => + CurrentEntity?.Hud ?? new AetheriaRuntimeCurrentEntityHudStatus(); + + public void Dispose() + { + _catalog.Dispose(); + _playerSettings.Dispose(); + _currentEntity.Dispose(); + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerHudState.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerHudState.cs.meta new file mode 100644 index 000000000..2c6b1c89e --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerHudState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0525c14d10a54fd2b92fbf9984a41dc0