A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using UnityEngine.SceneManagement;
5using UnityObject = UnityEngine.Object;
6
7namespace Unity.VisualScripting
8{
9 public static class UnityObjectUtility
10 {
11 public static bool IsDestroyed(this UnityObject target)
12 {
13 // Checks whether a Unity object is not actually a null reference,
14 // but a rather destroyed native instance.
15
16 return !ReferenceEquals(target, null) && target == null;
17 }
18
19 public static bool IsUnityNull(this object obj)
20 {
21 // Checks whether an object is null or Unity pseudo-null
22 // without having to cast to UnityEngine.Object manually
23
24 return obj == null || ((obj is UnityObject) && ((UnityObject)obj) == null);
25 }
26
27 public static string ToSafeString(this UnityObject uo)
28 {
29 if (ReferenceEquals(uo, null))
30 {
31 return "(null)";
32 }
33
34 if (!UnityThread.allowsAPI)
35 {
36 return uo.GetType().Name;
37 }
38
39 if (uo == null)
40 {
41 return "(Destroyed)";
42 }
43
44 try
45 {
46 return uo.name;
47 }
48 catch (Exception ex)
49 {
50 return $"({ex.GetType().Name} in ToString: {ex.Message})";
51 }
52 }
53
54 public static string ToSafeString(this object obj)
55 {
56 if (obj == null)
57 {
58 return "(null)";
59 }
60
61 if (obj is UnityObject uo)
62 {
63 return uo.ToSafeString();
64 }
65
66 try
67 {
68 return obj.ToString();
69 }
70 catch (Exception ex)
71 {
72 return $"({ex.GetType().Name} in ToString: {ex.Message})";
73 }
74 }
75
76 public static T AsUnityNull<T>(this T obj) where T : UnityObject
77 {
78 // Converts a Unity pseudo-null to a real null, allowing for coalesce operators.
79 // e.g.: destroyedObject.AsUnityNull() ?? otherObject
80
81 if (obj == null)
82 {
83 return null;
84 }
85
86 return obj;
87 }
88
89 public static bool TrulyEqual(UnityObject a, UnityObject b)
90 {
91 // This method is required when checking two references
92 // against one another, where one of them might have been destroyed.
93 // It is not required when checking against null.
94
95 // This is because Unity does not compare alive state
96 // in the CompareBaseObjects method unless one of the two
97 // operators is actually the null literal.
98
99 // From the source:
100 /*
101 bool lhsIsNull = (object) lhs == null;
102 bool rhsIsNull = (object) rhs == null;
103 if (rhsIsNull && lhsIsNull)
104 return true;
105 if (rhsIsNull)
106 return !Object.IsNativeObjectAlive(lhs);
107 if (lhsIsNull)
108 return !Object.IsNativeObjectAlive(rhs);
109 return lhs.m_InstanceID == rhs.m_InstanceID;
110 */
111
112 // As we can see, Object.IsNativeObjectAlive is not compared
113 // across the two objects unless one of the operands is actually null.
114 // But it can happen, for example when exiting play mode.
115 // If we stored a static reference to a scene object that was destroyed,
116 // the reference won't get cleared because assembly reloads don't happen
117 // when exiting playmode. But the instance ID of the object will stay
118 // the same, because it only gets reserialized. So if we compare our
119 // stale reference that was destroyed to a new reference to the object,
120 // it will return true, even though one reference is alive and the other isn't.
121
122 if (a != b)
123 {
124 return false;
125 }
126
127 if ((a == null) != (b == null))
128 {
129 return false;
130 }
131
132 return true;
133 }
134
135 public static IEnumerable<T> NotUnityNull<T>(this IEnumerable<T> enumerable) where T : UnityObject
136 {
137 return enumerable.Where(i => i != null);
138 }
139
140 public static IEnumerable<T> FindObjectsOfTypeIncludingInactive<T>()
141 {
142 for (int i = 0; i < SceneManager.sceneCount; i++)
143 {
144 var scene = SceneManager.GetSceneAt(i);
145
146 if (scene.isLoaded)
147 {
148 foreach (var rootGameObject in scene.GetRootGameObjects())
149 {
150 foreach (var result in rootGameObject.GetComponentsInChildren<T>(true))
151 {
152 yield return result;
153 }
154 }
155 }
156 }
157 }
158 }
159}