123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848 |
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Linq;
- using UnityEngine;
-
- namespace Unity.VisualScripting
- {
- public sealed class Flow : IPoolable, IDisposable
- {
- // We need to check for recursion by passing some additional
- // context information to avoid the same port in multiple different
- // nested flow graphs to count as the same item. Naively,
- // we're using the parent as the context, which seems to work;
- // it won't theoretically catch recursive nesting, but then recursive
- // nesting already isn't supported anyway, so this way we avoid hashing
- // or turning the stack into a reference.
- // https://support.ludiq.io/communities/5/topics/2122-r
- // We make this an equatable struct to avoid any allocation.
- private struct RecursionNode : IEquatable<RecursionNode>
- {
- public IUnitPort port { get; }
-
- public IGraphParent context { get; }
-
- public RecursionNode(IUnitPort port, GraphPointer pointer)
- {
- this.port = port;
- this.context = pointer.parent;
- }
-
- public bool Equals(RecursionNode other)
- {
- return other.port == port && other.context == context;
- }
-
- public override bool Equals(object obj)
- {
- return obj is RecursionNode other && Equals(other);
- }
-
- public override int GetHashCode()
- {
- return HashUtility.GetHashCode(port, context);
- }
- }
-
- public GraphStack stack { get; private set; }
-
- private Recursion<RecursionNode> recursion;
-
- private readonly Dictionary<IUnitValuePort, object> locals = new Dictionary<IUnitValuePort, object>();
-
- public readonly VariableDeclarations variables = new VariableDeclarations();
-
- private readonly Stack<int> loops = new Stack<int>();
-
- private readonly HashSet<GraphStack> preservedStacks = new HashSet<GraphStack>();
-
- public MonoBehaviour coroutineRunner { get; private set; }
-
- private ICollection<Flow> activeCoroutinesRegistry;
-
- private bool coroutineStopRequested;
-
- public bool isCoroutine { get; private set; }
-
- private IEnumerator coroutineEnumerator;
-
- public bool isPrediction { get; private set; }
-
- private bool disposed;
-
- public bool enableDebug
- {
- get
- {
- if (isPrediction)
- {
- return false;
- }
-
- if (!stack.hasDebugData)
- {
- return false;
- }
-
- return true;
- }
- }
-
- public static Func<GraphPointer, bool> isInspectedBinding { get; set; }
-
- public bool isInspected => isInspectedBinding?.Invoke(stack) ?? false;
-
-
- #region Lifecycle
-
- private Flow() { }
-
- public static Flow New(GraphReference reference)
- {
- Ensure.That(nameof(reference)).IsNotNull(reference);
-
- var flow = GenericPool<Flow>.New(() => new Flow()); ;
- flow.stack = reference.ToStackPooled();
-
- return flow;
- }
-
- void IPoolable.New()
- {
- disposed = false;
-
- recursion = Recursion<RecursionNode>.New();
- }
-
- public void Dispose()
- {
- if (disposed)
- {
- throw new ObjectDisposedException(ToString());
- }
-
- GenericPool<Flow>.Free(this);
- }
-
- void IPoolable.Free()
- {
- stack?.Dispose();
- recursion?.Dispose();
- locals.Clear();
- loops.Clear();
- variables.Clear();
-
- // Preserved stacks could remain if coroutine was interrupted
- foreach (var preservedStack in preservedStacks)
- {
- preservedStack.Dispose();
- }
-
- preservedStacks.Clear();
-
- loopIdentifier = -1;
- stack = null;
- recursion = null;
- isCoroutine = false;
- coroutineEnumerator = null;
- coroutineRunner = null;
- activeCoroutinesRegistry?.Remove(this);
- activeCoroutinesRegistry = null;
- coroutineStopRequested = false;
- isPrediction = false;
-
- disposed = true;
- }
-
- public GraphStack PreserveStack()
- {
- var preservedStack = stack.Clone();
- preservedStacks.Add(preservedStack);
- return preservedStack;
- }
-
- public void RestoreStack(GraphStack stack)
- {
- this.stack.CopyFrom(stack);
- }
-
- public void DisposePreservedStack(GraphStack stack)
- {
- stack.Dispose();
- preservedStacks.Remove(stack);
- }
-
- #endregion
-
-
- #region Loops
-
- public int loopIdentifier = -1;
-
- public int currentLoop
- {
- get
- {
- if (loops.Count > 0)
- {
- return loops.Peek();
- }
- else
- {
- return -1;
- }
- }
- }
-
- public bool LoopIsNotBroken(int loop)
- {
- return currentLoop == loop;
- }
-
- public int EnterLoop()
- {
- var loop = ++loopIdentifier;
-
- loops.Push(loop);
-
- return loop;
- }
-
- public void BreakLoop()
- {
- if (currentLoop < 0)
- {
- throw new InvalidOperationException("No active loop to break.");
- }
-
- loops.Pop();
- }
-
- public void ExitLoop(int loop)
- {
- if (loop != currentLoop)
- {
- // Already exited through break
- return;
- }
-
- loops.Pop();
- }
-
- #endregion
-
-
- #region Control
-
- public void Run(ControlOutput port)
- {
- Invoke(port);
- Dispose();
- }
-
- public void StartCoroutine(ControlOutput port, ICollection<Flow> registry = null)
- {
- isCoroutine = true;
-
- coroutineRunner = stack.component;
-
- if (coroutineRunner == null)
- {
- coroutineRunner = CoroutineRunner.instance;
- }
-
- activeCoroutinesRegistry = registry;
-
- activeCoroutinesRegistry?.Add(this);
-
- // We have to store the enumerator because Coroutine itself
- // can't be cast to IDisposable, which we'll need when stopping.
- coroutineEnumerator = Coroutine(port);
-
- coroutineRunner.StartCoroutine(coroutineEnumerator);
- }
-
- public void StopCoroutine(bool disposeInstantly)
- {
- if (!isCoroutine)
- {
- throw new NotSupportedException("Stop may only be called on coroutines.");
- }
-
- if (disposeInstantly)
- {
- StopCoroutineImmediate();
- }
- else
- {
- // We prefer a soft coroutine stop here that will happen at the *next frame*,
- // because we don't want the flow to be disposed just yet when the event node stops
- // listening, as we still need it for clean up operations.
- coroutineStopRequested = true;
- }
- }
-
- internal void StopCoroutineImmediate()
- {
- if (coroutineRunner && coroutineEnumerator != null)
- {
- coroutineRunner.StopCoroutine(coroutineEnumerator);
-
- // Unity doesn't dispose coroutines enumerators when calling StopCoroutine, so we have to do it manually:
- // https://forum.unity.com/threads/finally-block-not-executing-in-a-stopped-coroutine.320611/
- ((IDisposable)coroutineEnumerator).Dispose();
- }
- }
-
- private IEnumerator Coroutine(ControlOutput startPort)
- {
- try
- {
- foreach (var instruction in InvokeCoroutine(startPort))
- {
- if (coroutineStopRequested)
- {
- yield break;
- }
-
- yield return instruction;
-
- if (coroutineStopRequested)
- {
- yield break;
- }
- }
- }
- finally
- {
- // Manual disposal might have already occurred from StopCoroutine,
- // so we have to avoid double disposal, which would throw.
- if (!disposed)
- {
- Dispose();
- }
- }
- }
-
- public void Invoke(ControlOutput output)
- {
- Ensure.That(nameof(output)).IsNotNull(output);
-
- var connection = output.connection;
-
- if (connection == null)
- {
- return;
- }
-
- var input = connection.destination;
-
- var recursionNode = new RecursionNode(output, stack);
-
- BeforeInvoke(output, recursionNode);
-
- try
- {
- var nextPort = InvokeDelegate(input);
-
- if (nextPort != null)
- {
- Invoke(nextPort);
- }
- }
- finally
- {
- AfterInvoke(output, recursionNode);
- }
- }
-
- private IEnumerable InvokeCoroutine(ControlOutput output)
- {
- var connection = output.connection;
-
- if (connection == null)
- {
- yield break;
- }
-
- var input = connection.destination;
-
- var recursionNode = new RecursionNode(output, stack);
-
- BeforeInvoke(output, recursionNode);
-
- if (input.supportsCoroutine)
- {
- foreach (var instruction in InvokeCoroutineDelegate(input))
- {
- if (instruction is ControlOutput)
- {
- foreach (var unwrappedInstruction in InvokeCoroutine((ControlOutput)instruction))
- {
- yield return unwrappedInstruction;
- }
- }
- else
- {
- yield return instruction;
- }
- }
- }
- else
- {
- ControlOutput nextPort = InvokeDelegate(input);
-
- if (nextPort != null)
- {
- foreach (var instruction in InvokeCoroutine(nextPort))
- {
- yield return instruction;
- }
- }
- }
-
- AfterInvoke(output, recursionNode);
- }
-
- private RecursionNode BeforeInvoke(ControlOutput output, RecursionNode recursionNode)
- {
- try
- {
- recursion?.Enter(recursionNode);
- }
- catch (StackOverflowException ex)
- {
- output.unit.HandleException(stack, ex);
- throw;
- }
-
- var connection = output.connection;
- var input = connection.destination;
-
- if (enableDebug)
- {
- var connectionEditorData = stack.GetElementDebugData<IUnitConnectionDebugData>(connection);
- var inputUnitEditorData = stack.GetElementDebugData<IUnitDebugData>(input.unit);
-
- connectionEditorData.lastInvokeFrame = EditorTimeBinding.frame;
- connectionEditorData.lastInvokeTime = EditorTimeBinding.time;
- inputUnitEditorData.lastInvokeFrame = EditorTimeBinding.frame;
- inputUnitEditorData.lastInvokeTime = EditorTimeBinding.time;
- }
-
- return recursionNode;
- }
-
- private void AfterInvoke(ControlOutput output, RecursionNode recursionNode)
- {
- recursion?.Exit(recursionNode);
- }
-
- private ControlOutput InvokeDelegate(ControlInput input)
- {
- try
- {
- if (input.requiresCoroutine)
- {
- throw new InvalidOperationException($"Port '{input.key}' on '{input.unit}' can only be triggered in a coroutine.");
- }
-
- return input.action(this);
- }
- catch (Exception ex)
- {
- input.unit.HandleException(stack, ex);
- throw;
- }
- }
-
- private IEnumerable InvokeCoroutineDelegate(ControlInput input)
- {
- var instructions = input.coroutineAction(this);
-
- while (true)
- {
- object instruction;
-
- try
- {
- if (!instructions.MoveNext())
- {
- break;
- }
-
- instruction = instructions.Current;
- }
- catch (Exception ex)
- {
- input.unit.HandleException(stack, ex);
- throw;
- }
-
- yield return instruction;
- }
- }
-
- #endregion
-
-
- #region Values
-
- public bool IsLocal(IUnitValuePort port)
- {
- Ensure.That(nameof(port)).IsNotNull(port);
-
- return locals.ContainsKey(port);
- }
-
- public void SetValue(IUnitValuePort port, object value)
- {
- Ensure.That(nameof(port)).IsNotNull(port);
- Ensure.That(nameof(value)).IsOfType(value, port.type);
-
- if (locals.ContainsKey(port))
- {
- locals[port] = value;
- }
- else
- {
- locals.Add(port, value);
- }
- }
-
- public object GetValue(ValueInput input)
- {
- if (locals.TryGetValue(input, out var local))
- {
- return local;
- }
-
- var connection = input.connection;
-
- if (connection != null)
- {
- if (enableDebug)
- {
- var connectionEditorData = stack.GetElementDebugData<IUnitConnectionDebugData>(connection);
-
- connectionEditorData.lastInvokeFrame = EditorTimeBinding.frame;
- connectionEditorData.lastInvokeTime = EditorTimeBinding.time;
- }
-
- var output = connection.source;
-
- var value = GetValue(output);
-
- if (enableDebug)
- {
- var connectionEditorData = stack.GetElementDebugData<ValueConnection.DebugData>(connection);
-
- connectionEditorData.lastValue = value;
- connectionEditorData.assignedLastValue = true;
- }
-
- return value;
- }
- else if (TryGetDefaultValue(input, out var defaultValue))
- {
- return defaultValue;
- }
- else
- {
- throw new MissingValuePortInputException(input.key);
- }
- }
-
- private object GetValue(ValueOutput output)
- {
- if (locals.TryGetValue(output, out var local))
- {
- return local;
- }
-
- if (!output.supportsFetch)
- {
- throw new InvalidOperationException($"The value of '{output.key}' on '{output.unit}' cannot be fetched dynamically, it must be assigned.");
- }
-
- var recursionNode = new RecursionNode(output, stack);
-
- try
- {
- recursion?.Enter(recursionNode);
- }
- catch (StackOverflowException ex)
- {
- output.unit.HandleException(stack, ex);
- throw;
- }
-
- try
- {
- if (enableDebug)
- {
- var outputUnitEditorData = stack.GetElementDebugData<IUnitDebugData>(output.unit);
-
- outputUnitEditorData.lastInvokeFrame = EditorTimeBinding.frame;
- outputUnitEditorData.lastInvokeTime = EditorTimeBinding.time;
- }
-
- var value = GetValueDelegate(output);
-
- return value;
- }
- finally
- {
- recursion?.Exit(recursionNode);
- }
- }
-
- public object GetValue(ValueInput input, Type type)
- {
- return ConversionUtility.Convert(GetValue(input), type);
- }
-
- public T GetValue<T>(ValueInput input)
- {
- return (T)GetValue(input, typeof(T));
- }
-
- public object GetConvertedValue(ValueInput input)
- {
- return GetValue(input, input.type);
- }
-
- private object GetDefaultValue(ValueInput input)
- {
- if (!TryGetDefaultValue(input, out var defaultValue))
- {
- throw new InvalidOperationException("Value input port does not have a default value.");
- }
-
- return defaultValue;
- }
-
- public bool TryGetDefaultValue(ValueInput input, out object defaultValue)
- {
- if (!input.unit.defaultValues.TryGetValue(input.key, out defaultValue))
- {
- return false;
- }
-
- if (input.nullMeansSelf && defaultValue == null)
- {
- defaultValue = stack.self;
- }
-
- return true;
- }
-
- private object GetValueDelegate(ValueOutput output)
- {
- try
- {
- return output.getValue(this);
- }
- catch (Exception ex)
- {
- output.unit.HandleException(stack, ex);
- throw;
- }
- }
-
- public static object FetchValue(ValueInput input, GraphReference reference)
- {
- var flow = New(reference);
-
- var result = flow.GetValue(input);
-
- flow.Dispose();
-
- return result;
- }
-
- public static object FetchValue(ValueInput input, Type type, GraphReference reference)
- {
- return ConversionUtility.Convert(FetchValue(input, reference), type);
- }
-
- public static T FetchValue<T>(ValueInput input, GraphReference reference)
- {
- return (T)FetchValue(input, typeof(T), reference);
- }
-
- #endregion
-
-
- #region Value Prediction
-
- public static bool CanPredict(IUnitValuePort port, GraphReference reference)
- {
- Ensure.That(nameof(port)).IsNotNull(port);
-
- var flow = New(reference);
-
- flow.isPrediction = true;
-
- bool canPredict;
-
- if (port is ValueInput)
- {
- canPredict = flow.CanPredict((ValueInput)port);
- }
- else if (port is ValueOutput)
- {
- canPredict = flow.CanPredict((ValueOutput)port);
- }
- else
- {
- throw new NotSupportedException();
- }
-
- flow.Dispose();
-
- return canPredict;
- }
-
- private bool CanPredict(ValueInput input)
- {
- if (!input.hasValidConnection)
- {
- if (!TryGetDefaultValue(input, out var defaultValue))
- {
- return false;
- }
-
- if (typeof(Component).IsAssignableFrom(input.type))
- {
- defaultValue = defaultValue?.ConvertTo(input.type);
- }
-
- if (!input.allowsNull && defaultValue == null)
- {
- return false;
- }
-
- return true;
- }
-
- var output = input.validConnectedPorts.Single();
-
- if (!CanPredict(output))
- {
- return false;
- }
-
- var connectedValue = GetValue(output);
-
- if (!ConversionUtility.CanConvert(connectedValue, input.type, false))
- {
- return false;
- }
-
- if (typeof(Component).IsAssignableFrom(input.type))
- {
- connectedValue = connectedValue?.ConvertTo(input.type);
- }
-
- if (!input.allowsNull && connectedValue == null)
- {
- return false;
- }
-
- return true;
- }
-
- private bool CanPredict(ValueOutput output)
- {
- // Shortcircuit the expensive check if the port isn't marked as predictable
- if (!output.supportsPrediction)
- {
- return false;
- }
-
- var recursionNode = new RecursionNode(output, stack);
-
- if (!recursion?.TryEnter(recursionNode) ?? false)
- {
- return false;
- }
-
- // Check each value dependency
- foreach (var relation in output.unit.relations.WithDestination(output))
- {
- if (relation.source is ValueInput)
- {
- var source = (ValueInput)relation.source;
-
- if (!CanPredict(source))
- {
- recursion?.Exit(recursionNode);
- return false;
- }
- }
- }
-
- var value = CanPredictDelegate(output);
-
- recursion?.Exit(recursionNode);
-
- return value;
- }
-
- private bool CanPredictDelegate(ValueOutput output)
- {
- try
- {
- return output.canPredictValue(this);
- }
- catch (Exception ex)
- {
- Debug.LogWarning($"Prediction check failed for '{output.key}' on '{output.unit}':\n{ex}");
-
- return false;
- }
- }
-
- public static object Predict(IUnitValuePort port, GraphReference reference)
- {
- Ensure.That(nameof(port)).IsNotNull(port);
-
- var flow = New(reference);
-
- flow.isPrediction = true;
-
- object value;
-
- if (port is ValueInput)
- {
- value = flow.GetValue((ValueInput)port);
- }
- else if (port is ValueOutput)
- {
- value = flow.GetValue((ValueOutput)port);
- }
- else
- {
- throw new NotSupportedException();
- }
-
- flow.Dispose();
-
- return value;
- }
-
- public static object Predict(IUnitValuePort port, GraphReference reference, Type type)
- {
- return ConversionUtility.Convert(Predict(port, reference), type);
- }
-
- public static T Predict<T>(IUnitValuePort port, GraphReference pointer)
- {
- return (T)Predict(port, pointer, typeof(T));
- }
-
- #endregion
- }
- }
|