Code/UI/Spatial/Wire.cs
namespace Nodebox.UI.Spatial;
[Title("Nodebox Spatial Wire")]
[Category("Nodebox")]
public class Wire : Component, IMeta, IGraphStyle {
[RequireComponent] public LineRenderer LineRenderer { get; set; }
public Dictionary<string, object> Meta => Inner.Meta;
[Property]
public GraphStyle GraphStyle { get; set; }
private Nodebox.Wire _inner;
[Property, ReadOnly]
[JsonIgnore]
public Nodebox.Wire Inner => _inner; // TODO: IReadOnlyWire..?
[Property, Hide]
[JsonIgnore]
public Type SourceType => Inner?.SourceType;
[Property, Hide]
[JsonIgnore]
public Type DestinationType => Inner?.DestinationType;
private HashSet<Graph> _graphs = [];
[JsonIgnore]
public IReadOnlySet<Graph> Graphs => _graphs;
[JsonIgnore]
public Node Source => GameObject?.Parent?.GetComponent<Node>(false);
private PinIndex _sourceIndex = PinIndex.Invalid;
[Property]
public PinIndex SourceIndex {
get => _sourceIndex;
set {
if (_sourceIndex == value) { return; }
_sourceIndex = value;
Reconnect();
}
}
private Node _destination = null;
[Property]
public Node Destination {
get => _destination;
set {
if (_destination == value) { return; }
_destination?.OnRecreated -= Reconnect;
_destination?.OnComponentDisabled -= Disconnect;
_destination = value;
_destination?.OnRecreated += Reconnect;
_destination?.OnComponentDisabled += Disconnect;
Reconnect();
}
}
private PinIndex _destinationIndex = PinIndex.Invalid;
[Property]
public PinIndex DestinationIndex {
get => _destinationIndex;
set {
if (_destinationIndex == value) { return; }
_destinationIndex = value;
Reconnect();
}
}
protected override void OnAwake() {
base.OnAwake();
GraphStyle = GraphStyle.DefaultForExecutionContext();
GraphStyle.ConfigureLineRenderer(LineRenderer);
LineRenderer.Enabled = false;
LineRenderer.VectorPoints = [
new(), new(), new(), new(),
];
}
protected override void OnEnabled() {
base.OnEnabled();
Reconnect();
Source?.OnRecreated += Reconnect;
Source?.OnComponentDisabled += Disconnect;
}
protected override void OnDisabled() {
Source?.OnRecreated -= Reconnect;
Source?.OnComponentDisabled -= Disconnect;
Disconnect();
LineRenderer.Enabled = false;
base.OnDisabled();
}
protected override void OnRefresh() {
base.OnRefresh();
Reconnect();
}
internal void Connect() {
Assert.IsNull(_inner);
if (!Enabled) { return; }
if (!IsValidConnection()) { return; }
_inner = new(Source.Inner, SourceIndex, Destination.Inner, DestinationIndex) {
Spatial = this
};
// _inner.OnDestroyed += OnInnerDestroyed;
Assert.IsValid(_inner);
_graphs = [.. Source.Graphs];
RegisterInner();
}
internal void Disconnect() {
using var _ = new SuppressDestruction(this);
UnRegisterInner();
_graphs = [];
_inner?.Destroy();
_inner = null;
}
public void Reconnect() {
if (!Enabled) { return; }
var suppressPoly = _inner?.Source?.Spatial == Source &&
_inner?.SourceIndex == SourceIndex &&
_inner?.Destination.Spatial == Destination &&
_inner?.DestinationIndex == DestinationIndex;
if (suppressPoly && _inner.Source.Spatial.IsValid() && _inner.Destination.Spatial.IsValid()) {
using var _ = new Node.SuppressPolymorph(_inner.Source.Spatial);
using var __ = new Node.SuppressPolymorph(_inner.Destination.Spatial);
Disconnect();
Connect();
return;
}
Disconnect();
Connect();
}
private void RegisterInner() {
foreach (var graph in _graphs) {
if (graph.IsValid() && graph.Inner.IsValid()) {
if (_inner.IsValid()) {
graph.Inner.Add(_inner);
graph.Inner.OnDestroy += OnGraphDestroy;
}
}
}
}
private void UnRegisterInner() {
foreach (var graph in _graphs) {
if (graph.IsValid() && graph.Inner.IsValid()) {
if (_inner.IsValid()) {
graph.Inner.Remove(_inner);
}
graph.Inner.OnDestroy -= OnGraphDestroy;
}
}
_graphs.Clear();
}
private void OnGraphDestroy(Nodebox.Graph innerGraph) {
_graphs.Remove(innerGraph.Spatial);
}
private bool IsValidConnection() {
if (!Source.IsValid() || !Source.Enabled || !Destination.IsValid() || !Destination.Enabled) {
return false;
}
if (!Source.Inner.IsValid() || !Destination.Inner.IsValid()) {
return false;
}
try {
Nodebox.Wire.Check(Source.Inner, SourceIndex, Destination.Inner, DestinationIndex);
}
catch (Exception) {
return false;
}
return true;
}
private void OnInnerDestroyed(GraphElement _) {
// if (DestructionSuppressed > 0) { return; }
// Task.MainThread().OnCompleted(() => {
// DestroyGameObject();
// });
}
protected override void OnFixedUpdate() {
base.OnFixedUpdate();
if (!_inner.IsValid()) {
DestroyGameObject();
}
}
protected override void OnPreRender() {
if (!Inner.IsValid()) {
LineRenderer.Enabled = false;
return;
}
if (Source?.PanelToWorldRelativeToPin(
Flow.Output,
SourceIndex,
new Vector2(0.5f, 0.5f)
) is not Vector3 start) {
LineRenderer.Enabled = false;
return;
}
if (Destination?.PanelToWorldRelativeToPin(Flow.Input, DestinationIndex, new Vector2(0f, 0.5f)
) is not Vector3 end) {
LineRenderer.Enabled = false;
return;
}
var sourceColor = GraphStyle?.TypePalette?.Get(SourceType).Color ?? Color.White;
var destinationColor = GraphStyle?.TypePalette?.Get(DestinationType).Color ?? Color.White;
var gradient = new Gradient( // TODO: Cache
new Gradient.ColorFrame(0.0f, sourceColor),
new Gradient.ColorFrame(1.0f, destinationColor)
);
// DebugOverlay.Line(start, end, sourceColor);
var startNormal = Source?.WorldTransform.NormalToWorld(new Vector3(0f, 1f, 0f)) ?? Vector3.Zero;
var endNormal = Destination?.WorldTransform.NormalToWorld(new Vector3(0f, -1f, 0f)) ?? Vector3.Zero;
LineRenderer.Enabled = true;
LineRenderer.Color = gradient;
LineRenderer.VectorPoints[0] = start; //- startNormal * 0f;
LineRenderer.VectorPoints[1] = start + startNormal * 3f;
LineRenderer.VectorPoints[2] = end + endNormal * 3f;
LineRenderer.VectorPoints[3] = end + endNormal * 1f;
}
protected override void OnParentChanged(GameObject oldParent, GameObject newParent) {
base.OnParentChanged(oldParent, newParent);
Reconnect();
var oldSource = oldParent?.GetComponent<Node>();
if (oldSource.IsValid()) {
oldSource?.OnRecreated -= Reconnect;
oldSource?.OnComponentDisabled -= Reconnect;
}
Source?.OnRecreated += Reconnect;
Source?.OnComponentDisabled += Disconnect;
}
private uint DestructionSuppressed { get; set; } = 0;
public sealed class SuppressDestruction : IDisposable {
private Wire Wire { get; set; }
public SuppressDestruction(Wire wire) {
Wire = wire;
Wire.DestructionSuppressed += 1;
}
public void Dispose() {
Wire.DestructionSuppressed -= 1;
}
}
}