diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index 10d6580fa..959697a80 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -2434,7 +2434,7 @@ static void RequireDaemonRenderQueryAuthority(string root) ".Viewports", "ResolveObjectsViewport(viewport)", "private AetheriaRuntimeObjectsViewportDocument ResolveObjectsViewport(AetheriaRuntimeXzRect viewport)", - ".ReactiveObjects(viewportBounds)", + ".Reactive(viewportBounds)", "_objectsViewport?.Current", "foreach (var entity in objects?.Objects ?? Array.Empty())", "_observedEntitySnapshotsByDaemonIndex.TryGetValue(entityIndex, out var entity)", @@ -4554,7 +4554,7 @@ static void RequireSectorMapZoneDetailsUseEveSurface(string root) ".State", ".Reactive()", ".Details", - ".ReactiveZone(zoneIndex)", + ".Reactive(zoneIndex)", "_zoneDetails?.Current", "AetheriaClient", "AetheriaRuntimeZoneDetailsSurfaceCommands.TryRead(request, out var command)", @@ -4593,7 +4593,7 @@ static void RequireSectorMapZoneDetailsUseEveSurface(string root) "SectorRenderer", "AetheriaRuntimeZoneDetailsDocument", "_zoneDetails", - ".ReactiveZone(zoneIndex)", + ".Reactive(zoneIndex)", "AetheriaRuntimeZoneDetailsSession", ".ObserveZone(zoneIndex)"); RequireReactiveTypedDocumentAccess( @@ -7026,7 +7026,7 @@ static void RequireInventoryValidationUsesManagedTypedDocuments(string root) var clientState = File.ReadAllText(clientStatePath); if (!clientState.Contains("public CultMeshReactiveDocument ReactiveEntity(", StringComparison.Ordinal) || !clientState.Contains("public CultMeshReactiveDocument Reactive(", StringComparison.Ordinal) || - !clientState.Contains("public CultMeshReactiveDocument ReactiveInventory(", StringComparison.Ordinal)) + !clientState.Contains("public CultMeshReactiveDocument Reactive(", StringComparison.Ordinal)) { throw new InvalidOperationException( "AetheriaClientState must expose direct managed reactive typed access for current entity, station refit, and indexed inventory documents."); @@ -7047,7 +7047,7 @@ static void RequireInventoryValidationUsesManagedTypedDocuments(string root) "ResolveStationRefitDocument()", ".Reactive()", "ResolveInventory(entityIndex)", - ".ReactiveInventory(entityIndex)", + ".Reactive(entityIndex)", "_inventory?.Current" }; var missingSymbols = requiredSymbols @@ -7070,7 +7070,7 @@ static void RequireInventoryValidationUsesManagedTypedDocuments(string root) ".ObserveInventory(entityIndex)", ".Latest()", ".Latest()", - ".Details.LatestInventory(entityIndex)", + ".Details.Latest(entityIndex)", ".State.Current.Entity.LatestAsync()", ".State.StationRefit.LatestAsync()", ".Details.Inventory(entityIndex).LatestAsync()" @@ -7107,7 +7107,7 @@ static void RequireInventoryValidationUsesManagedTypedDocuments(string root) name, "AetheriaRuntimeInventoryDocument", "_inventory", - ".ReactiveInventory(entityIndex)", + ".Reactive(entityIndex)", "AetheriaRuntimeInventorySession", ".ObserveInventory(entityIndex)"); } @@ -7289,12 +7289,8 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) "public CultMeshReactiveDocument ReactiveEntity(", "public Task> ReactiveDockingAsync(", "public CultMeshReactiveDocument ReactiveDocking(", - "public Task> ReactiveZoneAsync(", - "public CultMeshReactiveDocument ReactiveZone(", - "public Task> ReactiveSelectedObjectAsync(", - "public CultMeshReactiveDocument ReactiveSelectedObject(", - "public Task> ReactiveInventoryAsync(", - "public CultMeshReactiveDocument ReactiveInventory(" + "public CultMeshDocumentHandle Document(int entityOrZoneIndex)", + "public CultMeshReactiveDocument Reactive(" }; var missingClientSymbols = requiredClientSymbols .Where(symbol => !clientState.Contains(symbol, StringComparison.Ordinal)) @@ -7525,8 +7521,8 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) { "CultMeshReactiveDocument _objectsViewport", "CultMeshReactiveDocument _renderSplatsViewport", - ".ReactiveObjects(viewport)", - ".ReactiveRenderSplats(viewport)", + ".Reactive(viewport)", + ".Reactive(viewport)", "ClearViewportCaches()", "_objectsViewport?.Dispose()", "_renderSplatsViewport?.Dispose()", @@ -7549,7 +7545,7 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) "MapRenderer", "AetheriaRuntimeObjectsViewportDocument", "_objectsViewport", - ".ReactiveObjects(viewport)", + ".Reactive(viewport)", "AetheriaRuntimeObjectsViewportSession", ".ObserveObjects(viewport)"); RequireReactiveTypedDocumentAccess( @@ -7557,7 +7553,7 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) "MapRenderer", "AetheriaRuntimeRenderSplatsViewportDocument", "_renderSplatsViewport", - ".ReactiveRenderSplats(viewport)", + ".Reactive(viewport)", "AetheriaRuntimeRenderSplatsViewportSession", ".ObserveRenderSplats(viewport)"); RequireReactiveTypedDocumentAccess( @@ -7760,7 +7756,7 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) ".Reactive()", ".Current", ".ReactiveZone()", - ".ReactiveZone(zoneIndex)", + ".Reactive(zoneIndex)", "_sectorMap?.Dispose()", "_currentZone?.Dispose()", "_zoneDetails?.Dispose()", @@ -7797,7 +7793,7 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) "SectorRenderer", "AetheriaRuntimeZoneDetailsDocument", "_zoneDetails", - ".ReactiveZone(zoneIndex)", + ".Reactive(zoneIndex)", "AetheriaRuntimeZoneDetailsSession", ".ObserveZone(zoneIndex)"); RequireReactiveTypedDocumentAccess( @@ -7913,12 +7909,12 @@ static void RequireUnityViewportAndMapReadsUseManagedAccessors(string root) "public AetheriaRuntimeCurrentZoneDocument LatestZone()", "public CultMeshReactiveDocument ReactiveZone(", "public AetheriaRuntimeCurrentEntityDocument LatestEntity()", - "public AetheriaRuntimeObjectsViewportDocument LatestObjects(AetheriaRuntimeRtsViewportBounds viewport)", - "public CultMeshReactiveDocument ReactiveObjects(", - "public AetheriaRuntimeRenderSplatsViewportDocument LatestRenderSplats(AetheriaRuntimeRtsViewportBounds viewport)", - "public CultMeshReactiveDocument ReactiveRenderSplats(", - "public AetheriaRuntimeZoneDetailsDocument LatestZone(int zoneIndex)", - "public CultMeshReactiveDocument ReactiveZone(" + "public CultMeshDocumentHandle Document(AetheriaRuntimeRtsViewportBounds viewport)", + "public TDocument Latest(AetheriaRuntimeRtsViewportBounds viewport)", + "public CultMeshReactiveDocument Reactive(", + "public CultMeshDocumentHandle Document(int entityOrZoneIndex)", + "public TDocument Latest(int entityOrZoneIndex)", + "public CultMeshReactiveDocument Reactive(" }; var missingClientSymbols = requiredClientSymbols .Where(symbol => !clientState.Contains(symbol, StringComparison.Ordinal)) @@ -7926,7 +7922,7 @@ static void RequireUnityViewportAndMapReadsUseManagedAccessors(string root) if (missingClientSymbols.Length > 0) { throw new InvalidOperationException( - "AetheriaClientState must expose named managed latest accessors for map, viewport, contacts, and current-entity documents: " + + "AetheriaClientState must expose generic managed parameterized access for map, viewport, contacts, and current-entity documents: " + string.Join(", ", missingClientSymbols)); } @@ -7974,7 +7970,7 @@ static void RequireUnityViewportAndMapReadsUseManagedAccessors(string root) if (offenders.Length > 0) { throw new InvalidOperationException( - "Unity map, viewport, contact, and current-entity reads still walk CultMesh document handles instead of named managed accessors: " + + "Unity map, viewport, contact, and current-entity reads still walk CultMesh document handles instead of managed typed document access: " + string.Join(", ", offenders)); } @@ -7987,7 +7983,7 @@ static void RequireUnityViewportAndMapReadsUseManagedAccessors(string root) var requiredRenderSplatViewportSourceSymbols = new[] { "CultMeshReactiveDocument _renderSplatsViewport", - ".ReactiveRenderSplats(viewport)", + ".Reactive(viewport)", "_renderSplatsViewport?.Current", "ClearViewportDocument()" }; @@ -8014,7 +8010,7 @@ static void RequireUnityViewportAndMapReadsUseManagedAccessors(string root) "AetheriaUnityRenderSplatViewportSource", "AetheriaRuntimeRenderSplatsViewportDocument", "_renderSplatsViewport", - ".ReactiveRenderSplats(viewport)", + ".Reactive(viewport)", "AetheriaRuntimeRenderSplatsViewportSession", ".ObserveRenderSplats(viewport)"); @@ -8027,7 +8023,7 @@ static void RequireUnityViewportAndMapReadsUseManagedAccessors(string root) var requiredZoneRendererViewportSymbols = new[] { "CultMeshReactiveDocument _objectsViewport", - ".ReactiveObjects(viewportBounds)", + ".Reactive(viewportBounds)", "_objectsViewport?.Current", "_objectsViewport?.Dispose()" }; @@ -8054,11 +8050,11 @@ static void RequireUnityViewportAndMapReadsUseManagedAccessors(string root) "ZoneRenderer", "AetheriaRuntimeObjectsViewportDocument", "_objectsViewport", - ".ReactiveObjects(viewportBounds)", + ".Reactive(viewportBounds)", "AetheriaRuntimeObjectsViewportSession", ".ObserveObjects(viewportBounds)"); - Console.WriteLine("Viewport and map document accessors: Unity reads map, contact, viewport, and current-entity state through named managed accessors"); + Console.WriteLine("Viewport and map document accessors: Unity reads map, contact, viewport, and current-entity state through managed typed document access"); } static void RequireAetheriaManagedStateAccessorsCoverDomainDocuments(string root) @@ -8141,9 +8137,10 @@ static void RequireAetheriaManagedStateAccessorsCoverDomainDocuments(string root "public CultMeshReactiveDocument ReactiveGameTuiSurface(", "public CultMeshReactiveDocument ReactiveEditorSurface(", "public CultMeshReactiveDocument ReactiveEditorTuiSurface(", - "public AetheriaRuntimeRtsViewportDocument LatestMap(AetheriaRuntimeRtsViewportBounds viewport)", - "public AetheriaRuntimeGravityViewportDocument LatestGravity(AetheriaRuntimeRtsViewportBounds viewport)", - "public AetheriaRuntimeSelectedObjectDocument LatestSelectedObject(int entityIndex)", + "public CultMeshDocumentHandle Document(AetheriaRuntimeRtsViewportBounds viewport)", + "public TDocument Latest(AetheriaRuntimeRtsViewportBounds viewport)", + "public CultMeshDocumentHandle Document(int entityOrZoneIndex)", + "public TDocument Latest(int entityOrZoneIndex)", "public Task LatestAsync()", "public TDocument Latest()", "public Task> ReactiveAsync(", @@ -8206,6 +8203,70 @@ static void RequireAetheriaManagedStateAccessorsCoverDomainDocuments(string root string.Join(", ", survivingFixedReactiveWrappers)); } + var viewportState = clientState.Split( + "public sealed class AetheriaClientViewportState", + StringSplitOptions.None).Last().Split( + "public sealed class AetheriaClientDetailState", + StringSplitOptions.None)[0]; + var forbiddenViewportConvenienceWrappers = new[] + { + "LatestMap(", + "LatestMapAsync(", + "ReactiveMap(", + "ReactiveMapAsync(", + "LatestObjects(", + "LatestObjectsAsync(", + "ReactiveObjects(", + "ReactiveObjectsAsync(", + "LatestGravity(", + "LatestGravityAsync(", + "ReactiveGravity(", + "ReactiveGravityAsync(", + "LatestRenderSplats(", + "LatestRenderSplatsAsync(", + "ReactiveRenderSplats(", + "ReactiveRenderSplatsAsync(" + }; + var survivingViewportConvenienceWrappers = forbiddenViewportConvenienceWrappers + .Where(symbol => viewportState.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (survivingViewportConvenienceWrappers.Length > 0) + { + throw new InvalidOperationException( + "AetheriaClientViewportState must use generic parameterized document access instead of named latest/reactive wrappers: " + + string.Join(", ", survivingViewportConvenienceWrappers)); + } + + var detailState = clientState.Split( + "public sealed class AetheriaClientDetailState", + StringSplitOptions.None).Last().Split( + "public sealed class AetheriaClientStarbridgeState", + StringSplitOptions.None)[0]; + var forbiddenDetailConvenienceWrappers = new[] + { + "LatestZone(", + "LatestZoneAsync(", + "ReactiveZone(", + "ReactiveZoneAsync(", + "LatestSelectedObject(", + "LatestSelectedObjectAsync(", + "ReactiveSelectedObject(", + "ReactiveSelectedObjectAsync(", + "LatestInventory(", + "LatestInventoryAsync(", + "ReactiveInventory(", + "ReactiveInventoryAsync(" + }; + var survivingDetailConvenienceWrappers = forbiddenDetailConvenienceWrappers + .Where(symbol => detailState.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (survivingDetailConvenienceWrappers.Length > 0) + { + throw new InvalidOperationException( + "AetheriaClientDetailState must use generic parameterized document access instead of named latest/reactive wrappers: " + + string.Join(", ", survivingDetailConvenienceWrappers)); + } + var observedDaemonState = File.ReadAllText(observedDaemonStatePath); if (!observedDaemonState.Contains("public static bool TryCreateCurrent(", StringComparison.Ordinal) || !observedDaemonState.Contains("new AetheriaRuntimeObservedDaemonState(currentFrame, currentSoaView, currentZoneRender)", StringComparison.Ordinal)) @@ -13947,14 +14008,14 @@ static void RequireUnityObserverDoesNotTickLocalSimulation(string root) if (!mapRenderer.Contains("AetheriaUnityRuntimeClientProvider.ResolveClient(", StringComparison.Ordinal) || !mapRenderer.Contains(".Viewports", StringComparison.Ordinal) || - !mapRenderer.Contains(".ReactiveObjects(viewport)", StringComparison.Ordinal) || - !mapRenderer.Contains(".ReactiveRenderSplats(viewport)", StringComparison.Ordinal) || + !mapRenderer.Contains(".Reactive(viewport)", StringComparison.Ordinal) || + !mapRenderer.Contains(".Reactive(viewport)", StringComparison.Ordinal) || !mapRenderer.Contains(".Reactive()", StringComparison.Ordinal) || !sectorRenderer.Contains("AetheriaUnityRuntimeClientProvider.ResolveClient(", StringComparison.Ordinal) || !sectorRenderer.Contains(".State", StringComparison.Ordinal) || !sectorRenderer.Contains(".Reactive()", StringComparison.Ordinal) || !sectorRenderer.Contains(".Details", StringComparison.Ordinal) || - !sectorRenderer.Contains(".ReactiveZone(zoneIndex)", StringComparison.Ordinal) || + !sectorRenderer.Contains(".Reactive(zoneIndex)", StringComparison.Ordinal) || !sectorRenderer.Contains(".Current", StringComparison.Ordinal) || !sectorRenderer.Contains("ResolveClient().State.Reactive()", StringComparison.Ordinal) || !sectorRenderer.Contains(".Reactive()", StringComparison.Ordinal) || @@ -16847,7 +16908,7 @@ static void RequireInventoryDoubleClickTransferRequestAuthority(string root) "TryValidateTypedCargoSlot(", "TryValidateTypedEquipmentSlot(", ".Details", - ".ReactiveInventory(entityIndex)", + ".Reactive(entityIndex)", "origin.Cargo.TryGetValue(item, out var originPosition)", "SourceIndex == cargoIndex", "SourceIndex == equipmentIndex", diff --git a/Assets/Scripts/Gameplay/AetheriaUnityRenderSplatViewportSource.cs b/Assets/Scripts/Gameplay/AetheriaUnityRenderSplatViewportSource.cs index 543eed377..50f65fcae 100644 --- a/Assets/Scripts/Gameplay/AetheriaUnityRenderSplatViewportSource.cs +++ b/Assets/Scripts/Gameplay/AetheriaUnityRenderSplatViewportSource.cs @@ -119,7 +119,7 @@ public sealed class AetheriaUnityRenderSplatViewportSource : MonoBehaviour var nextRenderSplatsViewport = ResolveClient() .State .Viewports - .ReactiveRenderSplats(viewport); + .Reactive(viewport); ClearViewportDocument(); _viewport = viewport; _renderSplatsViewport = nextRenderSplatsViewport; diff --git a/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs b/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs index 22bb3e7d1..55d2d41bb 100644 --- a/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs +++ b/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs @@ -186,13 +186,13 @@ public class DaemonRuntimeDocumentTests }; var objectsViewport = client.State .Viewports - .LatestObjects(viewport); + .Latest(viewport); var zoneDetails = client.State .Details - .LatestZone(0); + .Latest(0); var inventory = client.State .Details - .LatestInventory(0); + .Latest(0); var currentEntityFromClientType = client.State .LatestAsync() .GetAwaiter() @@ -217,13 +217,13 @@ public class DaemonRuntimeDocumentTests using var currentEntityDocumentReactive = client.State.Current.ReactiveEntity(); using var currentDockingReactive = client.State.Current.ReactiveDocking(); using var stationRefitReactive = client.State.Reactive(); - using var zoneDetailsReactive = client.State.Details.ReactiveZone(0); - using var selectedObjectReactive = client.State.Details.ReactiveSelectedObject(0); - using var inventoryReactive = client.State.Details.ReactiveInventory(0); - using var mapViewportReactive = client.State.Viewports.ReactiveMap(viewport); - using var objectsViewportReactive = client.State.Viewports.ReactiveObjects(viewport); - using var gravityViewportReactive = client.State.Viewports.ReactiveGravity(viewport); - using var renderSplatsViewportReactive = client.State.Viewports.ReactiveRenderSplats(viewport); + using var zoneDetailsReactive = client.State.Details.Reactive(0); + using var selectedObjectReactive = client.State.Details.Reactive(0); + using var inventoryReactive = client.State.Details.Reactive(0); + using var mapViewportReactive = client.State.Viewports.Reactive(viewport); + using var objectsViewportReactive = client.State.Viewports.Reactive(viewport); + using var gravityViewportReactive = client.State.Viewports.Reactive(viewport); + using var renderSplatsViewportReactive = client.State.Viewports.Reactive(viewport); using var playerHudCatalog = client.State.Reactive(); using var playerHudSettings = client.State.Reactive(); using var playerHudEntity = client.State.Current.ReactiveEntity(); diff --git a/Assets/Scripts/UI/Menu/InventoryMenu.cs b/Assets/Scripts/UI/Menu/InventoryMenu.cs index fd873060d..ee0b07991 100644 --- a/Assets/Scripts/UI/Menu/InventoryMenu.cs +++ b/Assets/Scripts/UI/Menu/InventoryMenu.cs @@ -1006,7 +1006,7 @@ public class InventoryMenu : MonoBehaviour var nextInventory = ResolveClient() .State .Details - .ReactiveInventory(entityIndex); + .Reactive(entityIndex); _inventory?.Dispose(); _inventoryEntityIndex = entityIndex; _inventory = nextInventory; diff --git a/Assets/Scripts/UI/Menu/InventoryPanel.cs b/Assets/Scripts/UI/Menu/InventoryPanel.cs index a6fc5eade..c65114b89 100644 --- a/Assets/Scripts/UI/Menu/InventoryPanel.cs +++ b/Assets/Scripts/UI/Menu/InventoryPanel.cs @@ -1572,7 +1572,7 @@ private void Update() var nextInventory = ResolveClient() .State .Details - .ReactiveInventory(entityIndex); + .Reactive(entityIndex); _inventory?.Dispose(); _inventoryEntityIndex = entityIndex; _inventory = nextInventory; diff --git a/Assets/Scripts/UI/Menu/MapRenderer.cs b/Assets/Scripts/UI/Menu/MapRenderer.cs index 27e93a88b..253296bb6 100644 --- a/Assets/Scripts/UI/Menu/MapRenderer.cs +++ b/Assets/Scripts/UI/Menu/MapRenderer.cs @@ -132,11 +132,11 @@ public class MapRenderer : MonoBehaviour _objectsViewport = client .State .Viewports - .ReactiveObjects(viewport); + .Reactive(viewport); _renderSplatsViewport = client .State .Viewports - .ReactiveRenderSplats(viewport); + .Reactive(viewport); var objectsViewport = _objectsViewport?.Current; var zoneName = string.IsNullOrWhiteSpace(objectsViewport?.ZoneName) diff --git a/Assets/Scripts/UI/Menu/SectorRenderer.cs b/Assets/Scripts/UI/Menu/SectorRenderer.cs index ac1ebbb79..780a21aeb 100644 --- a/Assets/Scripts/UI/Menu/SectorRenderer.cs +++ b/Assets/Scripts/UI/Menu/SectorRenderer.cs @@ -206,7 +206,7 @@ public class SectorRenderer : MonoBehaviour, IBeginDragHandler, IDragHandler, IS var nextZoneDetails = ResolveClient() .State .Details - .ReactiveZone(zoneIndex); + .Reactive(zoneIndex); _zoneDetails?.Dispose(); _zoneDetailsIndex = zoneIndex; _zoneDetails = nextZoneDetails; diff --git a/Assets/Scripts/Zone Display/ZoneRenderer.cs b/Assets/Scripts/Zone Display/ZoneRenderer.cs index a2867f7e1..e25f75324 100644 --- a/Assets/Scripts/Zone Display/ZoneRenderer.cs +++ b/Assets/Scripts/Zone Display/ZoneRenderer.cs @@ -1246,7 +1246,7 @@ public class ZoneRenderer : MonoBehaviour var nextObjectsViewport = ResolveClient() .State .Viewports - .ReactiveObjects(viewportBounds); + .Reactive(viewportBounds); _objectsViewport?.Dispose(); _objectsViewportBounds = viewportBounds; _objectsViewport = nextObjectsViewport; diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs index 9090792fe..1519c4ee3 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs @@ -843,28 +843,37 @@ namespace GameCult.Aetheria.State.Verse return _map(viewport ?? new AetheriaRuntimeRtsViewportBounds()); } - public Task LatestMapAsync(AetheriaRuntimeRtsViewportBounds viewport) - { - return Map(viewport).LatestAsync(); - } - - public AetheriaRuntimeRtsViewportDocument LatestMap(AetheriaRuntimeRtsViewportBounds viewport) - { - return LatestMapAsync(viewport).ConfigureAwait(false).GetAwaiter().GetResult(); - } - - public Task> ReactiveMapAsync( + public bool TryGetDocument( AetheriaRuntimeRtsViewportBounds viewport, - CultMeshReactiveDocumentOptions? options = null) + out CultMeshDocumentHandle document) + where TDocument : class { - return Map(viewport).ReactiveAsync(options); - } + if (typeof(TDocument) == typeof(AetheriaRuntimeRtsViewportDocument)) + { + document = (CultMeshDocumentHandle)(object)Map(viewport); + return true; + } - public CultMeshReactiveDocument ReactiveMap( - AetheriaRuntimeRtsViewportBounds viewport, - CultMeshReactiveDocumentOptions? options = null) - { - return Map(viewport).Reactive(options); + if (typeof(TDocument) == typeof(AetheriaRuntimeObjectsViewportDocument)) + { + document = (CultMeshDocumentHandle)(object)Objects(viewport); + return true; + } + + if (typeof(TDocument) == typeof(AetheriaRuntimeGravityViewportDocument)) + { + document = (CultMeshDocumentHandle)(object)Gravity(viewport); + return true; + } + + if (typeof(TDocument) == typeof(AetheriaRuntimeRenderSplatsViewportDocument)) + { + document = (CultMeshDocumentHandle)(object)RenderSplats(viewport); + return true; + } + + document = null!; + return false; } public CultMeshDocumentHandle Objects(AetheriaRuntimeRtsViewportBounds viewport) @@ -872,28 +881,42 @@ namespace GameCult.Aetheria.State.Verse return _objects(viewport ?? new AetheriaRuntimeRtsViewportBounds()); } - public Task LatestObjectsAsync(AetheriaRuntimeRtsViewportBounds viewport) + public CultMeshDocumentHandle Document(AetheriaRuntimeRtsViewportBounds viewport) + where TDocument : class { - return Objects(viewport).LatestAsync(); + if (TryGetDocument(viewport, out var document)) + return document; + + throw new NotSupportedException( + $"Aetheria viewport state does not expose a projected document for type '{typeof(TDocument).FullName}'."); } - public AetheriaRuntimeObjectsViewportDocument LatestObjects(AetheriaRuntimeRtsViewportBounds viewport) + public Task LatestAsync(AetheriaRuntimeRtsViewportBounds viewport) + where TDocument : class { - return LatestObjectsAsync(viewport).ConfigureAwait(false).GetAwaiter().GetResult(); + return Document(viewport).LatestAsync(); } - public Task> ReactiveObjectsAsync( + public TDocument Latest(AetheriaRuntimeRtsViewportBounds viewport) + where TDocument : class + { + return LatestAsync(viewport).ConfigureAwait(false).GetAwaiter().GetResult(); + } + + public Task> ReactiveAsync( AetheriaRuntimeRtsViewportBounds viewport, CultMeshReactiveDocumentOptions? options = null) + where TDocument : class { - return Objects(viewport).ReactiveAsync(options); + return Document(viewport).ReactiveAsync(options); } - public CultMeshReactiveDocument ReactiveObjects( + public CultMeshReactiveDocument Reactive( AetheriaRuntimeRtsViewportBounds viewport, CultMeshReactiveDocumentOptions? options = null) + where TDocument : class { - return Objects(viewport).Reactive(options); + return Document(viewport).Reactive(options); } public CultMeshDocumentHandle Gravity(AetheriaRuntimeRtsViewportBounds viewport) @@ -901,59 +924,11 @@ namespace GameCult.Aetheria.State.Verse return _gravity(viewport ?? new AetheriaRuntimeRtsViewportBounds()); } - public Task LatestGravityAsync(AetheriaRuntimeRtsViewportBounds viewport) - { - return Gravity(viewport).LatestAsync(); - } - - public AetheriaRuntimeGravityViewportDocument LatestGravity(AetheriaRuntimeRtsViewportBounds viewport) - { - return LatestGravityAsync(viewport).ConfigureAwait(false).GetAwaiter().GetResult(); - } - - public Task> ReactiveGravityAsync( - AetheriaRuntimeRtsViewportBounds viewport, - CultMeshReactiveDocumentOptions? options = null) - { - return Gravity(viewport).ReactiveAsync(options); - } - - public CultMeshReactiveDocument ReactiveGravity( - AetheriaRuntimeRtsViewportBounds viewport, - CultMeshReactiveDocumentOptions? options = null) - { - return Gravity(viewport).Reactive(options); - } - public CultMeshDocumentHandle RenderSplats(AetheriaRuntimeRtsViewportBounds viewport) { return _renderSplats(viewport ?? new AetheriaRuntimeRtsViewportBounds()); } - public Task LatestRenderSplatsAsync(AetheriaRuntimeRtsViewportBounds viewport) - { - return RenderSplats(viewport).LatestAsync(); - } - - public AetheriaRuntimeRenderSplatsViewportDocument LatestRenderSplats(AetheriaRuntimeRtsViewportBounds viewport) - { - return LatestRenderSplatsAsync(viewport).ConfigureAwait(false).GetAwaiter().GetResult(); - } - - public Task> ReactiveRenderSplatsAsync( - AetheriaRuntimeRtsViewportBounds viewport, - CultMeshReactiveDocumentOptions? options = null) - { - return RenderSplats(viewport).ReactiveAsync(options); - } - - public CultMeshReactiveDocument ReactiveRenderSplats( - AetheriaRuntimeRtsViewportBounds viewport, - CultMeshReactiveDocumentOptions? options = null) - { - return RenderSplats(viewport).Reactive(options); - } - } public sealed class AetheriaClientDetailState @@ -977,28 +952,31 @@ namespace GameCult.Aetheria.State.Verse return _zoneDetails(zoneIndex); } - public Task LatestZoneAsync(int zoneIndex) + public bool TryGetDocument( + int entityOrZoneIndex, + out CultMeshDocumentHandle document) + where TDocument : class { - return Zone(zoneIndex).LatestAsync(); - } + if (typeof(TDocument) == typeof(AetheriaRuntimeZoneDetailsDocument)) + { + document = (CultMeshDocumentHandle)(object)Zone(entityOrZoneIndex); + return true; + } - public AetheriaRuntimeZoneDetailsDocument LatestZone(int zoneIndex) - { - return LatestZoneAsync(zoneIndex).ConfigureAwait(false).GetAwaiter().GetResult(); - } + if (typeof(TDocument) == typeof(AetheriaRuntimeSelectedObjectDocument)) + { + document = (CultMeshDocumentHandle)(object)SelectedObject(entityOrZoneIndex); + return true; + } - public Task> ReactiveZoneAsync( - int zoneIndex, - CultMeshReactiveDocumentOptions? options = null) - { - return Zone(zoneIndex).ReactiveAsync(options); - } + if (typeof(TDocument) == typeof(AetheriaRuntimeInventoryDocument)) + { + document = (CultMeshDocumentHandle)(object)Inventory(entityOrZoneIndex); + return true; + } - public CultMeshReactiveDocument ReactiveZone( - int zoneIndex, - CultMeshReactiveDocumentOptions? options = null) - { - return Zone(zoneIndex).Reactive(options); + document = null!; + return false; } public CultMeshDocumentHandle SelectedObject(int entityIndex) @@ -1006,28 +984,42 @@ namespace GameCult.Aetheria.State.Verse return _selectedObject(entityIndex); } - public Task LatestSelectedObjectAsync(int entityIndex) + public CultMeshDocumentHandle Document(int entityOrZoneIndex) + where TDocument : class { - return SelectedObject(entityIndex).LatestAsync(); + if (TryGetDocument(entityOrZoneIndex, out var document)) + return document; + + throw new NotSupportedException( + $"Aetheria detail state does not expose a projected document for type '{typeof(TDocument).FullName}'."); } - public AetheriaRuntimeSelectedObjectDocument LatestSelectedObject(int entityIndex) + public Task LatestAsync(int entityOrZoneIndex) + where TDocument : class { - return LatestSelectedObjectAsync(entityIndex).ConfigureAwait(false).GetAwaiter().GetResult(); + return Document(entityOrZoneIndex).LatestAsync(); } - public Task> ReactiveSelectedObjectAsync( - int entityIndex, + public TDocument Latest(int entityOrZoneIndex) + where TDocument : class + { + return LatestAsync(entityOrZoneIndex).ConfigureAwait(false).GetAwaiter().GetResult(); + } + + public Task> ReactiveAsync( + int entityOrZoneIndex, CultMeshReactiveDocumentOptions? options = null) + where TDocument : class { - return SelectedObject(entityIndex).ReactiveAsync(options); + return Document(entityOrZoneIndex).ReactiveAsync(options); } - public CultMeshReactiveDocument ReactiveSelectedObject( - int entityIndex, + public CultMeshReactiveDocument Reactive( + int entityOrZoneIndex, CultMeshReactiveDocumentOptions? options = null) + where TDocument : class { - return SelectedObject(entityIndex).Reactive(options); + return Document(entityOrZoneIndex).Reactive(options); } public CultMeshDocumentHandle Inventory(int entityIndex) @@ -1035,30 +1027,6 @@ namespace GameCult.Aetheria.State.Verse return _inventory(entityIndex); } - public Task LatestInventoryAsync(int entityIndex) - { - return Inventory(entityIndex).LatestAsync(); - } - - public AetheriaRuntimeInventoryDocument LatestInventory(int entityIndex) - { - return LatestInventoryAsync(entityIndex).ConfigureAwait(false).GetAwaiter().GetResult(); - } - - public Task> ReactiveInventoryAsync( - int entityIndex, - CultMeshReactiveDocumentOptions? options = null) - { - return Inventory(entityIndex).ReactiveAsync(options); - } - - public CultMeshReactiveDocument ReactiveInventory( - int entityIndex, - CultMeshReactiveDocumentOptions? options = null) - { - return Inventory(entityIndex).Reactive(options); - } - } public sealed class AetheriaClientStarbridgeState