Aetheria/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSurfaceDocuments.cs
2026-07-04 22:46:04 +01:00

631 lines
23 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using GameCult.Caching;
using GameCult.Mesh;
using MessagePack;
using EveUiCommandTemplate = GameCult.Eve.Surface.EveCommandTemplate;
using EveUiEmbeddedDocumentSlot = GameCult.Eve.Surface.EveEmbeddedDocumentSlot;
using EveUiStyleToken = GameCult.Eve.Surface.EveStyleToken;
using EveUiSurfaceComponent = GameCult.Eve.Surface.EveSurfaceComponent;
using EveUiSurfaceDocument = GameCult.Eve.Surface.EveSurfaceDocument;
using EveUiSurfaceTree = GameCult.Eve.Surface.EveSurfaceTree;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
public static class AetheriaRuntimeSurfaceStateRefs
{
public const string Source = "stateRef";
public const string Value = "valueRef";
public const string Label = "labelRef";
public const string Format = "stateFormat";
public static (string Key, string Value) SourceRef(string reference)
{
return (Source, reference ?? "");
}
public static (string Key, string Value) ValueRef(string reference)
{
return (Value, reference ?? "");
}
public static (string Key, string Value) LabelRef(string reference)
{
return (Label, reference ?? "");
}
public static (string Key, string Value) FormatRef(string format)
{
return (Format, format ?? "");
}
}
public static class AetheriaRuntimeSurfaceStateBindings
{
public const string PropPrefix = "cultmesh.statePointer.";
public const string PointerIdSuffix = ".pointerId";
public const string SourceIdSuffix = ".sourceId";
public const string SchemaIdSuffix = ".schemaId";
public const string RouteKindSuffix = ".routeKind";
public const string RouteDescriptionSuffix = ".routeDescription";
public static CultMeshStateBindingDescriptor ForDaemonStateRef(
string targetProp,
string stateRef,
string schemaId = AetheriaRuntimeDaemonSchemas.Frame)
{
return new CultMeshStateBindingDescriptor(
targetProp,
ToPointerId(stateRef),
stateRef,
schemaId,
new CultMeshRouteHint(
CultMeshLocalityKind.SharedMemory,
"daemon-published CultCache state"));
}
public static IReadOnlyList<CultMeshStateBindingDescriptor> FromProps(
IReadOnlyDictionary<string, string> props)
{
if (props == null || props.Count == 0)
return Array.Empty<CultMeshStateBindingDescriptor>();
var bindings = new List<CultMeshStateBindingDescriptor>();
AddPropBinding(bindings, "value", Get(props, AetheriaRuntimeSurfaceStateRefs.Source));
foreach (var prop in props)
{
if (string.IsNullOrWhiteSpace(prop.Value) ||
string.Equals(prop.Key, AetheriaRuntimeSurfaceStateRefs.Source, StringComparison.Ordinal) ||
!prop.Key.EndsWith("Ref", StringComparison.Ordinal))
{
continue;
}
AddPropBinding(bindings, prop.Key.Substring(0, prop.Key.Length - "Ref".Length), prop.Value);
}
return bindings;
}
public static void AddPointerProps(
IDictionary<string, string> props,
IReadOnlyList<CultMeshStateBindingDescriptor> bindings)
{
if (props == null || bindings == null)
return;
foreach (var binding in bindings)
{
if (binding == null || string.IsNullOrWhiteSpace(binding.PointerId))
continue;
var record = CultMesh.StateBindingRecord(binding);
var prefix = PropPrefix + record.TargetProp;
props[prefix + PointerIdSuffix] = record.PointerId;
props[prefix + SourceIdSuffix] = record.SourceId;
props[prefix + SchemaIdSuffix] = record.SchemaId;
props[prefix + RouteKindSuffix] = record.RouteKind;
props[prefix + RouteDescriptionSuffix] = record.RouteDescription;
}
}
private static void AddPropBinding(
List<CultMeshStateBindingDescriptor> bindings,
string targetProp,
string stateRef)
{
if (string.IsNullOrWhiteSpace(stateRef))
return;
var schemaId = stateRef.StartsWith(AetheriaRuntimeDaemonItemStatQueries.StateRefPrefix + "/", StringComparison.Ordinal)
? AetheriaRuntimeDaemonSchemas.CurrentEntity
: AetheriaRuntimeDaemonSchemas.Frame;
bindings.Add(ForDaemonStateRef(targetProp, stateRef, schemaId));
}
private static string Get(IReadOnlyDictionary<string, string> props, string key)
{
return props.TryGetValue(key, out var value) ? value : "";
}
private static string ToPointerId(string stateRef)
{
if (string.IsNullOrWhiteSpace(stateRef))
return "";
var chars = stateRef
.Select(character => char.IsLetterOrDigit(character) ? char.ToLowerInvariant(character) : '.')
.ToArray();
var pointerId = new string(chars).Trim('.');
while (pointerId.Contains("..", StringComparison.Ordinal))
pointerId = pointerId.Replace("..", ".", StringComparison.Ordinal);
return string.IsNullOrWhiteSpace(pointerId) ? "aetheria.state.unknown" : pointerId;
}
}
[CultDocument("gamecult.aetheria.runtime_surface", "gamecult.aetheria.runtime_surface.v1")]
[MessagePackObject]
public sealed class AetheriaRuntimeSurfaceDocument
{
[SerializationConstructor]
public AetheriaRuntimeSurfaceDocument(
string providerId,
string providerKind,
string title,
long version,
string updatedAtUtc,
AetheriaRuntimeSurfaceTree surface,
IReadOnlyList<AetheriaRuntimeSurfaceCommandTemplate> commands)
{
ProviderId = providerId ?? "";
ProviderKind = providerKind ?? "";
Title = title ?? "";
Version = version;
UpdatedAtUtc = updatedAtUtc ?? "";
Surface = surface ?? throw new ArgumentNullException(nameof(surface));
Commands = commands ?? Array.Empty<AetheriaRuntimeSurfaceCommandTemplate>();
}
[Key(0)]
public string ProviderId { get; }
[Key(1)]
public string ProviderKind { get; }
[Key(2)]
public string Title { get; }
[Key(3)]
public long Version { get; }
[Key(4)]
public string UpdatedAtUtc { get; }
[Key(5)]
public AetheriaRuntimeSurfaceTree Surface { get; }
[Key(6)]
public IReadOnlyList<AetheriaRuntimeSurfaceCommandTemplate> Commands { get; }
}
[MessagePackObject]
public sealed class AetheriaRuntimeSurfaceTree
{
[SerializationConstructor]
public AetheriaRuntimeSurfaceTree(
string id,
AetheriaRuntimeSurfaceComponent root,
IReadOnlyList<AetheriaRuntimeSurfaceStyleToken> styles)
{
Id = id ?? "";
Root = root ?? throw new ArgumentNullException(nameof(root));
Styles = styles ?? Array.Empty<AetheriaRuntimeSurfaceStyleToken>();
}
[Key(0)]
public string Id { get; }
[Key(1)]
public AetheriaRuntimeSurfaceComponent Root { get; }
[Key(2)]
public IReadOnlyList<AetheriaRuntimeSurfaceStyleToken> Styles { get; }
}
[MessagePackObject]
public sealed class AetheriaRuntimeSurfaceComponent
{
public AetheriaRuntimeSurfaceComponent(
string id,
string kind,
IReadOnlyDictionary<string, string> props,
IReadOnlyList<AetheriaRuntimeSurfaceComponent> children)
: this(id, kind, props, children, AetheriaRuntimeSurfaceStateBindings.FromProps(props))
{
}
public AetheriaRuntimeSurfaceComponent(
string id,
string kind,
IReadOnlyDictionary<string, string> props,
IReadOnlyList<AetheriaRuntimeSurfaceComponent> children,
IReadOnlyList<CultMeshStateBindingDescriptor> stateBindings)
: this(id, kind, props, children, stateBindings, Array.Empty<AetheriaRuntimeEmbeddedDocumentSlot>())
{
}
[SerializationConstructor]
public AetheriaRuntimeSurfaceComponent(
string id,
string kind,
IReadOnlyDictionary<string, string> props,
IReadOnlyList<AetheriaRuntimeSurfaceComponent> children,
IReadOnlyList<CultMeshStateBindingDescriptor> stateBindings,
IReadOnlyList<AetheriaRuntimeEmbeddedDocumentSlot> embeddedDocuments,
IReadOnlyDictionary<string, string>? layout = null,
IReadOnlyDictionary<string, string>? style = null)
{
Id = id ?? "";
Kind = kind ?? "";
Props = props ?? new Dictionary<string, string>(StringComparer.Ordinal);
Children = children ?? Array.Empty<AetheriaRuntimeSurfaceComponent>();
StateBindings = stateBindings ?? Array.Empty<CultMeshStateBindingDescriptor>();
EmbeddedDocuments = embeddedDocuments ?? Array.Empty<AetheriaRuntimeEmbeddedDocumentSlot>();
Layout = layout ?? new Dictionary<string, string>(StringComparer.Ordinal);
Style = style ?? new Dictionary<string, string>(StringComparer.Ordinal);
}
[Key(0)]
public string Id { get; }
[Key(1)]
public string Kind { get; }
[Key(2)]
public IReadOnlyDictionary<string, string> Props { get; }
[Key(3)]
public IReadOnlyList<AetheriaRuntimeSurfaceComponent> Children { get; }
[Key(4)]
public IReadOnlyList<CultMeshStateBindingDescriptor> StateBindings { get; }
[Key(5)]
public IReadOnlyList<AetheriaRuntimeEmbeddedDocumentSlot> EmbeddedDocuments { get; }
[Key(6)]
public IReadOnlyDictionary<string, string> Layout { get; }
[Key(7)]
public IReadOnlyDictionary<string, string> Style { get; }
}
[MessagePackObject]
public sealed class AetheriaRuntimeEmbeddedDocumentSlot
{
[SerializationConstructor]
public AetheriaRuntimeEmbeddedDocumentSlot(
string slotId,
string documentId,
string schemaId,
string presentationKind,
CultMeshRouteHint routeHint)
{
SlotId = slotId ?? "";
DocumentId = documentId ?? "";
SchemaId = schemaId ?? "";
PresentationKind = presentationKind ?? "";
RouteHint = routeHint ?? CultMeshRouteHint.Automatic;
}
public AetheriaRuntimeEmbeddedDocumentSlot(
string slotId,
string documentId,
string schemaId,
string presentationKind)
: this(slotId, documentId, schemaId, presentationKind, CultMeshRouteHint.Automatic)
{
}
[Key(0)]
public string SlotId { get; }
[Key(1)]
public string DocumentId { get; }
[Key(2)]
public string SchemaId { get; }
[Key(3)]
public string PresentationKind { get; }
[Key(4)]
public CultMeshRouteHint RouteHint { get; }
}
[MessagePackObject]
public sealed class AetheriaRuntimeSurfaceStyleToken
{
[SerializationConstructor]
public AetheriaRuntimeSurfaceStyleToken(string name, string value)
{
Name = name ?? "";
Value = value ?? "";
}
[Key(0)]
public string Name { get; }
[Key(1)]
public string Value { get; }
}
[MessagePackObject]
public sealed class AetheriaRuntimeSurfaceCommandTemplate
{
public const string CultMeshTransport = "cultmesh";
public AetheriaRuntimeSurfaceCommandTemplate(string command, string label, string transport)
: this(CultMesh.OperationBindingRecord(
command,
label,
"",
nameof(CultMeshLocalityKind.Automatic),
transport).ToBinding())
{
}
[SerializationConstructor]
public AetheriaRuntimeSurfaceCommandTemplate(CultMeshOperationBindingDescriptor operation)
{
Operation = operation ?? throw new ArgumentNullException(nameof(operation));
}
[Key(0)]
public CultMeshOperationBindingDescriptor Operation { get; }
[IgnoreMember]
public string Command => Operation.OperationId;
[IgnoreMember]
public string Label => Operation.Label;
[IgnoreMember]
public string Transport => Operation.RouteHint.Description ?? "";
}
public static class AetheriaRuntimeSurfaceDocuments
{
public static EveUiSurfaceDocument ToPortableSurface(AetheriaRuntimeSurfaceDocument document)
{
if (document == null) throw new ArgumentNullException(nameof(document));
return new EveUiSurfaceDocument(
document.ProviderId,
document.ProviderKind,
document.Title,
document.Version,
document.UpdatedAtUtc,
new EveUiSurfaceTree(
document.Surface.Id,
ToEveSurfaceComponent(document.Surface.Root),
document.Surface.Styles
.Select(style => new EveUiStyleToken(style.Name, style.Value))
.ToArray()),
document.Commands
.Select(command => new EveUiCommandTemplate(command.Operation))
.ToArray());
}
public static AetheriaRuntimeSurfaceDocument FromPortableSurface(EveUiSurfaceDocument document)
{
if (document == null) throw new ArgumentNullException(nameof(document));
return new AetheriaRuntimeSurfaceDocument(
document.ProviderId,
document.ProviderKind,
document.Title,
document.Version,
document.UpdatedAtUtc,
new AetheriaRuntimeSurfaceTree(
document.Surface.Id,
FromPortableComponent(document.Surface.Root),
document.Surface.Styles
.Select(style => new AetheriaRuntimeSurfaceStyleToken(style.Name, style.Value))
.ToArray()),
document.Commands
.Select(command => new AetheriaRuntimeSurfaceCommandTemplate(
command.Operation))
.ToArray());
}
public static EveUiSurfaceDocument ToEveSurfaceDocument(AetheriaRuntimeSurfaceDocument document)
{
return ToEveSurfaceDocument(document, null);
}
public static EveUiSurfaceDocument ToEveSurfaceDocument(EveUiSurfaceDocument document)
{
return ToEveSurfaceDocument(document, null);
}
public static EveUiSurfaceDocument ToEveSurfaceDocument(
EveUiSurfaceDocument document,
CultMeshStateRefResolver? stateRefResolver)
{
if (document == null) throw new ArgumentNullException(nameof(document));
return ResolveStateRefs(document, stateRefResolver);
}
public static EveUiSurfaceDocument ToEveSurfaceDocument(
AetheriaRuntimeSurfaceDocument document,
CultMeshStateRefResolver? stateRefResolver)
{
if (document == null) throw new ArgumentNullException(nameof(document));
return ResolveStateRefs(ToPortableSurface(document), stateRefResolver);
}
public static EveUiSurfaceDocument ResolveStateRefs(
EveUiSurfaceDocument surface,
CultMeshStateRefResolver? stateRefResolver)
{
if (surface == null) throw new ArgumentNullException(nameof(surface));
if (stateRefResolver == null)
return surface;
var resolveStateRef = stateRefResolver.AsFunc();
return new EveUiSurfaceDocument(
surface.Type,
surface.Schema,
surface.ProviderId,
surface.ProviderKind,
surface.Title,
surface.Version,
surface.UpdatedAtUtc,
new EveUiSurfaceTree(
surface.Surface.Id,
ResolveStateRefs(surface.Surface.Root, resolveStateRef),
surface.Surface.Styles),
surface.Commands);
}
public static EveUiSurfaceDocument EmptySurface(string surfaceId)
{
var id = string.IsNullOrWhiteSpace(surfaceId) ? "aetheria.surface.missing" : surfaceId;
return new EveUiSurfaceDocument(
"surface-state",
"gamecult.eve.surface.v1",
"aetheria.daemon",
"daemon",
"",
0,
"",
new EveUiSurfaceTree(
id,
new EveUiSurfaceComponent(
id + ".missing",
"surface",
new Dictionary<string, string>(StringComparer.Ordinal),
Array.Empty<EveUiSurfaceComponent>()),
Array.Empty<EveUiStyleToken>()),
Array.Empty<EveUiCommandTemplate>());
}
private static EveUiSurfaceComponent ToEveSurfaceComponent(AetheriaRuntimeSurfaceComponent component)
{
var props = new Dictionary<string, string>(component.Props, StringComparer.Ordinal);
AetheriaRuntimeSurfaceStateBindings.AddPointerProps(props, component.StateBindings);
return new EveUiSurfaceComponent(
component.Id,
component.Kind,
props,
component.Children.Select(ToEveSurfaceComponent).ToArray(),
component.StateBindings.Select(ToCultMeshStateBinding).ToArray(),
component.EmbeddedDocuments.Select(ToEveEmbeddedDocumentSlot).ToArray(),
new Dictionary<string, string>(component.Layout, StringComparer.Ordinal),
new Dictionary<string, string>(component.Style, StringComparer.Ordinal));
}
private static EveUiEmbeddedDocumentSlot ToEveEmbeddedDocumentSlot(AetheriaRuntimeEmbeddedDocumentSlot slot)
{
return new EveUiEmbeddedDocumentSlot(
slot.SlotId,
slot.DocumentId,
slot.SchemaId,
slot.PresentationKind,
slot.RouteHint);
}
private static CultMeshStateBindingDescriptor ToCultMeshStateBinding(
CultMeshStateBindingDescriptor binding)
{
return binding;
}
private static CultMeshStateBindingDescriptor FromPortableStateBinding(
CultMeshStateBindingDescriptor binding)
{
return binding;
}
private static EveUiSurfaceComponent ResolveStateRefs(
EveUiSurfaceComponent component,
Func<string, string> resolveStateRef)
{
var props = new Dictionary<string, string>(component.Props, StringComparer.Ordinal);
ResolvePropRefs(props, resolveStateRef);
return new EveUiSurfaceComponent(
component.Id,
component.Kind,
props,
ResolveStateRefs(component.Children, resolveStateRef),
component.StateBindings,
component.EmbeddedDocuments,
component.Layout,
component.Style);
}
private static IReadOnlyList<EveUiSurfaceComponent> ResolveStateRefs(
IReadOnlyList<EveUiSurfaceComponent> children,
Func<string, string> resolveStateRef)
{
if (children == null || children.Count == 0)
return Array.Empty<EveUiSurfaceComponent>();
var resolved = new EveUiSurfaceComponent[children.Count];
for (var index = 0; index < children.Count; index++)
resolved[index] = ResolveStateRefs(children[index], resolveStateRef);
return resolved;
}
private static AetheriaRuntimeSurfaceComponent FromPortableComponent(EveUiSurfaceComponent component)
{
return new AetheriaRuntimeSurfaceComponent(
component.Id,
component.Kind,
new Dictionary<string, string>(component.Props, StringComparer.Ordinal),
component.Children.Select(FromPortableComponent).ToArray(),
component.StateBindings.Select(FromPortableStateBinding).ToArray(),
component.EmbeddedDocuments.Select(FromPortableEmbeddedDocumentSlot).ToArray(),
new Dictionary<string, string>(component.Layout, StringComparer.Ordinal),
new Dictionary<string, string>(component.Style, StringComparer.Ordinal));
}
private static AetheriaRuntimeEmbeddedDocumentSlot FromPortableEmbeddedDocumentSlot(
EveUiEmbeddedDocumentSlot slot)
{
return new AetheriaRuntimeEmbeddedDocumentSlot(
slot.SlotId,
slot.DocumentId,
slot.SchemaId,
slot.PresentationKind,
slot.RouteHint);
}
private static void ResolvePropRefs(
Dictionary<string, string> props,
Func<string, string> resolveStateRef)
{
ResolvePropRef(props, AetheriaRuntimeSurfaceStateRefs.Source, "value", resolveStateRef);
var refProps = props
.Where(prop => IsStatePointerProp(prop.Key) &&
!string.Equals(prop.Key, AetheriaRuntimeSurfaceStateRefs.Source, StringComparison.Ordinal) &&
!string.IsNullOrWhiteSpace(prop.Value))
.ToArray();
foreach (var refProp in refProps)
ResolvePropRef(props, refProp.Key, ResolvePointerValueKey(refProp.Key), resolveStateRef);
}
private static bool IsStatePointerProp(string key)
{
return key.EndsWith("Ref", StringComparison.Ordinal);
}
private static string ResolvePointerValueKey(string refKey)
{
return refKey.Substring(0, refKey.Length - "Ref".Length);
}
private static void ResolvePropRef(
Dictionary<string, string> props,
string refKey,
string valueKey,
Func<string, string> resolveStateRef)
{
if (!props.TryGetValue(refKey, out var stateRef) || string.IsNullOrWhiteSpace(stateRef))
return;
var resolved = resolveStateRef(stateRef);
if (!string.IsNullOrWhiteSpace(resolved))
props[valueKey] = resolved;
}
}
}