Code/Core/Node.cs
namespace Nodebox;
public abstract partial class Node : GraphElement, Node.INode, IMeta {
[JsonIgnore]
public override Type DirectoryType => typeof(Node);
public Dictionary<string, object> Meta { get; private set; } = [];
[JsonIgnore]
public string Name => DisplayInfo.Name;
[JsonIgnore]
public string NameWithGenerics => DisplayInfo.ForGenericType(GetType()).Name;
[JsonIgnore]
public string Description => DisplayInfo.Description;
[JsonIgnore]
public string Icon => DisplayInfo.Icon;
[JsonIgnore]
public string[] Tags => DisplayInfo.Tags;
[JsonIgnore]
public string[] Aliases => DisplayInfo.Alias;
[JsonIgnore]
public DisplayInfo DisplayInfo => DisplayInfo.ForType(GetType(), true);
[JsonIgnore]
public virtual bool AreValidGenerics => true;
public static bool GetAreValidGenerics(Type nodeType) =>
TypeLibrary.Create<Node>(nodeType, []).AreValidGenerics;
public static bool TryMakeGenericType(Type genericType, Type[] typeArguments, out Type type) {
type = null;
try {
type = TypeLibrary.GetType(genericType).MakeGenericType(typeArguments);
}
catch (Exception) {
// Log.Warning($"{wire} - {wireType} - {e}");
}
if (type == null) { return false; }
if (!GetAreValidGenerics(type)) { type = null; return false; }
return true;
}
[JsonIgnore]
public virtual Dictionary<string, object> InitialMeta => [];
[JsonIgnore]
public virtual bool NullInputs => false;
[JsonIgnore]
public virtual (List<Pin> In, List<Pin> Out) InitialPins => ([], []);
public static (List<Pin> In, List<Pin> Out) GetInitialPins(Type nodeType) =>
TypeLibrary.Create<Node>(nodeType, []).InitialPins;
public static (List<Pin> In, List<Pin> Out) GetInitialPins<T>() where T : Node => GetInitialPins(typeof(T));
#pragma warning disable IDE1006
private List<Pin> inputPins { get; set; } = [];
private List<Pin> outputPins { get; set; } = [];
private List<HashSet<Wire>> inputWires { get; set; } = [];
private List<HashSet<Wire>> outputWires { get; set; } = [];
[JsonInclude]
internal List<object> inputValues = [];
[JsonIgnore]
private ulong generation = 0;
#pragma warning restore IDE1006
public ulong Generation => generation;
public void MakeDirty() => generation += 1;
[JsonIgnore]
public Action PinsChanged { get; set; }
protected virtual void OnPinsChanged() { }
[JsonIgnore]
public Action InputsChanged { get; set; }
protected virtual void OnInputsChanged() { }
[JsonIgnore]
public Action<bool> WiresChanged { get; set; }
protected virtual void OnWiresChanged(bool added) { }
[JsonIgnore]
public IReadOnlyList<Pin> InputPins => inputPins;
[JsonIgnore]
public IReadOnlyList<Pin> OutputPins => outputPins;
[JsonIgnore]
public IEnumerable<PinRef> InputPinRefs {
get {
foreach (var index in Enumerable.Range(0, inputPins.Count)) {
yield return new PinRef(this, Flow.Input, (PinIndex)index);
}
}
}
[JsonIgnore]
public IEnumerable<PinRef> OutputPinRefs {
get {
foreach (var index in Enumerable.Range(0, outputPins.Count)) {
yield return new PinRef(this, Flow.Output, (PinIndex)index);
}
}
}
[JsonIgnore]
public IReadOnlyList<IReadOnlySet<Wire>> InputWires => inputWires;
[JsonIgnore]
public IReadOnlyList<IReadOnlySet<Wire>> OutputWires => outputWires;
[JsonIgnore]
public IEnumerable<Wire> AllWires => InputWires.SelectMany(x => x).Concat(OutputWires.SelectMany(x => x));
[JsonIgnore]
public IReadOnlyList<object> Inputs => inputValues;
public Node() {
Meta = InitialMeta;
inputPins.AddRange(InitialPins.In);
outputPins.AddRange(InitialPins.Out);
Resize();
}
public IReadOnlyList<Pin> GetPins(Flow flow) => GetPinsInternal(flow);
private List<Pin> GetPinsInternal(Flow flow) => flow == Flow.Input ? inputPins : outputPins;
public IEnumerable<(Flow, PinIndex, Pin)> GetAllPins() {
foreach (var (index, pin) in InputPins.Enumerate()) {
yield return (Flow.Input, (PinIndex)index, pin);
}
foreach (var (index, pin) in OutputPins.Enumerate()) {
yield return (Flow.Output, (PinIndex)index, pin);
}
}
public IReadOnlyList<IReadOnlySet<Wire>> GetWires(Flow flow) => GetWiresInternal(flow);
private List<HashSet<Wire>> GetWiresInternal(Flow flow) => flow == Flow.Input ? inputWires : outputWires;
internal void AddInputWire(int index, Wire wire) {
inputWires[index].Add(wire);
WiresChanged?.Invoke(true);
OnWiresChanged(true);
MakeDirty();
}
internal void AddOutputWire(int index, Wire wire) {
outputWires[index].Add(wire);
WiresChanged?.Invoke(true);
OnWiresChanged(true);
MakeDirty();
}
internal bool RemoveInputWire(int index, Wire wire) {
var success = inputWires[index]?.Remove(wire) ?? false;
if (success && !_destroying) {
WiresChanged?.Invoke(false);
OnWiresChanged(false);
MakeDirty();
}
return success;
}
internal bool RemoveOutputWire(int index, Wire wire) {
var success = outputWires[index]?.Remove(wire) ?? false;
if (success && !_destroying) {
WiresChanged?.Invoke(false);
OnWiresChanged(false);
MakeDirty();
}
return success;
}
public bool HasPin(Flow flow, PinIndex index) => GetPins(flow).Count > index;
public Pin GetPin(Flow flow, PinIndex index) => GetPins(flow)[index];
public bool TryGetPin(Flow flow, PinIndex index, out Pin pin) {
if (!HasPin(flow, index)) {
pin = default;
return false;
}
pin = GetPins(flow)[index];
return true;
}
public void SetPins((IList<Pin> Input, IList<Pin> Output) pins) {
inputPins = [.. pins.Input];
outputPins = [.. pins.Output];
Resize();
PinsChanged?.Invoke();
OnPinsChanged();
}
public void AddPin(Flow flow, Pin pin, PinIndex? index = null) {
var pins = GetPinsInternal(flow);
if (pins.Count >= PinIndex.Invalid - 1) {
throw new InvalidOperationException($"can't create more than {PinIndex.Invalid} pins");
}
var wires = GetWiresInternal(flow);
if (index is PinIndex i) {
pins.Insert(i, pin);
} else {
pins.Add(pin);
}
Resize();
PinsChanged?.Invoke();
OnPinsChanged();
}
public void RemovePin(Flow flow, PinIndex? index = null) {
var pins = GetPinsInternal(flow);
if (pins.Count == 0) {
throw new InvalidOperationException($"there are no {flow} pins left");
}
var wires = GetWiresInternal(flow);
PinIndex i = index ?? (PinIndex)(pins.Count - 1);
pins.RemoveAt(i);
foreach (var wire in wires[i]) {
wire.Destroy();
}
wires.RemoveAt(i);
if (flow == Flow.Input) {
inputValues.RemoveAt(i);
}
PinsChanged?.Invoke();
OnPinsChanged();
MakeDirty();
}
public void ReplacePin(Flow flow, PinIndex index, Pin pin) {
var pins = GetPinsInternal(flow);
var oldPin = pins[index];
pins[index] = pin; // bruh x3
if (!oldPin.Type.IsAssignableTo(pin.Type)) {
inputValues[index] = pin.CreateDefaultValue();
}
PinsChanged?.Invoke();
OnPinsChanged();
MakeDirty();
}
public void SetPinType(Flow flow, PinIndex index, Type type) =>
ReplacePin(flow, index, GetPin(flow, index).WithType(type));
public object GetInputValueOrNull(PinIndex index) {
if (index >= inputPins.Count) {
return null;
}
return Inputs[index];
}
public object GetInputValue(PinIndex index) {
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, inputPins.Count, nameof(index));
return Inputs[index];
}
public T GetInputValue<T>(PinIndex index) => (T)GetInputValue(index);
public void SetInputValue<T>(PinIndex index, T value) {
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, inputPins.Count, nameof(index));
if (value != null) {
if (!value.GetType().IsAssignableTo(inputPins[index].Type)) {
throw new InvalidCastException($"{value} is not assignable to {DisplayInfo.ForGenericType(inputPins[index].Type).Name}");
}
} else {
if (TypeLibrary.GetType(inputPins[index].Type).IsValueType) {
throw new InvalidCastException($"{value} is not assignable to {DisplayInfo.ForGenericType(inputPins[index].Type).Name}");
}
}
inputValues[index] = value;
MakeDirty();
InputsChanged?.Invoke();
OnInputsChanged();
}
public virtual void SetProperty(string name, object obj) { }
public object this[PinIndex index] {
get => GetInputValue(index);
set => SetInputValue(index, value);
}
private void Resize() {
static HashSet<Wire> WireSetGenerator(int _) => [];
static void WireSetDestructor(int _, HashSet<Wire> wireSet) {
foreach (var wire in wireSet) {
wire.Destroy();
}
}
inputWires.Resize(inputPins.Count, WireSetGenerator, WireSetDestructor);
outputWires.Resize(outputPins.Count, WireSetGenerator);
inputValues.Resize(inputPins.Count, (index) => {
if (NullInputs) {
return null;
}
return inputPins[index].CreateDefaultValue();
});
MakeDirty();
}
private bool _destroying = false;
protected override void Term() {
_destroying = true;
WiresChanged = null;
foreach (var set in inputWires) {
foreach (var wire in set) {
wire.Destroy();
}
}
foreach (var set in outputWires) {
foreach (var wire in set) {
wire.Destroy();
}
}
Meta.Clear();
base.Term();
}
~Node() {
Destroy();
}
}