File size: 5,632 Bytes
18a519f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | 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);
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)
{
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);
}
}
}
|