using System; using System.Collections.Generic; using System.Linq; #if UNITY_EDITOR using UnityEditor.SceneManagement; #endif using UnityEngine; using UnityEngine.SceneManagement; using UnityObject = UnityEngine.Object; namespace Unity.VisualScripting { /// /// Does not support objects hidden with hide flags. /// public static class SceneSingleton where T : MonoBehaviour, ISingleton { static SceneSingleton() { instances = new Dictionary(); attribute = typeof(T).GetAttribute(); if (attribute == null) { throw new InvalidImplementationException($"Missing singleton attribute for '{typeof(T)}'."); } } private static Dictionary instances; private static readonly SingletonAttribute attribute; private static bool persistent => attribute.Persistent; private static bool automatic => attribute.Automatic; private static string name => attribute.Name; private static HideFlags hideFlags => attribute.HideFlags; private static void EnsureSceneValid(Scene scene) { if (!scene.IsValid()) { throw new InvalidOperationException($"Scene '{scene.name}' is invalid and cannot be used in singleton operations."); } } public static bool InstantiatedIn(Scene scene) { EnsureSceneValid(scene); if (Application.isPlaying) { return instances.ContainsKey(scene); } else { return FindInstances(scene).Length == 1; } } public static T InstanceIn(Scene scene) { EnsureSceneValid(scene); if (Application.isPlaying) { if (instances.ContainsKey(scene)) { return instances[scene]; } else { return FindOrCreateInstance(scene); } } else { return FindOrCreateInstance(scene); } } private static T[] FindObjectsOfType() { #if UNITY_2023_1_OR_NEWER return UnityObject.FindObjectsByType(FindObjectsSortMode.None); #else return UnityObject.FindObjectsOfType(); #endif } private static T[] FindInstances(Scene scene) { EnsureSceneValid(scene); // Fails here on hidden hide flags return FindObjectsOfType().Where(o => o.gameObject.scene == scene).ToArray(); } private static T FindOrCreateInstance(Scene scene) { #if UNITY_EDITOR var prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); var targetScene = prefabStage == null ? scene : SceneManager.GetActiveScene(); #else var targetScene = scene; #endif EnsureSceneValid(targetScene); var instances = FindInstances(targetScene); if (instances.Length == 1) { return instances[0]; } else if (instances.Length == 0) { if (automatic) { // Because DontDestroyOnLoad moves objects to another scene internally, // the scene singleton system does not support persistence (which wouldn't // make sense logically either!) if (persistent) { throw new UnityException("Scene singletons cannot be persistent."); } // Create the parent game object with the proper hide flags var singleton = new GameObject(name ?? typeof(T).Name); singleton.hideFlags = hideFlags; // Immediately move it to the proper scene so that Register can determine dictionary key SceneManager.MoveGameObjectToScene(singleton, targetScene); // Instantiate the component, letting Awake register the instance if we're in play mode var instance = singleton.AddComponent(); instance.hideFlags = hideFlags; return instance; } else { throw new UnityException($"Missing '{typeof(T)}' singleton in scene '{scene.name}'."); } } else // if (instances.Length > 1) { throw new UnityException($"More than one '{typeof(T)}' singleton in scene '{scene.name}'."); } } public static void Awake(T instance) { Ensure.That(nameof(instance)).IsNotNull(instance); var scene = instance.gameObject.scene; EnsureSceneValid(scene); if (instances.ContainsKey(scene)) { throw new UnityException($"More than one '{typeof(T)}' singleton in scene '{scene.name}'."); } instances.Add(scene, instance); } public static void OnDestroy(T instance) { Ensure.That(nameof(instance)).IsNotNull(instance); var scene = instance.gameObject.scene; // If the scene is invalid, it has probably been unloaded already // (for example the DontDestroyOnLoad pseudo-scene), so we can't // access it. However, we'll need to check in the dictionary by // value to remove the entry anyway. if (!scene.IsValid()) { foreach (var kvp in instances) { if (kvp.Value == instance) { instances.Remove(kvp.Key); break; } } return; } if (instances.ContainsKey(scene)) { var sceneInstance = instances[scene]; if (sceneInstance == instance) { instances.Remove(scene); } else { throw new UnityException($"Trying to destroy invalid instance of '{typeof(T)}' singleton in scene '{scene.name}'."); } } else { throw new UnityException($"Trying to destroy invalid instance of '{typeof(T)}' singleton in scene '{scene.name}'."); } } } }