A game about forced loneliness, made by TACStudios
1using System; 2using System.Collections.Generic; 3using System.Linq; 4using System.Text; 5using UnityEngine; 6using UnityEngine.SceneManagement; 7using UnityObject = UnityEngine.Object; 8 9namespace Unity.VisualScripting 10{ 11 public abstract class GraphPointer 12 { 13 #region Lifecycle 14 15 protected static bool IsValidRoot(IGraphRoot root) 16 { 17 return root?.childGraph != null && root as UnityObject != null; 18 } 19 20 protected static bool IsValidRoot(UnityObject rootObject) 21 { 22 return rootObject != null && (rootObject as IGraphRoot)?.childGraph != null; 23 } 24 25 internal GraphPointer() { } 26 27 protected void Initialize(IGraphRoot root) 28 { 29 if (!IsValidRoot(root)) 30 { 31 throw new ArgumentException("Graph pointer root must be a valid Unity object with a non-null child graph.", nameof(root)); 32 } 33 34 if (!(root is IMachine && root is MonoBehaviour || root is IMacro && root is ScriptableObject)) 35 { 36 throw new ArgumentException("Graph pointer root must be either a machine or a macro.", nameof(root)); 37 } 38 39 this.root = root; 40 41 parentStack.Add(root); 42 43 graphStack.Add(root.childGraph); 44 45 dataStack.Add(machine?.graphData); 46 47 debugDataStack.Add(fetchRootDebugDataBinding?.Invoke(root)); 48 49 if (machine != null) 50 { 51 // Annoyingly, getting the gameObject property is an API call 52 // First, we'll try using our IMachine safe reference that is assigned in play mode on Awake 53 // If that fails, we'll try fetching it dynamically 54 55 if (machine.threadSafeGameObject != null) 56 { 57 gameObject = machine.threadSafeGameObject; 58 } 59 else if (UnityThread.allowsAPI) 60 { 61 gameObject = component.gameObject; 62 } 63 else 64 { 65 throw new GraphPointerException("Could not fetch graph pointer root game object.", this); 66 } 67 } 68 else 69 { 70 gameObject = null; 71 } 72 } 73 74 protected void Initialize(IGraphRoot root, IEnumerable<IGraphParentElement> parentElements, bool ensureValid) 75 { 76 Initialize(root); 77 78 Ensure.That(nameof(parentElements)).IsNotNull(parentElements); 79 80 foreach (var parentElement in parentElements) 81 { 82 if (!TryEnterParentElement(parentElement, out var error)) 83 { 84 if (ensureValid) 85 { 86 throw new GraphPointerException(error, this); 87 } 88 89 break; 90 } 91 } 92 } 93 94 protected void Initialize(UnityObject rootObject, IEnumerable<Guid> parentElementGuids, bool ensureValid) 95 { 96 Initialize(rootObject as IGraphRoot); 97 98 Ensure.That(nameof(parentElementGuids)).IsNotNull(parentElementGuids); 99 100 foreach (var parentElementGuid in parentElementGuids) 101 { 102 if (!TryEnterParentElement(parentElementGuid, out var error)) 103 { 104 if (ensureValid) 105 { 106 throw new GraphPointerException(error, this); 107 } 108 109 break; 110 } 111 } 112 } 113 114 #endregion 115 116 117 #region Conversion 118 119 public abstract GraphReference AsReference(); 120 121 public virtual void CopyFrom(GraphPointer other) 122 { 123 root = other.root; 124 gameObject = other.gameObject; 125 126 parentStack.Clear(); 127 parentElementStack.Clear(); 128 graphStack.Clear(); 129 dataStack.Clear(); 130 debugDataStack.Clear(); 131 132 foreach (var parent in other.parentStack) 133 { 134 parentStack.Add(parent); 135 } 136 137 foreach (var parentElement in other.parentElementStack) 138 { 139 parentElementStack.Add(parentElement); 140 } 141 142 foreach (var graph in other.graphStack) 143 { 144 graphStack.Add(graph); 145 } 146 147 foreach (var data in other.dataStack) 148 { 149 dataStack.Add(data); 150 } 151 152 foreach (var debugData in other.debugDataStack) 153 { 154 debugDataStack.Add(debugData); 155 } 156 } 157 158 #endregion 159 160 161 #region Stack 162 163 public IGraphRoot root { get; protected set; } 164 165 public UnityObject rootObject => root as UnityObject; 166 167 public IMachine machine => root as IMachine; 168 169 public IMacro macro => root as IMacro; 170 171 public MonoBehaviour component => root as MonoBehaviour; 172 173 public GameObject gameObject { get; private set; } 174 175 public GameObject self => gameObject; 176 177 public ScriptableObject scriptableObject => root as ScriptableObject; 178 179 public Scene? scene 180 { 181 get 182 { 183 if (gameObject == null) 184 { 185 return null; 186 } 187 188 var scene = gameObject.scene; 189 190 // We must allow to return unloaded scenes, because 191 // On Enable might try fetching scene variables for example 192 // See: https://support.ludiq.io/communities/5/topics/1864-/ 193 194 if (!scene.IsValid() /* || !scene.isLoaded */) 195 { 196 return null; 197 } 198 199 return scene; 200 } 201 } 202 203 public UnityObject serializedObject 204 { 205 get 206 { 207 var depth = this.depth; 208 209 while (depth > 0) 210 { 211 var parent = parentStack[depth - 1]; 212 213 if (parent.isSerializationRoot) 214 { 215 return parent.serializedObject; 216 } 217 218 depth--; 219 } 220 221 throw new GraphPointerException("Could not find serialized object.", this); 222 } 223 } 224 225 protected readonly List<IGraphParent> parentStack = new List<IGraphParent>(); 226 227 protected readonly List<IGraphParentElement> parentElementStack = new List<IGraphParentElement>(); 228 229 protected readonly List<IGraph> graphStack = new List<IGraph>(); 230 231 protected readonly List<IGraphData> dataStack = new List<IGraphData>(); 232 233 protected readonly List<IGraphDebugData> debugDataStack = new List<IGraphDebugData>(); 234 235 public IEnumerable<Guid> parentElementGuids => parentElementStack.Select(parentElement => parentElement.guid); 236 237 #endregion 238 239 240 #region Utility 241 242 public int depth => parentStack.Count; 243 244 public bool isRoot => depth == 1; 245 246 public bool isChild => depth > 1; 247 248 public void EnsureDepthValid(int depth) 249 { 250 Ensure.That(nameof(depth)).IsGte(depth, 1); 251 252 if (depth > this.depth) 253 { 254 throw new GraphPointerException($"Trying to fetch a graph pointer level above depth: {depth} > {this.depth}", this); 255 } 256 } 257 258 public void EnsureChild() 259 { 260 if (!isChild) 261 { 262 throw new GraphPointerException("Graph pointer does not point to a child graph.", this); 263 } 264 } 265 266 public bool IsWithin<T>() where T : IGraphParent 267 { 268 return parent is T; 269 } 270 271 public void EnsureWithin<T>() where T : IGraphParent 272 { 273 if (!IsWithin<T>()) 274 { 275 throw new GraphPointerException($"Graph pointer must be within a {typeof(T)} for this operation.", this); 276 } 277 } 278 279 public IGraphParent parent => parentStack[parentStack.Count - 1]; 280 281 public T GetParent<T>() where T : IGraphParent 282 { 283 EnsureWithin<T>(); 284 285 return (T)parent; 286 } 287 288 public IGraphParentElement parentElement 289 { 290 get 291 { 292 EnsureChild(); 293 294 return parentElementStack[parentElementStack.Count - 1]; 295 } 296 } 297 298 public IGraph rootGraph => graphStack[0]; 299 300 public IGraph graph => graphStack[graphStack.Count - 1]; 301 302 protected IGraphData _data 303 { 304 get => dataStack[dataStack.Count - 1]; 305 set => dataStack[dataStack.Count - 1] = value; 306 } 307 308 public IGraphData data 309 { 310 get 311 { 312 EnsureDataAvailable(); 313 return _data; 314 } 315 } 316 317 protected IGraphData _parentData => dataStack[dataStack.Count - 2]; 318 319 public bool hasData => _data != null; 320 321 public void EnsureDataAvailable() 322 { 323 if (!hasData) 324 { 325 throw new GraphPointerException($"Graph data is not available.", this); 326 } 327 } 328 329 public T GetGraphData<T>() where T : IGraphData 330 { 331 var data = this.data; 332 333 if (data is T) 334 { 335 return (T)data; 336 } 337 338 throw new GraphPointerException($"Graph data type mismatch. Found {data.GetType()}, expected {typeof(T)}.", this); 339 } 340 341 public T GetElementData<T>(IGraphElementWithData element) where T : IGraphElementData 342 { 343 if (_data.TryGetElementData(element, out var elementData)) 344 { 345 if (elementData is T) 346 { 347 return (T)elementData; 348 } 349 350 throw new GraphPointerException($"Graph element data type mismatch. Found {elementData.GetType()}, expected {typeof(T)}.", this); 351 } 352 353 throw new GraphPointerException($"Missing graph element data for {element}.", this); 354 } 355 356 public static Func<IGraphRoot, IGraphDebugData> fetchRootDebugDataBinding { get; set; } 357 internal static Action<IGraphRoot> releaseDebugDataBinding; 358 359 public bool hasDebugData => _debugData != null; 360 361 public void EnsureDebugDataAvailable() 362 { 363 if (!hasDebugData) 364 { 365 throw new GraphPointerException($"Graph debug data is not available.", this); 366 } 367 } 368 369 protected IGraphDebugData _debugData 370 { 371 get => debugDataStack[debugDataStack.Count - 1]; 372 set => debugDataStack[debugDataStack.Count - 1] = value; 373 } 374 375 public IGraphDebugData debugData 376 { 377 get 378 { 379 EnsureDebugDataAvailable(); 380 return _debugData; 381 } 382 } 383 384 public T GetGraphDebugData<T>() where T : IGraphDebugData 385 { 386 var debugData = this.debugData; 387 388 if (debugData is T) 389 { 390 return (T)debugData; 391 } 392 393 throw new GraphPointerException($"Graph debug data type mismatch. Found {debugData.GetType()}, expected {typeof(T)}.", this); 394 } 395 396 public T GetElementDebugData<T>(IGraphElementWithDebugData element) 397 { 398 var elementDebugData = debugData.GetOrCreateElementData(element); 399 400 if (elementDebugData is T) 401 { 402 return (T)elementDebugData; 403 } 404 405 throw new GraphPointerException($"Graph element runtime debug data type mismatch. Found {elementDebugData.GetType()}, expected {typeof(T)}.", this); 406 } 407 408 #endregion 409 410 411 #region Traversal 412 413 protected bool TryEnterParentElement(Guid parentElementGuid, out string error, int? maxRecursionDepth = null) 414 { 415 if (!graph.elements.TryGetValue(parentElementGuid, out var element)) 416 { 417 error = "Trying to enter a graph parent element with a GUID that is not within the current graph."; 418 return false; 419 } 420 421 if (!(element is IGraphParentElement)) 422 { 423 error = "Provided element GUID does not point to a graph parent element."; 424 return false; 425 } 426 427 var parentElement = (IGraphParentElement)element; 428 429 return TryEnterParentElement(parentElement, out error, maxRecursionDepth); 430 } 431 432 protected bool TryEnterParentElement(IGraphParentElement parentElement, out string error, int? maxRecursionDepth = null, bool skipContainsCheck = false) 433 { 434 // The contains check is expensive because variant+merged collection checks 435 // If we already know for sure this error cannot happen, skipping it provides a significant optim 436 if (!skipContainsCheck && !graph.elements.Contains(parentElement)) 437 { 438 error = "Trying to enter a graph parent element that is not within the current graph."; 439 return false; 440 } 441 442 var childGraph = parentElement.childGraph; 443 444 if (childGraph == null) 445 { 446 error = "Trying to enter a graph parent element without a child graph."; 447 return false; 448 } 449 450 if (Recursion.safeMode) 451 { 452 var recursionDepth = 0; 453 var _maxRecursionDepth = maxRecursionDepth ?? Recursion.defaultMaxDepth; 454 455 foreach (var parentGraph in graphStack) 456 { 457 if (parentGraph == childGraph) 458 { 459 recursionDepth++; 460 } 461 } 462 463 if (recursionDepth > _maxRecursionDepth) 464 { 465 error = $"Max recursion depth of {_maxRecursionDepth} has been exceeded. Are you nesting a graph within itself?\nIf not, consider increasing '{nameof(Recursion)}.{nameof(Recursion.defaultMaxDepth)}'."; 466 return false; 467 } 468 } 469 470 EnterValidParentElement(parentElement); 471 error = null; 472 return true; 473 } 474 475 protected void EnterParentElement(IGraphParentElement parentElement) 476 { 477 if (!TryEnterParentElement(parentElement, out var error)) 478 { 479 throw new GraphPointerException(error, this); 480 } 481 } 482 483 protected void EnterParentElement(Guid parentElementGuid) 484 { 485 if (!TryEnterParentElement(parentElementGuid, out var error)) 486 { 487 throw new GraphPointerException(error, this); 488 } 489 } 490 491 private void EnterValidParentElement(IGraphParentElement parentElement) 492 { 493 var childGraph = parentElement.childGraph; 494 495 parentStack.Add(parentElement); 496 parentElementStack.Add(parentElement); 497 graphStack.Add(childGraph); 498 499 IGraphData childGraphData = null; 500 _data?.TryGetChildGraphData(parentElement, out childGraphData); 501 dataStack.Add(childGraphData); 502 503 var childGraphDebugData = _debugData?.GetOrCreateChildGraphData(parentElement); 504 debugDataStack.Add(childGraphDebugData); 505 } 506 507 protected void ExitParentElement() 508 { 509 if (!isChild) 510 { 511 throw new GraphPointerException("Trying to exit the root graph.", this); 512 } 513 514 parentStack.RemoveAt(parentStack.Count - 1); 515 parentElementStack.RemoveAt(parentElementStack.Count - 1); 516 graphStack.RemoveAt(graphStack.Count - 1); 517 dataStack.RemoveAt(dataStack.Count - 1); 518 debugDataStack.RemoveAt(debugDataStack.Count - 1); 519 } 520 521 #endregion 522 523 524 #region Validation 525 526 public bool isValid 527 { 528 get 529 { 530 try 531 { 532 if (rootObject == null) 533 { 534 // Root object has been destroyed 535 return false; 536 } 537 538 if (rootGraph != root.childGraph) 539 { 540 // Root graph has changed 541 return false; 542 } 543 544 if (serializedObject == null) 545 { 546 // Serialized object has been destroyed 547 return false; 548 } 549 550 for (var depth = 1; depth < this.depth; depth++) 551 { 552 var parentElement = parentElementStack[depth - 1]; 553 var parentGraph = graphStack[depth - 1]; 554 var childGraph = graphStack[depth]; 555 556 // Important to check by object and not by GUID here, 557 // because object stack integrity has to be guaranteed 558 // (GUID integrity is implied because they're immutable) 559 if (!parentGraph.elements.Contains(parentElement)) 560 { 561 // Parent graph no longer contains the parent element 562 return false; 563 } 564 565 if (parentElement.childGraph != childGraph) 566 { 567 // Child graph has changed 568 return false; 569 } 570 } 571 572 return true; 573 } 574 catch (Exception ex) 575 { 576 Debug.LogWarning("Failed to check graph pointer validity: \n" + ex); 577 return false; 578 } 579 } 580 } 581 582 public void EnsureValid() 583 { 584 if (!isValid) 585 { 586 throw new GraphPointerException("Graph pointer is invalid.", this); 587 } 588 } 589 590 #endregion 591 592 593 #region Equality 594 595 public bool InstanceEquals(GraphPointer other) 596 { 597 if (ReferenceEquals(this, other)) 598 { 599 return true; 600 } 601 602 if (!UnityObjectUtility.TrulyEqual(rootObject, other.rootObject)) 603 { 604 return false; 605 } 606 607 if (!DefinitionEquals(other)) 608 { 609 return false; 610 } 611 612 var depth = this.depth; // Micro optimization 613 614 for (int d = 0; d < depth; d++) 615 { 616 var data = dataStack[d]; 617 var otherData = other.dataStack[d]; 618 619 if (data != otherData) 620 { 621 return false; 622 } 623 } 624 625 return true; 626 } 627 628 public bool DefinitionEquals(GraphPointer other) 629 { 630 if (other == null) 631 { 632 return false; 633 } 634 635 if (rootGraph != other.rootGraph) 636 { 637 return false; 638 } 639 640 var depth = this.depth; // Micro optimization 641 642 if (depth != other.depth) 643 { 644 return false; 645 } 646 647 for (int d = 1; d < depth; d++) 648 { 649 var parentElement = parentElementStack[d - 1]; 650 var otherParentElement = other.parentElementStack[d - 1]; 651 652 if (parentElement != otherParentElement) 653 { 654 return false; 655 } 656 } 657 658 return true; 659 } 660 661 public int ComputeHashCode() 662 { 663 var hashCode = 17; 664 665 hashCode = hashCode * 23 + (rootObject.AsUnityNull()?.GetHashCode() ?? 0); 666 667 hashCode = hashCode * 23 + (rootGraph?.GetHashCode() ?? 0); 668 669 var depth = this.depth; // Micro optimization 670 671 for (int d = 1; d < depth; d++) 672 { 673 var parentElementGuid = parentElementStack[d - 1].guid; 674 675 hashCode = hashCode * 23 + parentElementGuid.GetHashCode(); 676 } 677 678 return hashCode; 679 } 680 681 #endregion 682 683 684 #region Breadcrumbs 685 686 public override string ToString() 687 { 688 var sb = new StringBuilder(); 689 690 sb.Append("[ "); 691 692 sb.Append(rootObject.ToSafeString()); 693 694 for (var depth = 1; depth < this.depth; depth++) 695 { 696 sb.Append(" > "); 697 698 var parentElementIndex = depth - 1; 699 700 if (parentElementIndex >= parentElementStack.Count) 701 { 702 sb.Append("?"); 703 break; 704 } 705 706 var parentElement = parentElementStack[parentElementIndex]; 707 708 sb.Append(parentElement); 709 } 710 711 sb.Append(" ]"); 712 713 return sb.ToString(); 714 } 715 716 #endregion 717 } 718}