Search Unity

[Question] UnitySingleton class, by System.Attribute?

Discussion in 'Scripting' started by IsGreen, Oct 29, 2014.

  1. IsGreen

    IsGreen

    Joined:
    Jan 17, 2014
    Posts:
    206
    First post:
    Last Updated post: Download Test Scene.

    By creating an Editor class to limit the number of instances to one in Edit Mode, it made me rethink the situation. I will change the initial code of this post, for this.

    Base Singleton class with SingletonEditor class to limit the number of instances to one:
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4. public abstract class Singleton : MonoBehaviour {}
    5.  
    6. [CustomEditor(typeof(Singleton),true)]
    7. public class SingletonEditor : Editor{
    8.  
    9. void OnEnable(){
    10.  
    11. //Detect multiple instances
    12. if(Object.FindObjectsOfType(target.GetType()).Length > 1) DestroyImmediate(target);
    13. }
    14.  
    15. public override void OnInspectorGUI(){ DrawDefaultInspector(); }
    16.  
    17. }
    Then, to create singleton object, you only need to add two lines of code in classes derived from Singleton. For example:

    Code (CSharp):
    1. public class script : Singleton {
    2.  
    3. //Code related to Singleton
    4. public static script Instance;
    5. void Awake(){ if(Instance==null) Instance = this; else DestroyImmediate(this); }
    6.  
    7. //Code related to script class
    8. public int number = 1;
    9. //...
    10.  
    11. }
    Really, don't need anything else. You can access the singleton by:
    Code (CSharp):
    1. script.Instance.number;
    ________________________

    I would like to use System.Attribute instead Editor class to do the same as above, for example:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. [Singleton]
    5. public class script : MonoBehaviour {
    6.  
    7. //Code related to Singleton.
    8. public static script Instance;
    9. void Awake(){ if(Instance==null) Instance = this; else DestroyImmediate(this); }
    10.  
    11. //Code related to script class
    12. public int number = 1;
    13. //...
    14.  
    15. }
    The Singleton attribute does the same as the Editor class from OnEnable method, limit the number of instances to one, at the scene in Edit Mode.

    Furthermore, using an attribute instead of an Editor class to limit the number of instances allowed us to create a custom Editor for that class.
     
    Last edited: Oct 31, 2014
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    A few suggestions off the top of my head after just breezing through the code.

    1) your methods are all private, so they can't be accessed.

    2) Personally I'd create one single GameObject and attach all components to that GameObject. Reduce the number of GameObjects in scene for this.

    3) I'd store all created singletons in a static dictionary inside this class so that you don't have to 'FindObjectsOfType' every time you call to get the singleton out.
     
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    If I was going to do something like this. I'd do it something like this.

    First I'd have just one GameObject like I said before.

    Next I'd use that GameObject as the dictionary for looking up the instances semi-quickly (you could test adding a dictionary/table to speed it up... not sure if it'll actually be faster).

    I'd also make all the Singleton inherit from some abstract 'Singleton' class so that the enforcement of the Singleton status is done through that.

    Like so:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4.  
    5. public abstract class Singleton : MonoBehaviour
    6. {
    7.  
    8.     #region Static Interface
    9.  
    10.     private static GameObject _gameObject;
    11.  
    12.     static Singleton()
    13.     {
    14.         _gameObject = new GameObject("SingletonSource");
    15.         GameObject.DontDestroyOnLoad(_gameObject);
    16.     }
    17.  
    18.     public static T Instance<T>() where T : Singleton
    19.     {
    20.         var single = _gameObject.GetComponent<T>();
    21.         if(single == null)
    22.         {
    23.             single = _gameObject.AddComponent<T>();
    24.         }
    25.         return single;
    26.     }
    27.  
    28.     public static Singleton Instance(System.Type tp)
    29.     {
    30.         if (!typeof(Singleton).IsAssignableFrom(tp)) throw new System.ArgumentException("Type must inherit from Singleton.", "tp");
    31.  
    32.         var single = _gameObject.GetComponent(tp);
    33.         if(single == null)
    34.         {
    35.             single = _gameObject.AddComponent(tp) as Singleton;
    36.         }
    37.         return single as Singleton;
    38.     }
    39.  
    40.     public static Singleton Instance(string stype)
    41.     {
    42.         var single = _gameObject.GetComponent(stype);
    43.         if(single == null)
    44.         {
    45.             single = _gameObject.AddComponent(stype);
    46.         }
    47.         if(!(single is Singleton))
    48.         {
    49.             Object.Destroy(single);
    50.             throw new System.ArgumentException("Type must inherit from Singleton.", "tp");
    51.         }
    52.  
    53.         return single as Singleton;
    54.     }
    55.  
    56.     #endregion
    57.  
    58.     #region Singleton Enforcement
    59.  
    60.     protected virtual void Awake()
    61.     {
    62.         var c = _gameObject.GetComponent(this.GetType());
    63.         if(!Object.ReferenceEquals(c, this))
    64.         {
    65.             Object.Destroy(this);
    66.             throw new System.InvalidOperationException("Attempted to create an instance of a Singleton out of its appropriate operating bounds.");
    67.         }
    68.     }
    69.  
    70.     #endregion
    71.  
    72. }
    73.  
    Note - just slapped this together just now. Not tested at all.



    You could also include this generic class to compliment the previous class so you could have a generic gate for the Singleton. This would allow for faster lookups as the reference is stored.

    Code (csharp):
    1.  
    2. public static class Singleton<T> : Singleton where T : Singleton
    3. {
    4.  
    5.     private static T _instance;
    6.  
    7.     public static T Instance
    8.     {
    9.         get
    10.         {
    11.             if (_instance == null)
    12.                 _instance = Singleton.Instance<T>();
    13.  
    14.             return _instance;
    15.         }
    16.     }
    17. }
    18.  
     
    Last edited: Oct 29, 2014
  4. IsGreen

    IsGreen

    Joined:
    Jan 17, 2014
    Posts:
    206
    Produces error: Static class `Singleton<T>' cannot derive from type `Singleton'. Static classes must derive from object.

    How to use your Singleton class?

    To use my UnitySingleton class would be to add line directly in UnitySingleton class, no need create derived objects, or create GameObject before running game.

    Even, using partial class, it is possible to split the definition of a class or a struct, an interface or a method over two or more source files.Each source file contains a section of the type or method definition, and all parts are combined when the application is compiled.

    Code (CSharp):
    1. public static partial class UnitySingleton {
    2.  
    3.      //Game Singletons
    4.      public static script1 Script1 = Singleton<script1>("Singletons");
    5.      public static script2 Script2 = Singleton<script2>("Singletons");
    6.  
    7. }
     
    Last edited: Oct 29, 2014
  5. cmcpasserby

    cmcpasserby

    Joined:
    Jul 18, 2014
    Posts:
    315
    So what's the point of using this over just.
    Code (CSharp):
    1. public static class Singleton<T> : Singleton where T : Singleton
    2. {
    3.  
    4.     private static T _instance;
    5.  
    6.     public static T Instance
    7.     {
    8.         get
    9.         {
    10.             if (_instance == null)
    11.                 _instance = Singleton.Instance<T>();
    12.  
    13.             return _instance;
    14.         }
    15.     }
    16. }
    And tossing them all on a gameObject in the scene?
     
  6. IsGreen

    IsGreen

    Joined:
    Jan 17, 2014
    Posts:
    206
    Another topic, when copy/paste code from message, also copy lines number in script editor:

    1. public static class Singleton<T> : Singleton where T : Singleton
    2. {
    3. private static T _instance;
    4. public static T Instance
    5. {
    6. get
    7. {
    8. if (_instance == null)
    9. _instance = Singleton.Instance<T>();
    10. return _instance;
    11. }
    12. }
    Is there any way to prevent this from happening?
     
  7. cmcpasserby

    cmcpasserby

    Joined:
    Jul 18, 2014
    Posts:
    315
    it only selects the code not the line numbers for me.
     
  8. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    Singleton<T> shouldn't be inheriting from Singleton.

    I don't know how that creeped in there. I must have double typed when putting it into the thread or something.

    It should read:

    Code (csharp):
    1.  
    2. public static class Singleton<T> where T : Singleton
    3.  
    Every class you want to be a singleton would just inherit from Singleton. And you're done.

    Code (csharp):
    1.  
    2. public class MyGameManager : Singleton
    3. {
    4.  
    5.     public void Foo()
    6.     {
    7.  
    8.     }
    9.  
    10. }
    11.  
    Code (csharp):
    1.  
    2. public void SomeMethodElsewhereInGame()
    3. {
    4.     Singleton<MyGameManager>.Instance.Foo(); //if this is first ever time accessing it, the MyGameManager is created
    5. }
    6.  
    As the version stands you couldn't drop a GameObject in the scene and start adding components to it. You could change it though so you could do that...

    I'd write a menu item or something that created the GameObject with the correct name.

    Then in the static constructor you would do a GameObject.Find for that GameObject, and if it wasn't found create a new GameObject.

    You'd probably also want a way to configure if the GameObject did or didn't destroyonload, so that you can decide if only one is ever created or if, one loaded replaces the old one.

    As I said, my version was untested and unused. I slapped it together in a couple minutes.


    oh... so you expect to add a new line to your class to add new singletons.

    I went with a route that allowed me to add singletons at runtime as needed.
     
    Last edited: Oct 29, 2014
  9. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    The first class, the just 'Singleton', is the enforcer. It enforces Singleton status.

    Having a global access point isn't what makes a Singleton. What makes a Singleton is that there is code that makes it where only ONE instance can ever exist at a time.
     
  10. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    Modified version:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4.  
    5. public class Singleton : MonoBehaviour
    6. {
    7.  
    8.     #region Static Interface
    9.  
    10.     public const string GAMEOBJECT_NAME = "SingletonSource";
    11.  
    12.     private static GameObject _gameObject;
    13.  
    14.     public static GameObject GameObjectSource
    15.     {
    16.         get
    17.         {
    18.             if(_gameObject == null)
    19.             {
    20.                 _gameObject = GameObject.Find(GAMEOBJECT_NAME);
    21.                 if (_gameObject == null)
    22.                 {
    23.                     _gameObject = new GameObject(GAMEOBJECT_NAME);
    24.                     _gameObject.AddComponent<SingletonManager>();
    25.                 }
    26.             }
    27.             return _gameObject;
    28.         }
    29.     }
    30.  
    31.     public static T Instance<T>() where T : Singleton
    32.     {
    33.         var single = Singleton.GameObjectSource.GetComponent<T>();
    34.         if(single == null)
    35.         {
    36.             single = Singleton.GameObjectSource.AddComponent<T>();
    37.         }
    38.         return single;
    39.     }
    40.  
    41.     public static Singleton Instance(System.Type tp)
    42.     {
    43.         if (!typeof(Singleton).IsAssignableFrom(tp)) throw new System.ArgumentException("Type must inherit from Singleton.", "tp");
    44.  
    45.         var single = Singleton.GameObjectSource.GetComponent(tp);
    46.         if(single == null)
    47.         {
    48.             single = Singleton.GameObjectSource.AddComponent(tp) as Singleton;
    49.         }
    50.         return single as Singleton;
    51.     }
    52.  
    53.     public static Singleton Instance(string stype)
    54.     {
    55.         var single = Singleton.GameObjectSource.GetComponent(stype);
    56.         if(single == null)
    57.         {
    58.             single = Singleton.GameObjectSource.AddComponent(stype);
    59.         }
    60.         if(!(single is Singleton))
    61.         {
    62.             Object.Destroy(single);
    63.             throw new System.ArgumentException("Type must inherit from Singleton.", "tp");
    64.         }
    65.  
    66.         return single as Singleton;
    67.     }
    68.  
    69.     #endregion
    70.  
    71.     #region Singleton Enforcement
    72.  
    73.     protected virtual void Awake()
    74.     {
    75.         var c = Singleton.GameObjectSource.GetComponent(this.GetType());
    76.         if(!Object.ReferenceEquals(c, this))
    77.         {
    78.             Object.Destroy(this);
    79.             throw new System.InvalidOperationException("Attempted to create an instance of a Singleton out of its appropriate operating bounds.");
    80.         }
    81.     }
    82.  
    83.     #endregion
    84.  
    85. }
    86.  
    Code (csharp):
    1.  
    2. public static class Singleton<T> where T : Singleton
    3. {
    4.  
    5.     private static T _instance;
    6.  
    7.     public static T Instance
    8.     {
    9.         get
    10.         {
    11.             if (_instance == null)
    12.                 _instance = Singleton.Instance<T>();
    13.  
    14.             return _instance;
    15.         }
    16.     }
    17. }
    18.  
    A SingletonManager Singleton that stores settings:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class SingletonManager : Singleton
    6. {
    7.  
    8.     public bool MaintainSingletonsOnLoad = true;
    9.  
    10.     protected override void Awake()
    11.     {
    12.         base.Awake();
    13.  
    14.         if (MaintainSingletonsOnLoad)
    15.             GameObject.DontDestroyOnLoad(this.gameObject);
    16.     }
    17.  
    18. }
    19.  
    And the inspector/menu item to create the singleton in the scene, so you can add singletons at design time.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4. using System.Collections.Generic;
    5. using System.Linq;
    6.  
    7. [CustomEditor(typeof(SingletonManager))]
    8. public class SingletonEditor : Editor
    9. {
    10.  
    11.     [MenuItem("Singleton/Create SingletonSource")]
    12.     public static void CreateSingletonSource()
    13.     {
    14.         var go = new GameObject(Singleton.GAMEOBJECT_NAME);
    15.         go.AddComponent<SingletonManager>();
    16.     }
    17.  
    18.     [MenuItem("Singleton/Create SingletonSource", validate=true)]
    19.     public static bool CreateSingletonSource_Validate()
    20.     {
    21.         if (Application.isPlaying) return false;
    22.         var go = GameObject.Find(Singleton.GAMEOBJECT_NAME);
    23.         return (go == null);
    24.     }
    25.  
    26.  
    27.  
    28.  
    29.  
    30.     public override void OnInspectorGUI()
    31.     {
    32.         this.DrawDefaultInspector();
    33.  
    34.         var selectedTypes = (from c in (this.target as SingletonManager).GetComponents<Singleton>() select c.GetType()).ToArray();
    35.  
    36.         var singletonType = typeof(Singleton);
    37.         var types = (from a in System.AppDomain.CurrentDomain.GetAssemblies()
    38.                      from t in a.GetTypes()
    39.                      where singletonType.IsAssignableFrom(t) &&
    40.                      t != singletonType &&
    41.                      !selectedTypes.Contains(t)
    42.                      select t
    43.                      ).ToArray();
    44.         var typeNames = (from t in types select t.Name).ToArray();
    45.  
    46.         int index = EditorGUILayout.Popup("Add Singleton",-1, typeNames);
    47.         if(index >= 0)
    48.         {
    49.             var tp = types[index];
    50.             (this.target as SingletonManager).gameObject.AddComponent(tp);
    51.         }
    52.     }
    53.  
    54. }
    55.  
    So the SingletonManager is for configuring if the GameObject that the singletons are added to are maintained or not between loads.

    The inspector has a menu item to create the SingletonSource if it doesn't yet exist.

    As well as display the configuration, and have a drop down for adding known Singletons.



    [edit]
    Added to my spacepuppy framework project on google-code
    https://code.google.com/p/spacepupp...e/trunk/SpacepuppyUnityFramework/Singleton.cs

    https://code.google.com/p/spacepupp...meworkEditor/Inspectors/SingletonInspector.cs
     
    Last edited: Oct 30, 2014
  11. IsGreen

    IsGreen

    Joined:
    Jan 17, 2014
    Posts:
    206
    I answered myself, :), to copy/paste code without line number, press the 'Reply' button to display the message with code tags, then copy/paste code between CODE.../CODE tags.

    Theoretically, a singleton is a single instance of a class. But it is not always convenient to have a single instance.

    For example, when we have several data tables of the same type, or you need to configure differently some instances by default.

    I think it's more simple and versatile, creating a restricted to a single instance type. For example:
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4. public abstract class Single : MonoBehaviour {}
    5.  
    6. [CustomEditor(typeof(Single),true)]
    7. public class SingleEditor : Editor{
    8.  
    9.     void OnEnable(){
    10.  
    11.         //Detect multiple instances
    12.         Object[] objects = Object.FindObjectsOfType (target.GetType());
    13.         if(objects.Length>1) DestroyImmediate(target);
    14.  
    15.     }
    16.  
    17.     public override void OnInspectorGUI(){ DrawDefaultInspector(); }
    18.  
    19. }
    Thus, all types derived from Single type will be restricted to a single instance automatically.
     
  12. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    Yes.

    That's what the Singleton design pattern is. Where you enforce that only a single instance exists.

    If you don't have that, then you don't have a Singleton.

    This is what this part of my class is for:

    Code (csharp):
    1.  
    2.         #region Singleton Enforcement
    3.         protected virtual voidAwake()
    4.         {
    5.             var c =Singleton.GameObjectSource.GetComponent(this.GetType());
    6.             if(!Object.ReferenceEquals(c,this))
    7.             {
    8.                 Object.Destroy(this);
    9.                 thrownewSystem.InvalidOperationException("Attempted to create an instance of a Singleton out of its appropriate operating bounds.");
    10.             }
    11.         }
    12.         #endregion
    13.  
     
  13. IsGreen

    IsGreen

    Joined:
    Jan 17, 2014
    Posts:
    206
    But, you can add multiple instances of the same type. The instances are removed when the game starts and unverified.

    I think, It is preferable to remove the instance in edit mode from an Editor class.

    I've been looking for information about creating a System.Attribute, but it is quite difficult to detect the last target associated to attribute, return an array.
     
  14. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    1) you should always have a runtime check, an editor time check isn't guaranteed to be ran

    2) you can add a editor time check to my design easily

    3) What would the attribute be for?
     
  15. IsGreen

    IsGreen

    Joined:
    Jan 17, 2014
    Posts:
    206
    I have seen many examples of singleton classes, but is it really necessary to create as many methods or as many lines of code?

    By creating an Editor class to limit the number of instances to one in Edit Mode, it made me rethink the situation. I will change the initial code of this post, for this.

    Base Singleton class with SingletonEditor class to limit the number of instances to one:
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4. public abstract class Singleton : MonoBehaviour {}
    5.  
    6. [CustomEditor(typeof(Singleton),true)]
    7. public class SingletonEditor : Editor{
    8.  
    9.     void OnEnable(){
    10.  
    11.         //Detect multiple instances
    12.         if(Object.FindObjectsOfType(target.GetType()).Length > 1) DestroyImmediate(target);
    13.  
    14.     }
    15.  
    16.     public override void OnInspectorGUI(){ DrawDefaultInspector(); }
    17.  
    18. }
    Then, to create singleton object, you only need to add two lines of code in classes derived from Singleton. For example:

    Code (CSharp):
    1. public class script : Singleton {
    2.  
    3.     //Code related to Singleton
    4.     public static script Instance;
    5.     void Awake(){ if(Instance==null) Instance = this; else DestroyImmediate(this); }
    6.  
    7.     //Code related to script class
    8.     public int number = 1;
    9.     //...
    10.  
    11. }
    I really do not need anything else. You can access the singleton by:
    Code (CSharp):
    1. script.Instance.number;
    To do the same as above using a System.Attribute, for example:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. [Singleton]
    5. public class script : MonoBehaviour {
    6.  
    7.     //Code related to Singleton.
    8.     public static script Instance;
    9.     void Awake(){if(Instance==null) Instance = this; else DestroyImmediate(this); }
    10.  
    11.     //Code related to script class
    12.     public int number = 1;
    13.     //...
    14.  
    15. }
    The Singleton attribute does the same as the Editor class from OnEnable method, limit the number of instances to one, at the scene in Edit Mode.

    Furthermore, using an attribute instead of an Editor class to limit the number of instances allowed us to create a custom Editor for that class.
     
    Last edited: Oct 31, 2014