using System.Collections.Generic;
using UnityEngine;
using UnityObject = UnityEngine.Object;
namespace Unity.VisualScripting
{
///
/// Does not support objects hidden with hide flags.
///
public static class Singleton where T : MonoBehaviour, ISingleton
{
static Singleton()
{
awoken = new HashSet();
attribute = typeof(T).GetAttribute();
if (attribute == null)
{
throw new InvalidImplementationException($"Missing singleton attribute for '{typeof(T)}'.");
}
}
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 readonly object _lock = new object();
private static readonly HashSet awoken;
private static T _instance;
public static bool instantiated
{
get
{
lock (_lock)
{
if (Application.isPlaying)
{
return _instance != null;
}
else
{
return FindInstances().Length == 1;
}
}
}
}
public static T instance
{
get
{
lock (_lock)
{
if (Application.isPlaying)
{
if (_instance == null)
{
Instantiate();
}
return _instance;
}
else
{
return Instantiate();
}
}
}
}
private static T[] FindObjectsOfType()
{
#if UNITY_2023_1_OR_NEWER
return UnityObject.FindObjectsByType(FindObjectsSortMode.None);
#else
return UnityObject.FindObjectsOfType();
#endif
}
private static T[] FindInstances()
{
// Fails here on hidden hide flags
return FindObjectsOfType();
}
public static T Instantiate()
{
lock (_lock)
{
var instances = FindInstances();
if (instances.Length == 1)
{
_instance = instances[0];
}
else if (instances.Length == 0)
{
if (automatic)
{
// Create the parent game object with the proper hide flags
var singleton = new GameObject(name ?? typeof(T).Name);
singleton.hideFlags = hideFlags;
// Instantiate the component, letting Awake assign the real instance variable
var _instance = singleton.AddComponent();
_instance.hideFlags = hideFlags;
// Sometimes in the editor, for example when creating a new scene,
// AddComponent seems to call Awake add a later frame, making this call
// fail for exactly one frame. We'll force-awake it if need be.
Awake(_instance);
// Make the singleton persistent if need be
if (persistent && Application.isPlaying)
{
UnityObject.DontDestroyOnLoad(singleton);
}
}
else
{
throw new UnityException($"Missing '{typeof(T)}' singleton in the scene.");
}
}
else if (instances.Length > 1)
{
throw new UnityException($"More than one '{typeof(T)}' singleton in the scene.");
}
return _instance;
}
}
public static void Awake(T instance)
{
Ensure.That(nameof(instance)).IsNotNull(instance);
if (awoken.Contains(instance))
{
return;
}
if (_instance != null)
{
throw new UnityException($"More than one '{typeof(T)}' singleton in the scene.");
}
_instance = instance;
awoken.Add(instance);
}
public static void OnDestroy(T instance)
{
Ensure.That(nameof(instance)).IsNotNull(instance);
if (_instance == instance)
{
_instance = null;
}
else
{
throw new UnityException($"Trying to destroy invalid instance of '{typeof(T)}' singleton.");
}
}
}
}