421 lines
12 KiB
C#
421 lines
12 KiB
C#
|
using System;
|
||
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Linq;
|
||
|
using UnityEngine;
|
||
|
|
||
|
namespace Unity.VisualScripting
|
||
|
{
|
||
|
[SerializationVersion("A")]
|
||
|
public abstract class Unit : GraphElement<FlowGraph>, 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<ControlInput>(this);
|
||
|
controlOutputs = new UnitPortCollection<ControlOutput>(this);
|
||
|
valueInputs = new UnitPortCollection<ValueInput>(this);
|
||
|
valueOutputs = new UnitPortCollection<ValueOutput>(this);
|
||
|
invalidInputs = new UnitPortCollection<InvalidInput>(this);
|
||
|
invalidOutputs = new UnitPortCollection<InvalidOutput>(this);
|
||
|
|
||
|
relations = new ConnectionCollection<IUnitRelation, IUnitPort, IUnitPort>();
|
||
|
|
||
|
defaultValues = new Dictionary<string, object>();
|
||
|
}
|
||
|
|
||
|
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 node 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 node 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<ControlInput> controlInputs { get; }
|
||
|
|
||
|
[DoNotSerialize]
|
||
|
public IUnitPortCollection<ControlOutput> controlOutputs { get; }
|
||
|
|
||
|
[DoNotSerialize]
|
||
|
public IUnitPortCollection<ValueInput> valueInputs { get; }
|
||
|
|
||
|
[DoNotSerialize]
|
||
|
public IUnitPortCollection<ValueOutput> valueOutputs { get; }
|
||
|
|
||
|
[DoNotSerialize]
|
||
|
public IUnitPortCollection<InvalidInput> invalidInputs { get; }
|
||
|
|
||
|
[DoNotSerialize]
|
||
|
public IUnitPortCollection<InvalidOutput> invalidOutputs { get; }
|
||
|
|
||
|
[DoNotSerialize]
|
||
|
public IEnumerable<IUnitInputPort> inputs => LinqUtility.Concat<IUnitInputPort>(controlInputs, valueInputs, invalidInputs);
|
||
|
|
||
|
[DoNotSerialize]
|
||
|
public IEnumerable<IUnitOutputPort> outputs => LinqUtility.Concat<IUnitOutputPort>(controlOutputs, valueOutputs, invalidOutputs);
|
||
|
|
||
|
[DoNotSerialize]
|
||
|
public IEnumerable<IUnitInputPort> validInputs => LinqUtility.Concat<IUnitInputPort>(controlInputs, valueInputs);
|
||
|
|
||
|
[DoNotSerialize]
|
||
|
public IEnumerable<IUnitOutputPort> validOutputs => LinqUtility.Concat<IUnitOutputPort>(controlOutputs, valueOutputs);
|
||
|
|
||
|
[DoNotSerialize]
|
||
|
public IEnumerable<IUnitPort> ports => LinqUtility.Concat<IUnitPort>(inputs, outputs);
|
||
|
|
||
|
[DoNotSerialize]
|
||
|
public IEnumerable<IUnitPort> invalidPorts => LinqUtility.Concat<IUnitPort>(invalidInputs, invalidOutputs);
|
||
|
|
||
|
[DoNotSerialize]
|
||
|
public IEnumerable<IUnitPort> validPorts => LinqUtility.Concat<IUnitPort>(validInputs, validOutputs);
|
||
|
|
||
|
public event Action onPortsChanged;
|
||
|
|
||
|
public void PortsChanged()
|
||
|
{
|
||
|
onPortsChanged?.Invoke();
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region Default Values
|
||
|
|
||
|
[Serialize]
|
||
|
public Dictionary<string, object> defaultValues { get; private set; }
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region Connections
|
||
|
|
||
|
[DoNotSerialize]
|
||
|
public IConnectionCollection<IUnitRelation, IUnitPort, IUnitPort> relations { get; private set; }
|
||
|
|
||
|
[DoNotSerialize]
|
||
|
public IEnumerable<IUnitConnection> 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<Flow, ControlOutput> action)
|
||
|
{
|
||
|
EnsureUniqueInput(key);
|
||
|
var port = new ControlInput(key, action);
|
||
|
controlInputs.Add(port);
|
||
|
return port;
|
||
|
}
|
||
|
|
||
|
protected ControlInput ControlInputCoroutine(string key, Func<Flow, IEnumerator> coroutineAction)
|
||
|
{
|
||
|
EnsureUniqueInput(key);
|
||
|
var port = new ControlInput(key, coroutineAction);
|
||
|
controlInputs.Add(port);
|
||
|
return port;
|
||
|
}
|
||
|
|
||
|
protected ControlInput ControlInputCoroutine(string key, Func<Flow, ControlOutput> action, Func<Flow, IEnumerator> 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<T>(string key)
|
||
|
{
|
||
|
return ValueInput(typeof(T), key);
|
||
|
}
|
||
|
|
||
|
protected ValueInput ValueInput<T>(string key, T @default)
|
||
|
{
|
||
|
var port = ValueInput<T>(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<Flow, object> getValue)
|
||
|
{
|
||
|
EnsureUniqueOutput(key);
|
||
|
var port = new ValueOutput(key, type, getValue);
|
||
|
valueOutputs.Add(port);
|
||
|
return port;
|
||
|
}
|
||
|
|
||
|
protected ValueOutput ValueOutput<T>(string key)
|
||
|
{
|
||
|
return ValueOutput(typeof(T), key);
|
||
|
}
|
||
|
|
||
|
protected ValueOutput ValueOutput<T>(string key, Func<Flow, T> getValue)
|
||
|
{
|
||
|
return ValueOutput(typeof(T), key, (recursion) => getValue(recursion));
|
||
|
}
|
||
|
|
||
|
private void Relation(IUnitPort source, IUnitPort destination)
|
||
|
{
|
||
|
relations.Add(new UnitRelation(source, destination));
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Triggering the destination may fetch the source value.
|
||
|
/// </summary>
|
||
|
protected void Requirement(ValueInput source, ControlInput destination)
|
||
|
{
|
||
|
Relation(source, destination);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Getting the value of the destination may fetch the value of the source.
|
||
|
/// </summary>
|
||
|
protected void Requirement(ValueInput source, ValueOutput destination)
|
||
|
{
|
||
|
Relation(source, destination);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Triggering the source may assign the destination value on the flow.
|
||
|
/// </summary>
|
||
|
protected void Assignment(ControlInput source, ValueOutput destination)
|
||
|
{
|
||
|
Relation(source, destination);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Triggering the source may trigger the destination.
|
||
|
/// </summary>
|
||
|
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
|
||
|
|
||
|
#region Analytics
|
||
|
|
||
|
public override AnalyticsIdentifier GetAnalyticsIdentifier()
|
||
|
{
|
||
|
var aid = new AnalyticsIdentifier
|
||
|
{
|
||
|
Identifier = GetType().FullName,
|
||
|
Namespace = GetType().Namespace,
|
||
|
};
|
||
|
aid.Hashcode = aid.Identifier.GetHashCode();
|
||
|
return aid;
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
}
|
||
|
}
|