using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using Artemis.Core.Events; namespace Artemis.Core; /// /// Represents a kind of node inside a /// public abstract class Node : BreakableModel, INode { /// public event EventHandler? Resetting; /// public event EventHandler>? PinAdded; /// public event EventHandler>? PinRemoved; /// public event EventHandler>? PinCollectionAdded; /// public event EventHandler>? PinCollectionRemoved; #region Properties & Fields private readonly List _outputPinBucket = new(); private readonly List _inputPinBucket = new(); private Guid _id; /// public Guid Id { get => _id; set => SetAndNotify(ref _id, value); } private string _name; /// public string Name { get => _name; protected set => SetAndNotify(ref _name, value); } private string _description; /// public string Description { get => _description; protected set => SetAndNotify(ref _description, value); } private double _x; /// public double X { get => _x; set => SetAndNotify(ref _x, value); } private double _y; /// public double Y { get => _y; set => SetAndNotify(ref _y, value); } /// public virtual bool IsExitNode => false; /// public virtual bool IsDefaultNode => false; private readonly List _pins = new(); /// public IReadOnlyCollection Pins => new ReadOnlyCollection(_pins); private readonly List _pinCollections = new(); /// public IReadOnlyCollection PinCollections => new ReadOnlyCollection(_pinCollections); /// public override string BrokenDisplayName => Name; #endregion #region Construtors /// /// Creates a new instance of the class with an empty name and description /// protected Node() { _name = string.Empty; _description = string.Empty; _id = Guid.NewGuid(); } /// /// Creates a new instance of the class with the provided name and description /// protected Node(string name, string description) { _name = name; _description = description; _id = Guid.NewGuid(); } #endregion #region Methods /// /// Creates a new input pin and adds it to the collection /// /// The name of the pin /// The type of value the pin will hold /// The newly created pin public InputPin CreateInputPin(string name = "") { InputPin pin = new(this, name); _pins.Add(pin); PinAdded?.Invoke(this, new SingleValueEventArgs(pin)); return pin; } /// /// Creates a new input pin and adds it to the collection /// /// The type of value the pin will hold /// The name of the pin /// The newly created pin public InputPin CreateInputPin(Type type, string name = "") { InputPin pin = new(this, type, name); _pins.Add(pin); PinAdded?.Invoke(this, new SingleValueEventArgs(pin)); return pin; } /// /// Creates a new output pin and adds it to the collection /// /// The name of the pin /// The type of value the pin will hold /// The newly created pin public OutputPin CreateOutputPin(string name = "") { OutputPin pin = new(this, name); _pins.Add(pin); PinAdded?.Invoke(this, new SingleValueEventArgs(pin)); return pin; } /// /// Creates a new output pin and adds it to the collection /// /// The type of value the pin will hold /// The name of the pin /// The newly created pin public OutputPin CreateOutputPin(Type type, string name = "") { OutputPin pin = new(this, type, name); _pins.Add(pin); PinAdded?.Invoke(this, new SingleValueEventArgs(pin)); return pin; } /// /// Creates or adds an output pin to the node using a bucket. /// The bucket might grow a bit over time as the user edits the node but pins won't get lost, enabling undo/redo in the /// editor. /// public OutputPin CreateOrAddOutputPin(Type valueType, string displayName) { // Grab the first pin from the bucket that isn't on the node yet OutputPin? pin = _outputPinBucket.FirstOrDefault(p => !Pins.Contains(p)); if (Numeric.IsTypeCompatible(valueType)) valueType = typeof(Numeric); // If there is none, create a new one and add it to the bucket if (pin == null) { pin = CreateOutputPin(valueType, displayName); _outputPinBucket.Add(pin); } // If there was a pin in the bucket, update it's type and display name and reuse it else { pin.ChangeType(valueType); pin.Name = displayName; AddPin(pin); } return pin; } /// /// Creates or adds an input pin to the node using a bucket. /// The bucket might grow a bit over time as the user edits the node but pins won't get lost, enabling undo/redo in the /// editor. /// public InputPin CreateOrAddInputPin(Type valueType, string displayName) { // Grab the first pin from the bucket that isn't on the node yet InputPin? pin = _inputPinBucket.FirstOrDefault(p => !Pins.Contains(p)); if (Numeric.IsTypeCompatible(valueType)) valueType = typeof(Numeric); // If there is none, create a new one and add it to the bucket if (pin == null) { pin = CreateInputPin(valueType, displayName); _inputPinBucket.Add(pin); } // If there was a pin in the bucket, update it's type and display name and reuse it else { pin.ChangeType(valueType); pin.Name = displayName; AddPin(pin); } return pin; } /// /// Removes the provided from the node and it's collection /// /// The pin to remove /// if the pin was removed; otherwise . public bool RemovePin(Pin pin) { bool isRemoved = _pins.Remove(pin); if (isRemoved) { pin.DisconnectAll(); PinRemoved?.Invoke(this, new SingleValueEventArgs(pin)); } return isRemoved; } /// /// Adds an existing to the collection. /// /// The pin to add public void AddPin(Pin pin) { if (pin.Node != this) throw new ArtemisCoreException("Can't add a pin to a node that belongs to a different node than the one it's being added to."); if (_pins.Contains(pin)) return; _pins.Add(pin); PinAdded?.Invoke(this, new SingleValueEventArgs(pin)); } /// /// Creates a new input pin collection and adds it to the collection /// /// The type of value the pins of this collection will hold /// The name of the pin collection /// The amount of pins to initially add to the collection /// The resulting input pin collection public InputPinCollection CreateInputPinCollection(string name = "", int initialCount = 1) { InputPinCollection pin = new(this, name, initialCount); _pinCollections.Add(pin); PinCollectionAdded?.Invoke(this, new SingleValueEventArgs(pin)); return pin; } /// /// Creates a new input pin collection and adds it to the collection /// /// The type of value the pins of this collection will hold /// The name of the pin collection /// The amount of pins to initially add to the collection /// The resulting input pin collection public InputPinCollection CreateInputPinCollection(Type type, string name = "", int initialCount = 1) { InputPinCollection pin = new(this, type, name, initialCount); _pinCollections.Add(pin); PinCollectionAdded?.Invoke(this, new SingleValueEventArgs(pin)); return pin; } /// /// Creates a new output pin collection and adds it to the collection /// /// The type of value the pins of this collection will hold /// The name of the pin collection /// The amount of pins to initially add to the collection /// The resulting output pin collection public OutputPinCollection CreateOutputPinCollection(string name = "", int initialCount = 1) { OutputPinCollection pin = new(this, name, initialCount); _pinCollections.Add(pin); PinCollectionAdded?.Invoke(this, new SingleValueEventArgs(pin)); return pin; } /// /// Removes the provided from the node and it's /// collection /// /// The pin collection to remove /// if the pin collection was removed; otherwise . public bool RemovePinCollection(PinCollection pinCollection) { bool isRemoved = _pinCollections.Remove(pinCollection); if (isRemoved) { foreach (IPin pin in pinCollection) pin.DisconnectAll(); PinCollectionRemoved?.Invoke(this, new SingleValueEventArgs(pinCollection)); } return isRemoved; } /// /// Called when the node was loaded from storage or newly created, at this point pin connections aren't reestablished /// yet. /// /// The script the node is contained in public virtual void Initialize(INodeScript script) { } /// /// Evaluates the value of the output pins of this node /// public abstract void Evaluate(); /// public virtual void Reset() { foreach (IPin pin in _pins) pin.Reset(); foreach (IPinCollection pinCollection in _pinCollections) pinCollection.Reset(); Resetting?.Invoke(this, EventArgs.Empty); } /// public void TryInitialize(INodeScript script) { TryOrBreak(() => Initialize(script), "Failed to initialize"); } /// public void TryEvaluate() { TryOrBreak(Evaluate, "Failed to evaluate"); } /// /// Serializes the object into a string /// /// The serialized object public virtual string SerializeStorage() { return string.Empty; } /// /// Deserializes the object and sets it /// /// The serialized object public virtual void DeserializeStorage(string serialized) { } #endregion }