A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections;
3using System.Collections.Generic;
4using System.Linq;
5using UnityEngine;
6
7namespace Unity.VisualScripting
8{
9 [SerializationVersion("A")]
10 public abstract class Unit : GraphElement<FlowGraph>, IUnit
11 {
12 public class DebugData : IUnitDebugData
13 {
14 public int lastInvokeFrame { get; set; }
15
16 public float lastInvokeTime { get; set; }
17
18 public Exception runtimeException { get; set; }
19 }
20
21 protected Unit() : base()
22 {
23 controlInputs = new UnitPortCollection<ControlInput>(this);
24 controlOutputs = new UnitPortCollection<ControlOutput>(this);
25 valueInputs = new UnitPortCollection<ValueInput>(this);
26 valueOutputs = new UnitPortCollection<ValueOutput>(this);
27 invalidInputs = new UnitPortCollection<InvalidInput>(this);
28 invalidOutputs = new UnitPortCollection<InvalidOutput>(this);
29
30 relations = new ConnectionCollection<IUnitRelation, IUnitPort, IUnitPort>();
31
32 defaultValues = new Dictionary<string, object>();
33 }
34
35 public virtual IGraphElementDebugData CreateDebugData()
36 {
37 return new DebugData();
38 }
39
40 public override void AfterAdd()
41 {
42 // Important to define before notifying instances
43 Define();
44
45 base.AfterAdd();
46 }
47
48 public override void BeforeRemove()
49 {
50 base.BeforeRemove();
51
52 Disconnect();
53 }
54
55 public override void Instantiate(GraphReference instance)
56 {
57 base.Instantiate(instance);
58
59 if (this is IGraphEventListener listener && XGraphEventListener.IsHierarchyListening(instance))
60 {
61 listener.StartListening(instance);
62 }
63 }
64
65 public override void Uninstantiate(GraphReference instance)
66 {
67 if (this is IGraphEventListener listener)
68 {
69 listener.StopListening(instance);
70 }
71
72 base.Uninstantiate(instance);
73 }
74
75 #region Poutine
76
77 protected void CopyFrom(Unit source)
78 {
79 base.CopyFrom(source);
80
81 defaultValues = source.defaultValues;
82 }
83
84 #endregion
85
86 #region Definition
87
88 [DoNotSerialize]
89 public virtual bool canDefine => true;
90
91 [DoNotSerialize]
92 public bool failedToDefine => definitionException != null;
93
94 [DoNotSerialize]
95 public bool isDefined { get; private set; }
96
97 protected abstract void Definition();
98
99 protected virtual void AfterDefine() { }
100
101 protected virtual void BeforeUndefine() { }
102
103 private void Undefine()
104 {
105 // Because a node is always undefined on definition,
106 // even if it wasn't defined before, we make sure the user
107 // code for undefinition can safely presume it was defined.
108 if (isDefined)
109 {
110 BeforeUndefine();
111 }
112
113 Disconnect();
114 defaultValues.Clear();
115 controlInputs.Clear();
116 controlOutputs.Clear();
117 valueInputs.Clear();
118 valueOutputs.Clear();
119 invalidInputs.Clear();
120 invalidOutputs.Clear();
121 relations.Clear();
122 isDefined = false;
123 }
124
125 public void EnsureDefined()
126 {
127 if (!isDefined)
128 {
129 Define();
130 }
131 }
132
133 public void Define()
134 {
135 var preservation = UnitPreservation.Preserve(this);
136
137 // A node needs to undefine even if it wasn't defined,
138 // because there might be invalid ports and connections
139 // that we need to clear to avoid duplicates on definition.
140 Undefine();
141
142 if (canDefine)
143 {
144 try
145 {
146 Definition();
147 isDefined = true;
148 definitionException = null;
149 AfterDefine();
150 }
151 catch (Exception ex)
152 {
153 Undefine();
154 definitionException = ex;
155 Debug.LogWarning($"Failed to define {this}:\n{ex}");
156 }
157 }
158
159 preservation.RestoreTo(this);
160 }
161
162 public void RemoveUnconnectedInvalidPorts()
163 {
164 foreach (var unconnectedInvalidInput in invalidInputs.Where(p => !p.hasAnyConnection).ToArray())
165 {
166 invalidInputs.Remove(unconnectedInvalidInput);
167 }
168
169 foreach (var unconnectedInvalidOutput in invalidOutputs.Where(p => !p.hasAnyConnection).ToArray())
170 {
171 invalidOutputs.Remove(unconnectedInvalidOutput);
172 }
173 }
174
175 #endregion
176
177 #region Ports
178
179 [DoNotSerialize]
180 public IUnitPortCollection<ControlInput> controlInputs { get; }
181
182 [DoNotSerialize]
183 public IUnitPortCollection<ControlOutput> controlOutputs { get; }
184
185 [DoNotSerialize]
186 public IUnitPortCollection<ValueInput> valueInputs { get; }
187
188 [DoNotSerialize]
189 public IUnitPortCollection<ValueOutput> valueOutputs { get; }
190
191 [DoNotSerialize]
192 public IUnitPortCollection<InvalidInput> invalidInputs { get; }
193
194 [DoNotSerialize]
195 public IUnitPortCollection<InvalidOutput> invalidOutputs { get; }
196
197 [DoNotSerialize]
198 public IEnumerable<IUnitInputPort> inputs => LinqUtility.Concat<IUnitInputPort>(controlInputs, valueInputs, invalidInputs);
199
200 [DoNotSerialize]
201 public IEnumerable<IUnitOutputPort> outputs => LinqUtility.Concat<IUnitOutputPort>(controlOutputs, valueOutputs, invalidOutputs);
202
203 [DoNotSerialize]
204 public IEnumerable<IUnitInputPort> validInputs => LinqUtility.Concat<IUnitInputPort>(controlInputs, valueInputs);
205
206 [DoNotSerialize]
207 public IEnumerable<IUnitOutputPort> validOutputs => LinqUtility.Concat<IUnitOutputPort>(controlOutputs, valueOutputs);
208
209 [DoNotSerialize]
210 public IEnumerable<IUnitPort> ports => LinqUtility.Concat<IUnitPort>(inputs, outputs);
211
212 [DoNotSerialize]
213 public IEnumerable<IUnitPort> invalidPorts => LinqUtility.Concat<IUnitPort>(invalidInputs, invalidOutputs);
214
215 [DoNotSerialize]
216 public IEnumerable<IUnitPort> validPorts => LinqUtility.Concat<IUnitPort>(validInputs, validOutputs);
217
218 public event Action onPortsChanged;
219
220 public void PortsChanged()
221 {
222 onPortsChanged?.Invoke();
223 }
224
225 #endregion
226
227 #region Default Values
228
229 [Serialize]
230 public Dictionary<string, object> defaultValues { get; private set; }
231
232 #endregion
233
234 #region Connections
235
236 [DoNotSerialize]
237 public IConnectionCollection<IUnitRelation, IUnitPort, IUnitPort> relations { get; private set; }
238
239 [DoNotSerialize]
240 public IEnumerable<IUnitConnection> connections => ports.SelectMany(p => p.connections);
241
242 public void Disconnect()
243 {
244 // Can't use a foreach because invalid ports may get removed as they disconnect
245 while (ports.Any(p => p.hasAnyConnection))
246 {
247 ports.First(p => p.hasAnyConnection).Disconnect();
248 }
249 }
250
251 #endregion
252
253 #region Analysis
254
255 [DoNotSerialize]
256 public virtual bool isControlRoot { get; protected set; } = false;
257
258 #endregion
259
260 #region Helpers
261
262 protected void EnsureUniqueInput(string key)
263 {
264 if (controlInputs.Contains(key) || valueInputs.Contains(key) || invalidInputs.Contains(key))
265 {
266 throw new ArgumentException($"Duplicate input for '{key}' in {GetType()}.");
267 }
268 }
269
270 protected void EnsureUniqueOutput(string key)
271 {
272 if (controlOutputs.Contains(key) || valueOutputs.Contains(key) || invalidOutputs.Contains(key))
273 {
274 throw new ArgumentException($"Duplicate output for '{key}' in {GetType()}.");
275 }
276 }
277
278 protected ControlInput ControlInput(string key, Func<Flow, ControlOutput> action)
279 {
280 EnsureUniqueInput(key);
281 var port = new ControlInput(key, action);
282 controlInputs.Add(port);
283 return port;
284 }
285
286 protected ControlInput ControlInputCoroutine(string key, Func<Flow, IEnumerator> coroutineAction)
287 {
288 EnsureUniqueInput(key);
289 var port = new ControlInput(key, coroutineAction);
290 controlInputs.Add(port);
291 return port;
292 }
293
294 protected ControlInput ControlInputCoroutine(string key, Func<Flow, ControlOutput> action, Func<Flow, IEnumerator> coroutineAction)
295 {
296 EnsureUniqueInput(key);
297 var port = new ControlInput(key, action, coroutineAction);
298 controlInputs.Add(port);
299 return port;
300 }
301
302 protected ControlOutput ControlOutput(string key)
303 {
304 EnsureUniqueOutput(key);
305 var port = new ControlOutput(key);
306 controlOutputs.Add(port);
307 return port;
308 }
309
310 protected ValueInput ValueInput(Type type, string key)
311 {
312 EnsureUniqueInput(key);
313 var port = new ValueInput(key, type);
314 valueInputs.Add(port);
315 return port;
316 }
317
318 protected ValueInput ValueInput<T>(string key)
319 {
320 return ValueInput(typeof(T), key);
321 }
322
323 protected ValueInput ValueInput<T>(string key, T @default)
324 {
325 var port = ValueInput<T>(key);
326 port.SetDefaultValue(@default);
327 return port;
328 }
329
330 protected ValueOutput ValueOutput(Type type, string key)
331 {
332 EnsureUniqueOutput(key);
333 var port = new ValueOutput(key, type);
334 valueOutputs.Add(port);
335 return port;
336 }
337
338 protected ValueOutput ValueOutput(Type type, string key, Func<Flow, object> getValue)
339 {
340 EnsureUniqueOutput(key);
341 var port = new ValueOutput(key, type, getValue);
342 valueOutputs.Add(port);
343 return port;
344 }
345
346 protected ValueOutput ValueOutput<T>(string key)
347 {
348 return ValueOutput(typeof(T), key);
349 }
350
351 protected ValueOutput ValueOutput<T>(string key, Func<Flow, T> getValue)
352 {
353 return ValueOutput(typeof(T), key, (recursion) => getValue(recursion));
354 }
355
356 private void Relation(IUnitPort source, IUnitPort destination)
357 {
358 relations.Add(new UnitRelation(source, destination));
359 }
360
361 /// <summary>
362 /// Triggering the destination may fetch the source value.
363 /// </summary>
364 protected void Requirement(ValueInput source, ControlInput destination)
365 {
366 Relation(source, destination);
367 }
368
369 /// <summary>
370 /// Getting the value of the destination may fetch the value of the source.
371 /// </summary>
372 protected void Requirement(ValueInput source, ValueOutput destination)
373 {
374 Relation(source, destination);
375 }
376
377 /// <summary>
378 /// Triggering the source may assign the destination value on the flow.
379 /// </summary>
380 protected void Assignment(ControlInput source, ValueOutput destination)
381 {
382 Relation(source, destination);
383 }
384
385 /// <summary>
386 /// Triggering the source may trigger the destination.
387 /// </summary>
388 protected void Succession(ControlInput source, ControlOutput destination)
389 {
390 Relation(source, destination);
391 }
392
393 #endregion
394
395 #region Widget
396
397 [Serialize]
398 public Vector2 position { get; set; }
399
400 [DoNotSerialize]
401 public Exception definitionException { get; protected set; }
402
403 #endregion
404
405 #region Analytics
406
407 public override AnalyticsIdentifier GetAnalyticsIdentifier()
408 {
409 var aid = new AnalyticsIdentifier
410 {
411 Identifier = GetType().FullName,
412 Namespace = GetType().Namespace,
413 };
414 aid.Hashcode = aid.Identifier.GetHashCode();
415 return aid;
416 }
417
418 #endregion
419 }
420}