- Home /
Destroying and recreating a singleton.
Hi,
I'm working on the save/load system for my game. I have a number of singleton manager classes based on the implementation here, for the sake of simplicity and keeping things clean I would to destroy these managers and re-instantiate them from their prefabs. I am not particularly concerned with the persistence of the managers, only that there is always only one instance.
For example, in the case of my ObjectLocationManager I am doing the following:
ObjectLocationManager objectLocationManager = FindObjectOfType<ObjectLocationManager>();
Destroy(objectLocationManager.gameObject);
GameObject objectLocationManager = Instantiate(Resources.Load<GameObject>("ObjectLocationManager"));
However, it seems by the nature of this singleton implementation that they cannot be destroyed this easily as when another class attempts to access the new instance it gives the warning:
"[Singleton] Instance '"+ typeof(T) + "' already destroyed on application quit." + " Won't create again returning null."
Or if I disable the applicationIsQuitting sections of the code then:
"[Singleton] Using instance already created: " + _instance.gameObject.name
At the risk of displaying my ignorance, I believe this is because the instance is never really destroyed when the gameobject is destroyed as _instance is a static variable.
My question therefore, is it possible to destroy the singleton gameobject and the static instance with this singleton implementation? Or if that is not possible, what kind of singleton implementation could I use for a non-static singleton?
Thank you for any help.
Answer by Bunny83 · Sep 18, 2017 at 12:59 PM
Actually the implementation on the wiki is quite nonsense for several reasons:
locking the instance getter it completely pointless because you can't / shouldn't use it from any other thread anyways. Methods like "FindObjectOfType", "new GameObject", "AddComponent" and even setting the name of the gameobject can't be done on any other thread than the main thread. So locking in a monobehaviour based singleton is just a waste of performance.
Trying to fight multiple instances it actually pointless, especially if it's only done when the static instance hasn't been set yet.
It doesn't use DontDestroyOnLoad for singletons which are located in the scene. It only uses it when it creates an instance on the fly which is an inconsistent behaviour.
Finally implementing OnDestroy not as virtual makes it either impossible for the actual singleton to implement OnDestroy if it needs it, or it will break the functionality that the singleton might need.
It should be clear that there's no way to prevent multiple instances for MonoBehaviour based singletons as you can always use AddComponent with the singleton class to create a new instance. So you shouldn't care about that too much.
There are in general 3 different kinds of MonoBehaviour based singletons:
Just a usual singleton. It would create itself when it wasn't created yet. In this case you could actually use a true singleton (not derived from monobehaviour and with a private constructor). However if you want to be able to inspect the instance for debugging purposes it might come in handy.
A singleton that you create manually and that you save into the scene. This are usually manager on which you want to setup important links to other objects in the scene.
A singleton that is based on a prefab or ScriptableObject asset in a resources folder which is mainly used to hold references to other assets in the project.
Those three kinds would have quite different implementations.
The classic singleton
First the easiest threadsafe "non monobehaviour" singleton would look like this: public sealed class MySingleton
{
public static readonly MySingleton Instance = new MySingleton();
private MySingleton() { }
// your singleton stuff here
}
A static field initializer is threadsafe. The instance will be lazily created when you first access the Instance variable. The private constructor prevents the creation of instances from outside. The class is sealed so no one can derive a class from this one. This is the perfect singleton for pretty much all cases. Of course in this case we can't really define a reusable base class. Though it's not necessary for those few lines.
The scene based MonoBehaviour singleton
public class SceneSingleton<T> : MonoBehaviour where T : SceneSingleton<T>
{
private static T m_Instance = null;
public static T Instance
{
get
{
if (m_Instance == null)
{
m_Instance = FindObjectOfType<T>();
// fallback, might not be necessary.
if (m_Instance == null)
m_Instance = new GameObject(typeof(T).Name).AddComponent<T>();
DontDestroyOnLoad(m_Instance.gameObject);
}
return m_Instance;
}
}
}
This is all you will ever need if you want a manager that needs to be located and initialized in the scene. Adding an Awake method just complicates the actual implementation. FindObjectOfType isn't that bad when it's called once.
If the manager is required to be initialized in the scene, the fallback could be replaced with a Debug.LogError since lazy initialization won't help much as the instance won't be initialized (no fields assigned).
Note that you should not place such a singleton inside a scene which you intend to load multiple times. If you need this feature you should not use DontDestroyOnLoad. Just let the singleton be destroyed when the old scene get wiped out and let it automatically re-initialize when the new scene is loaded.
The prefab based singleton
public class PrefabSingleton<T> : MonoBehaviour where T : PrefabSingleton<T>
{
private const string m_AssetPath = "Singletons/";
private static T m_Instance = null;
public static T Instance
{
get
{
if (m_Instance == null)
{
m_Instance = FindObjectOfType<T>();
if (m_Instance == null)
{
var prefab = Resources.Load<T>(m_AssetPath + typeof(T).Name);
if (prefab == null)
Debug.LogError("singleton prefab missing: " + m_AssetPath + typeof(T).Name);
else
m_Instance = Instantiate(prefab);
}
DontDestroyOnLoad(m_Instance.gameObject);
}
return m_Instance;
}
}
}
This basically looks almost the same as the one above, but instead of creating an instance on the fly as fallback we load them from the resources folder. It will use the constant "m_AssetPath" inside the resources folder and expects the prefab to be named exactly the same as the actual singleton class name. Of course we could again implement a lazy inizialization fallback if the prefab isn't found, but since we explicitly want a prefab based singleton it's better to show an error.
The ScriptableObject singleton
public class SOSingleton<T> : ScriptableObject where T : SOSingleton<T>
{
private const string m_AssetPath = "Singletons/";
private static T m_Instance = null;
public static T Instance
{
get
{
if (m_Instance == null)
{
m_Instance = FindObjectOfType<T>();
if (m_Instance == null)
{
var m_Instance = Resources.Load<T>(m_AssetPath + typeof(T).Name);
if (m_Instance == null)
Debug.LogError("singleton asset missing: " + m_AssetPath + typeof(T).Name);
}
}
return m_Instance;
}
}
}
This is again very simiar to the one above but uses a ScriptableObject asset as base. There isn't any good reason to derive a singleton from ScriptableObject if you don't want it to initialize and store it as asset. So again lazy creation makes no sense. Note that this version does not create an instance of the asset, but uses the asset directly. Keep this in mind as any changes to serialized variables will actually persist when testing in the editor. The great thing is you can modify parameters on the scriptable object asset during runtime and get immediate results while in playmode. Also when you exit playmode the changes persist.
If you want you can just use Instantiate the same was as before to actually work on an instance during playmode. Of course changes to the instance wont' affect the asset and changes to the asset won't be noticed until a re-enter of the playmode.
Now about destroying / reinitializing a singleton. Usually you don't want to do this at all. But if required you can do it.
The true, non monobehaviour singleton can't really be destroyed or reinitialized as the static field is read only. If you need this you have to do the usual singleton approach with a normal private static variable. To force a reinitialization you just add a method "Destroy" which sets the static reference to "null".
The scene based monobehaviour singleton can't really be "reset". Of course you can simply destroy the instance but if it relies on an initialized instance in the scene this should only be done right before re-loading the scene.
The prefab based monobehaviour can simply be destroyed with this line
DestroyImmediate(MySingleton.Instance.gameObject);
. Since the singleton will automatically recreate itself from the prefab in case it's reference is null we don't have to do anything else.The scriptable object singleton that uses the asset directly must not be destroyed and can't be "reinitialized" since you directly work on the asset. If you use an instance you can simply use
DestroyImmediate(MySingleton.Instance);
just like with the prefab version. The singleton will reinitialize it self when accessed the next time.
Keep in mind that you should never "cache" a singleton reference locally. A singleton should always be accessed through it's single access point (the Instance property). Of course inside a method you can use a temporary local variable to cache it temporarily when you need to use it alot in a batch task. Though in general: caching singletons is bad.
public sealed class $$anonymous$$ySingleton
{
public static readonly $$anonymous$$ySingleton Instance = new $$anonymous$$ySingleton();
private $$anonymous$$ySingleton() { }
// your singleton stuff here
}
This is eager instantiation, not lazy instantiation. At the time the program is run, an instance is created.
For lazy instantiation you need
public sealed class $$anonymous$$ySingleton
{
private readonly $$anonymous$$ySingleton _instance;
public static $$anonymous$$ySingleton Instance()
{
if (_instance == null)
_instance = Instantiate(//blah blah)
return _instance;
}
{
Good write up otherwise
No, it's lazy. The static constructor and static field initializer is only invoked when you access the class for the first time. If you never use a certain class, it's static constructor will never run.
See this example:
public class StaticInitialization
{
public static int v = GetValue();
static StaticInitialization()
{
Debug.Log("static constructor");
}
private static int GetValue()
{
Debug.Log("GetValue");
return 0;
}
public static void Dummy()
{
}
}
If we never use this class, none of the debug log statements will run. However if you execute
StaticInitialization.Dummy();
or just doing
int i = StaticInitialization.v;
You will see that "GetValue" is invoked first (since field initializers are run before the constructor) and then the static constructor. Of course this only happens once during the lifetime of the AppDomain.
Finally a good explanation for the different Singletons! Thank you very much! It helped me a lot to get rid of a 3 days old bug!!
Your answer
Follow this Question
Related Questions
Destroying all enemy characters after the level is over 4 Answers
how to destroy an object then instantiate another from a prefab 1 Answer
Instantiating droppable items on destroy game object 1 Answer
Raycast Specific Object/Instantiate Explosion 1 Answer
how to make a Portal 2-esque laser beam "redirection" system? 0 Answers