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}