Expose Aetheria projected state as CultMesh handles
This commit is contained in:
parent
0fa7a9f15a
commit
dc6ac7f446
4 changed files with 49 additions and 214 deletions
|
|
@ -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<TDocument>" "Aetheria state documents must wrap CultMesh typed document handles."
|
||||
Assert-Contains $clientState "CultMeshDocumentHandle<TDocument>" "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<TDocument>" "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<TDocument>()" "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<TAlias>" "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<TDocument>()" "Aetheria state facade must support one-call typed document reads."
|
||||
Assert-Contains $clientState "Watch<TDocument>()" "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."
|
||||
|
|
|
|||
|
|
@ -40,14 +40,14 @@ namespace GameCult.Aetheria.State.Verse
|
|||
|
||||
public AetheriaClientState Aetheria() => State;
|
||||
|
||||
public AetheriaRuntimeProjectedDocument<TDocument> Document<TDocument>()
|
||||
public CultMeshDocumentHandle<TDocument> Document<TDocument>()
|
||||
where TDocument : class
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return State.Document<TDocument>();
|
||||
}
|
||||
|
||||
public IAetheriaRuntimeProjectedDocument DocumentBySchema(string schemaVersion)
|
||||
public ICultMeshDocumentHandle DocumentBySchema(string schemaVersion)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return State.DocumentBySchema(schemaVersion);
|
||||
|
|
|
|||
|
|
@ -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<TDocument> : IAetheriaRuntimeProjectedDocument
|
||||
where TDocument : class
|
||||
{
|
||||
private readonly CultMeshDocumentHandle<TDocument> _handle;
|
||||
|
||||
internal AetheriaRuntimeProjectedDocument(
|
||||
CultMeshDocumentHandle<TDocument> 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<CultMeshProjectionSource> Sources => _handle.Sources;
|
||||
|
||||
public bool CanReplace => _handle.CanReplace;
|
||||
|
||||
public CultMeshDocumentHandle<TDocument> Handle => _handle;
|
||||
|
||||
public ICultMeshDocumentHandle UntypedHandle => _handle;
|
||||
|
||||
public Task<TDocument> LatestAsync()
|
||||
{
|
||||
return _handle.LatestAsync();
|
||||
}
|
||||
|
||||
public TDocument Latest()
|
||||
{
|
||||
return _handle.LatestAsync().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public Observable<TDocument> Watch()
|
||||
{
|
||||
return _handle.Watch();
|
||||
}
|
||||
|
||||
public IDisposable Watch(Action<TDocument> 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<TDocument> Create(
|
||||
string documentId,
|
||||
string runtimeId,
|
||||
Func<Task<TDocument>> latest,
|
||||
Observable<AetheriaRuntimeDaemonFrameDocument> frameChanges,
|
||||
Func<AetheriaRuntimeDaemonFrameDocument, Task<TDocument>> 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<TDocument>(handle);
|
||||
}
|
||||
|
||||
public AetheriaRuntimeProjectedDocument<TAlias> AsSchemaAlias<TAlias>() where TAlias : class
|
||||
{
|
||||
return new AetheriaRuntimeProjectedDocument<TAlias>(_handle.AsSchemaAlias<TAlias>());
|
||||
}
|
||||
}
|
||||
|
||||
public interface IAetheriaRuntimeProjectedDocument
|
||||
{
|
||||
Type DocumentType { get; }
|
||||
|
||||
string DocumentId { get; }
|
||||
|
||||
string SchemaName { get; }
|
||||
|
||||
string SchemaVersion { get; }
|
||||
|
||||
CultMeshRouteHint RouteHint { get; }
|
||||
|
||||
System.Collections.Generic.IReadOnlyList<CultMeshProjectionSource> Sources { get; }
|
||||
|
||||
bool CanReplace { get; }
|
||||
|
||||
ICultMeshDocumentHandle UntypedHandle { get; }
|
||||
|
||||
AetheriaRuntimeProjectedDocument<TAlias> AsSchemaAlias<TAlias>() 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<CultMeshProjectionSource> Sources => _handle.Sources;
|
||||
|
||||
public bool CanReplace => _handle.CanReplace;
|
||||
|
||||
public ICultMeshDocumentHandle UntypedHandle => _handle;
|
||||
|
||||
public AetheriaRuntimeProjectedDocument<TAlias> AsSchemaAlias<TAlias>() where TAlias : class
|
||||
{
|
||||
return new AetheriaRuntimeProjectedDocument<TAlias>(_handle.AsSchemaAlias<TAlias>());
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AetheriaClientState
|
||||
{
|
||||
private readonly IReadOnlyDictionary<Type, object> _documentsByType;
|
||||
private readonly CultMeshDocumentCatalog _documents;
|
||||
|
||||
internal AetheriaClientState(
|
||||
AetheriaRuntimeProjectedDocument<AetheriaRuntimeCurrentZoneDocument> currentZone,
|
||||
AetheriaRuntimeProjectedDocument<AetheriaRuntimeCurrentEntityDocument> currentEntity,
|
||||
AetheriaRuntimeProjectedDocument<AetheriaRuntimeCurrentDockingDocument> currentDocking,
|
||||
AetheriaRuntimeProjectedDocument<AetheriaRuntimeZoneContactsDocument> zoneContacts,
|
||||
AetheriaRuntimeProjectedDocument<AetheriaRuntimeStationRefitDocument> stationRefit,
|
||||
AetheriaRuntimeProjectedDocument<AetheriaRuntimeSectorMapDocument> sectorMap,
|
||||
AetheriaRuntimeProjectedDocument<AetheriaRuntimeZoneRenderDocument> zoneRender)
|
||||
CultMeshDocumentHandle<AetheriaRuntimeCurrentZoneDocument> currentZone,
|
||||
CultMeshDocumentHandle<AetheriaRuntimeCurrentEntityDocument> currentEntity,
|
||||
CultMeshDocumentHandle<AetheriaRuntimeCurrentDockingDocument> currentDocking,
|
||||
CultMeshDocumentHandle<AetheriaRuntimeZoneContactsDocument> zoneContacts,
|
||||
CultMeshDocumentHandle<AetheriaRuntimeStationRefitDocument> stationRefit,
|
||||
CultMeshDocumentHandle<AetheriaRuntimeSectorMapDocument> sectorMap,
|
||||
CultMeshDocumentHandle<AetheriaRuntimeZoneRenderDocument> 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<Type, object>
|
||||
{
|
||||
[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<IAetheriaRuntimeProjectedDocument>()
|
||||
.Select(document => document.UntypedHandle));
|
||||
_documents = CultMesh.Documents(
|
||||
Current.Zone,
|
||||
Current.Entity,
|
||||
Current.Docking,
|
||||
ZoneContacts,
|
||||
StationRefit,
|
||||
SectorMap,
|
||||
ZoneRender);
|
||||
}
|
||||
|
||||
public AetheriaClientCurrentState Current { get; }
|
||||
|
||||
public AetheriaRuntimeProjectedDocument<AetheriaRuntimeZoneContactsDocument> ZoneContacts { get; }
|
||||
public CultMeshDocumentHandle<AetheriaRuntimeZoneContactsDocument> ZoneContacts { get; }
|
||||
|
||||
public AetheriaRuntimeProjectedDocument<AetheriaRuntimeStationRefitDocument> StationRefit { get; }
|
||||
public CultMeshDocumentHandle<AetheriaRuntimeStationRefitDocument> StationRefit { get; }
|
||||
|
||||
public AetheriaRuntimeProjectedDocument<AetheriaRuntimeSectorMapDocument> SectorMap { get; }
|
||||
public CultMeshDocumentHandle<AetheriaRuntimeSectorMapDocument> SectorMap { get; }
|
||||
|
||||
public AetheriaRuntimeProjectedDocument<AetheriaRuntimeZoneRenderDocument> ZoneRender { get; }
|
||||
public CultMeshDocumentHandle<AetheriaRuntimeZoneRenderDocument> 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<TDocument>(
|
||||
out AetheriaRuntimeProjectedDocument<TDocument> document)
|
||||
out CultMeshDocumentHandle<TDocument> document)
|
||||
where TDocument : class
|
||||
{
|
||||
if (_documents.TryGetDocument<TDocument>(out var handle))
|
||||
{
|
||||
document = new AetheriaRuntimeProjectedDocument<TDocument>(handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
document = null!;
|
||||
return false;
|
||||
return _documents.TryGetDocument(out document);
|
||||
}
|
||||
|
||||
public AetheriaRuntimeProjectedDocument<TDocument> Document<TDocument>()
|
||||
public CultMeshDocumentHandle<TDocument> Document<TDocument>()
|
||||
where TDocument : class
|
||||
{
|
||||
if (TryGetDocument<TDocument>(out var document))
|
||||
|
|
@ -253,7 +87,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
public TDocument Latest<TDocument>()
|
||||
where TDocument : class
|
||||
{
|
||||
return Document<TDocument>().Latest();
|
||||
return Document<TDocument>().LatestAsync().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public Observable<TDocument> Watch<TDocument>()
|
||||
|
|
@ -265,27 +99,27 @@ namespace GameCult.Aetheria.State.Verse
|
|||
public IDisposable Watch<TDocument>(Action<TDocument> onNext)
|
||||
where TDocument : class
|
||||
{
|
||||
if (onNext == null) throw new ArgumentNullException(nameof(onNext));
|
||||
return Document<TDocument>().Watch(onNext);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public sealed class AetheriaClientCurrentState
|
||||
{
|
||||
internal AetheriaClientCurrentState(
|
||||
AetheriaRuntimeProjectedDocument<AetheriaRuntimeCurrentZoneDocument> zone,
|
||||
AetheriaRuntimeProjectedDocument<AetheriaRuntimeCurrentEntityDocument> entity,
|
||||
AetheriaRuntimeProjectedDocument<AetheriaRuntimeCurrentDockingDocument> docking)
|
||||
CultMeshDocumentHandle<AetheriaRuntimeCurrentZoneDocument> zone,
|
||||
CultMeshDocumentHandle<AetheriaRuntimeCurrentEntityDocument> entity,
|
||||
CultMeshDocumentHandle<AetheriaRuntimeCurrentDockingDocument> docking)
|
||||
{
|
||||
Zone = zone ?? throw new ArgumentNullException(nameof(zone));
|
||||
Entity = entity ?? throw new ArgumentNullException(nameof(entity));
|
||||
Docking = docking ?? throw new ArgumentNullException(nameof(docking));
|
||||
}
|
||||
|
||||
public AetheriaRuntimeProjectedDocument<AetheriaRuntimeCurrentZoneDocument> Zone { get; }
|
||||
public CultMeshDocumentHandle<AetheriaRuntimeCurrentZoneDocument> Zone { get; }
|
||||
|
||||
public AetheriaRuntimeProjectedDocument<AetheriaRuntimeCurrentEntityDocument> Entity { get; }
|
||||
public CultMeshDocumentHandle<AetheriaRuntimeCurrentEntityDocument> Entity { get; }
|
||||
|
||||
public AetheriaRuntimeProjectedDocument<AetheriaRuntimeCurrentDockingDocument> Docking { get; }
|
||||
public CultMeshDocumentHandle<AetheriaRuntimeCurrentDockingDocument> Docking { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -685,7 +685,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
frame => Task.FromResult(AetheriaRuntimeRtsProjection.ProjectZoneRender(frame)),
|
||||
AetheriaRuntimeDaemonSchemas.ZoneRender));
|
||||
|
||||
AetheriaRuntimeProjectedDocument<TDocument> ProjectedDocument<TDocument>(
|
||||
CultMeshDocumentHandle<TDocument> ProjectedDocument<TDocument>(
|
||||
string documentId,
|
||||
Func<AetheriaRuntimeDaemonFrameDocument, Task<TDocument>> project,
|
||||
string schemaId,
|
||||
|
|
@ -703,13 +703,16 @@ namespace GameCult.Aetheria.State.Verse
|
|||
.Append(CultMesh.ProjectionSource(documentId, schemaId, "projected Aetheria client document"))
|
||||
.ToArray();
|
||||
|
||||
return AetheriaRuntimeProjectedDocument<TDocument>.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<AetheriaRuntimeStationRefitDocument> ProjectStationRefitAsync(
|
||||
|
|
|
|||
Loading…
Reference in a new issue