A game about forced loneliness, made by TACStudios
1using System; 2using System.Collections.Generic; 3using UnityEngine; 4 5namespace Unity.VisualScripting 6{ 7 [SerializationVersion("A")] 8 [SpecialUnit] 9 public abstract class EventUnit<TArgs> : Unit, IEventUnit, IGraphElementWithData, IGraphEventHandler<TArgs> 10 { 11 public class Data : IGraphElementData 12 { 13 public EventHook hook; 14 15 public Delegate handler; 16 17 public bool isListening; 18 19 public HashSet<Flow> activeCoroutines = new HashSet<Flow>(); 20 } 21 22 public virtual IGraphElementData CreateData() 23 { 24 return new Data(); 25 } 26 27 /// <summary> 28 /// Run this event in a coroutine, enabling asynchronous flow like wait nodes. 29 /// </summary> 30 [Serialize] 31 [Inspectable] 32 [InspectorExpandTooltip] 33 public bool coroutine { get; set; } = false; 34 35 [DoNotSerialize] 36 [PortLabelHidden] 37 public ControlOutput trigger { get; private set; } 38 39 [DoNotSerialize] 40 protected abstract bool register { get; } 41 42 protected override void Definition() 43 { 44 isControlRoot = true; 45 46 trigger = ControlOutput(nameof(trigger)); 47 } 48 49 public virtual EventHook GetHook(GraphReference reference) 50 { 51 throw new InvalidImplementationException($"Missing event hook for '{this}'."); 52 } 53 54 public virtual void StartListening(GraphStack stack) 55 { 56 var data = stack.GetElementData<Data>(this); 57 58 if (data.isListening) 59 { 60 return; 61 } 62 63 if (register) 64 { 65 var reference = stack.ToReference(); 66 var hook = GetHook(reference); 67 Action<TArgs> handler = args => Trigger(reference, args); 68 EventBus.Register(hook, handler); 69 70 data.hook = hook; 71 data.handler = handler; 72 } 73 74 data.isListening = true; 75 } 76 77 public virtual void StopListening(GraphStack stack) 78 { 79 var data = stack.GetElementData<Data>(this); 80 81 if (!data.isListening) 82 { 83 return; 84 } 85 86 // The coroutine's flow will dispose at the next frame, letting us 87 // keep the current flow for clean up operations if needed 88 foreach (var activeCoroutine in data.activeCoroutines) 89 { 90 activeCoroutine.StopCoroutine(false); 91 } 92 93 if (register) 94 { 95 EventBus.Unregister(data.hook, data.handler); 96 97 stack.ClearReference(); 98 99 data.handler = null; 100 } 101 102 data.isListening = false; 103 } 104 105 public override void Uninstantiate(GraphReference instance) 106 { 107 // Here, we're relying on the fact that OnDestroy calls Uninstantiate. 108 // We need to force-dispose any remaining coroutine to avoid 109 // memory leaks, because OnDestroy on the runner will not keep 110 // executing MoveNext() until our soft-destroy call at the end of Flow.Coroutine 111 // or even dispose the coroutine's enumerator (!). 112 var data = instance.GetElementData<Data>(this); 113 var coroutines = data.activeCoroutines.ToHashSetPooled(); 114 115#if UNITY_EDITOR 116 new FrameDelayedCallback(() => StopAllCoroutines(coroutines), 1); 117#else 118 StopAllCoroutines(coroutines); 119#endif 120 121 base.Uninstantiate(instance); 122 } 123 124 static void StopAllCoroutines(HashSet<Flow> activeCoroutines) 125 { 126 // The coroutine's flow will dispose instantly, thus modifying 127 // the activeCoroutines registry while we enumerate over it 128 foreach (var activeCoroutine in activeCoroutines) 129 { 130 activeCoroutine.StopCoroutineImmediate(); 131 } 132 activeCoroutines.Free(); 133 } 134 135 public bool IsListening(GraphPointer pointer) 136 { 137 if (!pointer.hasData) 138 { 139 return false; 140 } 141 142 return pointer.GetElementData<Data>(this).isListening; 143 } 144 145 public void Trigger(GraphReference reference, TArgs args) 146 { 147 InternalTrigger(reference, args); 148 } 149 150 private protected virtual void InternalTrigger(GraphReference reference, TArgs args) 151 { 152 var flow = Flow.New(reference); 153 154 if (!ShouldTrigger(flow, args)) 155 { 156 flow.Dispose(); 157 return; 158 } 159 160 AssignArguments(flow, args); 161 162 Run(flow); 163 } 164 165 protected virtual bool ShouldTrigger(Flow flow, TArgs args) 166 { 167 return true; 168 } 169 170 protected virtual void AssignArguments(Flow flow, TArgs args) 171 { 172 } 173 174 private void Run(Flow flow) 175 { 176 if (flow.enableDebug) 177 { 178 var editorData = flow.stack.GetElementDebugData<IUnitDebugData>(this); 179 180 editorData.lastInvokeFrame = EditorTimeBinding.frame; 181 editorData.lastInvokeTime = EditorTimeBinding.time; 182 } 183 184 if (coroutine) 185 { 186 flow.StartCoroutine(trigger, flow.stack.GetElementData<Data>(this).activeCoroutines); 187 } 188 else 189 { 190 flow.Run(trigger); 191 } 192 } 193 194 protected static bool CompareNames(Flow flow, ValueInput namePort, string calledName) 195 { 196 Ensure.That(nameof(calledName)).IsNotNull(calledName); 197 198 return calledName.Trim().Equals(flow.GetValue<string>(namePort)?.Trim(), StringComparison.OrdinalIgnoreCase); 199 } 200 } 201}