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 public sealed class Flow : IPoolable, IDisposable 10 { 11 // We need to check for recursion by passing some additional 12 // context information to avoid the same port in multiple different 13 // nested flow graphs to count as the same item. Naively, 14 // we're using the parent as the context, which seems to work; 15 // it won't theoretically catch recursive nesting, but then recursive 16 // nesting already isn't supported anyway, so this way we avoid hashing 17 // or turning the stack into a reference. 18 // https://support.ludiq.io/communities/5/topics/2122-r 19 // We make this an equatable struct to avoid any allocation. 20 private struct RecursionNode : IEquatable<RecursionNode> 21 { 22 public IUnitPort port { get; } 23 24 public IGraphParent context { get; } 25 26 public RecursionNode(IUnitPort port, GraphPointer pointer) 27 { 28 this.port = port; 29 this.context = pointer.parent; 30 } 31 32 public bool Equals(RecursionNode other) 33 { 34 return other.port == port && other.context == context; 35 } 36 37 public override bool Equals(object obj) 38 { 39 return obj is RecursionNode other && Equals(other); 40 } 41 42 public override int GetHashCode() 43 { 44 return HashUtility.GetHashCode(port, context); 45 } 46 } 47 48 public GraphStack stack { get; private set; } 49 50 private Recursion<RecursionNode> recursion; 51 52 private readonly Dictionary<IUnitValuePort, object> locals = new Dictionary<IUnitValuePort, object>(); 53 54 public readonly VariableDeclarations variables = new VariableDeclarations(); 55 56 private readonly Stack<int> loops = new Stack<int>(); 57 58 private readonly HashSet<GraphStack> preservedStacks = new HashSet<GraphStack>(); 59 60 public MonoBehaviour coroutineRunner { get; private set; } 61 62 private ICollection<Flow> activeCoroutinesRegistry; 63 64 private bool coroutineStopRequested; 65 66 public bool isCoroutine { get; private set; } 67 68 private IEnumerator coroutineEnumerator; 69 70 public bool isPrediction { get; private set; } 71 72 private bool disposed; 73 74 public bool enableDebug 75 { 76 get 77 { 78 if (isPrediction) 79 { 80 return false; 81 } 82 83 if (!stack.hasDebugData) 84 { 85 return false; 86 } 87 88 return true; 89 } 90 } 91 92 public static Func<GraphPointer, bool> isInspectedBinding { get; set; } 93 94 public bool isInspected => isInspectedBinding?.Invoke(stack) ?? false; 95 96 97 #region Lifecycle 98 99 private Flow() { } 100 101 public static Flow New(GraphReference reference) 102 { 103 Ensure.That(nameof(reference)).IsNotNull(reference); 104 105 var flow = GenericPool<Flow>.New(() => new Flow()); ; 106 flow.stack = reference.ToStackPooled(); 107 108 return flow; 109 } 110 111 void IPoolable.New() 112 { 113 disposed = false; 114 115 recursion = Recursion<RecursionNode>.New(); 116 } 117 118 public void Dispose() 119 { 120 if (disposed) 121 { 122 throw new ObjectDisposedException(ToString()); 123 } 124 125 GenericPool<Flow>.Free(this); 126 } 127 128 void IPoolable.Free() 129 { 130 stack?.Dispose(); 131 recursion?.Dispose(); 132 locals.Clear(); 133 loops.Clear(); 134 variables.Clear(); 135 136 // Preserved stacks could remain if coroutine was interrupted 137 foreach (var preservedStack in preservedStacks) 138 { 139 preservedStack.Dispose(); 140 } 141 142 preservedStacks.Clear(); 143 144 loopIdentifier = -1; 145 stack = null; 146 recursion = null; 147 isCoroutine = false; 148 coroutineEnumerator = null; 149 coroutineRunner = null; 150 activeCoroutinesRegistry?.Remove(this); 151 activeCoroutinesRegistry = null; 152 coroutineStopRequested = false; 153 isPrediction = false; 154 155 disposed = true; 156 } 157 158 public GraphStack PreserveStack() 159 { 160 var preservedStack = stack.Clone(); 161 preservedStacks.Add(preservedStack); 162 return preservedStack; 163 } 164 165 public void RestoreStack(GraphStack stack) 166 { 167 this.stack.CopyFrom(stack); 168 } 169 170 public void DisposePreservedStack(GraphStack stack) 171 { 172 stack.Dispose(); 173 preservedStacks.Remove(stack); 174 } 175 176 #endregion 177 178 179 #region Loops 180 181 public int loopIdentifier = -1; 182 183 public int currentLoop 184 { 185 get 186 { 187 if (loops.Count > 0) 188 { 189 return loops.Peek(); 190 } 191 else 192 { 193 return -1; 194 } 195 } 196 } 197 198 public bool LoopIsNotBroken(int loop) 199 { 200 return currentLoop == loop; 201 } 202 203 public int EnterLoop() 204 { 205 var loop = ++loopIdentifier; 206 207 loops.Push(loop); 208 209 return loop; 210 } 211 212 public void BreakLoop() 213 { 214 if (currentLoop < 0) 215 { 216 throw new InvalidOperationException("No active loop to break."); 217 } 218 219 loops.Pop(); 220 } 221 222 public void ExitLoop(int loop) 223 { 224 if (loop != currentLoop) 225 { 226 // Already exited through break 227 return; 228 } 229 230 loops.Pop(); 231 } 232 233 #endregion 234 235 236 #region Control 237 238 public void Run(ControlOutput port) 239 { 240 Invoke(port); 241 Dispose(); 242 } 243 244 public void StartCoroutine(ControlOutput port, ICollection<Flow> registry = null) 245 { 246 isCoroutine = true; 247 248 coroutineRunner = stack.component; 249 250 if (coroutineRunner == null) 251 { 252 coroutineRunner = CoroutineRunner.instance; 253 } 254 255 activeCoroutinesRegistry = registry; 256 257 activeCoroutinesRegistry?.Add(this); 258 259 // We have to store the enumerator because Coroutine itself 260 // can't be cast to IDisposable, which we'll need when stopping. 261 coroutineEnumerator = Coroutine(port); 262 263 coroutineRunner.StartCoroutine(coroutineEnumerator); 264 } 265 266 public void StopCoroutine(bool disposeInstantly) 267 { 268 if (!isCoroutine) 269 { 270 throw new NotSupportedException("Stop may only be called on coroutines."); 271 } 272 273 if (disposeInstantly) 274 { 275 StopCoroutineImmediate(); 276 } 277 else 278 { 279 // We prefer a soft coroutine stop here that will happen at the *next frame*, 280 // because we don't want the flow to be disposed just yet when the event node stops 281 // listening, as we still need it for clean up operations. 282 coroutineStopRequested = true; 283 } 284 } 285 286 internal void StopCoroutineImmediate() 287 { 288 if (coroutineRunner && coroutineEnumerator != null) 289 { 290 coroutineRunner.StopCoroutine(coroutineEnumerator); 291 292 // Unity doesn't dispose coroutines enumerators when calling StopCoroutine, so we have to do it manually: 293 // https://forum.unity.com/threads/finally-block-not-executing-in-a-stopped-coroutine.320611/ 294 ((IDisposable)coroutineEnumerator).Dispose(); 295 } 296 } 297 298 private IEnumerator Coroutine(ControlOutput startPort) 299 { 300 try 301 { 302 foreach (var instruction in InvokeCoroutine(startPort)) 303 { 304 if (coroutineStopRequested) 305 { 306 yield break; 307 } 308 309 yield return instruction; 310 311 if (coroutineStopRequested) 312 { 313 yield break; 314 } 315 } 316 } 317 finally 318 { 319 // Manual disposal might have already occurred from StopCoroutine, 320 // so we have to avoid double disposal, which would throw. 321 if (!disposed) 322 { 323 Dispose(); 324 } 325 } 326 } 327 328 public void Invoke(ControlOutput output) 329 { 330 Ensure.That(nameof(output)).IsNotNull(output); 331 332 var connection = output.connection; 333 334 if (connection == null) 335 { 336 return; 337 } 338 339 var input = connection.destination; 340 341 var recursionNode = new RecursionNode(output, stack); 342 343 BeforeInvoke(output, recursionNode); 344 345 try 346 { 347 var nextPort = InvokeDelegate(input); 348 349 if (nextPort != null) 350 { 351 Invoke(nextPort); 352 } 353 } 354 finally 355 { 356 AfterInvoke(output, recursionNode); 357 } 358 } 359 360 private IEnumerable InvokeCoroutine(ControlOutput output) 361 { 362 var connection = output.connection; 363 364 if (connection == null) 365 { 366 yield break; 367 } 368 369 var input = connection.destination; 370 371 var recursionNode = new RecursionNode(output, stack); 372 373 BeforeInvoke(output, recursionNode); 374 375 if (input.supportsCoroutine) 376 { 377 foreach (var instruction in InvokeCoroutineDelegate(input)) 378 { 379 if (instruction is ControlOutput) 380 { 381 foreach (var unwrappedInstruction in InvokeCoroutine((ControlOutput)instruction)) 382 { 383 yield return unwrappedInstruction; 384 } 385 } 386 else 387 { 388 yield return instruction; 389 } 390 } 391 } 392 else 393 { 394 ControlOutput nextPort = InvokeDelegate(input); 395 396 if (nextPort != null) 397 { 398 foreach (var instruction in InvokeCoroutine(nextPort)) 399 { 400 yield return instruction; 401 } 402 } 403 } 404 405 AfterInvoke(output, recursionNode); 406 } 407 408 private RecursionNode BeforeInvoke(ControlOutput output, RecursionNode recursionNode) 409 { 410 try 411 { 412 recursion?.Enter(recursionNode); 413 } 414 catch (StackOverflowException ex) 415 { 416 output.unit.HandleException(stack, ex); 417 throw; 418 } 419 420 var connection = output.connection; 421 var input = connection.destination; 422 423 if (enableDebug) 424 { 425 var connectionEditorData = stack.GetElementDebugData<IUnitConnectionDebugData>(connection); 426 var inputUnitEditorData = stack.GetElementDebugData<IUnitDebugData>(input.unit); 427 428 connectionEditorData.lastInvokeFrame = EditorTimeBinding.frame; 429 connectionEditorData.lastInvokeTime = EditorTimeBinding.time; 430 inputUnitEditorData.lastInvokeFrame = EditorTimeBinding.frame; 431 inputUnitEditorData.lastInvokeTime = EditorTimeBinding.time; 432 } 433 434 return recursionNode; 435 } 436 437 private void AfterInvoke(ControlOutput output, RecursionNode recursionNode) 438 { 439 recursion?.Exit(recursionNode); 440 } 441 442 private ControlOutput InvokeDelegate(ControlInput input) 443 { 444 try 445 { 446 if (input.requiresCoroutine) 447 { 448 throw new InvalidOperationException($"Port '{input.key}' on '{input.unit}' can only be triggered in a coroutine."); 449 } 450 451 return input.action(this); 452 } 453 catch (Exception ex) 454 { 455 input.unit.HandleException(stack, ex); 456 throw; 457 } 458 } 459 460 private IEnumerable InvokeCoroutineDelegate(ControlInput input) 461 { 462 var instructions = input.coroutineAction(this); 463 464 while (true) 465 { 466 object instruction; 467 468 try 469 { 470 if (!instructions.MoveNext()) 471 { 472 break; 473 } 474 475 instruction = instructions.Current; 476 } 477 catch (Exception ex) 478 { 479 input.unit.HandleException(stack, ex); 480 throw; 481 } 482 483 yield return instruction; 484 } 485 } 486 487 #endregion 488 489 490 #region Values 491 492 public bool IsLocal(IUnitValuePort port) 493 { 494 Ensure.That(nameof(port)).IsNotNull(port); 495 496 return locals.ContainsKey(port); 497 } 498 499 public void SetValue(IUnitValuePort port, object value) 500 { 501 Ensure.That(nameof(port)).IsNotNull(port); 502 Ensure.That(nameof(value)).IsOfType(value, port.type); 503 504 if (locals.ContainsKey(port)) 505 { 506 locals[port] = value; 507 } 508 else 509 { 510 locals.Add(port, value); 511 } 512 } 513 514 public object GetValue(ValueInput input) 515 { 516 if (locals.TryGetValue(input, out var local)) 517 { 518 return local; 519 } 520 521 var connection = input.connection; 522 523 if (connection != null) 524 { 525 if (enableDebug) 526 { 527 var connectionEditorData = stack.GetElementDebugData<IUnitConnectionDebugData>(connection); 528 529 connectionEditorData.lastInvokeFrame = EditorTimeBinding.frame; 530 connectionEditorData.lastInvokeTime = EditorTimeBinding.time; 531 } 532 533 var output = connection.source; 534 535 var value = GetValue(output); 536 537 if (enableDebug) 538 { 539 var connectionEditorData = stack.GetElementDebugData<ValueConnection.DebugData>(connection); 540 541 connectionEditorData.lastValue = value; 542 connectionEditorData.assignedLastValue = true; 543 } 544 545 return value; 546 } 547 else if (TryGetDefaultValue(input, out var defaultValue)) 548 { 549 return defaultValue; 550 } 551 else 552 { 553 throw new MissingValuePortInputException(input.key); 554 } 555 } 556 557 private object GetValue(ValueOutput output) 558 { 559 if (locals.TryGetValue(output, out var local)) 560 { 561 return local; 562 } 563 564 if (!output.supportsFetch) 565 { 566 throw new InvalidOperationException($"The value of '{output.key}' on '{output.unit}' cannot be fetched dynamically, it must be assigned."); 567 } 568 569 var recursionNode = new RecursionNode(output, stack); 570 571 try 572 { 573 recursion?.Enter(recursionNode); 574 } 575 catch (StackOverflowException ex) 576 { 577 output.unit.HandleException(stack, ex); 578 throw; 579 } 580 581 try 582 { 583 if (enableDebug) 584 { 585 var outputUnitEditorData = stack.GetElementDebugData<IUnitDebugData>(output.unit); 586 587 outputUnitEditorData.lastInvokeFrame = EditorTimeBinding.frame; 588 outputUnitEditorData.lastInvokeTime = EditorTimeBinding.time; 589 } 590 591 var value = GetValueDelegate(output); 592 593 return value; 594 } 595 finally 596 { 597 recursion?.Exit(recursionNode); 598 } 599 } 600 601 public object GetValue(ValueInput input, Type type) 602 { 603 return ConversionUtility.Convert(GetValue(input), type); 604 } 605 606 public T GetValue<T>(ValueInput input) 607 { 608 return (T)GetValue(input, typeof(T)); 609 } 610 611 public object GetConvertedValue(ValueInput input) 612 { 613 return GetValue(input, input.type); 614 } 615 616 private object GetDefaultValue(ValueInput input) 617 { 618 if (!TryGetDefaultValue(input, out var defaultValue)) 619 { 620 throw new InvalidOperationException("Value input port does not have a default value."); 621 } 622 623 return defaultValue; 624 } 625 626 public bool TryGetDefaultValue(ValueInput input, out object defaultValue) 627 { 628 if (!input.unit.defaultValues.TryGetValue(input.key, out defaultValue)) 629 { 630 return false; 631 } 632 633 if (input.nullMeansSelf && defaultValue == null) 634 { 635 defaultValue = stack.self; 636 } 637 638 return true; 639 } 640 641 private object GetValueDelegate(ValueOutput output) 642 { 643 try 644 { 645 return output.getValue(this); 646 } 647 catch (Exception ex) 648 { 649 output.unit.HandleException(stack, ex); 650 throw; 651 } 652 } 653 654 public static object FetchValue(ValueInput input, GraphReference reference) 655 { 656 var flow = New(reference); 657 658 var result = flow.GetValue(input); 659 660 flow.Dispose(); 661 662 return result; 663 } 664 665 public static object FetchValue(ValueInput input, Type type, GraphReference reference) 666 { 667 return ConversionUtility.Convert(FetchValue(input, reference), type); 668 } 669 670 public static T FetchValue<T>(ValueInput input, GraphReference reference) 671 { 672 return (T)FetchValue(input, typeof(T), reference); 673 } 674 675 #endregion 676 677 678 #region Value Prediction 679 680 public static bool CanPredict(IUnitValuePort port, GraphReference reference) 681 { 682 Ensure.That(nameof(port)).IsNotNull(port); 683 684 var flow = New(reference); 685 686 flow.isPrediction = true; 687 688 bool canPredict; 689 690 if (port is ValueInput) 691 { 692 canPredict = flow.CanPredict((ValueInput)port); 693 } 694 else if (port is ValueOutput) 695 { 696 canPredict = flow.CanPredict((ValueOutput)port); 697 } 698 else 699 { 700 throw new NotSupportedException(); 701 } 702 703 flow.Dispose(); 704 705 return canPredict; 706 } 707 708 private bool CanPredict(ValueInput input) 709 { 710 if (!input.hasValidConnection) 711 { 712 if (!TryGetDefaultValue(input, out var defaultValue)) 713 { 714 return false; 715 } 716 717 if (typeof(Component).IsAssignableFrom(input.type)) 718 { 719 defaultValue = defaultValue?.ConvertTo(input.type); 720 } 721 722 if (!input.allowsNull && defaultValue == null) 723 { 724 return false; 725 } 726 727 return true; 728 } 729 730 var output = input.validConnectedPorts.Single(); 731 732 if (!CanPredict(output)) 733 { 734 return false; 735 } 736 737 var connectedValue = GetValue(output); 738 739 if (!ConversionUtility.CanConvert(connectedValue, input.type, false)) 740 { 741 return false; 742 } 743 744 if (typeof(Component).IsAssignableFrom(input.type)) 745 { 746 connectedValue = connectedValue?.ConvertTo(input.type); 747 } 748 749 if (!input.allowsNull && connectedValue == null) 750 { 751 return false; 752 } 753 754 return true; 755 } 756 757 private bool CanPredict(ValueOutput output) 758 { 759 // Shortcircuit the expensive check if the port isn't marked as predictable 760 if (!output.supportsPrediction) 761 { 762 return false; 763 } 764 765 var recursionNode = new RecursionNode(output, stack); 766 767 if (!recursion?.TryEnter(recursionNode) ?? false) 768 { 769 return false; 770 } 771 772 // Check each value dependency 773 foreach (var relation in output.unit.relations.WithDestination(output)) 774 { 775 if (relation.source is ValueInput) 776 { 777 var source = (ValueInput)relation.source; 778 779 if (!CanPredict(source)) 780 { 781 recursion?.Exit(recursionNode); 782 return false; 783 } 784 } 785 } 786 787 var value = CanPredictDelegate(output); 788 789 recursion?.Exit(recursionNode); 790 791 return value; 792 } 793 794 private bool CanPredictDelegate(ValueOutput output) 795 { 796 try 797 { 798 return output.canPredictValue(this); 799 } 800 catch (Exception ex) 801 { 802 Debug.LogWarning($"Prediction check failed for '{output.key}' on '{output.unit}':\n{ex}"); 803 804 return false; 805 } 806 } 807 808 public static object Predict(IUnitValuePort port, GraphReference reference) 809 { 810 Ensure.That(nameof(port)).IsNotNull(port); 811 812 var flow = New(reference); 813 814 flow.isPrediction = true; 815 816 object value; 817 818 if (port is ValueInput) 819 { 820 value = flow.GetValue((ValueInput)port); 821 } 822 else if (port is ValueOutput) 823 { 824 value = flow.GetValue((ValueOutput)port); 825 } 826 else 827 { 828 throw new NotSupportedException(); 829 } 830 831 flow.Dispose(); 832 833 return value; 834 } 835 836 public static object Predict(IUnitValuePort port, GraphReference reference, Type type) 837 { 838 return ConversionUtility.Convert(Predict(port, reference), type); 839 } 840 841 public static T Predict<T>(IUnitValuePort port, GraphReference pointer) 842 { 843 return (T)Predict(port, pointer, typeof(T)); 844 } 845 846 #endregion 847 } 848}