Singleton

From Unify Community Wiki
(Difference between revisions)
Jump to: navigation, search
m
(Added implementation description and refined the code.)
Line 1: Line 1:
= Alternatives =
+
== Alternatives ==
One nice alternative to the singleton pattern is the use of "ScriptableObject Variables”. These work like global variables but offer many advantages over traditional global variable and singleton concepts. Ryan Hipple from Schell Games gave an excellent presentation at Unite Austin 2017 titled [https://youtu.be/raQ3iHhE_Kk Game Architecture with Scriptable Objects] that explains how to implement them and the advantages of this technique over singletons.
+
  
Another alternative to the standard singleton is called a [[Toolbox]], which is a kind of improved singleton that encourages better coding practices.
+
==== ScriptableObjects ====
 +
One excellent alternative to the singleton pattern in Unity is the use of ScriptableObjects as a type of global variable. Ryan Hipple from Schell Games gave a presentation at Unite Austin 2017 titled [https://youtu.be/raQ3iHhE_Kk Game Architecture with Scriptable Objects] that explains how to implement them and the many advantages over singletons.
  
= Introduction =
+
==== Toolbox ====
 +
The "[[Toolbox]]" concept improves upon the singleton pattern further by offering a different approach that makes them more modular and improves testability.
 +
 
 +
== Introduction ==
 
The [http://en.wikipedia.org/wiki/Singleton_pattern singleton] pattern is a way to ensure a class has only a single globally accessible instance available at all times. Behaving much like a regular static class but with some advantages. This is very useful for making global manager type classes that hold global variables and functions that many other classes need to access. However, the convenience of the pattern can easily lead to misuse and abuse and this has made it [https://softwareengineering.stackexchange.com/questions/40373/so-singletons-are-bad-then-what/218322#218322 somewhat controversial] with many critics considered it an anti-pattern that should be avoided. But like any design pattern, singletons can be very useful in certain situations and it's ultimately up to the developer to decide if it's right for them.
 
The [http://en.wikipedia.org/wiki/Singleton_pattern singleton] pattern is a way to ensure a class has only a single globally accessible instance available at all times. Behaving much like a regular static class but with some advantages. This is very useful for making global manager type classes that hold global variables and functions that many other classes need to access. However, the convenience of the pattern can easily lead to misuse and abuse and this has made it [https://softwareengineering.stackexchange.com/questions/40373/so-singletons-are-bad-then-what/218322#218322 somewhat controversial] with many critics considered it an anti-pattern that should be avoided. But like any design pattern, singletons can be very useful in certain situations and it's ultimately up to the developer to decide if it's right for them.
  
Line 22: Line 25:
 
* Not very testable.
 
* Not very testable.
  
= Usage Example =
+
== Implementation ==
A manager class that inherits from the Singleton base class.
+
The singleton pattern is commonly applied to multiple classes but the implementation is always the same. So creating a singleton base class others can inherit from is ideal because it eliminates the need to recreate the same code over and over again for each class that needs to be a singleton.
<syntaxhighlight lang="csharp">
+
public class Manager : Singleton<Manager> {
+
protected Manager () {} // guarantee this will be always a singleton only - can't use the constructor!
+
  
public string myGlobalVar = "whatever";
+
==== Notes: ====
}
+
* This script will not prevent non singleton constructors from being used in your derived classes. To prevent this, add a protected constructor to each derived class.
</syntaxhighlight>
+
* When Unity quits it destroys objects in a random order and this can create issues for singletons. So we prevent access to the singleton instance when the application quits to prevent problems.
A MonoBehaviour class accessing the Manager singleton.
+
<syntaxhighlight lang="csharp">
+
public class MyClass : MonoBehaviour {
+
void Awake () {
+
Debug.Log(Manager.Instance.myGlobalVar);
+
}
+
}
+
</syntaxhighlight>
+
  
 
+
=== Singleton.cs ===
= Implementation =
+
'''Singleton.cs'''
+
 
<syntaxhighlight lang="csharp">
 
<syntaxhighlight lang="csharp">
 
using UnityEngine;
 
using UnityEngine;
  
 
/// <summary>
 
/// <summary>
/// Be aware this will not prevent a non singleton constructor
+
/// Inherit from this base class to create a singleton.
///   such as `T myT = new T();`
+
/// e.g. public class MyClassName : Singleton<MyClassName> {}
/// To prevent that, add `protected T () {}` to your singleton class.
+
///
+
/// As a note, this is made as MonoBehaviour because we need Coroutines.
+
 
/// </summary>
 
/// </summary>
 
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
 
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
 
{
 
{
private static T _instance;
+
    // Check to see if we're about to be destroyed.
+
    private static bool m_ShuttingDown = false;
private static object _lock = new object();
+
    private static object m_Lock = new object();
+
    private static T m_Instance;
public static T Instance
+
 
{
+
    /// <summary>
get
+
    /// Access singleton instance through this propriety.
{
+
    /// </summary>
if (applicationIsQuitting) {
+
    public static T Instance
Debug.LogWarning("[Singleton] Instance '"+ typeof(T) +
+
    {
"' already destroyed on application quit." +
+
        get
" Won't create again - returning null.");
+
        {
return null;
+
            if (m_ShuttingDown)
}
+
            {
+
                Debug.LogWarning("[Singleton] Instance '" + typeof(T) +
lock(_lock)
+
                    "' already destroyed. Returning null.");
{
+
                return null;
if (_instance == null)
+
            }
{
+
 
_instance = (T) FindObjectOfType(typeof(T));
+
            lock (m_Lock)
+
            {
if ( FindObjectsOfType(typeof(T)).Length > 1 )
+
                if (m_Instance == null)
{
+
                {
Debug.LogError("[Singleton] Something went really wrong " +
+
                    // Search for existing instance.
" - there should never be more than 1 singleton!" +
+
                    m_Instance = (T)FindObjectOfType(typeof(T));
" Reopening the scene might fix it.");
+
 
return _instance;
+
                    // Create new instance if one doesn't already exist.
}
+
                    if (m_Instance == null)
+
                    {
if (_instance == null)
+
                        // Need to create a new GameObject to attach the singleton to.
{
+
                        var singletonObject = new GameObject();
GameObject singleton = new GameObject();
+
                        m_Instance = singletonObject.AddComponent<T>();
_instance = singleton.AddComponent<T>();
+
                        singletonObject.name = typeof(T).ToString() + " (Singleton)";
singleton.name = "(singleton) "+ typeof(T).ToString();
+
 
+
                        // Make instance persistent.
DontDestroyOnLoad(singleton);
+
                        DontDestroyOnLoad(singletonObject);
+
                    }
Debug.Log("[Singleton] An instance of " + typeof(T) +
+
                }
" is needed in the scene, so '" + singleton +
+
 
"' was created with DontDestroyOnLoad.");
+
                return m_Instance;
} else {
+
            }
Debug.Log("[Singleton] Using instance already created: " +
+
        }
_instance.gameObject.name);
+
    }
}
+
 
}
+
 
+
    private void OnApplicationQuit()
return _instance;
+
    {
}
+
        m_ShuttingDown = true;
}
+
    }
}
+
 
+
 
private static bool applicationIsQuitting = false;
+
    private void OnDestroy()
/// <summary>
+
    {
/// When Unity quits, it destroys objects in a random order.
+
        m_ShuttingDown = true;
/// In principle, a Singleton is only destroyed when application quits.
+
    }
/// If any script calls Instance after it have been destroyed,
+
///  it will create a buggy ghost object that will stay on the Editor scene
+
///  even after stopping playing the Application. Really bad!
+
/// So, this was made to be sure we're not creating that buggy ghost object.
+
/// </summary>
+
public void OnDestroy () {
+
applicationIsQuitting = true;
+
}
+
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
= Requirements =
+
== Requirements ==
 
The above script makes use of the custom extension method [[GetOrAddComponent]] which is not a part of Unity.
 
The above script makes use of the custom extension method [[GetOrAddComponent]] which is not a part of Unity.
 +
 +
== Usage ==
 +
To make any class a singleton, simply inherit from the Singleton base class instead of MonoBehaviour, like so:
 +
 +
<syntaxhighlight lang="csharp">
 +
public class MySingleton : Singleton<MySingleton>
 +
{
 +
    // (Optional) Prevent non-singleton constructor use.
 +
    protected MySingleton() { }
 +
 +
    // Then add whatever code to the class you need as you normally would.
 +
    public string MyTestString = "Hello world!";
 +
}
 +
</syntaxhighlight>
 +
 +
Now you can access all public fields, properties and methods from the class anywhere using '''<ClassName>.Instance''':
 +
 +
<syntaxhighlight lang="csharp">
 +
public class MyClass : MonoBehaviour
 +
{
 +
    private void OnEnable()
 +
    {
 +
        Debug.Log(MySingleton.Instance.MyTestString);
 +
    }
 +
}
 +
</syntaxhighlight>

Revision as of 21:47, 13 November 2018

Contents

Alternatives

ScriptableObjects

One excellent alternative to the singleton pattern in Unity is the use of ScriptableObjects as a type of global variable. Ryan Hipple from Schell Games gave a presentation at Unite Austin 2017 titled Game Architecture with Scriptable Objects that explains how to implement them and the many advantages over singletons.

Toolbox

The "Toolbox" concept improves upon the singleton pattern further by offering a different approach that makes them more modular and improves testability.

Introduction

The singleton pattern is a way to ensure a class has only a single globally accessible instance available at all times. Behaving much like a regular static class but with some advantages. This is very useful for making global manager type classes that hold global variables and functions that many other classes need to access. However, the convenience of the pattern can easily lead to misuse and abuse and this has made it somewhat controversial with many critics considered it an anti-pattern that should be avoided. But like any design pattern, singletons can be very useful in certain situations and it's ultimately up to the developer to decide if it's right for them.

Advantages

  • Globally accessible. No need to search for or maintain a reference to the class.
  • Persistent data. Can be used to maintain data across scenes.
  • Supports interfaces. Static classes can not implement interfaces.
  • Supports inheritance. Static classes can not inherent for another class.

The advantage of using singletons in Unity, rather than static parameters and methods, is that static classes are lazy-loaded when they are first referenced, but must have an empty static constructor (or one is generated for you). This means it's easier to mess up and break code if you're not careful and know what you're doing. As for using the Singleton Pattern, you automatically already do lots of neat stuff, such as creating them with a static initialization method and making them immutable.

Disadvantages

  • Must use the Instance keyword (e.g. <ClassName>.Instance) to access the singleton class.
  • There can only ever be one instance of the class active at a time.
  • Tight connections. Modifying the singleton can easily break all other code that depends on it. Requiring a lot of refactoring.
  • No polymorphism.
  • Not very testable.

Implementation

The singleton pattern is commonly applied to multiple classes but the implementation is always the same. So creating a singleton base class others can inherit from is ideal because it eliminates the need to recreate the same code over and over again for each class that needs to be a singleton.

Notes:

  • This script will not prevent non singleton constructors from being used in your derived classes. To prevent this, add a protected constructor to each derived class.
  • When Unity quits it destroys objects in a random order and this can create issues for singletons. So we prevent access to the singleton instance when the application quits to prevent problems.

Singleton.cs

using UnityEngine;
 
/// <summary>
/// Inherit from this base class to create a singleton.
/// e.g. public class MyClassName : Singleton<MyClassName> {}
/// </summary>
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    // Check to see if we're about to be destroyed.
    private static bool m_ShuttingDown = false;
    private static object m_Lock = new object();
    private static T m_Instance;
 
    /// <summary>
    /// Access singleton instance through this propriety.
    /// </summary>
    public static T Instance
    {
        get
        {
            if (m_ShuttingDown)
            {
                Debug.LogWarning("[Singleton] Instance '" + typeof(T) +
                    "' already destroyed. Returning null.");
                return null;
            }
 
            lock (m_Lock)
            {
                if (m_Instance == null)
                {
                    // Search for existing instance.
                    m_Instance = (T)FindObjectOfType(typeof(T));
 
                    // Create new instance if one doesn't already exist.
                    if (m_Instance == null)
                    {
                        // Need to create a new GameObject to attach the singleton to.
                        var singletonObject = new GameObject();
                        m_Instance = singletonObject.AddComponent<T>();
                        singletonObject.name = typeof(T).ToString() + " (Singleton)";
 
                        // Make instance persistent.
                        DontDestroyOnLoad(singletonObject);
                    }
                }
 
                return m_Instance;
            }
        }
    }
 
 
    private void OnApplicationQuit()
    {
        m_ShuttingDown = true;
    }
 
 
    private void OnDestroy()
    {
        m_ShuttingDown = true;
    }
}

Requirements

The above script makes use of the custom extension method GetOrAddComponent which is not a part of Unity.

Usage

To make any class a singleton, simply inherit from the Singleton base class instead of MonoBehaviour, like so:

public class MySingleton : Singleton<MySingleton>
{
    // (Optional) Prevent non-singleton constructor use.
    protected MySingleton() { }
 
    // Then add whatever code to the class you need as you normally would.
    public string MyTestString = "Hello world!";
}

Now you can access all public fields, properties and methods from the class anywhere using <ClassName>.Instance:

public class MyClass : MonoBehaviour
{
    private void OnEnable()
    {
        Debug.Log(MySingleton.Instance.MyTestString);
    }
}
Personal tools
Namespaces

Variants
Actions
Navigation
Extras
Toolbox