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
}