A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using UnityEngine;
4
5namespace Unity.VisualScripting
6{
7 public class Recursion<T> : IPoolable, IDisposable
8 {
9 protected Recursion()
10 {
11 traversedOrder = new Stack<T>();
12 traversedCount = new Dictionary<T, int>();
13 }
14
15 private readonly Stack<T> traversedOrder;
16
17 private readonly Dictionary<T, int> traversedCount;
18
19 private bool disposed;
20
21 protected int maxDepth;
22
23 public void Enter(T o)
24 {
25 if (!TryEnter(o))
26 {
27 throw new StackOverflowException($"Max recursion depth of {maxDepth} has been exceeded. Consider increasing '{nameof(Recursion)}.{nameof(Recursion.defaultMaxDepth)}'.");
28 }
29 }
30
31 public bool TryEnter(T o)
32 {
33 if (disposed)
34 {
35 throw new ObjectDisposedException(ToString());
36 }
37
38 // Disable null check because it boxes o
39 // Ensure.That(nameof(o)).IsNotNull(o);
40
41 if (traversedCount.TryGetValue(o, out var depth))
42 {
43 if (depth < maxDepth)
44 {
45 traversedOrder.Push(o);
46 traversedCount[o]++;
47 return true;
48 }
49 else
50 {
51 return false;
52 }
53 }
54 else
55 {
56 traversedOrder.Push(o);
57 traversedCount.Add(o, 1);
58 return true;
59 }
60 }
61
62 public void Exit(T o)
63 {
64 if (traversedOrder.Count == 0)
65 {
66 throw new InvalidOperationException("Trying to exit an empty recursion stack.");
67 }
68
69 var current = traversedOrder.Peek();
70
71 if (!EqualityComparer<T>.Default.Equals(o, current))
72 {
73 throw new InvalidOperationException($"Exiting recursion stack in a non-consecutive order:\nProvided: {o} / Expected: {current}");
74 }
75
76 traversedOrder.Pop();
77
78 var newDepth = traversedCount[current]--;
79
80 if (newDepth == 0)
81 {
82 traversedCount.Remove(current);
83 }
84 }
85
86 public void Dispose()
87 {
88 if (disposed)
89 {
90 throw new ObjectDisposedException(ToString());
91 }
92
93 Free();
94 }
95
96 protected virtual void Free()
97 {
98 GenericPool<Recursion<T>>.Free(this);
99 }
100
101 void IPoolable.New()
102 {
103 disposed = false;
104 }
105
106 void IPoolable.Free()
107 {
108 disposed = true;
109 traversedCount.Clear();
110 traversedOrder.Clear();
111 }
112
113 public static Recursion<T> New()
114 {
115 return New(Recursion.defaultMaxDepth);
116 }
117
118 public static Recursion<T> New(int maxDepth)
119 {
120 if (!Recursion.safeMode)
121 {
122 return null;
123 }
124
125 if (maxDepth < 1)
126 {
127 throw new ArgumentException("Max recursion depth must be at least one.", nameof(maxDepth));
128 }
129
130 var recursion = GenericPool<Recursion<T>>.New(() => new Recursion<T>());
131
132 recursion.maxDepth = maxDepth;
133
134 return recursion;
135 }
136 }
137
138 public sealed class Recursion : Recursion<object>
139 {
140 private Recursion() : base() { }
141
142 public static int defaultMaxDepth { get; set; } = 100;
143
144 public static bool safeMode { get; set; }
145
146 internal static void OnRuntimeMethodLoad()
147 {
148 safeMode = Application.isEditor || Debug.isDebugBuild;
149 }
150
151 protected override void Free()
152 {
153 GenericPool<Recursion>.Free(this);
154 }
155
156 public new static Recursion New()
157 {
158 return New(defaultMaxDepth);
159 }
160
161 public new static Recursion New(int maxDepth)
162 {
163 if (!safeMode)
164 {
165 return null;
166 }
167
168 if (maxDepth < 1)
169 {
170 throw new ArgumentException("Max recursion depth must be at least one.", nameof(maxDepth));
171 }
172
173 var recursion = GenericPool<Recursion>.New(() => new Recursion());
174
175 recursion.maxDepth = maxDepth;
176
177 return recursion;
178 }
179 }
180}