using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using GameCult.Caching;
using GameCult.Caching.MessagePack;
using MessagePack;
using R3;
namespace GameCult.Mesh
{
///
/// Describes the route CultMesh used or should prefer for a typed operation, query, pointer, or native view.
///
public enum CultMeshLocalityKind
{
/// Let CultMesh choose the fastest valid route.
Automatic,
/// The target is in the same process.
InProcess,
/// The target is available through a shared CultCache slab or native shared memory view.
SharedMemory,
/// The target is reachable through local inter-process transport.
Ipc,
/// The target is reachable through a network Verse route.
Network,
/// The target is hosted in or exposed to a WebAssembly runtime.
Wasm
}
///
/// Names a CultMesh locality decision without exposing transport plumbing to application code.
///
public sealed class CultMeshRouteHint
{
/// Creates a route hint.
public CultMeshRouteHint(CultMeshLocalityKind kind = CultMeshLocalityKind.Automatic, string? description = null)
{
Kind = kind;
Description = description;
}
/// Gets the requested or observed locality kind.
public CultMeshLocalityKind Kind { get; }
/// Gets optional human-facing route diagnostics.
public string? Description { get; }
/// Gets the default automatic route hint.
public static CultMeshRouteHint Automatic { get; } = new();
}
///
/// Flat, transport-friendly fields for a CultMesh route hint.
///
public sealed class CultMeshRouteRecord
{
/// Creates flat route fields.
public CultMeshRouteRecord(string? kind = null, string? description = null)
{
Kind = kind ?? "";
Description = description ?? "";
}
/// Gets the serialized route locality kind.
public string Kind { get; }
/// Gets the serialized route description.
public string Description { get; }
/// Creates flat route fields from a route hint.
public static CultMeshRouteRecord FromRoute(CultMeshRouteHint? routeHint)
{
var route = routeHint ?? CultMeshRouteHint.Automatic;
return new CultMeshRouteRecord(route.Kind.ToString(), route.Description);
}
/// Rehydrates flat route fields into a route hint.
public CultMeshRouteHint ToRoute(CultMeshRouteHint? fallback = null)
{
var fallbackRoute = fallback ?? CultMeshRouteHint.Automatic;
return new CultMeshRouteHint(
ParseKind(Kind, fallbackRoute.Kind),
string.IsNullOrWhiteSpace(Description) ? fallbackRoute.Description : Description);
}
private static CultMeshLocalityKind ParseKind(string? value, CultMeshLocalityKind fallback)
{
if (string.IsNullOrWhiteSpace(value))
return fallback;
if (Enum.TryParse(value, ignoreCase: true, out var parsed))
return parsed;
var normalized = value.Replace("-", "", StringComparison.Ordinal);
foreach (CultMeshLocalityKind candidate in Enum.GetValues(typeof(CultMeshLocalityKind)))
{
if (string.Equals(candidate.ToString(), normalized, StringComparison.OrdinalIgnoreCase))
return candidate;
}
return fallback;
}
}
///
/// Shared Verse invocation context used by generated domain sugar.
///
public sealed class CultMeshVerseContext
{
/// Creates a Verse context.
public CultMeshVerseContext(
string verseId,
string runtimeId,
CultMeshRouteHint? routeHint = null,
IEnumerable? claims = null)
{
VerseId = RequireNonEmpty(verseId, nameof(verseId));
RuntimeId = RequireNonEmpty(runtimeId, nameof(runtimeId));
RouteHint = routeHint ?? CultMeshRouteHint.Automatic;
Claims = claims?.ToArray() ?? Array.Empty();
}
/// Gets the semantic Verse id.
public string VerseId { get; }
/// Gets the runtime using the Verse.
public string RuntimeId { get; }
/// Gets the preferred route for generated operations and queries.
public CultMeshRouteHint RouteHint { get; }
/// Gets authority claims carried by generated operations.
public IReadOnlyList Claims { get; }
/// Returns a copy with one additional authority claim.
public CultMeshVerseContext WithClaim(CultMeshAuthorityClaim claim)
{
if (claim == null) throw new ArgumentNullException(nameof(claim));
return new CultMeshVerseContext(VerseId, RuntimeId, RouteHint, Claims.Concat(new[] { claim }));
}
/// Returns a copy with the supplied route hint.
public CultMeshVerseContext WithRoute(CultMeshRouteHint routeHint)
{
if (routeHint == null) throw new ArgumentNullException(nameof(routeHint));
return new CultMeshVerseContext(VerseId, RuntimeId, routeHint, Claims);
}
/// Creates a typed operation context from the Verse context.
public CultMeshOperationContext OperationContext(string? idempotencyKey = null)
{
return new CultMeshOperationContext(RuntimeId, Claims, RouteHint, idempotencyKey);
}
/// Creates a typed query context from the Verse context.
public CultMeshQueryContext QueryContext()
{
return new CultMeshQueryContext(RuntimeId, RouteHint);
}
private static string RequireNonEmpty(string value, string paramName)
{
return string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("Value must be non-empty.", paramName)
: value;
}
}
///
/// Managed Verse handle that generated domain packages can bind to.
///
public sealed class CultMeshVerse
{
/// Creates a managed Verse handle.
public CultMeshVerse(CultMeshVerseContext context)
{
Context = context ?? throw new ArgumentNullException(nameof(context));
}
/// Gets the Verse context shared by generated domain sugar.
public CultMeshVerseContext Context { get; }
/// Gets the semantic Verse id.
public string VerseId => Context.VerseId;
/// Gets the runtime using the Verse.
public string RuntimeId => Context.RuntimeId;
/// Binds this Verse to a generated domain facade.
public TSchema Use(Func schemaFactory)
{
if (schemaFactory == null) throw new ArgumentNullException(nameof(schemaFactory));
return schemaFactory(Context);
}
/// Returns a Verse handle with one additional authority claim.
public CultMeshVerse WithClaim(CultMeshAuthorityClaim claim)
{
return new CultMeshVerse(Context.WithClaim(claim));
}
/// Returns a Verse handle with the supplied route hint.
public CultMeshVerse WithRoute(CultMeshRouteHint routeHint)
{
return new CultMeshVerse(Context.WithRoute(routeHint));
}
/// Creates a typed operation context from this Verse.
public CultMeshOperationContext OperationContext(string? idempotencyKey = null)
{
return Context.OperationContext(idempotencyKey);
}
/// Creates a typed query context from this Verse.
public CultMeshQueryContext QueryContext()
{
return Context.QueryContext();
}
/// Binds an operation handle to this Verse.
public CultMeshBoundOperationHandle BindOperation(
CultMeshOperationHandle operation)
{
return new CultMeshBoundOperationHandle(Context, operation);
}
/// Binds a query surface to this Verse.
public CultMeshBoundQuerySurface BindQuery(
CultMeshQuerySurface query)
{
return new CultMeshBoundQuerySurface(Context, query);
}
/// Binds a live feed to this Verse.
public CultMeshBoundLiveFeed BindLiveFeed(
CultMeshLiveFeed feed)
{
return new CultMeshBoundLiveFeed(Context, feed);
}
/// Binds a state pointer to this Verse.
public CultMeshBoundStatePointer BindStatePointer(
CultMeshStatePointer pointer)
{
return new CultMeshBoundStatePointer(Context, pointer);
}
/// Binds a mutable state pointer to this Verse.
public CultMeshBoundMutableStatePointer BindMutableStatePointer(
CultMeshMutableStatePointer pointer)
{
return new CultMeshBoundMutableStatePointer(Context, pointer);
}
}
///
/// Describes a runtime authority claim attached to a typed operation.
///
public sealed class CultMeshAuthorityClaim
{
/// Creates an authority claim.
public CultMeshAuthorityClaim(string role, string? shardId = null, string? leaseId = null)
{
Role = RequireNonEmpty(role, nameof(role));
ShardId = shardId;
LeaseId = leaseId;
}
/// Gets the role or claim kind being asserted.
public string Role { get; }
/// Gets the optional shard covered by the claim.
public string? ShardId { get; }
/// Gets the optional lease id backing the claim.
public string? LeaseId { get; }
private static string RequireNonEmpty(string value, string paramName)
{
return string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("Value must be non-empty.", paramName)
: value;
}
}
///
/// Context supplied when invoking a typed CultMesh operation.
///
public sealed class CultMeshOperationContext
{
/// Creates an operation context.
public CultMeshOperationContext(
string runtimeId,
IEnumerable? claims = null,
CultMeshRouteHint? routeHint = null,
string? idempotencyKey = null)
{
RuntimeId = RequireNonEmpty(runtimeId, nameof(runtimeId));
Claims = claims?.ToArray() ?? Array.Empty();
RouteHint = routeHint ?? CultMeshRouteHint.Automatic;
IdempotencyKey = idempotencyKey;
}
/// Gets the runtime invoking the operation.
public string RuntimeId { get; }
/// Gets authority claims attached to the operation.
public IReadOnlyList Claims { get; }
/// Gets the preferred route for the operation.
public CultMeshRouteHint RouteHint { get; }
/// Gets an optional operation idempotency key.
public string? IdempotencyKey { get; }
/// Creates an operation context for a runtime.
public static CultMeshOperationContext ForRuntime(string runtimeId)
{
return new CultMeshOperationContext(runtimeId);
}
/// Returns a copy with one additional authority claim.
public CultMeshOperationContext WithClaim(CultMeshAuthorityClaim claim)
{
if (claim == null) throw new ArgumentNullException(nameof(claim));
return new CultMeshOperationContext(RuntimeId, Claims.Concat(new[] { claim }), RouteHint, IdempotencyKey);
}
/// Returns a copy with the supplied route hint.
public CultMeshOperationContext WithRoute(CultMeshRouteHint routeHint)
{
if (routeHint == null) throw new ArgumentNullException(nameof(routeHint));
return new CultMeshOperationContext(RuntimeId, Claims, routeHint, IdempotencyKey);
}
/// Returns a copy with the supplied idempotency key.
public CultMeshOperationContext WithIdempotencyKey(string idempotencyKey)
{
return new CultMeshOperationContext(
RuntimeId,
Claims,
RouteHint,
RequireNonEmpty(idempotencyKey, nameof(idempotencyKey)));
}
private static string RequireNonEmpty(string value, string paramName)
{
return string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("Value must be non-empty.", paramName)
: value;
}
}
///
/// Receipt returned by a typed CultMesh operation.
///
public sealed class CultMeshOperationReceipt
{
/// Creates an operation receipt.
public CultMeshOperationReceipt(
string operationId,
bool accepted,
CultMeshRouteHint? route = null,
string? diagnostic = null)
{
OperationId = RequireNonEmpty(operationId, nameof(operationId));
Accepted = accepted;
Route = route ?? CultMeshRouteHint.Automatic;
Diagnostic = diagnostic;
}
/// Gets the semantic operation id.
public string OperationId { get; }
/// Gets whether the operation was accepted by the target authority boundary.
public bool Accepted { get; }
/// Gets the route used for the operation.
public CultMeshRouteHint Route { get; }
/// Gets optional operation diagnostics.
public string? Diagnostic { get; }
private static string RequireNonEmpty(string value, string paramName)
{
return string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("Value must be non-empty.", paramName)
: value;
}
}
///
/// Method-shaped handle for a typed CultMesh operation.
///
public sealed class CultMeshOperationHandle
{
private readonly Func> _invoke;
/// Creates an operation handle.
public CultMeshOperationHandle(
string operationId,
Func> invoke)
{
OperationId = RequireNonEmpty(operationId, nameof(operationId));
_invoke = invoke ?? throw new ArgumentNullException(nameof(invoke));
}
/// Gets the semantic operation id.
public string OperationId { get; }
/// Invokes the typed operation.
public Task InvokeAsync(TRequest request, CultMeshOperationContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
return _invoke(request, context);
}
/// Invokes the typed operation for one runtime using default context.
public Task InvokeAsync(TRequest request, string runtimeId)
{
return InvokeAsync(request, CultMeshOperationContext.ForRuntime(runtimeId));
}
/// Binds this operation to a Verse context.
public CultMeshBoundOperationHandle Bind(CultMeshVerseContext context)
{
return new CultMeshBoundOperationHandle(context, this);
}
/// Binds this operation to a Verse.
public CultMeshBoundOperationHandle Bind(CultMeshVerse verse)
{
if (verse == null) throw new ArgumentNullException(nameof(verse));
return Bind(verse.Context);
}
private static string RequireNonEmpty(string value, string paramName)
{
return string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("Value must be non-empty.", paramName)
: value;
}
}
///
/// Operation handle pre-bound to a Verse so generated domain sugar can invoke without context plumbing.
///
public sealed class CultMeshBoundOperationHandle
{
/// Creates a Verse-bound operation handle.
public CultMeshBoundOperationHandle(
CultMeshVerseContext context,
CultMeshOperationHandle operation)
{
Context = context ?? throw new ArgumentNullException(nameof(context));
Operation = operation ?? throw new ArgumentNullException(nameof(operation));
}
/// Gets the bound Verse context.
public CultMeshVerseContext Context { get; }
/// Gets the underlying operation handle.
public CultMeshOperationHandle Operation { get; }
/// Gets the semantic operation id.
public string OperationId => Operation.OperationId;
/// Invokes the operation through the bound Verse.
public Task InvokeAsync(TRequest request, string? idempotencyKey = null)
{
return Operation.InvokeAsync(request, Context.OperationContext(idempotencyKey));
}
}
///
/// Context supplied when executing a typed CultMesh query surface.
///
public sealed class CultMeshQueryContext
{
/// Creates a query context.
public CultMeshQueryContext(string runtimeId, CultMeshRouteHint? routeHint = null)
{
RuntimeId = RequireNonEmpty(runtimeId, nameof(runtimeId));
RouteHint = routeHint ?? CultMeshRouteHint.Automatic;
}
/// Gets the runtime executing the query.
public string RuntimeId { get; }
/// Gets the preferred route for the query.
public CultMeshRouteHint RouteHint { get; }
/// Creates a query context for a runtime.
public static CultMeshQueryContext ForRuntime(string runtimeId)
{
return new CultMeshQueryContext(runtimeId);
}
/// Returns a copy with the supplied route hint.
public CultMeshQueryContext WithRoute(CultMeshRouteHint routeHint)
{
if (routeHint == null) throw new ArgumentNullException(nameof(routeHint));
return new CultMeshQueryContext(RuntimeId, routeHint);
}
private static string RequireNonEmpty(string value, string paramName)
{
return string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("Value must be non-empty.", paramName)
: value;
}
}
///
/// Fluent builder for typed operation context. This is sugar over ,
/// intended for call sites that should read like native Verse operations instead of transport setup.
///
public sealed class CultMeshOperationContextBuilder
{
private readonly string _runtimeId;
private readonly List _claims = new();
private CultMeshRouteHint _routeHint = CultMeshRouteHint.Automatic;
private string? _idempotencyKey;
/// Creates a builder for one runtime.
public CultMeshOperationContextBuilder(string runtimeId)
{
_runtimeId = RequireNonEmpty(runtimeId, nameof(runtimeId));
}
/// Adds an authority claim.
public CultMeshOperationContextBuilder Claim(string role, string? shardId = null, string? leaseId = null)
{
_claims.Add(new CultMeshAuthorityClaim(role, shardId, leaseId));
return this;
}
/// Adds an authority claim.
public CultMeshOperationContextBuilder Claim(CultMeshAuthorityClaim claim)
{
_claims.Add(claim ?? throw new ArgumentNullException(nameof(claim)));
return this;
}
/// Adds many authority claims.
public CultMeshOperationContextBuilder Claims(IEnumerable claims)
{
if (claims == null) throw new ArgumentNullException(nameof(claims));
_claims.AddRange(claims);
return this;
}
/// Sets the preferred route hint.
public CultMeshOperationContextBuilder Route(CultMeshLocalityKind kind, string? description = null)
{
_routeHint = new CultMeshRouteHint(kind, description);
return this;
}
/// Sets the operation idempotency key.
public CultMeshOperationContextBuilder Idempotency(string idempotencyKey)
{
_idempotencyKey = RequireNonEmpty(idempotencyKey, nameof(idempotencyKey));
return this;
}
/// Creates the immutable operation context.
public CultMeshOperationContext Build()
{
return new CultMeshOperationContext(_runtimeId, _claims, _routeHint, _idempotencyKey);
}
private static string RequireNonEmpty(string value, string paramName)
{
return string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("Value must be non-empty.", paramName)
: value;
}
}
///
/// Fluent builder for typed query context.
///
public sealed class CultMeshQueryContextBuilder
{
private readonly string _runtimeId;
private CultMeshRouteHint _routeHint = CultMeshRouteHint.Automatic;
/// Creates a builder for one runtime.
public CultMeshQueryContextBuilder(string runtimeId)
{
_runtimeId = RequireNonEmpty(runtimeId, nameof(runtimeId));
}
/// Sets the preferred route hint.
public CultMeshQueryContextBuilder Route(CultMeshLocalityKind kind, string? description = null)
{
_routeHint = new CultMeshRouteHint(kind, description);
return this;
}
/// Creates the immutable query context.
public CultMeshQueryContext Build()
{
return new CultMeshQueryContext(_runtimeId, _routeHint);
}
private static string RequireNonEmpty(string value, string paramName)
{
return string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("Value must be non-empty.", paramName)
: value;
}
}
///
/// Typed derived-state query surface.
///
public sealed class CultMeshQuerySurface
{
private readonly Func> _execute;
private readonly Func>? _watch;
/// Creates a query surface.
public CultMeshQuerySurface(
string queryId,
Func> execute,
Func>? watch = null,
IEnumerable? sources = null,
CultMeshRouteHint? routeHint = null)
{
QueryId = RequireNonEmpty(queryId, nameof(queryId));
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_watch = watch;
Sources = sources?.ToArray() ?? Array.Empty();
RouteHint = routeHint ?? CultMeshRouteHint.Automatic;
}
/// Gets the semantic query id.
public string QueryId { get; }
/// Gets the typed state sources this query depends on, when known.
public IReadOnlyList Sources { get; }
/// Gets the preferred or observed route for the query.
public CultMeshRouteHint RouteHint { get; }
/// Executes the query once.
public Task ExecuteAsync(TParameters parameters, CultMeshQueryContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
return _execute(parameters, context);
}
/// Executes the query once for one runtime using default context.
public Task ExecuteAsync(TParameters parameters, string runtimeId)
{
return ExecuteAsync(parameters, CultMeshQueryContext.ForRuntime(runtimeId));
}
/// Watches query results when the surface supports reactive execution.
public Observable Watch(TParameters parameters, CultMeshQueryContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (_watch == null)
{
throw new NotSupportedException($"Query surface '{QueryId}' does not support reactive watches.");
}
return _watch(parameters, context);
}
/// Binds this query surface to a Verse context.
public CultMeshBoundQuerySurface Bind(CultMeshVerseContext context)
{
return new CultMeshBoundQuerySurface(context, this);
}
/// Binds this query surface to a Verse.
public CultMeshBoundQuerySurface Bind(CultMeshVerse verse)
{
if (verse == null) throw new ArgumentNullException(nameof(verse));
return Bind(verse.Context);
}
private static string RequireNonEmpty(string value, string paramName)
{
return string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("Value must be non-empty.", paramName)
: value;
}
}
///
/// Query surface pre-bound to a Verse so generated domain sugar can execute without context plumbing.
///
public sealed class CultMeshBoundQuerySurface
{
/// Creates a Verse-bound query surface.
public CultMeshBoundQuerySurface(
CultMeshVerseContext context,
CultMeshQuerySurface query)
{
Context = context ?? throw new ArgumentNullException(nameof(context));
Query = query ?? throw new ArgumentNullException(nameof(query));
}
/// Gets the bound Verse context.
public CultMeshVerseContext Context { get; }
/// Gets the underlying query surface.
public CultMeshQuerySurface Query { get; }
/// Gets the semantic query id.
public string QueryId => Query.QueryId;
/// Gets the typed state sources this query depends on, when known.
public IReadOnlyList Sources => Query.Sources;
/// Gets the preferred or observed route for the query.
public CultMeshRouteHint RouteHint => Query.RouteHint;
/// Executes the query through the bound Verse.
public Task ExecuteAsync(TParameters parameters)
{
return Query.ExecuteAsync(parameters, Context.QueryContext());
}
/// Watches query results through the bound Verse.
public Observable Watch(TParameters parameters)
{
return Query.Watch(parameters, Context.QueryContext());
}
}
///
/// Typed live view surface for composed client snapshots.
///
public sealed class CultMeshLiveFeed
{
private readonly Func> _snapshot;
private readonly Func>? _watch;
/// Creates a live feed surface.
public CultMeshLiveFeed(
string feedId,
Func> snapshot,
Func>? watch = null,
IEnumerable? sources = null,
CultMeshRouteHint? routeHint = null)
{
FeedId = RequireNonEmpty(feedId, nameof(feedId));
_snapshot = snapshot ?? throw new ArgumentNullException(nameof(snapshot));
_watch = watch;
Sources = sources?.ToArray() ?? Array.Empty();
RouteHint = routeHint ?? CultMeshRouteHint.Automatic;
}
/// Gets the semantic feed id.
public string FeedId { get; }
/// Gets the typed state sources this live feed depends on, when known.
public IReadOnlyList Sources { get; }
/// Gets the preferred or observed route for the feed.
public CultMeshRouteHint RouteHint { get; }
/// Reads one coherent feed snapshot.
public Task SnapshotAsync(TParameters parameters, CultMeshQueryContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
return _snapshot(parameters, ResolveContext(context));
}
/// Reads one coherent feed snapshot for one runtime using default context.
public Task SnapshotAsync(TParameters parameters, string runtimeId)
{
return SnapshotAsync(parameters, CultMeshQueryContext.ForRuntime(runtimeId));
}
/// Watches coherent feed snapshots when the feed supports reactive execution.
public Observable Watch(TParameters parameters, CultMeshQueryContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (_watch == null)
{
throw new NotSupportedException($"Live feed '{FeedId}' does not support reactive watches.");
}
return _watch(parameters, ResolveContext(context));
}
/// Binds this live feed to a Verse context.
public CultMeshBoundLiveFeed Bind(CultMeshVerseContext context)
{
return new CultMeshBoundLiveFeed(context, this);
}
/// Binds this live feed to a Verse.
public CultMeshBoundLiveFeed Bind(CultMeshVerse verse)
{
if (verse == null) throw new ArgumentNullException(nameof(verse));
return Bind(verse.Context);
}
private CultMeshQueryContext ResolveContext(CultMeshQueryContext context)
{
return context.RouteHint.Kind == CultMeshLocalityKind.Automatic &&
RouteHint.Kind != CultMeshLocalityKind.Automatic
? context.WithRoute(RouteHint)
: context;
}
private static string RequireNonEmpty(string value, string paramName)
{
return string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("Value must be non-empty.", paramName)
: value;
}
}
///
/// Live feed pre-bound to a Verse so generated domain sugar can snapshot or watch without context plumbing.
///
public sealed class CultMeshBoundLiveFeed
{
/// Creates a Verse-bound live feed.
public CultMeshBoundLiveFeed(
CultMeshVerseContext context,
CultMeshLiveFeed feed)
{
Context = context ?? throw new ArgumentNullException(nameof(context));
Feed = feed ?? throw new ArgumentNullException(nameof(feed));
}
/// Gets the bound Verse context.
public CultMeshVerseContext Context { get; }
/// Gets the underlying live feed.
public CultMeshLiveFeed Feed { get; }
/// Gets the semantic feed id.
public string FeedId => Feed.FeedId;
/// Gets the typed state sources this live feed depends on, when known.
public IReadOnlyList Sources => Feed.Sources;
/// Gets the preferred or observed route for the feed.
public CultMeshRouteHint RouteHint => Feed.RouteHint;
/// Reads one coherent feed snapshot through the bound Verse.
public Task SnapshotAsync(TParameters parameters)
{
return Feed.SnapshotAsync(parameters, Context.QueryContext());
}
/// Watches coherent feed snapshots through the bound Verse.
public Observable Watch(TParameters parameters)
{
return Feed.Watch(parameters, Context.QueryContext());
}
}
///
/// Empty parameter object for document handles that expose one typed document as a live feed.
///
public readonly struct CultMeshDocumentQueryParameters
{
/// Gets the singleton empty document query.
public static CultMeshDocumentQueryParameters Empty { get; } = new();
}
///
/// Inspectable typed document handle exposed by CultMesh.
///
public interface ICultMeshDocumentHandle
{
/// Gets the CLR document type presented by this handle.
Type DocumentType { get; }
/// Gets the semantic document id.
string DocumentId { get; }
/// Gets the stable CultCache schema name.
string SchemaName { get; }
/// Gets the stable CultCache schema version.
string SchemaVersion { get; }
/// Gets the preferred or observed route for document access.
CultMeshRouteHint RouteHint { get; }
/// Gets typed state sources this document handle depends on, when known.
IReadOnlyList Sources { get; }
/// Gets whether this handle can replace the underlying document value.
bool CanReplace { get; }
/// Gets whether this handle can submit a client prediction for the underlying document value.
bool CanSubmitPrediction { get; }
/// Gets whether this handle can accept a transparent document mutation.
bool CanSet { get; }
/// Creates a same-schema alias presentation for another CLR document type.
CultMeshDocumentHandle AsSchemaAlias() where TAlias : class;
}
///
/// Typed reactive document handle with schema-aware alias conversion.
///
public sealed class CultMeshDocumentHandle : ICultMeshDocumentHandle
where TDocument : class
{
private static readonly CultDocumentDescriptor Descriptor =
CultDocumentRegistry.Shared.GetRequired();
private readonly CultMeshBoundLiveFeed _feed;
private readonly Func? _replace;
private readonly Func? _submitPrediction;
/// Creates a document handle from a Verse-bound live feed.
public CultMeshDocumentHandle(
CultMeshBoundLiveFeed feed,
Func? replace = null,
Func? submitPrediction = null)
{
_feed = feed ?? throw new ArgumentNullException(nameof(feed));
_replace = replace;
_submitPrediction = submitPrediction;
}
/// Gets the semantic document id.
public string DocumentId => _feed.FeedId;
/// Gets the CLR document type presented by this handle.
public Type DocumentType => typeof(TDocument);
/// Gets the stable CultCache schema name.
public string SchemaName => Descriptor.SchemaName;
/// Gets the stable CultCache schema version.
public string SchemaVersion => Descriptor.SchemaVersion;
/// Gets the content-derived schema identifier.
public string SchemaId => Descriptor.SchemaId;
/// Gets the preferred or observed route for document access.
public CultMeshRouteHint RouteHint => _feed.RouteHint;
/// Gets typed state sources this document handle depends on, when known.
public IReadOnlyList Sources => _feed.Sources;
/// Gets the bound Verse context.
public CultMeshVerseContext Context => _feed.Context;
/// Gets the underlying live feed used by this document handle.
public CultMeshLiveFeed Feed => _feed.Feed;
/// Gets whether this handle can replace the underlying document value.
public bool CanReplace => _replace != null;
/// Gets whether this handle can submit a client prediction for the underlying document value.
public bool CanSubmitPrediction => _submitPrediction != null;
/// Gets whether this handle can accept a transparent document mutation.
public bool CanSet => CanSubmitPrediction || CanReplace;
/// Reads one coherent document snapshot.
public Task LatestAsync()
{
return _feed.SnapshotAsync(CultMeshDocumentQueryParameters.Empty);
}
/// Reads the latest document snapshot synchronously for host APIs that cannot be async.
public TDocument Latest()
{
return LatestAsync().ConfigureAwait(false).GetAwaiter().GetResult();
}
/// Watches coherent document snapshots.
public Observable Watch()
{
return _feed.Watch(CultMeshDocumentQueryParameters.Empty);
}
/// Subscribes to coherent document snapshots.
public IDisposable Watch(Action onNext)
{
if (onNext == null) throw new ArgumentNullException(nameof(onNext));
return Watch().Subscribe(onNext);
}
/// Replaces the underlying document when this handle is backed by mutable state.
public Task ReplaceAsync(TDocument value)
{
if (value == null) throw new ArgumentNullException(nameof(value));
if (_replace == null)
{
throw new NotSupportedException($"Document handle '{DocumentId}' is read-only.");
}
return _replace(value);
}
/// Submits a locally predicted document value when this handle is backed by client-authoritative state.
public Task SubmitPredictionAsync(TDocument value)
{
if (value == null) throw new ArgumentNullException(nameof(value));
if (_submitPrediction == null)
{
throw new NotSupportedException($"Document handle '{DocumentId}' does not accept client predictions.");
}
return _submitPrediction(value);
}
///
/// Sets the document value through the configured authority shape.
/// Prediction-backed documents publish a prediction; mutable documents replace authoritatively.
///
public Task SetAsync(TDocument value)
{
if (value == null) throw new ArgumentNullException(nameof(value));
if (_submitPrediction != null)
return _submitPrediction(value);
if (_replace != null)
return _replace(value);
throw new NotSupportedException($"Document handle '{DocumentId}' does not accept mutations.");
}
/// Reads, updates, and sets the document value through the configured authority shape.
public async Task UpdateAsync(Func update)
{
if (update == null) throw new ArgumentNullException(nameof(update));
var next = update(await LatestAsync().ConfigureAwait(false));
await SetAsync(next).ConfigureAwait(false);
return next;
}
/// Reads, updates, and sets the document value through the configured authority shape.
public async Task UpdateAsync(Func> update)
{
if (update == null) throw new ArgumentNullException(nameof(update));
var next = await update(await LatestAsync().ConfigureAwait(false)).ConfigureAwait(false);
await SetAsync(next).ConfigureAwait(false);
return next;
}
///
/// Creates a managed reactive document mirror whose local edits can be coalesced into one prediction or replacement.
///
public async Task> ReactiveAsync(
CultMeshReactiveDocumentOptions? options = null)
{
var current = CloneDocument(await LatestAsync().ConfigureAwait(false));
var reactive = new CultMeshReactiveDocument(this, current, options);
reactive.Start();
return reactive;
}
///
/// Creates a managed reactive document mirror synchronously for host APIs that cannot be async.
///
public CultMeshReactiveDocument Reactive(CultMeshReactiveDocumentOptions? options = null)
{
return ReactiveAsync(options).ConfigureAwait(false).GetAwaiter().GetResult();
}
/// Creates a same-schema alias presentation for another CLR document type.
public CultMeshDocumentHandle AsSchemaAlias() where TAlias : class
{
var aliasDescriptor = CultDocumentRegistry.Shared.GetRequired();
if (!string.Equals(Descriptor.SchemaName, aliasDescriptor.SchemaName, StringComparison.Ordinal) ||
!string.Equals(Descriptor.SchemaVersion, aliasDescriptor.SchemaVersion, StringComparison.Ordinal))
{
throw new InvalidOperationException(
$"Document type {typeof(TAlias).FullName} uses schema '{aliasDescriptor.SchemaName}' " +
$"version '{aliasDescriptor.SchemaVersion}', but handle '{DocumentId}' exposes " +
$"schema '{SchemaName}' version '{SchemaVersion}'.");
}
var aliasFeed = new CultMeshLiveFeed(
DocumentId,
async (parameters, context) => ConvertDocument(
await Feed.SnapshotAsync(parameters, context).ConfigureAwait(false)),
(parameters, context) => Feed
.Watch(parameters, context)
.Select(ConvertDocument),
Sources,
RouteHint);
Func? replace = _replace == null
? null
: value => _replace(ConvertDocument(value));
Func? submitPrediction = _submitPrediction == null
? null
: value => _submitPrediction(ConvertDocument(value));
return new CultMeshDocumentHandle(aliasFeed.Bind(Context), replace, submitPrediction);
}
internal static TDocumentValue CloneDocument(TDocumentValue document)
where TDocumentValue : class
{
if (document == null) throw new ArgumentNullException(nameof(document));
var payload = CultDocumentMessagePackSerialization.SerializeUntyped(document, typeof(TDocumentValue));
return (TDocumentValue)CultDocumentMessagePackSerialization.DeserializeUntyped(
typeof(TDocumentValue),
payload);
}
internal static TTarget ConvertDocument(TSource document)
where TSource : class
where TTarget : class
{
if (document == null) throw new ArgumentNullException(nameof(document));
if (document is TTarget alreadyTyped)
{
return alreadyTyped;
}
var payload = CultDocumentMessagePackSerialization.SerializeUntyped(document, typeof(TSource));
return (TTarget)CultDocumentMessagePackSerialization.DeserializeUntyped(typeof(TTarget), payload);
}
}
///
/// Configures a managed CultMesh reactive document mirror.
///
public sealed class CultMeshReactiveDocumentOptions
{
///
/// Gets or sets the write coalescing window. The default approximates a frame boundary for non-Unity hosts.
///
public TimeSpan FlushDelay { get; set; } = TimeSpan.FromMilliseconds(16);
/// Gets or sets whether canonical snapshots replace dirty local predictions immediately.
public bool ReplaceDirtyCurrentOnCanonicalSnapshot { get; set; }
}
///
/// Captures a canonical snapshot that arrived while the local reactive document had an outstanding prediction.
///
public sealed class CultMeshReactiveDocumentReconciliation
where TDocument : class
{
internal CultMeshReactiveDocumentReconciliation(
TDocument canonical,
TDocument predicted,
DateTimeOffset receivedAt)
{
Canonical = canonical;
Predicted = predicted;
ReceivedAt = receivedAt;
}
/// Gets the authoritative canonical document snapshot.
public TDocument Canonical { get; }
/// Gets the locally predicted document snapshot that was active when the canonical value arrived.
public TDocument Predicted { get; }
/// Gets when the canonical snapshot was received.
public DateTimeOffset ReceivedAt { get; }
}
///
/// Managed typed document mirror that keeps an editable current value and coalesces local edits into CultMesh mutations.
///
public sealed class CultMeshReactiveDocument : IDisposable
where TDocument : class
{
private readonly CultMeshDocumentHandle _document;
private readonly CultMeshReactiveDocumentOptions _options;
private readonly object _gate = new();
private IDisposable? _subscription;
private Timer? _timer;
private bool _dirty;
private bool _flushQueued;
private bool _flushing;
private bool _disposed;
internal CultMeshReactiveDocument(
CultMeshDocumentHandle document,
TDocument current,
CultMeshReactiveDocumentOptions? options)
{
_document = document ?? throw new ArgumentNullException(nameof(document));
Current = current ?? throw new ArgumentNullException(nameof(current));
_options = options ?? new CultMeshReactiveDocumentOptions();
}
/// Gets the underlying document handle.
public CultMeshDocumentHandle Document => _document;
/// Gets the editable local document value.
public TDocument Current { get; private set; }
/// Gets whether local edits are waiting to be sent.
public bool IsDirty
{
get
{
lock (_gate)
return _dirty;
}
}
/// Gets the most recent reconciliation snapshot, when a canonical value arrived during local prediction.
public CultMeshReactiveDocumentReconciliation? Reconciliation { get; private set; }
internal void Start()
{
_subscription = _document.Watch(ApplyCanonicalSnapshot);
}
/// Marks the current value dirty and schedules a coalesced prediction or replacement.
public void MarkDirty()
{
ThrowIfDisposed();
lock (_gate)
{
_dirty = true;
ScheduleFlushLocked();
}
}
/// Mutates the current value and schedules a coalesced prediction or replacement.
public TDocument Update(Action update)
{
if (update == null) throw new ArgumentNullException(nameof(update));
ThrowIfDisposed();
lock (_gate)
{
update(Current);
_dirty = true;
ScheduleFlushLocked();
return Current;
}
}
/// Replaces the current local value and schedules a coalesced prediction or replacement.
public TDocument SetCurrent(TDocument value)
{
if (value == null) throw new ArgumentNullException(nameof(value));
ThrowIfDisposed();
lock (_gate)
{
Current = CultMeshDocumentHandle.CloneDocument(value);
_dirty = true;
ScheduleFlushLocked();
return Current;
}
}
/// Reads a fresh canonical snapshot and adopts it as the current value.
public async Task RefreshAsync()
{
ThrowIfDisposed();
var latest = CultMeshDocumentHandle.CloneDocument(
await _document.LatestAsync().ConfigureAwait(false));
lock (_gate)
{
Current = latest;
_dirty = false;
Reconciliation = null;
return Current;
}
}
/// Immediately sends the latest local dirty value, if any, through the document authority shape.
public async Task FlushAsync()
{
TDocument predicted;
lock (_gate)
{
ThrowIfDisposed();
if (!_dirty)
return;
if (_flushing)
{
_flushQueued = true;
return;
}
_timer?.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
_flushing = true;
_dirty = false;
predicted = CultMeshDocumentHandle.CloneDocument(Current);
}
try
{
await _document.SetAsync(predicted).ConfigureAwait(false);
}
finally
{
var shouldFlushAgain = false;
lock (_gate)
{
_flushing = false;
shouldFlushAgain = _flushQueued || _dirty;
_flushQueued = false;
}
if (shouldFlushAgain)
await FlushAsync().ConfigureAwait(false);
}
}
/// Clears the most recent reconciliation metadata.
public void ClearReconciliation()
{
ThrowIfDisposed();
Reconciliation = null;
}
///
public void Dispose()
{
lock (_gate)
{
if (_disposed)
return;
_disposed = true;
}
_subscription?.Dispose();
_timer?.Dispose();
}
private void ApplyCanonicalSnapshot(TDocument canonical)
{
if (canonical == null)
return;
lock (_gate)
{
if (_disposed)
return;
var nextCanonical = CultMeshDocumentHandle.CloneDocument(canonical);
if (_dirty || _flushing)
{
Reconciliation = new CultMeshReactiveDocumentReconciliation(
nextCanonical,
CultMeshDocumentHandle.CloneDocument(Current),
DateTimeOffset.UtcNow);
if (!_options.ReplaceDirtyCurrentOnCanonicalSnapshot)
return;
}
Current = nextCanonical;
Reconciliation = null;
}
}
private void ScheduleFlushLocked()
{
if (_options.FlushDelay <= TimeSpan.Zero)
{
_ = Task.Run(FlushAsync);
return;
}
_timer ??= new Timer(_ => _ = Task.Run(FlushAsync));
_timer.Change(_options.FlushDelay, Timeout.InfiniteTimeSpan);
}
private void ThrowIfDisposed()
{
if (_disposed)
throw new ObjectDisposedException(nameof(CultMeshReactiveDocument));
}
}
///
/// Schema-aware lookup table for typed CultMesh document handles.
///
public sealed class CultMeshDocumentCatalog
{
private readonly IReadOnlyList _documents;
private readonly IReadOnlyDictionary _documentsByType;
private readonly IReadOnlyDictionary _documentsBySchema;
/// Creates a document catalog from known handles.
public CultMeshDocumentCatalog(IEnumerable documents)
{
if (documents == null) throw new ArgumentNullException(nameof(documents));
var documentList = documents
.Where(document => document != null)
.ToArray();
var byType = new Dictionary();
var bySchema = new Dictionary(StringComparer.Ordinal);
foreach (var document in documentList)
{
byType[document.DocumentType] = document;
if (!string.IsNullOrWhiteSpace(document.SchemaVersion))
bySchema[document.SchemaVersion] = document;
if (!string.IsNullOrWhiteSpace(document.SchemaName))
bySchema[document.SchemaName] = document;
}
_documents = documentList;
_documentsByType = byType;
_documentsBySchema = bySchema;
}
/// Gets the handles in this catalog.
public IReadOnlyList Documents => _documents;
/// Looks up a document handle by shared schema name or schema version.
public bool TryGetDocumentBySchema(
string schema,
out ICultMeshDocumentHandle document)
{
if (!string.IsNullOrWhiteSpace(schema) &&
_documentsBySchema.TryGetValue(schema, out document!))
{
return true;
}
document = null!;
return false;
}
/// Looks up a document handle by shared schema name or schema version.
public ICultMeshDocumentHandle DocumentBySchema(string schema)
{
if (TryGetDocumentBySchema(schema, out var document))
return document;
throw new NotSupportedException(
$"CultMesh document catalog does not expose a document for schema '{schema}'.");
}
/// Looks up a typed document handle by CLR type or same-schema alias.
public bool TryGetDocument(
out CultMeshDocumentHandle document)
where TDocument : class
{
if (_documentsByType.TryGetValue(typeof(TDocument), out var untypedDocument) &&
untypedDocument is CultMeshDocumentHandle typedDocument)
{
document = typedDocument;
return true;
}
var descriptor = CultDocumentRegistry.Shared.GetRequired();
if (_documentsBySchema.TryGetValue(descriptor.SchemaVersion, out var schemaDocument) ||
_documentsBySchema.TryGetValue(descriptor.SchemaName, out schemaDocument!))
{
if (schemaDocument is CultMeshDocumentHandle schemaTypedDocument)
{
document = schemaTypedDocument;
return true;
}
document = schemaDocument.AsSchemaAlias();
return true;
}
document = null!;
return false;
}
/// Looks up a typed document handle by CLR type or same-schema alias.
public CultMeshDocumentHandle Document()
where TDocument : class
{
if (TryGetDocument(out var document))
return document;
throw new NotSupportedException(
$"CultMesh document catalog does not expose a document for {typeof(TDocument).FullName}.");
}
/// Reads one typed document snapshot by CLR type or same-schema alias.
public Task LatestAsync()
where TDocument : class
{
return Document().LatestAsync();
}
/// Reads one typed document synchronously by CLR type or same-schema alias.
public TDocument Latest()
where TDocument : class
{
return Document().Latest();
}
/// Gets whether one typed document can be replaced by CLR type or same-schema alias.
public bool CanReplace()
where TDocument : class
{
return TryGetDocument(out var document) && document.CanReplace;
}
/// Replaces one typed document by CLR type or same-schema alias.
public Task ReplaceAsync(TDocument value)
where TDocument : class
{
return Document().ReplaceAsync(value);
}
/// Gets whether one typed document can submit client predictions by CLR type or same-schema alias.
public bool CanSubmitPrediction()
where TDocument : class
{
return TryGetDocument(out var document) && document.CanSubmitPrediction;
}
/// Gets whether one typed document can accept transparent mutations by CLR type or same-schema alias.
public bool CanSet()
where TDocument : class
{
return TryGetDocument(out var document) && document.CanSet;
}
/// Submits a client prediction for one typed document by CLR type or same-schema alias.
public Task SubmitPredictionAsync(TDocument value)
where TDocument : class
{
return Document().SubmitPredictionAsync(value);
}
/// Sets one typed document through its configured authority shape by CLR type or same-schema alias.
public Task SetAsync(TDocument value)
where TDocument : class
{
return Document().SetAsync(value);
}
/// Reads, updates, and sets one typed document through its configured authority shape.
public Task UpdateAsync(Func update)
where TDocument : class
{
return Document().UpdateAsync(update);
}
/// Reads, updates, and sets one typed document through its configured authority shape.
public Task UpdateAsync(Func> update)
where TDocument : class
{
return Document().UpdateAsync(update);
}
/// Watches one typed document by CLR type or same-schema alias.
public Observable Watch()
where TDocument : class
{
return Document().Watch();
}
/// Subscribes to one typed document by CLR type or same-schema alias.
public IDisposable Watch(Action onNext)
where TDocument : class
{
return Document().Watch(onNext);
}
/// Creates a managed reactive document mirror by CLR type or same-schema alias.
public Task> ReactiveAsync(
CultMeshReactiveDocumentOptions? options = null)
where TDocument : class
{
return Document().ReactiveAsync(options);
}
/// Creates a managed reactive document mirror synchronously by CLR type or same-schema alias.
public CultMeshReactiveDocument Reactive(
CultMeshReactiveDocumentOptions? options = null)
where TDocument : class
{
return Document().Reactive(options);
}
}
///
/// Describes how a CultMesh collection document changed.
///
public enum CultMeshCollectionChangeKind
{
/// A document was added.
Added,
/// A document was updated.
Updated,
/// A document was removed.
Removed,
/// A local prediction was published before authority accepted it.
Predicted,
/// A predicted document was reconciled with authority.
Reconciled,
/// A document was accepted through schema migration.
SchemaMigrated,
/// A document change was rejected.
Rejected
}
///
/// One typed document change observed through a CultMesh collection handle.
///
public sealed class CultMeshCollectionChange
where TDocument : class
{
/// Creates a collection change.
public CultMeshCollectionChange(
CultMeshCollectionChangeKind kind,
CultRecordKey key,
string schemaId,
TDocument? document,
TDocument? previousDocument,
string? message = null)
{
Kind = kind;
Key = key;
SchemaId = schemaId ?? "";
Document = document;
PreviousDocument = previousDocument;
Message = message;
}
/// Gets the change kind.
public CultMeshCollectionChangeKind Kind { get; }
/// Gets the changed record key.
public CultRecordKey Key { get; }
/// Gets the schema id of the changed document.
public string SchemaId { get; }
/// Gets the current document, when present.
public TDocument? Document { get; }
/// Gets the previous document, when present.
public TDocument? PreviousDocument { get; }
/// Gets an optional rejection or diagnostic message.
public string? Message { get; }
}
///
/// Typed reactive collection handle with schema-aware alias conversion.
///
public sealed class CultMeshCollectionHandle
where TDocument : class
{
private static readonly CultDocumentDescriptor Descriptor =
CultDocumentRegistry.Shared.GetRequired();
private readonly Func>> _latest;
private readonly Func>> _watchChanges;
/// Creates a collection handle.
public CultMeshCollectionHandle(
string collectionId,
Func>> latest,
Func>> watchChanges,
IEnumerable? sources = null,
CultMeshRouteHint? routeHint = null)
{
CollectionId = string.IsNullOrWhiteSpace(collectionId)
? throw new ArgumentException("Value must be non-empty.", nameof(collectionId))
: collectionId;
_latest = latest ?? throw new ArgumentNullException(nameof(latest));
_watchChanges = watchChanges ?? throw new ArgumentNullException(nameof(watchChanges));
Sources = sources?.ToArray() ?? Array.Empty();
RouteHint = routeHint ?? CultMeshRouteHint.Automatic;
}
/// Gets the semantic collection id.
public string CollectionId { get; }
/// Gets the CLR document type presented by this collection.
public Type DocumentType => typeof(TDocument);
/// Gets the stable CultCache schema name.
public string SchemaName => Descriptor.SchemaName;
/// Gets the stable CultCache schema version.
public string SchemaVersion => Descriptor.SchemaVersion;
/// Gets the content-derived schema identifier.
public string SchemaId => Descriptor.SchemaId;
/// Gets the preferred or observed route for collection access.
public CultMeshRouteHint RouteHint { get; }
/// Gets typed state sources this collection handle depends on, when known.
public IReadOnlyList Sources { get; }
/// Reads one coherent collection snapshot.
public Task> LatestAsync()
{
return _latest();
}
/// Watches typed collection changes.
public Observable> WatchChanges()
{
return _watchChanges();
}
/// Subscribes to typed collection changes.
public IDisposable WatchChanges(Action> onNext)
{
if (onNext == null) throw new ArgumentNullException(nameof(onNext));
return WatchChanges().Subscribe(onNext);
}
/// Creates a same-schema alias presentation for another CLR document type.
public CultMeshCollectionHandle AsSchemaAlias() where TAlias : class
{
var aliasDescriptor = CultDocumentRegistry.Shared.GetRequired();
if (!string.Equals(Descriptor.SchemaName, aliasDescriptor.SchemaName, StringComparison.Ordinal) ||
!string.Equals(Descriptor.SchemaVersion, aliasDescriptor.SchemaVersion, StringComparison.Ordinal))
{
throw new InvalidOperationException(
$"Document type {typeof(TAlias).FullName} uses schema '{aliasDescriptor.SchemaName}' " +
$"version '{aliasDescriptor.SchemaVersion}', but collection '{CollectionId}' exposes " +
$"schema '{SchemaName}' version '{SchemaVersion}'.");
}
return new CultMeshCollectionHandle(
CollectionId,
async () => (await LatestAsync().ConfigureAwait(false))
.Select(ConvertDocument)
.ToArray(),
() => WatchChanges().Select(change => new CultMeshCollectionChange(
change.Kind,
change.Key,
change.SchemaId,
change.Document == null ? null : ConvertDocument(change.Document),
change.PreviousDocument == null ? null : ConvertDocument(change.PreviousDocument),
change.Message)),
Sources,
RouteHint);
}
private static TTarget ConvertDocument(TSource document)
where TSource : class
where TTarget : class
{
if (document == null) throw new ArgumentNullException(nameof(document));
if (document is TTarget alreadyTyped)
{
return alreadyTyped;
}
var payload = CultDocumentMessagePackSerialization.SerializeUntyped(document, typeof(TSource));
return (TTarget)CultDocumentMessagePackSerialization.DeserializeUntyped(typeof(TTarget), payload);
}
}
///
/// Inspectable metadata for a typed live feed surface.
///
public sealed class CultMeshLiveFeedDiagnostic
{
/// Creates live feed diagnostics.
public CultMeshLiveFeedDiagnostic(
string feedId,
CultMeshRouteHint? routeHint = null,
IEnumerable? sources = null)
{
FeedId = RequireNonEmpty(feedId, nameof(feedId));
RouteHint = routeHint ?? CultMeshRouteHint.Automatic;
Sources = sources?.ToArray() ?? Array.Empty();
}
/// Gets the semantic feed id.
public string FeedId { get; }
/// Gets the preferred or observed route for the feed.
public CultMeshRouteHint RouteHint { get; }
/// Gets the typed state sources this live feed depends on, when known.
public IReadOnlyList Sources { get; }
private static string RequireNonEmpty(string value, string paramName)
{
return string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("Value must be non-empty.", paramName)
: value;
}
}
///
/// Inspectable metadata for a typed query surface.
///
public sealed class CultMeshQuerySurfaceDiagnostic
{
/// Creates query surface diagnostics.
public CultMeshQuerySurfaceDiagnostic(
string queryId,
CultMeshRouteHint? routeHint = null,
IEnumerable? sources = null)
{
QueryId = RequireNonEmpty(queryId, nameof(queryId));
RouteHint = routeHint ?? CultMeshRouteHint.Automatic;
Sources = sources?.ToArray() ?? Array.Empty();
}
/// Gets the semantic query id.
public string QueryId { get; }
/// Gets the preferred or observed route for the query.
public CultMeshRouteHint RouteHint { get; }
/// Gets the typed state sources this query depends on, when known.
public IReadOnlyList Sources { get; }
private static string RequireNonEmpty(string value, string paramName)
{
return string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("Value must be non-empty.", paramName)
: value;
}
}
///
/// Inspectable metadata for a typed operation handle.
///
public sealed class CultMeshOperationHandleDiagnostic
{
/// Creates operation handle diagnostics.
public CultMeshOperationHandleDiagnostic(string operationId)
{
OperationId = RequireNonEmpty(operationId, nameof(operationId));
}
/// Gets the semantic operation id.
public string OperationId { get; }
private static string RequireNonEmpty(string value, string paramName)
{
return string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("Value must be non-empty.", paramName)
: value;
}
}
///
/// Inspectable metadata for a typed state pointer.
///
public sealed class CultMeshStatePointerDiagnostic
{
/// Creates state pointer diagnostics.
public CultMeshStatePointerDiagnostic(
string pointerId,
CultMeshRouteHint? routeHint = null,
IEnumerable? sources = null)
{
PointerId = RequireNonEmpty(pointerId, nameof(pointerId));
RouteHint = routeHint ?? CultMeshRouteHint.Automatic;
Sources = sources?.ToArray() ?? Array.Empty();
}
/// Gets the semantic pointer id.
public string PointerId { get; }
/// Gets the preferred or observed route for pointer resolution.
public CultMeshRouteHint RouteHint { get; }
/// Gets typed state sources this pointer depends on, when known.
public IReadOnlyList Sources { get; }
private static string RequireNonEmpty(string value, string paramName)
{
return string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("Value must be non-empty.", paramName)
: value;
}
}
///
/// Inspectable metadata for a typed projection recipe.
///
public sealed class CultMeshProjectionRecipeDiagnostic
{
/// Creates projection recipe diagnostics.
public CultMeshProjectionRecipeDiagnostic(
string projectionId,
CultMeshRouteHint? routeHint = null,
IEnumerable? sources = null)
{
ProjectionId = RequireNonEmpty(projectionId, nameof(projectionId));
RouteHint = routeHint ?? CultMeshRouteHint.Automatic;
Sources = sources?.ToArray() ?? Array.Empty();
}
/// Gets the semantic projection id.
public string ProjectionId { get; }
/// Gets the preferred or observed route for the projection.
public CultMeshRouteHint RouteHint { get; }
/// Gets the typed state sources this projection depends on, when known.
public IReadOnlyList Sources { get; }
private static string RequireNonEmpty(string value, string paramName)
{
return string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("Value must be non-empty.", paramName)
: value;
}
}
///
/// Names the kind of typed surface described in a CultMesh surface catalog.
///
public enum CultMeshSurfaceKind
{
/// A typed derived-state query surface.
Query,
/// A reusable typed projection recipe.
ProjectionRecipe,
/// A coherent live view feed.
LiveFeed,
/// A typed operation handle.
Operation,
/// A typed reactive document handle.
Document,
/// A typed reactive collection handle.
Collection,
/// A typed pointer to Verse state.
StatePointer,
/// A native slice or slab view.
NativeSliceView
}
///
/// One inspectable typed surface advertised by a runtime.
///
public sealed class CultMeshSurfaceDiagnostic
{
/// Creates a surface diagnostic.
public CultMeshSurfaceDiagnostic(
CultMeshSurfaceKind kind,
string surfaceId,
CultMeshRouteHint? routeHint = null,
IEnumerable? sources = null)
{
Kind = kind;
SurfaceId = RequireNonEmpty(surfaceId, nameof(surfaceId));
RouteHint = routeHint ?? CultMeshRouteHint.Automatic;
Sources = sources?.ToArray() ?? Array.Empty();
}
/// Gets the kind of surface.
public CultMeshSurfaceKind Kind { get; }
/// Gets the semantic surface id.
public string SurfaceId { get; }
/// Gets the preferred or observed route for the surface.
public CultMeshRouteHint RouteHint { get; }
/// Gets typed state sources this surface depends on, when known.
public IReadOnlyList Sources { get; }
private static string RequireNonEmpty(string value, string paramName)
{
return string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("Value must be non-empty.", paramName)
: value;
}
}
///
/// Inspectable catalog of typed surfaces exposed by a runtime or generated binding.
///
public sealed class CultMeshSurfaceCatalogDiagnostic
{
/// Creates a surface catalog diagnostic.
public CultMeshSurfaceCatalogDiagnostic(
string catalogId,
IEnumerable surfaces)
{
CatalogId = RequireNonEmpty(catalogId, nameof(catalogId));
Surfaces = (surfaces ?? throw new ArgumentNullException(nameof(surfaces))).ToArray();
}
/// Gets the semantic catalog id.
public string CatalogId { get; }
/// Gets the advertised typed surfaces.
public IReadOnlyList Surfaces { get; }
/// Finds a surface by semantic id.
public CultMeshSurfaceDiagnostic? Find(string surfaceId)
{
if (string.IsNullOrWhiteSpace(surfaceId)) return null;
return Surfaces.FirstOrDefault(surface => string.Equals(surface.SurfaceId, surfaceId, StringComparison.Ordinal));
}
/// Finds surfaces by kind in catalog order.
public IReadOnlyList FindByKind(CultMeshSurfaceKind kind)
{
return Surfaces.Where(surface => surface.Kind == kind).ToArray();
}
/// Groups surfaces by kind for generated bindings and tooling.
public CultMeshSurfaceCatalogIndexDiagnostic IndexByKind()
{
return new CultMeshSurfaceCatalogIndexDiagnostic(this);
}
private static string RequireNonEmpty(string value, string paramName)
{
return string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("Value must be non-empty.", paramName)
: value;
}
}
///
/// Kind-indexed view of a typed surface catalog for generated bindings, UI runtimes, and tools.
///
public sealed class CultMeshSurfaceCatalogIndexDiagnostic
{
/// Creates a kind-indexed catalog diagnostic.
public CultMeshSurfaceCatalogIndexDiagnostic(CultMeshSurfaceCatalogDiagnostic catalog)
{
if (catalog == null) throw new ArgumentNullException(nameof(catalog));
CatalogId = catalog.CatalogId;
Queries = catalog.FindByKind(CultMeshSurfaceKind.Query);
ProjectionRecipes = catalog.FindByKind(CultMeshSurfaceKind.ProjectionRecipe);
LiveFeeds = catalog.FindByKind(CultMeshSurfaceKind.LiveFeed);
Operations = catalog.FindByKind(CultMeshSurfaceKind.Operation);
Documents = catalog.FindByKind(CultMeshSurfaceKind.Document);
Collections = catalog.FindByKind(CultMeshSurfaceKind.Collection);
StatePointers = catalog.FindByKind(CultMeshSurfaceKind.StatePointer);
NativeSliceViews = catalog.FindByKind(CultMeshSurfaceKind.NativeSliceView);
}
/// Gets the semantic catalog id.
public string CatalogId { get; }
/// Gets typed derived-state query surfaces.
public IReadOnlyList Queries { get; }
/// Gets reusable typed projection recipes.
public IReadOnlyList ProjectionRecipes { get; }
/// Gets coherent live view feeds.
public IReadOnlyList LiveFeeds { get; }
/// Gets typed operation handles.
public IReadOnlyList Operations { get; }
/// Gets typed reactive document handles.
public IReadOnlyList Documents { get; }
/// Gets typed reactive collection handles.
public IReadOnlyList Collections { get; }
/// Gets typed pointers to Verse state.
public IReadOnlyList StatePointers { get; }
/// Gets native slice or slab views.
public IReadOnlyList NativeSliceViews { get; }
}
///
/// Options for a polling watch fallback over a typed query or live feed snapshot.
///
public sealed class CultMeshPollingWatchOptions
{
/// Creates polling watch options.
public CultMeshPollingWatchOptions(
TimeSpan? interval = null,
bool emitInitial = true,
IEqualityComparer? comparer = null)
{
Interval = interval ?? TimeSpan.FromMilliseconds(50);
if (Interval <= TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(nameof(interval), "Polling interval must be positive.");
}
EmitInitial = emitInitial;
Comparer = comparer ?? EqualityComparer.Default;
}
/// Gets the polling interval.
public TimeSpan Interval { get; }
/// Gets whether the first sampled value should be emitted.
public bool EmitInitial { get; }
/// Gets the equality comparer used to suppress unchanged samples.
public IEqualityComparer Comparer { get; }
}
///
/// Timer-backed fallback watcher for runtimes that do not yet have native reactive transport.
///
internal sealed class CultMeshPollingWatcher : IDisposable
{
private readonly Func> _sample;
private readonly CultMeshPollingWatchOptions _options;
private readonly Subject _subject = new();
private readonly Timer _timer;
private readonly object _gate = new();
private readonly TParameters _parameters;
private readonly CultMeshQueryContext _context;
private TResult? _last;
private bool _hasLast;
private bool _running;
private bool _disposed;
public CultMeshPollingWatcher(
Func> sample,
TParameters parameters,
CultMeshQueryContext context,
CultMeshPollingWatchOptions options)
{
_sample = sample ?? throw new ArgumentNullException(nameof(sample));
_parameters = parameters;
_context = context ?? throw new ArgumentNullException(nameof(context));
_options = options ?? throw new ArgumentNullException(nameof(options));
_timer = new Timer(OnTimer, null, TimeSpan.Zero, _options.Interval);
}
public Observable Observable => _subject;
public void Dispose()
{
lock (_gate)
{
if (_disposed)
{
return;
}
_disposed = true;
}
_timer.Dispose();
_subject.Dispose();
}
private void OnTimer(object? _)
{
lock (_gate)
{
if (_disposed || _running)
{
return;
}
_running = true;
}
_ = SampleAsync();
}
private async Task SampleAsync()
{
try
{
var next = await _sample(_parameters, _context).ConfigureAwait(false);
var shouldEmit = false;
lock (_gate)
{
if (_disposed)
{
return;
}
shouldEmit = _hasLast
? !_options.Comparer.Equals(_last!, next)
: _options.EmitInitial;
_last = next;
_hasLast = true;
}
if (shouldEmit)
{
_subject.OnNext(next);
}
}
finally
{
lock (_gate)
{
_running = false;
}
}
}
}
internal sealed class CultMeshCompositeDisposable : IDisposable
{
private readonly IDisposable[] _disposables;
private int _disposed;
public CultMeshCompositeDisposable(params IDisposable[] disposables)
{
_disposables = disposables ?? throw new ArgumentNullException(nameof(disposables));
}
public void Dispose()
{
if (Interlocked.Exchange(ref _disposed, 1) != 0)
{
return;
}
foreach (var disposable in _disposables)
{
disposable.Dispose();
}
}
}
///
/// Typed pointer to Verse state that UI surfaces and tools can resolve and watch.
///
public sealed class CultMeshStatePointer
{
private readonly Func> _resolve;
private readonly Func> _watch;
/// Creates a state pointer.
public CultMeshStatePointer(
string pointerId,
Func> resolve,
Func> watch,
CultMeshRouteHint? routeHint = null,
IEnumerable? sources = null)
: this(
pointerId,
_ => (resolve ?? throw new ArgumentNullException(nameof(resolve)))(),
_ => (watch ?? throw new ArgumentNullException(nameof(watch)))(),
routeHint,
sources)
{
}
/// Creates a state pointer that can resolve through a Verse query context.
public CultMeshStatePointer(
string pointerId,
Func> resolve,
Func> watch,
CultMeshRouteHint? routeHint = null,
IEnumerable