using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; namespace Unity.VisualScripting { [SerializationVersion("A")] public abstract class Unit : GraphElement, IUnit { public class DebugData : IUnitDebugData { public int lastInvokeFrame { get; set; } public float lastInvokeTime { get; set; } public Exception runtimeException { get; set; } } protected Unit() : base() { controlInputs = new UnitPortCollection(this); controlOutputs = new UnitPortCollection(this); valueInputs = new UnitPortCollection(this); valueOutputs = new UnitPortCollection(this); invalidInputs = new UnitPortCollection(this); invalidOutputs = new UnitPortCollection(this); relations = new ConnectionCollection(); defaultValues = new Dictionary(); } public virtual IGraphElementDebugData CreateDebugData() { return new DebugData(); } public override void AfterAdd() { // Important to define before notifying instances Define(); base.AfterAdd(); } public override void BeforeRemove() { base.BeforeRemove(); Disconnect(); } public override void Instantiate(GraphReference instance) { base.Instantiate(instance); if (this is IGraphEventListener listener && XGraphEventListener.IsHierarchyListening(instance)) { listener.StartListening(instance); } } public override void Uninstantiate(GraphReference instance) { if (this is IGraphEventListener listener) { listener.StopListening(instance); } base.Uninstantiate(instance); } #region Poutine protected void CopyFrom(Unit source) { base.CopyFrom(source); defaultValues = source.defaultValues; } #endregion #region Definition [DoNotSerialize] public virtual bool canDefine => true; [DoNotSerialize] public bool failedToDefine => definitionException != null; [DoNotSerialize] public bool isDefined { get; private set; } protected abstract void Definition(); protected virtual void AfterDefine() { } protected virtual void BeforeUndefine() { } private void Undefine() { // Because a unit is always undefined on definition, // even if it wasn't defined before, we make sure the user // code for undefinition can safely presume it was defined. if (isDefined) { BeforeUndefine(); } Disconnect(); defaultValues.Clear(); controlInputs.Clear(); controlOutputs.Clear(); valueInputs.Clear(); valueOutputs.Clear(); invalidInputs.Clear(); invalidOutputs.Clear(); relations.Clear(); isDefined = false; } public void EnsureDefined() { if (!isDefined) { Define(); } } public void Define() { var preservation = UnitPreservation.Preserve(this); // A unit needs to undefine even if it wasn't defined, // because there might be invalid ports and connections // that we need to clear to avoid duplicates on definition. Undefine(); if (canDefine) { try { Definition(); isDefined = true; definitionException = null; AfterDefine(); } catch (Exception ex) { Undefine(); definitionException = ex; Debug.LogWarning($"Failed to define {this}:\n{ex}"); } } preservation.RestoreTo(this); } public void RemoveUnconnectedInvalidPorts() { foreach (var unconnectedInvalidInput in invalidInputs.Where(p => !p.hasAnyConnection).ToArray()) { invalidInputs.Remove(unconnectedInvalidInput); } foreach (var unconnectedInvalidOutput in invalidOutputs.Where(p => !p.hasAnyConnection).ToArray()) { invalidOutputs.Remove(unconnectedInvalidOutput); } } #endregion #region Ports [DoNotSerialize] public IUnitPortCollection controlInputs { get; } [DoNotSerialize] public IUnitPortCollection controlOutputs { get; } [DoNotSerialize] public IUnitPortCollection valueInputs { get; } [DoNotSerialize] public IUnitPortCollection valueOutputs { get; } [DoNotSerialize] public IUnitPortCollection invalidInputs { get; } [DoNotSerialize] public IUnitPortCollection invalidOutputs { get; } [DoNotSerialize] public IEnumerable inputs => LinqUtility.Concat(controlInputs, valueInputs, invalidInputs); [DoNotSerialize] public IEnumerable outputs => LinqUtility.Concat(controlOutputs, valueOutputs, invalidOutputs); [DoNotSerialize] public IEnumerable validInputs => LinqUtility.Concat(controlInputs, valueInputs); [DoNotSerialize] public IEnumerable validOutputs => LinqUtility.Concat(controlOutputs, valueOutputs); [DoNotSerialize] public IEnumerable ports => LinqUtility.Concat(inputs, outputs); [DoNotSerialize] public IEnumerable invalidPorts => LinqUtility.Concat(invalidInputs, invalidOutputs); [DoNotSerialize] public IEnumerable validPorts => LinqUtility.Concat(validInputs, validOutputs); public event Action onPortsChanged; public void PortsChanged() { onPortsChanged?.Invoke(); } #endregion #region Default Values [Serialize] public Dictionary defaultValues { get; private set; } #endregion #region Connections [DoNotSerialize] public IConnectionCollection relations { get; private set; } [DoNotSerialize] public IEnumerable connections => ports.SelectMany(p => p.connections); public void Disconnect() { // Can't use a foreach because invalid ports may get removed as they disconnect while (ports.Any(p => p.hasAnyConnection)) { ports.First(p => p.hasAnyConnection).Disconnect(); } } #endregion #region Analysis [DoNotSerialize] public virtual bool isControlRoot { get; protected set; } = false; #endregion #region Helpers protected void EnsureUniqueInput(string key) { if (controlInputs.Contains(key) || valueInputs.Contains(key) || invalidInputs.Contains(key)) { throw new ArgumentException($"Duplicate input for '{key}' in {GetType()}."); } } protected void EnsureUniqueOutput(string key) { if (controlOutputs.Contains(key) || valueOutputs.Contains(key) || invalidOutputs.Contains(key)) { throw new ArgumentException($"Duplicate output for '{key}' in {GetType()}."); } } protected ControlInput ControlInput(string key, Func action) { EnsureUniqueInput(key); var port = new ControlInput(key, action); controlInputs.Add(port); return port; } protected ControlInput ControlInputCoroutine(string key, Func coroutineAction) { EnsureUniqueInput(key); var port = new ControlInput(key, coroutineAction); controlInputs.Add(port); return port; } protected ControlInput ControlInputCoroutine(string key, Func action, Func coroutineAction) { EnsureUniqueInput(key); var port = new ControlInput(key, action, coroutineAction); controlInputs.Add(port); return port; } protected ControlOutput ControlOutput(string key) { EnsureUniqueOutput(key); var port = new ControlOutput(key); controlOutputs.Add(port); return port; } protected ValueInput ValueInput(Type type, string key) { EnsureUniqueInput(key); var port = new ValueInput(key, type); valueInputs.Add(port); return port; } protected ValueInput ValueInput(string key) { return ValueInput(typeof(T), key); } protected ValueInput ValueInput(string key, T @default) { var port = ValueInput(key); port.SetDefaultValue(@default); return port; } protected ValueOutput ValueOutput(Type type, string key) { EnsureUniqueOutput(key); var port = new ValueOutput(key, type); valueOutputs.Add(port); return port; } protected ValueOutput ValueOutput(Type type, string key, Func getValue) { EnsureUniqueOutput(key); var port = new ValueOutput(key, type, getValue); valueOutputs.Add(port); return port; } protected ValueOutput ValueOutput(string key) { return ValueOutput(typeof(T), key); } protected ValueOutput ValueOutput(string key, Func getValue) { return ValueOutput(typeof(T), key, (recursion) => getValue(recursion)); } private void Relation(IUnitPort source, IUnitPort destination) { relations.Add(new UnitRelation(source, destination)); } /// /// Triggering the destination may fetch the source value. /// protected void Requirement(ValueInput source, ControlInput destination) { Relation(source, destination); } /// /// Getting the value of the destination may fetch the value of the source. /// protected void Requirement(ValueInput source, ValueOutput destination) { Relation(source, destination); } /// /// Triggering the source may assign the destination value on the flow. /// protected void Assignment(ControlInput source, ValueOutput destination) { Relation(source, destination); } /// /// Triggering the source may trigger the destination. /// protected void Succession(ControlInput source, ControlOutput destination) { Relation(source, destination); } #endregion #region Widget [Serialize] public Vector2 position { get; set; } [DoNotSerialize] public Exception definitionException { get; protected set; } #endregion } }