A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using UnityEngine;
5using UnityObject = UnityEngine.Object;
6
7namespace Unity.VisualScripting
8{
9 public sealed class GraphReference : GraphPointer
10 {
11 static GraphReference()
12 {
13 ReferenceCollector.onSceneUnloaded += FreeInvalidInterns;
14 }
15
16 #region Lifecycle
17
18 private GraphReference() { }
19
20 public static GraphReference New(IGraphRoot root, bool ensureValid)
21 {
22 if (!ensureValid && !IsValidRoot(root))
23 {
24 return null;
25 }
26
27 var reference = new GraphReference();
28 reference.Initialize(root);
29 reference.Hash();
30 return reference;
31 }
32
33 public static GraphReference New(IGraphRoot root, IEnumerable<IGraphParentElement> parentElements, bool ensureValid)
34 {
35 if (!ensureValid && !IsValidRoot(root))
36 {
37 return null;
38 }
39
40 var reference = new GraphReference();
41 reference.Initialize(root, parentElements, ensureValid);
42 reference.Hash();
43 return reference;
44 }
45
46 public static GraphReference New(UnityObject rootObject, IEnumerable<Guid> parentElementGuids, bool ensureValid)
47 {
48 if (!ensureValid && !IsValidRoot(rootObject))
49 {
50 return null;
51 }
52
53 var reference = new GraphReference();
54 reference.Initialize(rootObject, parentElementGuids, ensureValid);
55 reference.Hash();
56 return reference;
57 }
58
59 private static GraphReference New(GraphPointer model)
60 {
61 var reference = new GraphReference();
62 reference.CopyFrom(model);
63 return reference;
64 }
65
66 public override void CopyFrom(GraphPointer other)
67 {
68 base.CopyFrom(other);
69
70 if (other is GraphReference reference)
71 {
72 hashCode = reference.hashCode;
73 }
74 else
75 {
76 Hash();
77 }
78 }
79
80 public GraphReference Clone()
81 {
82 return New(this);
83 }
84
85 #endregion
86
87
88 #region Conversion
89
90 public override GraphReference AsReference()
91 {
92 return this;
93 }
94
95 public GraphStack ToStackPooled()
96 {
97 return GraphStack.New(this);
98 }
99
100 internal void Release()
101 {
102 releaseDebugDataBinding?.Invoke(root);
103 }
104
105 #endregion
106
107
108 #region Instantiation
109
110 public void CreateGraphData()
111 {
112 if (_data != null)
113 {
114 throw new GraphPointerException("Graph data already exists.", this);
115 }
116
117 if (isRoot)
118 {
119 if (machine != null)
120 {
121 // Debug.Log($"Creating root graph data for {this}");
122
123 _data = machine.graphData = graph.CreateData();
124 }
125 else
126 {
127 throw new GraphPointerException("Root graph data can only be created on machines.", this);
128 }
129 }
130 else
131 {
132 if (_parentData == null)
133 {
134 throw new GraphPointerException("Child graph data can only be created from parent graph data.", this);
135 }
136
137 _data = _parentData.CreateChildGraphData(parentElement);
138 }
139 }
140
141 public void FreeGraphData()
142 {
143 if (_data == null)
144 {
145 throw new GraphPointerException("Graph data does not exist.", this);
146 }
147
148 if (isRoot)
149 {
150 if (machine != null)
151 {
152 // Debug.Log($"Freeing root graph data for {this}");
153
154 _data = machine.graphData = null;
155 }
156 else
157 {
158 throw new GraphPointerException("Root graph data can only be freed on machines.", this);
159 }
160 }
161 else
162 {
163 if (_parentData == null)
164 {
165 throw new GraphPointerException("Child graph data can only be freed from parent graph data.", this);
166 }
167
168 _parentData.FreeChildGraphData(parentElement);
169 _data = null;
170 }
171 }
172
173 #endregion
174
175
176 #region Equality
177
178 [DoNotSerialize]
179 private int hashCode;
180
181 public override bool Equals(object obj)
182 {
183 if (!(obj is GraphReference other))
184 {
185 return false;
186 }
187
188 return InstanceEquals(other);
189 }
190
191 private void Hash()
192 {
193 hashCode = ComputeHashCode();
194 }
195
196 public override int GetHashCode()
197 {
198 return hashCode;
199 }
200
201 public static bool operator ==(GraphReference x, GraphReference y)
202 {
203 if (ReferenceEquals(x, y))
204 {
205 return true;
206 }
207
208 if (ReferenceEquals(x, null) || ReferenceEquals(y, null))
209 {
210 return false;
211 }
212
213 return x.Equals(y);
214 }
215
216 public static bool operator !=(GraphReference x, GraphReference y)
217 {
218 return !(x == y);
219 }
220
221 #endregion
222
223
224 #region Traversal
225
226 public GraphReference ParentReference(bool ensureValid)
227 {
228 if (isRoot)
229 {
230 if (ensureValid)
231 {
232 throw new GraphPointerException("Trying to get parent graph reference of a root.", this);
233 }
234 else
235 {
236 return null;
237 }
238 }
239
240 var pointer = Clone();
241 pointer.ExitParentElement();
242 pointer.Hash();
243 return pointer;
244 }
245
246 public GraphReference ChildReference(IGraphParentElement parentElement, bool ensureValid, int? maxRecursionDepth = null)
247 {
248 var pointer = Clone();
249
250 if (!pointer.TryEnterParentElement(parentElement, out var error, maxRecursionDepth))
251 {
252 if (ensureValid)
253 {
254 throw new GraphPointerException(error, this);
255 }
256 else
257 {
258 return null;
259 }
260 }
261
262 pointer.Hash();
263 return pointer;
264 }
265
266 #endregion
267
268
269 #region Validation
270
271 public GraphReference Revalidate(bool ensureValid)
272 {
273 try
274 {
275 // Important to recreate by GUIDs to avoid serialization ghosting
276 return New(rootObject, parentElementGuids, ensureValid);
277 }
278 catch (Exception ex)
279 {
280 if (ensureValid)
281 {
282 throw;
283 }
284
285 Debug.LogWarning("Failed to revalidate graph pointer: \n" + ex);
286 return null;
287 }
288 }
289
290 #endregion
291
292
293 #region Navigation
294
295 public IEnumerable<GraphReference> GetBreadcrumbs()
296 {
297 for (int depth = 0; depth < this.depth; depth++)
298 {
299 yield return New(root, parentElementStack.Take(depth), true);
300 }
301 }
302
303 #endregion
304
305
306 #region Interning
307
308 private static readonly Dictionary<int, List<GraphReference>> internPool = new Dictionary<int, List<GraphReference>>();
309
310 public static GraphReference Intern(GraphPointer pointer)
311 {
312 var hash = pointer.ComputeHashCode();
313
314 if (internPool.TryGetValue(hash, out var interns))
315 {
316 foreach (var intern in interns)
317 {
318 if (intern.InstanceEquals(pointer))
319 {
320 return intern;
321 }
322 }
323
324 var reference = New(pointer);
325 interns.Add(reference);
326 return reference;
327 }
328 else
329 {
330 var reference = New(pointer);
331 internPool.Add(reference.hashCode, new List<GraphReference>() { reference });
332 return reference;
333 }
334 }
335
336 internal static void ClearIntern(GraphPointer pointer)
337 {
338 var hash = pointer.ComputeHashCode();
339
340 if (!internPool.TryGetValue(hash, out var interns)) return;
341
342 for (var i = interns.Count - 1; i >= 0; i--)
343 {
344 if (interns[i].InstanceEquals(pointer))
345 {
346 interns.RemoveAt(i);
347 break;
348 }
349 }
350
351 if (interns.Count == 0)
352 {
353 internPool.Remove(hash);
354 }
355 }
356
357 public static void FreeInvalidInterns()
358 {
359 var invalidHashes = ListPool<int>.New();
360
361 foreach (var internsByHash in internPool)
362 {
363 var hash = internsByHash.Key;
364 var interns = internsByHash.Value;
365
366 var invalidInterns = ListPool<GraphReference>.New();
367
368 foreach (var intern in interns)
369 {
370 if (!intern.isValid)
371 {
372 invalidInterns.Add(intern);
373 }
374 }
375
376 foreach (var intern in invalidInterns)
377 {
378 interns.Remove(intern);
379 }
380
381 if (interns.Count == 0)
382 {
383 invalidHashes.Add(hash);
384 }
385
386 invalidInterns.Free();
387 }
388
389 foreach (var hash in invalidHashes)
390 {
391 internPool.Remove(hash);
392 }
393
394 invalidHashes.Free();
395 }
396
397 #endregion
398 }
399}