123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- using System;
- using System.Collections.Generic;
- using UnityEngine;
-
- namespace Unity.VisualScripting
- {
- [SerializationVersion("A")]
- [SpecialUnit]
- public abstract class EventUnit<TArgs> : Unit, IEventUnit, IGraphElementWithData, IGraphEventHandler<TArgs>
- {
- public class Data : IGraphElementData
- {
- public EventHook hook;
-
- public Delegate handler;
-
- public bool isListening;
-
- public HashSet<Flow> activeCoroutines = new HashSet<Flow>();
- }
-
- public virtual IGraphElementData CreateData()
- {
- return new Data();
- }
-
- /// <summary>
- /// Run this event in a coroutine, enabling asynchronous flow like wait nodes.
- /// </summary>
- [Serialize]
- [Inspectable]
- [InspectorExpandTooltip]
- public bool coroutine { get; set; } = false;
-
- [DoNotSerialize]
- [PortLabelHidden]
- public ControlOutput trigger { get; private set; }
-
- [DoNotSerialize]
- protected abstract bool register { get; }
-
- protected override void Definition()
- {
- isControlRoot = true;
-
- trigger = ControlOutput(nameof(trigger));
- }
-
- public virtual EventHook GetHook(GraphReference reference)
- {
- throw new InvalidImplementationException($"Missing event hook for '{this}'.");
- }
-
- public virtual void StartListening(GraphStack stack)
- {
- var data = stack.GetElementData<Data>(this);
-
- if (data.isListening)
- {
- return;
- }
-
- if (register)
- {
- var reference = stack.ToReference();
- var hook = GetHook(reference);
- Action<TArgs> handler = args => Trigger(reference, args);
- EventBus.Register(hook, handler);
-
- data.hook = hook;
- data.handler = handler;
- }
-
- data.isListening = true;
- }
-
- public virtual void StopListening(GraphStack stack)
- {
- var data = stack.GetElementData<Data>(this);
-
- if (!data.isListening)
- {
- return;
- }
-
- // The coroutine's flow will dispose at the next frame, letting us
- // keep the current flow for clean up operations if needed
- foreach (var activeCoroutine in data.activeCoroutines)
- {
- activeCoroutine.StopCoroutine(false);
- }
-
- if (register)
- {
- EventBus.Unregister(data.hook, data.handler);
-
- stack.ClearReference();
-
- data.handler = null;
- }
-
- data.isListening = false;
- }
-
- public override void Uninstantiate(GraphReference instance)
- {
- // Here, we're relying on the fact that OnDestroy calls Uninstantiate.
- // We need to force-dispose any remaining coroutine to avoid
- // memory leaks, because OnDestroy on the runner will not keep
- // executing MoveNext() until our soft-destroy call at the end of Flow.Coroutine
- // or even dispose the coroutine's enumerator (!).
- var data = instance.GetElementData<Data>(this);
- var coroutines = data.activeCoroutines.ToHashSetPooled();
-
- #if UNITY_EDITOR
- new FrameDelayedCallback(() => StopAllCoroutines(coroutines), 1);
- #else
- StopAllCoroutines(coroutines);
- #endif
-
- base.Uninstantiate(instance);
- }
-
- static void StopAllCoroutines(HashSet<Flow> activeCoroutines)
- {
- // The coroutine's flow will dispose instantly, thus modifying
- // the activeCoroutines registry while we enumerate over it
- foreach (var activeCoroutine in activeCoroutines)
- {
- activeCoroutine.StopCoroutineImmediate();
- }
- activeCoroutines.Free();
- }
-
- public bool IsListening(GraphPointer pointer)
- {
- if (!pointer.hasData)
- {
- return false;
- }
-
- return pointer.GetElementData<Data>(this).isListening;
- }
-
- public void Trigger(GraphReference reference, TArgs args)
- {
- InternalTrigger(reference, args);
- }
-
- private protected virtual void InternalTrigger(GraphReference reference, TArgs args)
- {
- var flow = Flow.New(reference);
-
- if (!ShouldTrigger(flow, args))
- {
- flow.Dispose();
- return;
- }
-
- AssignArguments(flow, args);
-
- Run(flow);
- }
-
- protected virtual bool ShouldTrigger(Flow flow, TArgs args)
- {
- return true;
- }
-
- protected virtual void AssignArguments(Flow flow, TArgs args)
- {
- }
-
- private void Run(Flow flow)
- {
- if (flow.enableDebug)
- {
- var editorData = flow.stack.GetElementDebugData<IUnitDebugData>(this);
-
- editorData.lastInvokeFrame = EditorTimeBinding.frame;
- editorData.lastInvokeTime = EditorTimeBinding.time;
- }
-
- if (coroutine)
- {
- flow.StartCoroutine(trigger, flow.stack.GetElementData<Data>(this).activeCoroutines);
- }
- else
- {
- flow.Run(trigger);
- }
- }
-
- protected static bool CompareNames(Flow flow, ValueInput namePort, string calledName)
- {
- Ensure.That(nameof(calledName)).IsNotNull(calledName);
-
- return calledName.Trim().Equals(flow.GetValue<string>(namePort)?.Trim(), StringComparison.OrdinalIgnoreCase);
- }
- }
- }
|