What should a Singleton Do?
After my last post about Singletons, I spend some time thinking about what the Singleton Pattern really means. According to Wikipedia a Singleton has the following to requirements:
- provide global access to that instance
- ensure that only one instance of the singleton class ever exists
Did the old implementation achieve that?
So how did my old implementation of a Singleton Monobehaiviou ensure that only one instance of the singleton class ever exists?
- The SingletonMonobehaiviour (SiMo for short) has a static Instance property.
- When the Awake method of a SiMo is called it checks if Instance is already assigned.
- If the Instance is not assigned, it assigns itself to Instance.
- If the Instance is already assigned the SiMO destroy the GameObject, it is a component of.
Which means that in fact, the SiMo does not ensure that only one instance ever exists It only ensures that when unity instantiates an instance of the SiMo and somewhere down the road, its Awake method is called, then the duplicate instance is destroyed. However, in the time between instantiation and Awake, two, three or even more instances can exist. Because Unity creates all MonoBehaiviour instances for us, we can only kind of enforce a singular instance at runtime.
If somebody adds a second SiMo to a GameObject, no warning is given. If a merge results in two SiMos in a scene no warning will be given. So could the Singleton pattern be enforced at Design time? That was the question my teacher Lars Kokemohr asked me. So I set out to create a Singleton Monobehaiviour that would warn users at design time if two singletons exist in the same scene.
A new Goal
If somebody adds a second SiMo to a GameObject, no warning is given. If a merge results in two SiMos in a scene no warning will be given. So could it be enforced at Design time? That was the question my teacher Lars Kokemohr asked me. So I set out to create a Singleton Monobehaiviour that would warn users at design time if a SingletonMonobehaiviour was added to a scene that already contained one.
The new Implementation of my SingletonMonobehaiviour can be found in my UnityGoodies repository on GitHub. The four parts are:
- The abstract SingletonMonoBehaviour<T> base class which all SiMos inherit from.
- The MultipleSingletonInstanceException which is thrown when multiple instances of a singleton are present in a scene.
- The SingletonMonobehaiviourHelper<T> which allows for singleton instance checking in the editor
- the TransformUtility which permits the creation of an instance trace (path in scene hierarchy to a particular transform).
The SingletonMonobehaiviour<T> allows for two distinct behaviours. If the game is playing (in the editor or build), the old singleton behaviour is executed. However, if a SiMo is created in the editor, when not playing, the new editor behaviour of the SiMo is run. That works as follows:
- When a MonoBehaiviouris added to a GameObject, the Reset function of that MonoBehaiviouris called.
- When that happens, the ValidateSingletonInEditor method of SingletonMonobehaiviourHelper is called.
- This function uses the potentially expensive Resources.FindObjectsOfTypeAll is used to find all instances of the calling SiMo.
- If there is more than one instance, a dialogue is displayed that shows the how many instances where detected, the name and the hierarchy path to the GameObjects they belong to and then asks the user if that information should be logged.
- if chosen by the user the instance trace is logged
Pros and Cons
- multiple singleton instances in a scene can be detected and revealed at design time
- is multiple singleton instances are identified in the active scene a warning dialogue that completely pauses the editor is displayed
- the user can see which GameObjects contain SiMo instances
- this information can be logged
- Instance checking in editor occurs only if a SiMo is added to a GameObject or it’s reset method is called in the inspector
- Only the currently open editor scenes ae checked
- therefore it cannot prevent runtime errors if multiple scenes are opened during runtime
- Resources.FindObjectsOfTypeAll is a very expensive method that can take a while if the hierarchy is big
- getting the trace information also can take a considerable amount of time with a big hierarchy
I think the new implementation is more efficient as it can detect multiple instances during design time. Though it’s design time checking can be rather expensive in a big, complex project. But seeing as a check occurs only when adding a new SiMo instance to a game object or calling its reset method, checking should not happen often. I will probably use this implementation for all my future centerpiece Singletons.