using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Linq.Expressions; 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. /// [MessagePackObject] [MessagePackFormatter(typeof(CultMeshRouteRecordFormatter))] public sealed class CultMeshRouteRecord { /// Creates flat route fields. [SerializationConstructor] public CultMeshRouteRecord(string? kind = null, string? description = null) { Kind = kind ?? ""; Description = description ?? ""; } /// Gets the serialized route locality kind. [Key(0)] public string Kind { get; } /// Gets the serialized route description. [Key(1)] 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 content-derived schema identifier. string SchemaId { 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; } /// 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; // Set only over a CultCache record, whose changes carry the cache's Sequence. private readonly Func>>? _recordChanges; private readonly Func<(TDocument Document, long Sequence)>? _sequencedLatest; /// 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; } internal CultMeshDocumentHandle( CultMeshBoundLiveFeed feed, Func replace, Func>> recordChanges, Func<(TDocument Document, long Sequence)> sequencedLatest) : this(feed, replace) { _recordChanges = recordChanges; _sequencedLatest = sequencedLatest; } /// 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; /// 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); } // Latest-value mirrors: a cache-backed handle passes each snapshot's Sequence; other sources pass null. internal IDisposable WatchSequenced(Action onNext) => _recordChanges == null ? Watch(document => onNext(document, null)) : _recordChanges() .Where(change => change.Document != null) .Subscribe(change => onNext(change.Document!, change.Sequence)); // A mirror subscribes first, then adopts under its own gate. A cache-backed handle reads synchronously there, so // its Sequence is at or above every change the mirror has applied and a later straggler at or below it drops. internal async Task AdoptLatestAsync(object gate, Func adopt) { if (_sequencedLatest != null) { lock (gate) { var (document, sequence) = _sequencedLatest(); return adopt(document, sequence); } } var latest = await LatestAsync().ConfigureAwait(false); lock (gate) return adopt(latest, null); } /// /// Projects one typed document field into a stable reactive state pointer. The expression is /// structural metadata: only member access and constant collection indices are accepted. /// public CultMeshStatePointer Field(Expression> field) { if (field == null) throw new ArgumentNullException(nameof(field)); var path = CultMeshFieldPath.Parse(field.Body, field.Parameters[0]); var read = field.Compile(); return new CultMeshStatePointer( DocumentId + "#" + path, async context => read(await Feed .SnapshotAsync(CultMeshDocumentQueryParameters.Empty, context) .ConfigureAwait(false)), context => Feed .Watch(CultMeshDocumentQueryParameters.Empty, context) .Select(read), RouteHint, Sources); } /// 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); } /// Gets an explicit authoritative replacement writer for this document. public CultMeshDocumentWriter AuthoritativeWriter() { if (_replace == null) throw new NotSupportedException($"Document handle '{DocumentId}' is read-only."); return new CultMeshDocumentWriter( this, CultMeshDocumentWriteKind.AuthoritativeReplacement, _replace); } /// Gets an explicit client prediction writer for this document. public CultMeshDocumentWriter PredictionWriter() { if (_submitPrediction == null) throw new NotSupportedException($"Document handle '{DocumentId}' does not accept client predictions."); return new CultMeshDocumentWriter( this, CultMeshDocumentWriteKind.Prediction, _submitPrediction); } /// Creates a read-only in-memory mirror updated by the document watch. public async Task> ObserveAsync() { var observed = new CultMeshObservedDocument(this); await observed.StartAsync().ConfigureAwait(false); return observed; } /// Creates a read-only in-memory mirror synchronously. public CultMeshObservedDocument Observe() { return ObserveAsync().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); } private static class CultMeshFieldPath { public static string Parse(Expression expression, ParameterExpression root) { var segments = new Stack(); while (expression != root) { if (expression is MemberExpression member) { segments.Push(member.Member.Name); expression = member.Expression ?? throw Invalid(); continue; } if (expression is MethodCallExpression call && call.Method.Name == "get_Item" && call.Object != null && call.Arguments.Count == 1) { segments.Push("[" + ConstantIndex(call.Arguments[0]) + "]"); expression = call.Object; continue; } if (expression is BinaryExpression binary && binary.NodeType == ExpressionType.ArrayIndex) { segments.Push("[" + ConstantIndex(binary.Right) + "]"); expression = binary.Left; continue; } throw Invalid(); } if (segments.Count == 0) throw Invalid(); var path = string.Empty; foreach (var segment in segments) path += segment.StartsWith("[", StringComparison.Ordinal) || path.Length == 0 ? segment : "." + segment; return path; } private static string ConstantIndex(Expression expression) { if (expression is ConstantExpression constant && constant.Value != null) return Convert.ToString(constant.Value, CultureInfo.InvariantCulture)!; throw Invalid(); } private static ArgumentException Invalid() => new( "CultMesh field selectors may contain only member access and constant collection indices.", "field"); } 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); } } /// /// A read-only local mirror of a watched typed document. Observation never acquires a write /// capability and never polls or serializes the document while idle. /// public sealed class CultMeshObservedDocument : IDisposable where TDocument : class { private readonly object _gate = new(); private IDisposable? _subscription; private TDocument _current; private long _appliedSequence; private bool _disposed; internal CultMeshObservedDocument(CultMeshDocumentHandle document) { Document = document ?? throw new ArgumentNullException(nameof(document)); _current = null!; } /// Gets the underlying read/watch handle. public CultMeshDocumentHandle Document { get; } /// /// Gets the current borrowed snapshot. Treat the object graph as read-only; local mutation /// cannot publish and is replaced by the next canonical snapshot. /// public TDocument Current { get { lock (_gate) { ThrowIfDisposed(); return _current; } } } internal async Task StartAsync() { _subscription = Document.WatchSequenced(ApplyCanonicalSnapshot); try { await RefreshAsync().ConfigureAwait(false); } catch { Dispose(); throw; } } /// Reads and adopts a fresh canonical snapshot. public Task RefreshAsync() { ThrowIfDisposed(); return Document.AdoptLatestAsync(_gate, (latest, sequence) => { ThrowIfDisposed(); _current = CultMeshDocumentHandle.CloneDocument(latest); _appliedSequence = sequence ?? _appliedSequence; return _current; }); } /// public void Dispose() { lock (_gate) { if (_disposed) return; _disposed = true; } _subscription?.Dispose(); } private void ApplyCanonicalSnapshot(TDocument canonical, long? sequence) { if (canonical == null) return; var next = CultMeshDocumentHandle.CloneDocument(canonical); lock (_gate) { if (_disposed || sequence <= _appliedSequence) return; _appliedSequence = sequence ?? _appliedSequence; _current = next; } } private void ThrowIfDisposed() { if (_disposed) throw new ObjectDisposedException(nameof(CultMeshObservedDocument)); } } /// Names the one immutable authority path selected by a document writer. public enum CultMeshDocumentWriteKind { /// The writer replaces canonical state through its owning provider. AuthoritativeReplacement, /// The writer submits a client prediction or candidate for validation. Prediction } /// /// An explicit, immutable write capability for one typed document. A writer never switches /// between authoritative replacement and prediction based on runtime availability. /// public sealed class CultMeshDocumentWriter where TDocument : class { private readonly Func _write; internal CultMeshDocumentWriter( CultMeshDocumentHandle document, CultMeshDocumentWriteKind kind, Func write) { Document = document ?? throw new ArgumentNullException(nameof(document)); Kind = kind; _write = write ?? throw new ArgumentNullException(nameof(write)); } /// Gets the document read/watch handle paired with this writer. public CultMeshDocumentHandle Document { get; } /// Gets the immutable authority path selected for this writer. public CultMeshDocumentWriteKind Kind { get; } /// Writes one value through this writer's named authority path. public Task WriteAsync(TDocument value) { if (value == null) throw new ArgumentNullException(nameof(value)); return _write(value); } /// Reads, transforms, and writes through this writer's named authority path. public async Task UpdateAsync(Func update) { if (update == null) throw new ArgumentNullException(nameof(update)); var next = update(await Document.LatestAsync().ConfigureAwait(false)); await WriteAsync(next).ConfigureAwait(false); return next; } /// Reads, transforms, and writes through this writer's named authority path. public async Task UpdateAsync(Func> update) { if (update == null) throw new ArgumentNullException(nameof(update)); var next = await update(await Document.LatestAsync().ConfigureAwait(false)).ConfigureAwait(false); await WriteAsync(next).ConfigureAwait(false); return next; } /// Creates an explicitly writable reactive mirror. public async Task> ReactiveAsync( CultMeshReactiveDocumentOptions? options = null) { var reactive = new CultMeshReactiveDocument(this, options); await reactive.StartAsync().ConfigureAwait(false); return reactive; } /// Creates an explicitly writable reactive mirror synchronously. public CultMeshReactiveDocument Reactive(CultMeshReactiveDocumentOptions? options = null) { return ReactiveAsync(options).ConfigureAwait(false).GetAwaiter().GetResult(); } } /// /// 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 the clock that owns delayed flush scheduling. Tests and deterministic hosts may /// replace the system clock without introducing a second scheduler abstraction. /// public ICultMeshClock Clock { get; set; } = CultMeshSystemClock.Instance; /// 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, IReadOnlyDictionary delta, int version, DateTimeOffset receivedAt) { Canonical = canonical; Predicted = predicted; Delta = delta ?? throw new ArgumentNullException(nameof(delta)); Version = version; ReceivedAt = receivedAt; } /// Gets the monotonically increasing reconciliation version for this reactive document. public int Version { get; } /// 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 the predicted-vs-canonical member delta. Numeric members store predicted minus canonical; /// non-numeric members store the predicted value. /// public IReadOnlyDictionary Delta { get; } /// Gets when the canonical snapshot was received. public DateTimeOffset ReceivedAt { get; } } /// /// Managed typed document mirror that coalesces explicit local edits through one selected writer. /// public sealed class CultMeshReactiveDocument : IDisposable where TDocument : class { private static readonly CultMeshComparableMember[] ComparableMembers = GetComparableMembers(typeof(TDocument)).ToArray(); private readonly CultMeshDocumentWriter _writer; private readonly CultMeshDocumentHandle _document; private readonly CultMeshReactiveDocumentOptions _options; private readonly object _gate = new(); private IDisposable? _subscription; private CancellationTokenSource? _flushDelayCancellation; private TDocument _current; private bool _dirty; private bool _flushQueued; private bool _flushing; private bool _disposed; private long _appliedSequence; private int _reconciliationVersion; internal CultMeshReactiveDocument( CultMeshDocumentWriter writer, CultMeshReactiveDocumentOptions? options) { _writer = writer ?? throw new ArgumentNullException(nameof(writer)); _document = writer.Document; _current = null!; _options = options ?? new CultMeshReactiveDocumentOptions(); } /// Gets the immutable write capability selected for this mirror. public CultMeshDocumentWriter Writer => _writer; /// Gets the underlying document handle. public CultMeshDocumentHandle Document => _document; /// /// Gets a cloned local snapshot. Mutating the returned object cannot mutate or publish the mirror; /// use or for edits. /// public TDocument Snapshot { get { lock (_gate) { ThrowIfDisposed(); return CultMeshDocumentHandle.CloneDocument(_current); } } } /// 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 async Task StartAsync() { _subscription = _document.WatchSequenced(ApplyCanonicalSnapshot); try { await RefreshAsync().ConfigureAwait(false); } catch { Dispose(); throw; } } /// 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 CultMeshDocumentHandle.CloneDocument(_current); } } /// Replaces the current local value and schedules a coalesced prediction or replacement. public TDocument ReplaceLocal(TDocument value) { if (value == null) throw new ArgumentNullException(nameof(value)); ThrowIfDisposed(); lock (_gate) { _current = CultMeshDocumentHandle.CloneDocument(value); _dirty = true; ScheduleFlushLocked(); return CultMeshDocumentHandle.CloneDocument(_current); } } /// Reads a fresh canonical snapshot and adopts it as the current value. public Task RefreshAsync() { ThrowIfDisposed(); return _document.AdoptLatestAsync(_gate, (latest, sequence) => { _current = CultMeshDocumentHandle.CloneDocument(latest); _appliedSequence = sequence ?? _appliedSequence; _dirty = false; Reconciliation = null; return CultMeshDocumentHandle.CloneDocument(_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; } CancelScheduledFlushLocked(); _flushing = true; _dirty = false; predicted = CultMeshDocumentHandle.CloneDocument(_current); } try { await _writer.WriteAsync(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; CancelScheduledFlushLocked(); } _subscription?.Dispose(); } private void ApplyCanonicalSnapshot(TDocument canonical, long? sequence) { if (canonical == null) return; lock (_gate) { if (_disposed || sequence <= _appliedSequence) return; _appliedSequence = sequence ?? _appliedSequence; TDocument? replacement = null; if (_dirty || _flushing) { var delta = CreateReconciliationDelta(_current, canonical); if (delta.Count == 0) { Reconciliation = null; if (!_options.ReplaceDirtyCurrentOnCanonicalSnapshot) return; } else { var nextCanonical = CultMeshDocumentHandle.CloneDocument(canonical); var predicted = CultMeshDocumentHandle.CloneDocument(_current); replacement = nextCanonical; Reconciliation = new CultMeshReactiveDocumentReconciliation( nextCanonical, predicted, delta, ++_reconciliationVersion, DateTimeOffset.UtcNow); if (!_options.ReplaceDirtyCurrentOnCanonicalSnapshot) return; } } _current = replacement ?? CultMeshDocumentHandle.CloneDocument(canonical); Reconciliation = null; } } private void ScheduleFlushLocked() { if (_options.FlushDelay <= TimeSpan.Zero) { _ = Task.Run(FlushAsync); return; } CancelScheduledFlushLocked(); var cancellation = new CancellationTokenSource(); _flushDelayCancellation = cancellation; _ = FlushAfterDelayAsync(cancellation); } private async Task FlushAfterDelayAsync(CancellationTokenSource cancellation) { try { await _options.Clock.DelayAsync(_options.FlushDelay, cancellation.Token).ConfigureAwait(false); lock (_gate) { if (_disposed || cancellation.IsCancellationRequested) return; } await FlushAsync().ConfigureAwait(false); } catch (OperationCanceledException) when (cancellation.IsCancellationRequested) { } finally { lock (_gate) { if (ReferenceEquals(_flushDelayCancellation, cancellation)) _flushDelayCancellation = null; } cancellation.Dispose(); } } private void CancelScheduledFlushLocked() { var cancellation = _flushDelayCancellation; _flushDelayCancellation = null; if (cancellation == null) return; cancellation.Cancel(); } private static IReadOnlyDictionary CreateReconciliationDelta( TDocument predicted, TDocument canonical) { var delta = new Dictionary(StringComparer.Ordinal); foreach (var member in ComparableMembers) { var predictedValue = member.GetValue(predicted); var canonicalValue = member.GetValue(canonical); if (CultMeshValuesEqual(predictedValue, canonicalValue)) continue; delta[member.Name] = TryCreateNumericDelta(predictedValue, canonicalValue, out var numericDelta) ? numericDelta : predictedValue; } return delta; } private static IEnumerable GetComparableMembers(Type documentType) { foreach (var property in documentType.GetProperties(BindingFlags.Instance | BindingFlags.Public)) { if (property.GetMethod == null || property.GetIndexParameters().Length != 0) continue; yield return new CultMeshComparableMember(property.Name, property.GetValue); } foreach (var field in documentType.GetFields(BindingFlags.Instance | BindingFlags.Public)) { yield return new CultMeshComparableMember(field.Name, field.GetValue); } } private static bool TryCreateNumericDelta(object? predicted, object? canonical, out object? delta) { delta = null; if (predicted == null || canonical == null) return false; var predictedType = Nullable.GetUnderlyingType(predicted.GetType()) ?? predicted.GetType(); var canonicalType = Nullable.GetUnderlyingType(canonical.GetType()) ?? canonical.GetType(); if (!IsNumericType(predictedType) || !IsNumericType(canonicalType)) return false; delta = Convert.ToDouble(predicted, CultureInfo.InvariantCulture) - Convert.ToDouble(canonical, CultureInfo.InvariantCulture); return true; } private static bool IsNumericType(Type type) { if (type.IsEnum) return false; return Type.GetTypeCode(type) switch { TypeCode.Byte or TypeCode.SByte or TypeCode.UInt16 or TypeCode.UInt32 or TypeCode.UInt64 or TypeCode.Int16 or TypeCode.Int32 or TypeCode.Int64 or TypeCode.Decimal or TypeCode.Double or TypeCode.Single => true, _ => false }; } private static bool CultMeshValuesEqual(object? left, object? right) { if (ReferenceEquals(left, right)) return true; if (left == null || right == null) return false; if (left.Equals(right)) return true; if (left is string || right is string) return false; if (left is IEnumerable leftEnumerable && right is IEnumerable rightEnumerable) return leftEnumerable.Cast().SequenceEqual(rightEnumerable.Cast()); return false; } private void ThrowIfDisposed() { if (_disposed) throw new ObjectDisposedException(nameof(CultMeshReactiveDocument)); } private readonly struct CultMeshComparableMember { public CultMeshComparableMember(string name, Func getValue) { Name = name; GetValue = getValue; } public string Name { get; } public Func GetValue { get; } } } /// /// 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.SchemaId)) bySchema[document.SchemaId] = 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 schema id, 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 schema id, 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; } /// 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); } /// Gets an explicit authoritative writer by CLR type or same-schema alias. public CultMeshDocumentWriter AuthoritativeWriter() where TDocument : class { return Document().AuthoritativeWriter(); } /// Gets an explicit prediction writer by CLR type or same-schema alias. public CultMeshDocumentWriter PredictionWriter() where TDocument : class { return Document().PredictionWriter(); } /// 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); } } /// /// Inspectable typed collection handle exposed by CultMesh. /// public interface ICultMeshCollectionHandle { /// Gets the CLR document type presented by this collection. Type DocumentType { get; } /// Gets the semantic collection id. string CollectionId { get; } /// Gets the stable CultCache schema name. string SchemaName { get; } /// Gets the stable CultCache schema version. string SchemaVersion { get; } /// Gets the content-derived schema identifier. string SchemaId { get; } /// Gets the preferred or observed route for collection access. CultMeshRouteHint RouteHint { get; } /// Gets typed state sources this collection handle depends on, when known. IReadOnlyList Sources { get; } /// Creates a same-schema alias presentation for another CLR document type. CultMeshCollectionHandle AsSchemaAlias() where TAlias : class; } /// /// Schema-aware lookup table for typed CultMesh collection handles. /// public sealed class CultMeshCollectionCatalog { private readonly IReadOnlyList _collections; private readonly IReadOnlyDictionary _collectionsByType; private readonly IReadOnlyDictionary _collectionsBySchema; /// Creates a collection catalog from known handles. public CultMeshCollectionCatalog(IEnumerable collections) { if (collections == null) throw new ArgumentNullException(nameof(collections)); var collectionList = collections .Where(collection => collection != null) .ToArray(); var byType = new Dictionary(); var bySchema = new Dictionary(StringComparer.Ordinal); foreach (var collection in collectionList) { byType[collection.DocumentType] = collection; if (!string.IsNullOrWhiteSpace(collection.SchemaId)) bySchema[collection.SchemaId] = collection; if (!string.IsNullOrWhiteSpace(collection.SchemaVersion)) bySchema[collection.SchemaVersion] = collection; if (!string.IsNullOrWhiteSpace(collection.SchemaName)) bySchema[collection.SchemaName] = collection; } _collections = collectionList; _collectionsByType = byType; _collectionsBySchema = bySchema; } /// Gets the handles in this catalog. public IReadOnlyList Collections => _collections; /// Looks up a collection handle by schema id, shared schema name, or schema version. public bool TryGetCollectionBySchema( string schema, out ICultMeshCollectionHandle collection) { if (!string.IsNullOrWhiteSpace(schema) && _collectionsBySchema.TryGetValue(schema, out collection!)) { return true; } collection = null!; return false; } /// Looks up a collection handle by schema id, shared schema name, or schema version. public ICultMeshCollectionHandle CollectionBySchema(string schema) { if (TryGetCollectionBySchema(schema, out var collection)) return collection; throw new NotSupportedException( $"CultMesh collection catalog does not expose a collection for schema '{schema}'."); } /// Looks up a typed collection handle by CLR type or same-schema alias. public bool TryGetCollection( out CultMeshCollectionHandle collection) where TDocument : class { if (_collectionsByType.TryGetValue(typeof(TDocument), out var untypedCollection) && untypedCollection is CultMeshCollectionHandle typedCollection) { collection = typedCollection; return true; } var descriptor = CultDocumentRegistry.Shared.GetRequired(); if (_collectionsBySchema.TryGetValue(descriptor.SchemaId, out var schemaCollection) || _collectionsBySchema.TryGetValue(descriptor.SchemaVersion, out schemaCollection!) || _collectionsBySchema.TryGetValue(descriptor.SchemaName, out schemaCollection!)) { if (schemaCollection is CultMeshCollectionHandle schemaTypedCollection) { collection = schemaTypedCollection; return true; } collection = schemaCollection.AsSchemaAlias(); return true; } collection = null!; return false; } /// Looks up a typed collection handle by CLR type or same-schema alias. public CultMeshCollectionHandle Collection() where TDocument : class { if (TryGetCollection(out var collection)) return collection; throw new NotSupportedException( $"CultMesh collection catalog does not expose a collection for {typeof(TDocument).FullName}."); } /// Reads one typed collection snapshot by CLR type or same-schema alias. public Task> LatestAsync() where TDocument : class { return Collection().LatestAsync(); } /// Watches one typed collection by CLR type or same-schema alias. public Observable> WatchChanges() where TDocument : class { return Collection().WatchChanges(); } /// Subscribes to one typed collection by CLR type or same-schema alias. public IDisposable WatchChanges(Action> onNext) where TDocument : class { return Collection().WatchChanges(onNext); } } /// /// 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 : ICultMeshCollectionHandle 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? sources = null) { PointerId = RequireNonEmpty(pointerId, nameof(pointerId)); _resolve = resolve ?? throw new ArgumentNullException(nameof(resolve)); _watch = watch ?? throw new ArgumentNullException(nameof(watch)); 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; } /// Resolves the pointer once. public Task ResolveAsync() { return ResolveAsync(CultMeshQueryContext.ForRuntime("local")); } /// Resolves the pointer once through a query context. public Task ResolveAsync(CultMeshQueryContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); return _resolve(ResolveContext(context)); } /// Resolves the pointer once for one runtime using default context. public Task ResolveAsync(string runtimeId) { return ResolveAsync(CultMeshQueryContext.ForRuntime(runtimeId)); } /// Watches resolved values for this pointer. public Observable Watch() { return Watch(CultMeshQueryContext.ForRuntime("local")); } /// Watches resolved values for this pointer through a query context. public Observable Watch(CultMeshQueryContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); return _watch(ResolveContext(context)); } /// Binds this pointer to a Verse context. public CultMeshBoundStatePointer Bind(CultMeshVerseContext context) { return new CultMeshBoundStatePointer(context, this); } /// Binds this pointer to a Verse. public CultMeshBoundStatePointer 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; } } /// /// State pointer pre-bound to a Verse so UI/tool runtimes can resolve state without context plumbing. /// public sealed class CultMeshBoundStatePointer { /// Creates a Verse-bound state pointer. public CultMeshBoundStatePointer( CultMeshVerseContext context, CultMeshStatePointer pointer) { Context = context ?? throw new ArgumentNullException(nameof(context)); Pointer = pointer ?? throw new ArgumentNullException(nameof(pointer)); } /// Gets the bound Verse context. public CultMeshVerseContext Context { get; } /// Gets the underlying state pointer. public CultMeshStatePointer Pointer { get; } /// Gets the semantic pointer id. public string PointerId => Pointer.PointerId; /// Gets typed state sources this pointer depends on, when known. public IReadOnlyList Sources => Pointer.Sources; /// Gets the preferred or observed route for pointer resolution. public CultMeshRouteHint RouteHint => Pointer.RouteHint; /// Resolves the pointer through the bound Verse. public Task ResolveAsync() { return Pointer.ResolveAsync(Context.QueryContext()); } /// Watches the pointer through the bound Verse. public Observable Watch() { return Pointer.Watch(Context.QueryContext()); } } /// /// Typed pointer to Verse state that UI surfaces and tools can resolve, watch, and replace. /// public sealed class CultMeshMutableStatePointer { private readonly Func> _resolve; private readonly Func> _watch; private readonly Func _replace; /// Creates a mutable state pointer. public CultMeshMutableStatePointer( string pointerId, Func> resolve, Func> watch, Func replace, CultMeshRouteHint? routeHint = null, IEnumerable? sources = null) : this( pointerId, _ => (resolve ?? throw new ArgumentNullException(nameof(resolve)))(), _ => (watch ?? throw new ArgumentNullException(nameof(watch)))(), (_, value) => (replace ?? throw new ArgumentNullException(nameof(replace)))(value), routeHint, sources) { } /// Creates a mutable state pointer that can operate through a Verse query context. public CultMeshMutableStatePointer( string pointerId, Func> resolve, Func> watch, Func replace, CultMeshRouteHint? routeHint = null, IEnumerable? sources = null) { PointerId = RequireNonEmpty(pointerId, nameof(pointerId)); _resolve = resolve ?? throw new ArgumentNullException(nameof(resolve)); _watch = watch ?? throw new ArgumentNullException(nameof(watch)); _replace = replace ?? throw new ArgumentNullException(nameof(replace)); 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 access. public CultMeshRouteHint RouteHint { get; } /// Gets typed state sources this pointer depends on, when known. public IReadOnlyList Sources { get; } /// Resolves the pointer once. public Task ResolveAsync() { return ResolveAsync(CultMeshQueryContext.ForRuntime("local")); } /// Resolves the pointer once. public Task ReadAsync() { return ResolveAsync(); } /// Resolves the pointer once through a query context. public Task ResolveAsync(CultMeshQueryContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); return _resolve(ResolveContext(context)); } /// Resolves the pointer once through a query context. public Task ReadAsync(CultMeshQueryContext context) { return ResolveAsync(context); } /// Resolves the pointer once for one runtime using default context. public Task ResolveAsync(string runtimeId) { return ResolveAsync(CultMeshQueryContext.ForRuntime(runtimeId)); } /// Resolves the pointer once for one runtime using default context. public Task ReadAsync(string runtimeId) { return ResolveAsync(runtimeId); } /// Replaces the pointed state value. public Task ReplaceAsync(T value) { return ReplaceAsync(value, CultMeshQueryContext.ForRuntime("local")); } /// Replaces the pointed state value through a query context. public Task ReplaceAsync(T value, CultMeshQueryContext context) { if (value == null) throw new ArgumentNullException(nameof(value)); if (context == null) throw new ArgumentNullException(nameof(context)); return _replace(ResolveContext(context), value); } /// Replaces the pointed state value for one runtime using default context. public Task ReplaceAsync(T value, string runtimeId) { return ReplaceAsync(value, CultMeshQueryContext.ForRuntime(runtimeId)); } /// Watches resolved values for this pointer. public Observable Watch() { return Watch(CultMeshQueryContext.ForRuntime("local")); } /// Watches resolved values for this pointer through a query context. public Observable Watch(CultMeshQueryContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); return _watch(ResolveContext(context)); } /// Views this mutable pointer as a read/watch state pointer. public CultMeshStatePointer AsStatePointer() { return new CultMeshStatePointer( PointerId, (Func>)ResolveAsync, (Func>)Watch, RouteHint, Sources); } /// Binds this pointer to a Verse context. public CultMeshBoundMutableStatePointer Bind(CultMeshVerseContext context) { return new CultMeshBoundMutableStatePointer(context, this); } /// Binds this pointer to a Verse. public CultMeshBoundMutableStatePointer 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; } } /// /// Mutable state pointer pre-bound to a Verse so UI/tool runtimes can read, watch, and replace state without context plumbing. /// public sealed class CultMeshBoundMutableStatePointer { /// Creates a Verse-bound mutable state pointer. public CultMeshBoundMutableStatePointer( CultMeshVerseContext context, CultMeshMutableStatePointer pointer) { Context = context ?? throw new ArgumentNullException(nameof(context)); Pointer = pointer ?? throw new ArgumentNullException(nameof(pointer)); } /// Gets the bound Verse context. public CultMeshVerseContext Context { get; } /// Gets the underlying mutable state pointer. public CultMeshMutableStatePointer Pointer { get; } /// Gets the semantic pointer id. public string PointerId => Pointer.PointerId; /// Gets typed state sources this pointer depends on, when known. public IReadOnlyList Sources => Pointer.Sources; /// Gets the preferred or observed route for pointer access. public CultMeshRouteHint RouteHint => Pointer.RouteHint; /// Resolves the pointer through the bound Verse. public Task ResolveAsync() { return Pointer.ResolveAsync(Context.QueryContext()); } /// Resolves the pointer through the bound Verse. public Task ReadAsync() { return ResolveAsync(); } /// Replaces the pointer value through the bound Verse. public Task ReplaceAsync(T value) { return Pointer.ReplaceAsync(value, Context.QueryContext()); } /// Watches the pointer through the bound Verse. public Observable Watch() { return Pointer.Watch(Context.QueryContext()); } /// Views this mutable pointer as a read/watch state pointer bound to the same Verse. public CultMeshBoundStatePointer AsStatePointer() { return new CultMeshBoundStatePointer(Context, Pointer.AsStatePointer()); } } /// /// Describes one typed source consumed by a projection recipe. /// public sealed class CultMeshProjectionSource { /// Creates a projection source descriptor. public CultMeshProjectionSource(string sourceId, string? schemaId = null, string? description = null) { SourceId = RequireNonEmpty(sourceId, nameof(sourceId)); SchemaId = schemaId; Description = description; } /// Gets the semantic source id, such as a state pointer, document handle, or native view id. public string SourceId { get; } /// Gets the optional schema id for the source. public string? SchemaId { get; } /// Gets optional human-facing source diagnostics. public string? Description { get; } private static string RequireNonEmpty(string value, string paramName) { return string.IsNullOrWhiteSpace(value) ? throw new ArgumentException("Value must be non-empty.", paramName) : value; } } /// /// Resolves string state references embedded in portable UI/tool surfaces through a named CultMesh resolver. /// public sealed class CultMeshStateRefResolver { private readonly Func _resolve; /// Creates a state-reference resolver. public CultMeshStateRefResolver( string resolverId, Func resolve, IEnumerable? sources = null, CultMeshRouteHint? routeHint = null) { ResolverId = RequireNonEmpty(resolverId, nameof(resolverId)); _resolve = resolve ?? throw new ArgumentNullException(nameof(resolve)); Sources = sources?.ToArray() ?? Array.Empty(); RouteHint = routeHint ?? CultMeshRouteHint.Automatic; } /// Gets the semantic resolver id. public string ResolverId { get; } /// Gets the source state read by the resolver. public IReadOnlyList Sources { get; } /// Gets the preferred route for resolving state refs. public CultMeshRouteHint RouteHint { get; } /// Gets an empty resolver that always returns an empty string. public static CultMeshStateRefResolver Empty { get; } = new("cultmesh.state_refs.empty", (_stateRef, _context) => ""); /// Resolves a state ref using a local query context. public string Resolve(string stateRef) { return Resolve(stateRef, CultMeshQueryContext.ForRuntime("local")); } /// Resolves a state ref using a runtime id. public string Resolve(string stateRef, string runtimeId) { return Resolve(stateRef, CultMeshQueryContext.ForRuntime(runtimeId)); } /// Resolves a state ref using an explicit query context. public string Resolve(string stateRef, CultMeshQueryContext context) { if (string.IsNullOrWhiteSpace(stateRef)) return ""; if (context == null) throw new ArgumentNullException(nameof(context)); return _resolve(stateRef, ResolveContext(context)) ?? ""; } /// Attempts to resolve a non-empty value. public bool TryResolve(string stateRef, out string value) { value = Resolve(stateRef); return !string.IsNullOrEmpty(value); } /// Attempts to resolve a non-empty value. public bool TryResolve(string stateRef, CultMeshQueryContext context, out string value) { value = Resolve(stateRef, context); return !string.IsNullOrEmpty(value); } /// Creates a resolver that falls back to another resolver when this resolver returns empty. public CultMeshStateRefResolver Or(CultMeshStateRefResolver fallback) { if (fallback == null) throw new ArgumentNullException(nameof(fallback)); return new CultMeshStateRefResolver( ResolverId + "|" + fallback.ResolverId, (stateRef, context) => { var value = Resolve(stateRef, context); return string.IsNullOrEmpty(value) ? fallback.Resolve(stateRef, context) : value; }, Sources.Concat(fallback.Sources), RouteHint.Kind == CultMeshLocalityKind.Automatic ? fallback.RouteHint : RouteHint); } /// Returns the legacy function shape for existing surface renderers. public Func AsFunc() { return Resolve; } private CultMeshQueryContext ResolveContext(CultMeshQueryContext context) { if (context.RouteHint.Kind != CultMeshLocalityKind.Automatic || RouteHint.Kind == CultMeshLocalityKind.Automatic) return context; return context.WithRoute(RouteHint); } private static string RequireNonEmpty(string value, string paramName) { return string.IsNullOrWhiteSpace(value) ? throw new ArgumentException("Value must be non-empty.", paramName) : value; } } /// /// Diagnostics for a named CultMesh state-reference resolver. /// public sealed class CultMeshStateRefResolverDiagnostic { /// Creates a state-reference resolver diagnostic. public CultMeshStateRefResolverDiagnostic( string resolverId, CultMeshRouteHint routeHint, IEnumerable? sources = null) { ResolverId = RequireNonEmpty(resolverId, nameof(resolverId)); RouteHint = routeHint ?? CultMeshRouteHint.Automatic; Sources = sources?.ToArray() ?? Array.Empty(); } /// Gets the semantic resolver id. public string ResolverId { get; } /// Gets the preferred route for resolving refs. public CultMeshRouteHint RouteHint { get; } /// Gets source state used by the resolver. 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; } } /// /// Describes one UI/tool surface property bound to a typed CultMesh state pointer. /// public sealed class CultMeshStateBindingDescriptor { /// Creates a state binding descriptor. public CultMeshStateBindingDescriptor( string targetProp, string pointerId, string? sourceId = null, string? schemaId = null, CultMeshRouteHint? routeHint = null, string? bindingName = null, string? documentId = null, string? fieldPath = null, string? valueKind = null, string? accessMode = null, string? authority = null, string? writeCommand = null) { TargetProp = RequireNonEmpty(targetProp, nameof(targetProp)); PointerId = RequireNonEmpty(pointerId, nameof(pointerId)); SourceId = sourceId; SchemaId = schemaId; RouteHint = routeHint ?? CultMeshRouteHint.Automatic; BindingName = bindingName ?? ""; DocumentId = documentId ?? ""; FieldPath = fieldPath ?? ""; ValueKind = valueKind ?? ""; AccessMode = accessMode ?? ""; Authority = authority ?? ""; WriteCommand = writeCommand ?? ""; } /// Gets the component property receiving the resolved state value. public string TargetProp { get; } /// Gets the semantic CultMesh state pointer id. public string PointerId { get; } /// Gets the optional source document, cache, native view, or daemon source id. public string? SourceId { get; } /// Gets the optional schema id for the source state. public string? SchemaId { get; } /// Gets the preferred route for resolving and watching the binding. public CultMeshRouteHint RouteHint { get; } /// Gets the stable renderer-facing name used to capture this binding in an operation. public string BindingName { get; } /// Gets the optional identity of the provider-owned backing document. public string DocumentId { get; } /// Gets the optional typed field path inside the backing document. public string FieldPath { get; } /// Gets the optional portable value kind. public string ValueKind { get; } /// Gets the optional read, write, read-write, or local-draft access mode. public string AccessMode { get; } /// Gets the authority responsible for accepting writes to this binding. public string Authority { get; } /// Gets the optional typed operation id used for direct writes. public string WriteCommand { get; } /// Creates a binding from an existing state pointer. public static CultMeshStateBindingDescriptor FromPointer( string targetProp, CultMeshStatePointer pointer) { if (pointer == null) throw new ArgumentNullException(nameof(pointer)); var source = pointer.Sources.FirstOrDefault(); return new CultMeshStateBindingDescriptor( targetProp, pointer.PointerId, source?.SourceId, source?.SchemaId, pointer.RouteHint); } private static string RequireNonEmpty(string value, string paramName) { return string.IsNullOrWhiteSpace(value) ? throw new ArgumentException("Value must be non-empty.", paramName) : value; } } /// /// Transport-friendly fields for a typed state binding descriptor. /// [MessagePackObject] public sealed class CultMeshStateBindingRecord { /// Creates a state binding record. [SerializationConstructor] public CultMeshStateBindingRecord( string? targetProp = null, string? pointerId = null, string? sourceId = null, string? schemaId = null, string? routeKind = null, string? routeDescription = null, string? bindingName = null, string? documentId = null, string? fieldPath = null, string? valueKind = null, string? accessMode = null, string? authority = null, string? writeCommand = null) { TargetProp = string.IsNullOrWhiteSpace(targetProp) ? "value" : targetProp!; PointerId = pointerId ?? ""; SourceId = sourceId ?? ""; SchemaId = schemaId ?? ""; RouteKind = routeKind ?? ""; RouteDescription = routeDescription ?? ""; BindingName = bindingName ?? ""; DocumentId = documentId ?? ""; FieldPath = fieldPath ?? ""; ValueKind = valueKind ?? ""; AccessMode = accessMode ?? ""; Authority = authority ?? ""; WriteCommand = writeCommand ?? ""; } /// Gets the component property receiving the resolved state value. [Key(0)] public string TargetProp { get; } /// Gets the semantic CultMesh state pointer id. [Key(1)] public string PointerId { get; } /// Gets the source document, cache, native view, or daemon source id. [Key(2)] public string SourceId { get; } /// Gets the source schema id. [Key(3)] public string SchemaId { get; } /// Gets the flattened route kind. [Key(4)] public string RouteKind { get; } /// Gets the flattened route description. [Key(5)] public string RouteDescription { get; } [Key(6)] public string BindingName { get; } [Key(7)] public string DocumentId { get; } [Key(8)] public string FieldPath { get; } [Key(9)] public string ValueKind { get; } [Key(10)] public string AccessMode { get; } [Key(11)] public string Authority { get; } [Key(12)] public string WriteCommand { get; } /// Creates transport-friendly fields from a state binding descriptor. public static CultMeshStateBindingRecord FromBinding(CultMeshStateBindingDescriptor? binding) { var route = CultMeshRouteRecord.FromRoute(binding?.RouteHint); return new CultMeshStateBindingRecord( binding?.TargetProp, binding?.PointerId, binding?.SourceId, binding?.SchemaId, route.Kind, route.Description, binding?.BindingName, binding?.DocumentId, binding?.FieldPath, binding?.ValueKind, binding?.AccessMode, binding?.Authority, binding?.WriteCommand); } /// Rehydrates this record as a state binding descriptor. public CultMeshStateBindingDescriptor ToBinding( CultMeshRouteHint? fallbackRouteHint = null, string? fallbackTargetProp = null) { var targetProp = string.IsNullOrWhiteSpace(TargetProp) ? (string.IsNullOrWhiteSpace(fallbackTargetProp) ? "value" : fallbackTargetProp!) : TargetProp; var pointerId = string.IsNullOrWhiteSpace(PointerId) ? $"{targetProp}.unknown" : PointerId; var route = new CultMeshRouteRecord(RouteKind, RouteDescription) .ToRoute(fallbackRouteHint ?? CultMeshRouteHint.Automatic); return new CultMeshStateBindingDescriptor( targetProp, pointerId, SourceId, SchemaId, route, BindingName, DocumentId, FieldPath, ValueKind, AccessMode, Authority, WriteCommand); } } /// /// Describes one UI/tool command bound to a typed CultMesh operation. /// public sealed class CultMeshOperationBindingDescriptor { /// Creates an operation binding descriptor. public CultMeshOperationBindingDescriptor( string operationId, string? label = null, string? schemaId = null, CultMeshRouteHint? routeHint = null) { OperationId = RequireNonEmpty(operationId, nameof(operationId)); Label = label ?? ""; SchemaId = schemaId ?? ""; RouteHint = routeHint ?? CultMeshRouteHint.Automatic; } /// Gets the semantic CultMesh operation id. public string OperationId { get; } /// Gets the human-facing command label. public string Label { get; } /// Gets the optional request schema id for the operation. public string SchemaId { get; } /// Gets the preferred route for invoking the operation. public CultMeshRouteHint RouteHint { get; } /// Creates a binding from an existing typed operation handle. public static CultMeshOperationBindingDescriptor FromOperation( CultMeshOperationHandle operation, string? label = null, string? schemaId = null, CultMeshRouteHint? routeHint = null) { if (operation == null) throw new ArgumentNullException(nameof(operation)); return new CultMeshOperationBindingDescriptor( operation.OperationId, label, schemaId, routeHint); } private static string RequireNonEmpty(string value, string paramName) { return string.IsNullOrWhiteSpace(value) ? throw new ArgumentException("Value must be non-empty.", paramName) : value; } } /// /// Transport-friendly fields for a typed operation binding descriptor. /// [MessagePackObject] public sealed class CultMeshOperationBindingRecord { /// Creates an operation binding record. [SerializationConstructor] public CultMeshOperationBindingRecord( string? operationId = null, string? label = null, string? schemaId = null, string? routeKind = null, string? routeDescription = null) { OperationId = operationId ?? ""; Label = label ?? ""; SchemaId = schemaId ?? ""; RouteKind = routeKind ?? ""; RouteDescription = routeDescription ?? ""; } /// Gets the semantic CultMesh operation id. [Key(0)] public string OperationId { get; } /// Gets the human-facing command label. [Key(1)] public string Label { get; } /// Gets the optional request schema id for the operation. [Key(2)] public string SchemaId { get; } /// Gets the flattened route kind. [Key(3)] public string RouteKind { get; } /// Gets the flattened route description. [Key(4)] public string RouteDescription { get; } /// Creates transport-friendly fields from an operation binding descriptor. public static CultMeshOperationBindingRecord FromBinding(CultMeshOperationBindingDescriptor? binding) { var route = CultMeshRouteRecord.FromRoute(binding?.RouteHint); return new CultMeshOperationBindingRecord( binding?.OperationId, binding?.Label, binding?.SchemaId, route.Kind, route.Description); } /// Rehydrates this record as an operation binding descriptor. public CultMeshOperationBindingDescriptor ToBinding( CultMeshRouteHint? fallbackRouteHint = null, string? fallbackOperationId = null) { var operationId = string.IsNullOrWhiteSpace(OperationId) ? (fallbackOperationId ?? "") : OperationId; var route = new CultMeshRouteRecord(RouteKind, RouteDescription) .ToRoute(fallbackRouteHint ?? CultMeshRouteHint.Automatic); return new CultMeshOperationBindingDescriptor( operationId, Label, SchemaId, route); } } /// /// Describes one concrete invocation of a typed CultMesh operation. /// public sealed class CultMeshOperationInvocationDescriptor { /// Creates an operation invocation descriptor. public CultMeshOperationInvocationDescriptor( string operationId, string? schemaId = null, CultMeshRouteHint? routeHint = null, string? idempotencyKey = null) { OperationId = RequireNonEmpty(operationId, nameof(operationId)); SchemaId = schemaId ?? ""; RouteHint = routeHint ?? CultMeshRouteHint.Automatic; IdempotencyKey = idempotencyKey; } /// Gets the semantic CultMesh operation id being invoked. public string OperationId { get; } /// Gets the optional request schema id for the operation. public string SchemaId { get; } /// Gets the preferred route for invoking the operation. public CultMeshRouteHint RouteHint { get; } /// Gets an optional caller-provided idempotency key. public string? IdempotencyKey { get; } /// Creates an invocation from an advertised operation binding. public static CultMeshOperationInvocationDescriptor FromBinding( CultMeshOperationBindingDescriptor binding, string? idempotencyKey = null) { if (binding == null) throw new ArgumentNullException(nameof(binding)); return new CultMeshOperationInvocationDescriptor( binding.OperationId, binding.SchemaId, binding.RouteHint, idempotencyKey); } /// Creates an invocation from an existing typed operation handle. public static CultMeshOperationInvocationDescriptor FromOperation( CultMeshOperationHandle operation, string? schemaId = null, CultMeshRouteHint? routeHint = null, string? idempotencyKey = null) { if (operation == null) throw new ArgumentNullException(nameof(operation)); return new CultMeshOperationInvocationDescriptor( operation.OperationId, schemaId, routeHint, idempotencyKey); } private static string RequireNonEmpty(string value, string paramName) { return string.IsNullOrWhiteSpace(value) ? throw new ArgumentException("Value must be non-empty.", paramName) : value; } } /// /// Flat, transport-friendly fields for a concrete CultMesh operation invocation. /// [MessagePackObject] [MessagePackFormatter(typeof(CultMeshOperationInvocationRecordFormatter))] public sealed class CultMeshOperationInvocationRecord { /// Creates flat operation invocation fields. [SerializationConstructor] public CultMeshOperationInvocationRecord( string? operationId = null, string? schemaId = null, string? routeKind = null, string? routeDescription = null, string? idempotencyKey = null) { OperationId = operationId ?? ""; SchemaId = schemaId ?? ""; RouteKind = routeKind ?? ""; RouteDescription = routeDescription ?? ""; IdempotencyKey = idempotencyKey ?? ""; } /// Gets the semantic CultMesh operation id. [Key(0)] public string OperationId { get; } /// Gets the optional request schema id. [Key(1)] public string SchemaId { get; } /// Gets the serialized route locality kind. [Key(2)] public string RouteKind { get; } /// Gets the serialized route description. [Key(3)] public string RouteDescription { get; } /// Gets the serialized idempotency key. [Key(4)] public string IdempotencyKey { get; } /// Creates flat fields from a typed invocation descriptor. public static CultMeshOperationInvocationRecord FromInvocation( CultMeshOperationInvocationDescriptor? invocation, string? fallbackOperationId = null, string? fallbackSchemaId = null, CultMeshRouteHint? fallbackRouteHint = null, string? fallbackIdempotencyKey = null) { var route = CultMeshRouteRecord.FromRoute(invocation?.RouteHint ?? fallbackRouteHint); return new CultMeshOperationInvocationRecord( string.IsNullOrWhiteSpace(invocation?.OperationId) ? fallbackOperationId : invocation!.OperationId, string.IsNullOrWhiteSpace(invocation?.SchemaId) ? fallbackSchemaId : invocation!.SchemaId, route.Kind, route.Description, string.IsNullOrWhiteSpace(invocation?.IdempotencyKey) ? fallbackIdempotencyKey : invocation!.IdempotencyKey); } /// Rehydrates the flat fields into a typed invocation descriptor. public CultMeshOperationInvocationDescriptor ToInvocation( string? fallbackOperationId = null, string? fallbackSchemaId = null, CultMeshRouteHint? fallbackRouteHint = null, string? fallbackIdempotencyKey = null) { var operationId = string.IsNullOrWhiteSpace(OperationId) ? fallbackOperationId : OperationId; if (string.IsNullOrWhiteSpace(operationId)) throw new InvalidOperationException("CultMesh operation invocation fields do not contain an operation id."); var route = new CultMeshRouteRecord(RouteKind, RouteDescription) .ToRoute(fallbackRouteHint); return new CultMeshOperationInvocationDescriptor( operationId!, string.IsNullOrWhiteSpace(SchemaId) ? fallbackSchemaId : SchemaId, route, string.IsNullOrWhiteSpace(IdempotencyKey) ? fallbackIdempotencyKey : IdempotencyKey); } } /// /// Shared payload value carried by one concrete CultMesh operation invocation. /// public sealed class CultMeshOperationPayload : IReadOnlyDictionary { private static readonly IReadOnlyDictionary EmptyFields = new Dictionary(0, StringComparer.Ordinal); private readonly IReadOnlyDictionary _fields; /// Creates an empty operation payload. public CultMeshOperationPayload() : this(EmptyFields) { } /// Creates an operation payload from string-compatible fields. public CultMeshOperationPayload(IEnumerable>? fields) { if (fields == null) { _fields = EmptyFields; return; } var copy = new Dictionary(StringComparer.Ordinal); foreach (var field in fields) { if (string.IsNullOrWhiteSpace(field.Key)) continue; copy[field.Key] = field.Value ?? ""; } _fields = copy; } /// Gets the empty operation payload. public static CultMeshOperationPayload Empty { get; } = new(); /// Gets the payload field names. public IEnumerable Keys => _fields.Keys; /// Gets the payload field values. public IEnumerable Values => _fields.Values; /// Gets the number of payload fields. public int Count => _fields.Count; /// Gets a payload field by name. public string this[string key] => _fields[key]; /// Gets whether the payload contains a field. public bool ContainsKey(string key) { return _fields.ContainsKey(key); } /// Tries to read a raw string field. public bool TryGetValue(string key, out string value) { return _fields.TryGetValue(key, out value); } /// Reads a raw string field with a default value. public string GetString(string key, string defaultValue = "") { return _fields.TryGetValue(key, out var value) ? value ?? defaultValue : defaultValue; } /// Reads an integer field using invariant parsing. public int GetInt32(string key, int defaultValue = 0) { return int.TryParse(GetString(key), NumberStyles.Integer, CultureInfo.InvariantCulture, out var value) ? value : defaultValue; } /// Reads a floating point field using invariant parsing. public double GetDouble(string key, double defaultValue = 0) { return double.TryParse(GetString(key), NumberStyles.Float, CultureInfo.InvariantCulture, out var value) ? value : defaultValue; } /// Reads a boolean field using common CultMesh surface spellings. public bool GetBoolean(string key, bool defaultValue = false) { var value = GetString(key); if (string.Equals(value, "true", StringComparison.OrdinalIgnoreCase) || string.Equals(value, "1", StringComparison.Ordinal) || string.Equals(value, "yes", StringComparison.OrdinalIgnoreCase) || string.Equals(value, "on", StringComparison.OrdinalIgnoreCase)) return true; if (string.Equals(value, "false", StringComparison.OrdinalIgnoreCase) || string.Equals(value, "0", StringComparison.Ordinal) || string.Equals(value, "no", StringComparison.OrdinalIgnoreCase) || string.Equals(value, "off", StringComparison.OrdinalIgnoreCase)) return false; return defaultValue; } /// Returns a copy with one field overwritten. public CultMeshOperationPayload With(string key, string value) { if (string.IsNullOrWhiteSpace(key)) throw new ArgumentException("Value must be non-empty.", nameof(key)); var copy = new Dictionary(_fields, StringComparer.Ordinal) { [key] = value ?? "" }; return new CultMeshOperationPayload(copy); } /// Copies payload fields into a mutable dictionary. public Dictionary ToDictionary(IEqualityComparer? comparer = null) { var copy = new Dictionary(comparer ?? StringComparer.Ordinal); foreach (var field in _fields) copy[field.Key] = field.Value ?? ""; return copy; } /// Creates a payload from string-compatible fields. public static CultMeshOperationPayload FromStrings(IEnumerable>? fields) { return new CultMeshOperationPayload(fields); } /// public IEnumerator> GetEnumerator() { return _fields.GetEnumerator(); } /// System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } /// /// Names a reusable projection from typed source state into derived state. /// public sealed class CultMeshProjectionRecipe { private readonly Func> _project; private readonly Func>? _watch; /// Creates a projection recipe. public CultMeshProjectionRecipe( string projectionId, IEnumerable sources, Func> project, CultMeshRouteHint? routeHint = null, Func>? watch = null) { ProjectionId = RequireNonEmpty(projectionId, nameof(projectionId)); Sources = sources?.ToArray() ?? throw new ArgumentNullException(nameof(sources)); _project = project ?? throw new ArgumentNullException(nameof(project)); RouteHint = routeHint ?? CultMeshRouteHint.Automatic; _watch = watch; } /// Gets the semantic projection id. public string ProjectionId { get; } /// Gets the typed state sources this projection depends on. public IReadOnlyList Sources { get; } /// Gets the preferred or observed route for the projection. public CultMeshRouteHint RouteHint { get; } /// Projects derived state once. public Task ProjectAsync(TParameters parameters, CultMeshQueryContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); return _project(parameters, ResolveContext(context)); } /// Projects derived state once for one runtime using default context. public Task ProjectAsync(TParameters parameters, string runtimeId) { return ProjectAsync(parameters, CultMeshQueryContext.ForRuntime(runtimeId)); } /// Watches projection results when the recipe supports reactive execution. public Observable Watch(TParameters parameters, CultMeshQueryContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); if (_watch == null) { throw new NotSupportedException($"Projection recipe '{ProjectionId}' does not support reactive watches."); } return _watch(parameters, ResolveContext(context)); } /// Views this projection recipe as a typed query surface. public CultMeshQuerySurface AsQuerySurface() { return new CultMeshQuerySurface( ProjectionId, ProjectAsync, _watch == null ? null : Watch, Sources, RouteHint); } 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; } } /// /// Describes one column in a native CultMesh slice view. /// public sealed class CultMeshNativeSliceColumn { /// Creates a native slice column descriptor. public CultMeshNativeSliceColumn(string name, string valueType, int elementSizeBytes) { Name = RequireNonEmpty(name, nameof(name)); ValueType = RequireNonEmpty(valueType, nameof(valueType)); if (elementSizeBytes <= 0) { throw new ArgumentOutOfRangeException(nameof(elementSizeBytes), "Element size must be positive."); } ElementSizeBytes = elementSizeBytes; } /// Gets the column name. public string Name { get; } /// Gets the schema value type for each element. public string ValueType { get; } /// Gets the size of one element in bytes. public int ElementSizeBytes { get; } /// Creates a native slice column descriptor for an unmanaged CLR value type. public static CultMeshNativeSliceColumn For(string name) where TValue : unmanaged { return new CultMeshNativeSliceColumn(name, typeof(TValue).FullName ?? typeof(TValue).Name, SizeOf()); } private static int SizeOf() where TValue : unmanaged { return System.Runtime.InteropServices.Marshal.SizeOf(); } private static string RequireNonEmpty(string value, string paramName) { return string.IsNullOrWhiteSpace(value) ? throw new ArgumentException("Value must be non-empty.", paramName) : value; } } /// /// Describes a typed native slice view exposed by CultMesh. /// public sealed class CultMeshNativeSliceViewDescriptor { /// Creates a native slice view descriptor. public CultMeshNativeSliceViewDescriptor( string viewId, string schemaId, int rowCount, IEnumerable columns, CultMeshRouteHint? route = null, string? nativeHandle = null) { ViewId = RequireNonEmpty(viewId, nameof(viewId)); SchemaId = RequireNonEmpty(schemaId, nameof(schemaId)); if (rowCount < 0) { throw new ArgumentOutOfRangeException(nameof(rowCount), "Row count cannot be negative."); } RowCount = rowCount; Columns = columns?.ToArray() ?? throw new ArgumentNullException(nameof(columns)); Route = route ?? CultMeshRouteHint.Automatic; NativeHandle = nativeHandle; } /// Gets the semantic view id. public string ViewId { get; } /// Gets the schema id for the rows in this view. public string SchemaId { get; } /// Gets the number of rows in this view. public int RowCount { get; } /// Gets column descriptors in view order. public IReadOnlyList Columns { get; } /// Gets the route that exposes this view. public CultMeshRouteHint Route { get; } /// Gets an optional runtime-specific native handle. public string? NativeHandle { get; } /// Gets the estimated byte stride for one row when all columns are densely packed. public int DenseRowStrideBytes => Columns.Sum(column => column.ElementSizeBytes); /// Finds a column descriptor by name. public CultMeshNativeSliceColumn? FindColumn(string name) { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value must be non-empty.", nameof(name)); return Columns.FirstOrDefault(column => string.Equals(column.Name, name, StringComparison.Ordinal)); } 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 native slice view. /// public sealed class CultMeshNativeSliceViewDiagnostic { /// Creates native slice view diagnostics. public CultMeshNativeSliceViewDiagnostic(CultMeshNativeSliceViewDescriptor view) { if (view == null) throw new ArgumentNullException(nameof(view)); ViewId = view.ViewId; SchemaId = view.SchemaId; RowCount = view.RowCount; Columns = view.Columns.ToArray(); Route = view.Route; NativeHandle = view.NativeHandle; DenseRowStrideBytes = view.DenseRowStrideBytes; } /// Gets the semantic view id. public string ViewId { get; } /// Gets the schema id for rows in this view. public string SchemaId { get; } /// Gets the row count advertised by this view. public int RowCount { get; } /// Gets column descriptors in view order. public IReadOnlyList Columns { get; } /// Gets the route that exposes this view. public CultMeshRouteHint Route { get; } /// Gets an optional runtime-specific native handle. public string? NativeHandle { get; } /// Gets the estimated byte stride for one densely packed row. public int DenseRowStrideBytes { get; } } }