Editor/Core/RuntimeAuthority.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Nodes;
namespace TeamCreate;
public static class RuntimeAuthorityContract
{
public const string OperationKind = "runtime-operation";
public const string SnapshotKind = "snapshot";
public const string EditKind = "edit";
}
public sealed class RuntimeAuthority
{
private sealed class Namespace
{
public string RunId;
public string ScenePath;
public Dictionary<string, ObjectState> State = new Dictionary<string, ObjectState>(StringComparer.Ordinal);
public Dictionary<OperationIdentity, Packet> Receipts = new Dictionary<OperationIdentity, Packet>();
public long Revision;
public long LastSequence;
public long LastTick = -1L;
}
private readonly record struct OperationIdentity(string Peer, string Id);
private sealed record ValidatedOperation(int? ProtocolVersion, CollaborationMode? Mode, string Id, string Peer, string ScenePath, string RunId, long Sequence, long Tick, string OperationKind);
private readonly Dictionary<string, Namespace> namespaces = new Dictionary<string, Namespace>(StringComparer.Ordinal);
private readonly Dictionary<string, string> runScenes = new Dictionary<string, string>(StringComparer.Ordinal);
private readonly Action<Packet> journal;
private bool journalInProgress;
public RuntimeAuthority(Action<Packet> journal)
{
this.journal = journal ?? throw new ArgumentNullException("journal");
}
public Packet Apply(Packet operation)
{
if (journalInProgress)
{
return Reject(operation, CurrentRevision(operation?.RuntimeRunId), "Runtime authority rejects re-entrant Apply while journal persistence is in progress.");
}
if (!TryValidateEnvelope(operation, out var error))
{
return Reject(operation, CurrentRevision(operation?.RuntimeRunId), error);
}
ValidatedOperation validatedOperation = CaptureValidatedOperation(operation);
if (runScenes.TryGetValue(validatedOperation.RunId, out var value) && !string.Equals(value, validatedOperation.ScenePath, StringComparison.Ordinal))
{
return Reject(CreateCanonicalPacket(validatedOperation, operation), CurrentRevision(validatedOperation.RunId), $"Runtime run '{validatedOperation.RunId}' is bound to scene '{value}', not '{validatedOperation.ScenePath}'.");
}
string key = MakeNamespaceKey(validatedOperation.RunId, validatedOperation.ScenePath);
namespaces.TryGetValue(key, out var value2);
if (value2 != null)
{
OperationIdentity key2 = new OperationIdentity(validatedOperation.Peer, validatedOperation.Id);
if (value2.Receipts.TryGetValue(key2, out var value3))
{
try
{
return CloneReceipt(value3);
}
catch (Exception ex)
{
return Reject(CreateCanonicalPacket(validatedOperation, operation), value2.Revision, "Runtime accepted receipt could not be copied: " + ex.Message);
}
}
if (validatedOperation.Sequence <= value2.LastSequence)
{
return Reject(CreateCanonicalPacket(validatedOperation, operation), value2.Revision, $"Runtime sequence {validatedOperation.Sequence} is stale or duplicate; last accepted sequence is {value2.LastSequence}.");
}
if (validatedOperation.Tick < value2.LastTick)
{
return Reject(CreateCanonicalPacket(validatedOperation, operation), value2.Revision, $"Runtime tick {validatedOperation.Tick} is stale; last accepted tick is {value2.LastTick}.");
}
}
else if (!string.Equals(validatedOperation.OperationKind, "snapshot", StringComparison.Ordinal))
{
return Reject(CreateCanonicalPacket(validatedOperation, operation), 0L, "Runtime run '" + validatedOperation.RunId + "' has no authority namespace; begin with a complete snapshot operation.");
}
Dictionary<string, ObjectState> next;
if (string.Equals(validatedOperation.OperationKind, "snapshot", StringComparison.Ordinal))
{
if (!TryBuildSnapshot(validatedOperation.ScenePath, operation.Objects, out next, out error))
{
return Reject(CreateCanonicalPacket(validatedOperation, operation), value2?.Revision ?? 0, error);
}
}
else if (!TryBuildChanges(value2, operation.Changes, out next, out error))
{
return Reject(CreateCanonicalPacket(validatedOperation, operation), value2?.Revision ?? 0, error);
}
long revision = (value2?.Revision ?? 0) + 1;
Packet packet;
try
{
List<Change> changes = ((validatedOperation.OperationKind == "edit") ? operation.Changes.Select((Change change) => new Change(change?.Id, CloneIfPresent(change?.Before), CloneIfPresent(change?.After))).ToList() : null);
Packet operation2 = CreateCanonicalPacket(validatedOperation, operation);
packet = CreateReceipt(operation2, "accepted", revision, null, changes);
}
catch (Exception ex2)
{
return Reject(CreateCanonicalPacket(validatedOperation, operation), value2?.Revision ?? 0, "Runtime operation payload could not be copied for an accepted receipt: " + ex2.Message);
}
Packet packet2;
try
{
packet2 = CloneReceipt(packet);
}
catch (Exception ex3)
{
return Reject(packet, value2?.Revision ?? 0, "Runtime accepted receipt could not be copied: " + ex3.Message);
}
journalInProgress = true;
try
{
Packet packet3 = CloneReceipt(packet2);
if (packet3.Objects == null && packet.Objects != null)
{
throw new InvalidOperationException("Runtime accepted receipt could not be isolated for journaling.");
}
if (packet3.Changes == null && packet.Changes != null)
{
throw new InvalidOperationException("Runtime accepted receipt could not be isolated for journaling.");
}
journal(packet3);
}
catch (Exception ex4)
{
return Reject(packet, value2?.Revision ?? 0, "Runtime journal persistence failed: " + ex4.Message);
}
finally
{
journalInProgress = false;
}
if (value2 == null)
{
value2 = new Namespace
{
RunId = validatedOperation.RunId,
ScenePath = validatedOperation.ScenePath
};
namespaces.Add(key, value2);
runScenes.Add(validatedOperation.RunId, validatedOperation.ScenePath);
}
value2.State = next;
value2.Revision = revision;
value2.LastSequence = validatedOperation.Sequence;
value2.LastTick = validatedOperation.Tick;
value2.Receipts.Add(new OperationIdentity(validatedOperation.Peer, validatedOperation.Id), packet2);
return packet;
}
public IReadOnlyDictionary<string, ObjectState> State(string runId, string scenePath)
{
if (!TryGetNamespace(runId, scenePath, out var current))
{
throw MissingNamespace(runId, scenePath);
}
return current.State.Values.ToDictionary((ObjectState state) => state.Id, (ObjectState state) => Clone(state), StringComparer.Ordinal);
}
public long Revision(string runId, string scenePath)
{
if (!TryGetNamespace(runId, scenePath, out var current))
{
throw MissingNamespace(runId, scenePath);
}
return current.Revision;
}
public long LastSequence(string runId, string scenePath)
{
if (!TryGetNamespace(runId, scenePath, out var current))
{
throw MissingNamespace(runId, scenePath);
}
return current.LastSequence;
}
public long LastTick(string runId, string scenePath)
{
if (!TryGetNamespace(runId, scenePath, out var current))
{
throw MissingNamespace(runId, scenePath);
}
return current.LastTick;
}
public int ReceiptCount(string runId, string scenePath)
{
if (!TryGetNamespace(runId, scenePath, out var current))
{
throw MissingNamespace(runId, scenePath);
}
return current.Receipts.Count;
}
private bool TryBuildSnapshot(string scenePath, IEnumerable<ObjectState> objects, out Dictionary<string, ObjectState> next, out string error)
{
next = null;
error = null;
if (objects == null)
{
error = "Runtime snapshot operation requires a complete Objects list.";
return false;
}
Dictionary<string, ObjectState> dictionary = new Dictionary<string, ObjectState>(StringComparer.Ordinal);
try
{
foreach (ObjectState @object in objects)
{
RuntimeStateRules.ValidateObject(@object);
ObjectState objectState = Normalize(@object);
if (!dictionary.TryAdd(objectState.Id, objectState))
{
throw new InvalidOperationException("Runtime object identity is invalid: snapshot duplicates object " + objectState.Id);
}
}
RuntimeStateRules.ValidateSnapshot(scenePath, dictionary);
}
catch (Exception ex)
{
error = ex.Message;
return false;
}
next = dictionary;
return true;
}
private bool TryBuildChanges(Namespace current, IEnumerable<Change> changes, out Dictionary<string, ObjectState> next, out string error)
{
next = null;
error = null;
if (changes == null || !changes.Any())
{
error = "Runtime edit operation requires at least one change.";
return false;
}
Dictionary<string, ObjectState> dictionary = current.State.Values.ToDictionary((ObjectState state) => state.Id, Clone, StringComparer.Ordinal);
HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
try
{
foreach (Change change in changes)
{
if ((object)change == null || !Guid.TryParse(change.Id, out var result))
{
throw new InvalidOperationException("Runtime object identity is invalid: change id must be a GUID.");
}
if (!hashSet.Add(result.ToString("D")))
{
throw new InvalidOperationException($"Runtime operation changes object {result:D} more than once.");
}
foreach (ObjectState item in new ObjectState[2] { change.Before, change.After }.Where((ObjectState state) => (object)state != null))
{
RuntimeStateRules.ValidateObject(item);
if (!Guid.TryParse(item.Id, out var result2) || result2 != result)
{
throw new InvalidOperationException($"Runtime object identity is invalid: change {result:D} does not match object {item.Id}.");
}
}
string text = result.ToString("D");
current.State.TryGetValue(text, out var value);
if (!RuntimeEqual(value, change.Before))
{
throw new InvalidOperationException("Runtime conflict on " + text + ": first accepted edit wins; reconcile and retry this object.");
}
if ((object)change.After == null)
{
dictionary.Remove(text);
}
else
{
dictionary[text] = Normalize(change.After);
}
}
RuntimeStateRules.ValidateSnapshot(current.ScenePath, dictionary);
}
catch (Exception ex)
{
error = ex.Message;
return false;
}
next = dictionary;
return true;
}
private static Packet CreateCanonicalPacket(ValidatedOperation validated, Packet operation)
{
return new Packet
{
ProtocolVersion = validated.ProtocolVersion,
Mode = validated.Mode,
Id = validated.Id,
Peer = validated.Peer,
Scene = validated.ScenePath,
RuntimeRunId = validated.RunId,
RuntimeSequence = validated.Sequence,
RuntimeTick = validated.Tick,
RuntimeOperationKind = validated.OperationKind,
Objects = operation.Objects,
Changes = operation.Changes
};
}
private static ValidatedOperation CaptureValidatedOperation(Packet operation)
{
return new ValidatedOperation(operation.ProtocolVersion, operation.Mode, operation.Id, operation.Peer, operation.Scene, operation.RuntimeRunId, operation.RuntimeSequence.Value, operation.RuntimeTick.Value, operation.RuntimeOperationKind);
}
private static bool TryValidateEnvelope(Packet operation, out string error)
{
if (operation == null)
{
error = "Runtime operation is missing.";
return false;
}
if (!string.Equals(operation.Kind, "runtime-operation", StringComparison.Ordinal))
{
error = "Runtime authority accepts only explicit runtime-operation envelopes.";
return false;
}
if (operation.ProtocolVersion != 4)
{
error = $"Runtime operation requires protocol version {4} ({"teamcreate-4"}).";
return false;
}
if (operation.Mode != CollaborationMode.RuntimeExperimental)
{
error = "Runtime operation requires experimental runtime collaboration mode.";
return false;
}
if (string.IsNullOrWhiteSpace(operation.Id))
{
error = "Runtime operation requires a non-empty operation ID.";
return false;
}
if (string.IsNullOrWhiteSpace(operation.Peer))
{
error = "Runtime operation requires a non-empty peer identity.";
return false;
}
if (!RuntimeStateRules.IsValidRunId(operation.RuntimeRunId))
{
error = "Runtime operation requires a valid runtime run ID.";
return false;
}
if (!RuntimeStateRules.IsSafeScenePath(operation.Scene))
{
error = "Runtime scene path is invalid: " + (operation.Scene ?? "<null>");
return false;
}
if (!operation.RuntimeSequence.HasValue || operation.RuntimeSequence.Value <= 0)
{
error = "Runtime operation requires a positive sequence number.";
return false;
}
if (!operation.RuntimeTick.HasValue || operation.RuntimeTick.Value < 0)
{
error = "Runtime operation requires a non-negative tick.";
return false;
}
string runtimeOperationKind = operation.RuntimeOperationKind;
if ((!(runtimeOperationKind == "snapshot") && !(runtimeOperationKind == "edit")) || 1 == 0)
{
error = "Runtime operation kind must be 'snapshot' or 'edit'.";
return false;
}
error = null;
return true;
}
private static Packet Reject(Packet operation, long revision, string error)
{
try
{
return CreateReceipt(operation, "reject", revision, error);
}
catch (Exception ex)
{
string text = (string.IsNullOrWhiteSpace(error) ? "Runtime operation payload is malformed." : error);
return CreateMinimalReceipt(operation, "reject", revision, text + " Payload could not be copied for the reject receipt: " + ex.Message);
}
}
private static Packet CreateReceipt(Packet operation, string kind, long revision, string error, List<Change> changes = null)
{
Packet packet = new Packet();
packet.Kind = kind;
packet.Id = operation?.Id;
packet.Peer = operation?.Peer;
packet.Scene = operation?.Scene;
packet.Error = error;
packet.Revision = revision;
packet.ProtocolVersion = operation?.ProtocolVersion;
packet.Mode = operation?.Mode;
packet.RuntimeRunId = operation?.RuntimeRunId;
packet.RuntimeSequence = operation?.RuntimeSequence;
packet.RuntimeTick = operation?.RuntimeTick;
packet.RuntimeOperationKind = operation?.RuntimeOperationKind;
packet.Objects = operation?.Objects?.Select(Clone).ToList();
packet.Changes = changes ?? operation?.Changes?.Select((Change change) => ((object)change == null) ? null : new Change(change.Id, CloneIfPresent(change.Before), CloneIfPresent(change.After))).ToList();
return packet;
}
private static Packet CloneReceipt(Packet packet)
{
return CreateReceipt(packet, packet.Kind, packet.Revision, packet.Error);
}
private static Packet CreateMinimalReceipt(Packet operation, string kind, long revision, string error)
{
return new Packet
{
Kind = kind,
Id = operation?.Id,
Peer = operation?.Peer,
Scene = operation?.Scene,
Error = error,
Revision = revision,
ProtocolVersion = operation?.ProtocolVersion,
Mode = operation?.Mode,
RuntimeRunId = operation?.RuntimeRunId,
RuntimeSequence = operation?.RuntimeSequence,
RuntimeTick = operation?.RuntimeTick,
RuntimeOperationKind = operation?.RuntimeOperationKind
};
}
private static ObjectState Clone(ObjectState state)
{
if ((object)state == null || state.Data == null)
{
throw new InvalidOperationException("Runtime object state is missing or has no data.");
}
return state with
{
Data = (JsonObject)state.Data.DeepClone()
};
}
private static ObjectState CloneIfPresent(ObjectState state)
{
return ((object)state == null) ? null : Clone(state);
}
private static ObjectState Normalize(ObjectState state)
{
string text = Guid.Parse(state.Id).ToString("D");
string parent = (string.IsNullOrEmpty(state.Parent) ? state.Parent : Guid.Parse(state.Parent).ToString("D"));
JsonObject jsonObject = (JsonObject)state.Data.DeepClone();
jsonObject["__guid"] = text;
if (jsonObject["Components"] is JsonArray jsonArray)
{
for (int i = 0; i < jsonArray.Count; i++)
{
if (!(jsonArray[i] is JsonObject jsonObject2) || !jsonObject2.TryGetPropertyValue("__guid", out JsonNode jsonNode) || !(jsonNode is JsonValue jsonValue) || !jsonValue.TryGetValue<string>(out string value))
{
throw new InvalidOperationException("Runtime state shape is invalid: component __guid is missing.");
}
jsonObject2["__guid"] = Guid.Parse(value).ToString("D");
}
}
return new ObjectState(text, parent, jsonObject);
}
private static bool RuntimeEqual(ObjectState left, ObjectState right)
{
if ((object)left == null || (object)right == null)
{
return (object)left == null && (object)right == null;
}
ObjectState objectState = Normalize(left);
ObjectState objectState2 = Normalize(right);
if (!string.Equals(objectState.Id, objectState2.Id, StringComparison.Ordinal))
{
return false;
}
if (!string.Equals(objectState.Parent, objectState2.Parent, StringComparison.Ordinal))
{
return false;
}
return JsonNode.DeepEquals(objectState.Data, objectState2.Data);
}
private long CurrentRevision(string runId)
{
string value;
long revision;
return (!string.IsNullOrWhiteSpace(runId) && runScenes.TryGetValue(runId, out value) && CurrentRevision(runId, value, out revision)) ? revision : 0;
}
private bool CurrentRevision(string runId, string scenePath, out long revision)
{
if (namespaces.TryGetValue(MakeNamespaceKey(runId, scenePath), out var value))
{
revision = value.Revision;
return true;
}
revision = 0L;
return false;
}
private bool TryGetNamespace(string runId, string scenePath, out Namespace current)
{
return namespaces.TryGetValue(MakeNamespaceKey(runId, scenePath), out current);
}
private static InvalidOperationException MissingNamespace(string runId, string scenePath)
{
return new InvalidOperationException($"Runtime authority namespace does not exist for run '{runId ?? "<null>"}' and scene '{scenePath ?? "<null>"}'.");
}
private static string MakeNamespaceKey(string runId, string scenePath)
{
if (runId == null || scenePath == null)
{
throw MissingNamespace(runId, scenePath);
}
return $"{runId.Length}:{runId}{scenePath.Length}:{scenePath}";
}
}