diff --git a/Aetheria.State/scripts/verify-stage7d-unity-parity.ps1 b/Aetheria.State/scripts/verify-stage7d-unity-parity.ps1 index 16a3d3d83..a8c453143 100644 --- a/Aetheria.State/scripts/verify-stage7d-unity-parity.ps1 +++ b/Aetheria.State/scripts/verify-stage7d-unity-parity.ps1 @@ -596,21 +596,19 @@ Assert-Contains $verseClient "AetheriaClientState Aetheria()" "AetheriaRuntimeVe Assert-Contains $verseClient "CreateAetheriaStateFacade" "AetheriaRuntimeVerseClient must own projected state facade creation." $clientState = Read-Text "Packages\org.gamecult.aetheria.state\Runtime\AetheriaClientState.cs" -Assert-Contains $clientState "CultMeshDocumentHandle" "Aetheria state documents must wrap CultMesh typed document handles." +Assert-Contains $clientState "CultMeshDocumentHandle" "Aetheria state documents must expose CultMesh typed document handles directly." Assert-Contains $clientState "CultMeshDocumentCatalog" "Aetheria state facade must delegate type/schema lookup to CultMesh." Assert-Contains $clientState "CultMesh.Documents" "Aetheria state facade must build its projected document catalog through CultMesh." -Assert-Contains $clientState "IAetheriaRuntimeProjectedDocument" "Aetheria projected documents must expose schema metadata independent of exact CLR type." -Assert-Contains $clientState "_documentsByType" "Aetheria state facade must index projected documents by shared document type." +Assert-Contains $clientState "ICultMeshDocumentHandle" "Aetheria schema lookup must expose CultMesh schema metadata independent of exact CLR type." +Assert-NotContains $clientState "AetheriaRuntimeProjectedDocument" "Aetheria state facade must not wrap CultMesh document handles in a private projected-document abstraction." +Assert-NotContains $clientState "_documentsByType" "Aetheria state facade must not maintain a private type index beside CultMeshDocumentCatalog." Assert-Contains $clientState "TryGetDocument" "Aetheria state facade must support safe typed document lookup." Assert-Contains $clientState "TryGetDocumentBySchema" "Aetheria state facade must support safe schema document lookup." Assert-Contains $clientState "Document()" "Aetheria state facade must support direct typed document lookup." Assert-Contains $clientState "DocumentBySchema(string schemaVersion)" "Aetheria state facade must support direct schema document lookup." -Assert-Contains $clientState "AsSchemaAlias" "Aetheria state facade must expose CultMesh same-schema aliases." Assert-NotContains $clientState "CultDocumentMessagePackSerialization" "Aetheria projected document aliasing must be owned by CultMesh, not the Aetheria facade." Assert-Contains $clientState "LatestAsync()" "Aetheria state facade must support one-call typed document reads." Assert-Contains $clientState "Watch()" "Aetheria state facade must support reactive typed document lookup." -Assert-Contains $clientState "LatestAsync()" "Aetheria state documents must support one-shot typed reads." -Assert-Contains $clientState "Watch()" "Aetheria state documents must support reactive typed reads." $projection = Read-Text "Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeRtsProjection.cs" Assert-Contains $projection "ProjectZoneDetails" "Shared projection code must own the zone-details feed." diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs index d654073de..b1c30b129 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs @@ -40,14 +40,14 @@ namespace GameCult.Aetheria.State.Verse public AetheriaClientState Aetheria() => State; - public AetheriaRuntimeProjectedDocument Document() + public CultMeshDocumentHandle Document() where TDocument : class { ThrowIfDisposed(); return State.Document(); } - public IAetheriaRuntimeProjectedDocument DocumentBySchema(string schemaVersion) + public ICultMeshDocumentHandle DocumentBySchema(string schemaVersion) { ThrowIfDisposed(); return State.DocumentBySchema(schemaVersion); diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs index daea2aca2..01f097d92 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using GameCult.Mesh; using R3; @@ -9,209 +7,52 @@ using R3; namespace GameCult.Aetheria.State.Verse { - public sealed class AetheriaRuntimeProjectedDocument : IAetheriaRuntimeProjectedDocument - where TDocument : class - { - private readonly CultMeshDocumentHandle _handle; - - internal AetheriaRuntimeProjectedDocument( - CultMeshDocumentHandle handle) - { - _handle = handle ?? throw new ArgumentNullException(nameof(handle)); - } - - public string DocumentId => _handle.DocumentId; - - public Type DocumentType => _handle.DocumentType; - - public string SchemaName => _handle.SchemaName; - - public string SchemaVersion => _handle.SchemaVersion; - - public CultMeshRouteHint RouteHint => _handle.RouteHint; - - public System.Collections.Generic.IReadOnlyList Sources => _handle.Sources; - - public bool CanReplace => _handle.CanReplace; - - public CultMeshDocumentHandle Handle => _handle; - - public ICultMeshDocumentHandle UntypedHandle => _handle; - - public Task LatestAsync() - { - return _handle.LatestAsync(); - } - - public TDocument Latest() - { - return _handle.LatestAsync().ConfigureAwait(false).GetAwaiter().GetResult(); - } - - public Observable Watch() - { - return _handle.Watch(); - } - - public IDisposable Watch(Action onNext) - { - if (onNext == null) throw new ArgumentNullException(nameof(onNext)); - return _handle.Watch(onNext); - } - - public Task ReplaceAsync(TDocument value) - { - return _handle.ReplaceAsync(value); - } - - internal static AetheriaRuntimeProjectedDocument Create( - string documentId, - string runtimeId, - Func> latest, - Observable frameChanges, - Func> projectFrame, - params CultMeshProjectionSource[] sources) - { - if (string.IsNullOrWhiteSpace(documentId)) - throw new ArgumentException("Document id must be non-empty.", nameof(documentId)); - if (string.IsNullOrWhiteSpace(runtimeId)) - throw new ArgumentException("Runtime id must be non-empty.", nameof(runtimeId)); - if (latest == null) throw new ArgumentNullException(nameof(latest)); - if (frameChanges == null) throw new ArgumentNullException(nameof(frameChanges)); - if (projectFrame == null) throw new ArgumentNullException(nameof(projectFrame)); - - var verse = CultMesh.Verse("aetheria.local", runtimeId); - var handle = CultMesh.Document( - documentId, - verse, - _ => latest(), - _ => frameChanges - .SelectAwait(async (frame, cancellationToken) => - await projectFrame(frame).ConfigureAwait(false)), - sources: sources, - routeHint: new CultMeshRouteHint(CultMeshLocalityKind.SharedMemory, "Aetheria typed projected state")); - - return new AetheriaRuntimeProjectedDocument(handle); - } - - public AetheriaRuntimeProjectedDocument AsSchemaAlias() where TAlias : class - { - return new AetheriaRuntimeProjectedDocument(_handle.AsSchemaAlias()); - } - } - - public interface IAetheriaRuntimeProjectedDocument - { - Type DocumentType { get; } - - string DocumentId { get; } - - string SchemaName { get; } - - string SchemaVersion { get; } - - CultMeshRouteHint RouteHint { get; } - - System.Collections.Generic.IReadOnlyList Sources { get; } - - bool CanReplace { get; } - - ICultMeshDocumentHandle UntypedHandle { get; } - - AetheriaRuntimeProjectedDocument AsSchemaAlias() where TAlias : class; - } - - internal sealed class AetheriaRuntimeUntypedProjectedDocument : IAetheriaRuntimeProjectedDocument - { - private readonly ICultMeshDocumentHandle _handle; - - public AetheriaRuntimeUntypedProjectedDocument(ICultMeshDocumentHandle handle) - { - _handle = handle ?? throw new ArgumentNullException(nameof(handle)); - } - - public Type DocumentType => _handle.DocumentType; - - public string DocumentId => _handle.DocumentId; - - public string SchemaName => _handle.SchemaName; - - public string SchemaVersion => _handle.SchemaVersion; - - public CultMeshRouteHint RouteHint => _handle.RouteHint; - - public System.Collections.Generic.IReadOnlyList Sources => _handle.Sources; - - public bool CanReplace => _handle.CanReplace; - - public ICultMeshDocumentHandle UntypedHandle => _handle; - - public AetheriaRuntimeProjectedDocument AsSchemaAlias() where TAlias : class - { - return new AetheriaRuntimeProjectedDocument(_handle.AsSchemaAlias()); - } - } - public sealed class AetheriaClientState { - private readonly IReadOnlyDictionary _documentsByType; private readonly CultMeshDocumentCatalog _documents; internal AetheriaClientState( - AetheriaRuntimeProjectedDocument currentZone, - AetheriaRuntimeProjectedDocument currentEntity, - AetheriaRuntimeProjectedDocument currentDocking, - AetheriaRuntimeProjectedDocument zoneContacts, - AetheriaRuntimeProjectedDocument stationRefit, - AetheriaRuntimeProjectedDocument sectorMap, - AetheriaRuntimeProjectedDocument zoneRender) + CultMeshDocumentHandle currentZone, + CultMeshDocumentHandle currentEntity, + CultMeshDocumentHandle currentDocking, + CultMeshDocumentHandle zoneContacts, + CultMeshDocumentHandle stationRefit, + CultMeshDocumentHandle sectorMap, + CultMeshDocumentHandle zoneRender) { Current = new AetheriaClientCurrentState(currentZone, currentEntity, currentDocking); ZoneContacts = zoneContacts ?? throw new ArgumentNullException(nameof(zoneContacts)); StationRefit = stationRefit ?? throw new ArgumentNullException(nameof(stationRefit)); SectorMap = sectorMap ?? throw new ArgumentNullException(nameof(sectorMap)); ZoneRender = zoneRender ?? throw new ArgumentNullException(nameof(zoneRender)); - _documentsByType = new Dictionary - { - [typeof(AetheriaRuntimeCurrentZoneDocument)] = Current.Zone, - [typeof(AetheriaRuntimeCurrentEntityDocument)] = Current.Entity, - [typeof(AetheriaRuntimeCurrentDockingDocument)] = Current.Docking, - [typeof(AetheriaRuntimeZoneContactsDocument)] = ZoneContacts, - [typeof(AetheriaRuntimeStationRefitDocument)] = StationRefit, - [typeof(AetheriaRuntimeSectorMapDocument)] = SectorMap, - [typeof(AetheriaRuntimeZoneRenderDocument)] = ZoneRender - }; - _documents = CultMesh.Documents(_documentsByType - .Values - .OfType() - .Select(document => document.UntypedHandle)); + _documents = CultMesh.Documents( + Current.Zone, + Current.Entity, + Current.Docking, + ZoneContacts, + StationRefit, + SectorMap, + ZoneRender); } public AetheriaClientCurrentState Current { get; } - public AetheriaRuntimeProjectedDocument ZoneContacts { get; } + public CultMeshDocumentHandle ZoneContacts { get; } - public AetheriaRuntimeProjectedDocument StationRefit { get; } + public CultMeshDocumentHandle StationRefit { get; } - public AetheriaRuntimeProjectedDocument SectorMap { get; } + public CultMeshDocumentHandle SectorMap { get; } - public AetheriaRuntimeProjectedDocument ZoneRender { get; } + public CultMeshDocumentHandle ZoneRender { get; } public bool TryGetDocumentBySchema( string schemaVersion, - out IAetheriaRuntimeProjectedDocument document) + out ICultMeshDocumentHandle document) { - if (_documents.TryGetDocumentBySchema(schemaVersion, out var handle)) - { - document = new AetheriaRuntimeUntypedProjectedDocument(handle); - return true; - } - - document = null!; - return false; + return _documents.TryGetDocumentBySchema(schemaVersion, out document); } - public IAetheriaRuntimeProjectedDocument DocumentBySchema(string schemaVersion) + public ICultMeshDocumentHandle DocumentBySchema(string schemaVersion) { if (TryGetDocumentBySchema(schemaVersion, out var document)) return document; @@ -221,20 +62,13 @@ namespace GameCult.Aetheria.State.Verse } public bool TryGetDocument( - out AetheriaRuntimeProjectedDocument document) + out CultMeshDocumentHandle document) where TDocument : class { - if (_documents.TryGetDocument(out var handle)) - { - document = new AetheriaRuntimeProjectedDocument(handle); - return true; - } - - document = null!; - return false; + return _documents.TryGetDocument(out document); } - public AetheriaRuntimeProjectedDocument Document() + public CultMeshDocumentHandle Document() where TDocument : class { if (TryGetDocument(out var document)) @@ -253,7 +87,7 @@ namespace GameCult.Aetheria.State.Verse public TDocument Latest() where TDocument : class { - return Document().Latest(); + return Document().LatestAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } public Observable Watch() @@ -265,27 +99,27 @@ namespace GameCult.Aetheria.State.Verse public IDisposable Watch(Action onNext) where TDocument : class { + if (onNext == null) throw new ArgumentNullException(nameof(onNext)); return Document().Watch(onNext); } - } public sealed class AetheriaClientCurrentState { internal AetheriaClientCurrentState( - AetheriaRuntimeProjectedDocument zone, - AetheriaRuntimeProjectedDocument entity, - AetheriaRuntimeProjectedDocument docking) + CultMeshDocumentHandle zone, + CultMeshDocumentHandle entity, + CultMeshDocumentHandle docking) { Zone = zone ?? throw new ArgumentNullException(nameof(zone)); Entity = entity ?? throw new ArgumentNullException(nameof(entity)); Docking = docking ?? throw new ArgumentNullException(nameof(docking)); } - public AetheriaRuntimeProjectedDocument Zone { get; } + public CultMeshDocumentHandle Zone { get; } - public AetheriaRuntimeProjectedDocument Entity { get; } + public CultMeshDocumentHandle Entity { get; } - public AetheriaRuntimeProjectedDocument Docking { get; } + public CultMeshDocumentHandle Docking { get; } } } diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs index 83f0fb8ea..7b63b8cc7 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs @@ -685,7 +685,7 @@ namespace GameCult.Aetheria.State.Verse frame => Task.FromResult(AetheriaRuntimeRtsProjection.ProjectZoneRender(frame)), AetheriaRuntimeDaemonSchemas.ZoneRender)); - AetheriaRuntimeProjectedDocument ProjectedDocument( + CultMeshDocumentHandle ProjectedDocument( string documentId, Func> project, string schemaId, @@ -703,13 +703,16 @@ namespace GameCult.Aetheria.State.Verse .Append(CultMesh.ProjectionSource(documentId, schemaId, "projected Aetheria client document")) .ToArray(); - return AetheriaRuntimeProjectedDocument.Create( + var verse = CultMesh.Verse("aetheria.local", RuntimeId); + return CultMesh.Document( documentId, - RuntimeId, - async () => await project(await RequireFrameAsync().ConfigureAwait(false)).ConfigureAwait(false), - frameChanges, - project, - sources); + verse, + async _ => await project(await RequireFrameAsync().ConfigureAwait(false)).ConfigureAwait(false), + _ => frameChanges + .SelectAwait(async (frame, cancellationToken) => + await project(frame).ConfigureAwait(false)), + sources: sources, + routeHint: new CultMeshRouteHint(CultMeshLocalityKind.SharedMemory, "Aetheria typed projected state")); } async Task ProjectStationRefitAsync(