A game about forced loneliness, made by TACStudios
1using System; 2using System.Collections.Generic; 3using System.Linq; 4#if UNITY_EDITOR 5using UnityEditor.SceneManagement; 6#endif 7using UnityEngine; 8using UnityEngine.SceneManagement; 9using UnityObject = UnityEngine.Object; 10 11namespace Unity.VisualScripting 12{ 13 /// <remarks> 14 /// Does not support objects hidden with hide flags. 15 /// </remarks> 16 public static class SceneSingleton<T> where T : MonoBehaviour, ISingleton 17 { 18 static SceneSingleton() 19 { 20 instances = new Dictionary<Scene, T>(); 21 22 attribute = typeof(T).GetAttribute<SingletonAttribute>(); 23 24 if (attribute == null) 25 { 26 throw new InvalidImplementationException($"Missing singleton attribute for '{typeof(T)}'."); 27 } 28 } 29 30 private static Dictionary<Scene, T> instances; 31 32 private static readonly SingletonAttribute attribute; 33 private static bool persistent => attribute.Persistent; 34 private static bool automatic => attribute.Automatic; 35 private static string name => attribute.Name; 36 private static HideFlags hideFlags => attribute.HideFlags; 37 38 private static void EnsureSceneValid(Scene scene) 39 { 40 if (!scene.IsValid()) 41 { 42 throw new InvalidOperationException($"Scene '{scene.name}' is invalid and cannot be used in singleton operations."); 43 } 44 } 45 46 public static bool InstantiatedIn(Scene scene) 47 { 48 EnsureSceneValid(scene); 49 50 if (Application.isPlaying) 51 { 52 return instances.ContainsKey(scene); 53 } 54 else 55 { 56 return FindInstances(scene).Length == 1; 57 } 58 } 59 60 public static T InstanceIn(Scene scene) 61 { 62 EnsureSceneValid(scene); 63 64 if (Application.isPlaying) 65 { 66 if (instances.ContainsKey(scene)) 67 { 68 return instances[scene]; 69 } 70 else 71 { 72 return FindOrCreateInstance(scene); 73 } 74 } 75 else 76 { 77 return FindOrCreateInstance(scene); 78 } 79 } 80 81 private static T[] FindObjectsOfType() 82 { 83#if UNITY_2023_1_OR_NEWER 84 return UnityObject.FindObjectsByType<T>(FindObjectsSortMode.None); 85#else 86 return UnityObject.FindObjectsOfType<T>(); 87#endif 88 } 89 90 private static T[] FindInstances(Scene scene) 91 { 92 EnsureSceneValid(scene); 93 94 // Fails here on hidden hide flags 95 return FindObjectsOfType().Where(o => o.gameObject.scene == scene).ToArray(); 96 } 97 98 private static T FindOrCreateInstance(Scene scene) 99 { 100#if UNITY_EDITOR 101 var prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); 102 103 var targetScene = prefabStage == null ? scene : SceneManager.GetActiveScene(); 104#else 105 var targetScene = scene; 106#endif 107 108 EnsureSceneValid(targetScene); 109 110 var instances = FindInstances(targetScene); 111 112 if (instances.Length == 1) 113 { 114 return instances[0]; 115 } 116 else if (instances.Length == 0) 117 { 118 if (automatic) 119 { 120 // Because DontDestroyOnLoad moves objects to another scene internally, 121 // the scene singleton system does not support persistence (which wouldn't 122 // make sense logically either!) 123 if (persistent) 124 { 125 throw new UnityException("Scene singletons cannot be persistent."); 126 } 127 128 // Create the parent game object with the proper hide flags 129 var singleton = new GameObject(name ?? typeof(T).Name); 130 singleton.hideFlags = hideFlags; 131 132 // Immediately move it to the proper scene so that Register can determine dictionary key 133 SceneManager.MoveGameObjectToScene(singleton, targetScene); 134 135 // Instantiate the component, letting Awake register the instance if we're in play mode 136 var instance = singleton.AddComponent<T>(); 137 instance.hideFlags = hideFlags; 138 139 return instance; 140 } 141 else 142 { 143 throw new UnityException($"Missing '{typeof(T)}' singleton in scene '{scene.name}'."); 144 } 145 } 146 else // if (instances.Length > 1) 147 { 148 throw new UnityException($"More than one '{typeof(T)}' singleton in scene '{scene.name}'."); 149 } 150 } 151 152 public static void Awake(T instance) 153 { 154 Ensure.That(nameof(instance)).IsNotNull(instance); 155 156 var scene = instance.gameObject.scene; 157 158 EnsureSceneValid(scene); 159 160 if (instances.ContainsKey(scene)) 161 { 162 throw new UnityException($"More than one '{typeof(T)}' singleton in scene '{scene.name}'."); 163 } 164 165 instances.Add(scene, instance); 166 } 167 168 public static void OnDestroy(T instance) 169 { 170 Ensure.That(nameof(instance)).IsNotNull(instance); 171 172 var scene = instance.gameObject.scene; 173 174 // If the scene is invalid, it has probably been unloaded already 175 // (for example the DontDestroyOnLoad pseudo-scene), so we can't 176 // access it. However, we'll need to check in the dictionary by 177 // value to remove the entry anyway. 178 if (!scene.IsValid()) 179 { 180 foreach (var kvp in instances) 181 { 182 if (kvp.Value == instance) 183 { 184 instances.Remove(kvp.Key); 185 186 break; 187 } 188 } 189 190 return; 191 } 192 193 if (instances.ContainsKey(scene)) 194 { 195 var sceneInstance = instances[scene]; 196 197 if (sceneInstance == instance) 198 { 199 instances.Remove(scene); 200 } 201 else 202 { 203 throw new UnityException($"Trying to destroy invalid instance of '{typeof(T)}' singleton in scene '{scene.name}'."); 204 } 205 } 206 else 207 { 208 throw new UnityException($"Trying to destroy invalid instance of '{typeof(T)}' singleton in scene '{scene.name}'."); 209 } 210 } 211 } 212}