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}