Defines immutable records for graph connections used by the editor: Edge (with ids, endpoints, optional via points and fill) and BrokenEdge with a reason enum for unresolved connections. Provides helpers to create edges, compute PortRef properties, validity checks, touch test, string descriptions and user-facing messages for broken edges.
using Editor.Prism.Core;
namespace Editor.Prism.Model;
/// <summary>
/// A connection between two ports.
/// <para>
/// Prism stores connections as an explicit, id-bearing edge list rather than as a property on the
/// consuming node. That is what makes diffs small, undo cheap, per-edge metadata possible, and
/// dangling connections <em>visible</em> instead of silently dropped on load.
/// </para>
/// </summary>
public sealed record Edge(
EdgeId Id, NodeId FromNode, PortId FromPort, NodeId ToNode, PortId ToPort, Vector2[] Via )
{
/// <summary>Build an edge between two port references, minting a fresh id.</summary>
public static Edge Between( PortRef from, PortRef to ) =>
new( EdgeId.New(), from.Node, from.Port, to.Node, to.Port, null );
/// <summary>Build an edge between two port references with an explicit id.</summary>
public static Edge Between( EdgeId id, PortRef from, PortRef to ) =>
new( id, from.Node, from.Port, to.Node, to.Port, null );
/// <summary>The producing end.</summary>
public PortRef From => new( FromNode, FromPort );
/// <summary>The consuming end.</summary>
public PortRef To => new( ToNode, ToPort );
/// <summary>
/// Per-edge override for the value written into components invented by a padding conversion.
/// Null means use <see cref="TypeRules.DefaultFill"/>.
/// </summary>
public float? Fill { get; init; }
/// <summary>True when both ends reference something.</summary>
public bool IsValid => FromNode.IsValid && FromPort.IsValid && ToNode.IsValid && ToPort.IsValid;
/// <summary>True when this edge touches the given node at either end.</summary>
public bool Touches( NodeId node ) => FromNode == node || ToNode == node;
/// <inheritdoc/>
public override string ToString() => $"{FromNode}.{FromPort} -> {ToNode}.{ToPort}";
}
/// <summary>Why an edge could not be resolved against the loaded graph.</summary>
public enum BrokenEdgeReason
{
/// <summary>The producing node is not in the document.</summary>
MissingFromNode,
/// <summary>The consuming node is not in the document.</summary>
MissingToNode,
/// <summary>The producing node exists but no longer declares that port.</summary>
MissingFromPort,
/// <summary>The consuming node exists but no longer declares that port.</summary>
MissingToPort,
/// <summary>Both ends exist but the types cannot be connected.</summary>
TypeMismatch,
/// <summary>The edge was part of a cycle and had to be cut to compile.</summary>
Cycle,
/// <summary>The edge could not be deserialized at all.</summary>
Malformed
}
/// <summary>
/// A connection that could not be resolved. Prism keeps these — they are drawn as dashed red ghosts
/// with a context menu offering reconnect or delete, and they round-trip through a save. The built-in
/// editor deletes such connections silently, which is how graphs quietly lose work.
/// </summary>
public sealed record BrokenEdge( Edge Edge, BrokenEdgeReason Reason, string Detail )
{
/// <summary>The edge id, for diagnostics and UI selection.</summary>
public EdgeId Id => Edge?.Id ?? EdgeId.None;
/// <summary>A short user-facing explanation.</summary>
public string Describe() => Reason switch
{
BrokenEdgeReason.MissingFromNode => "Source node is missing",
BrokenEdgeReason.MissingToNode => "Target node is missing",
BrokenEdgeReason.MissingFromPort => "Source port no longer exists",
BrokenEdgeReason.MissingToPort => "Target port no longer exists",
BrokenEdgeReason.TypeMismatch => "Types are not connectable",
BrokenEdgeReason.Cycle => "Connection would create a cycle",
_ => "Connection could not be read"
};
/// <inheritdoc/>
public override string ToString() => $"{Edge} ({Reason})";
}