Search Unity

Singleton alternative for managers, without global access evil

Discussion in 'Scripting' started by glantucan, Nov 8, 2015.

  1. glantucan

    glantucan

    Joined:
    Aug 31, 2014
    Posts:
    7
    I've been wondering for a while about how to adress persistance of gameobjecst across scenes without using a singleton (you know, the *gobal access is evil* thing) and today I came out with a solution for my music player and score manager that may be extended for any use... I believe...
    It is actually kind of singleton pattern for Unity but without the global access static field.

    Before we start ranting about the use of singletons and globals I would like to explain a little about the motivation of this. This is for an example game I'm preparing for my students on the second year of High School on programming. They already know how to program and we make a lot of emphasis on good programming practices. I know sigleton is a valid option when you are in a hurry (i.e. prototyping) or working on a small project in which you are the only developer. But it's not when you are working on a team and have dozens of classes and working for a client that will ask for changes frecuently during development.

    It's a little bit convoluted but all you have to do is to inherit it from any game manager, and remember to override the Start and Update functions calling the base function on the first line if you need to use Start or Update on your manager. I include a dummy scoreManager example at the end of the post.

    The idea is that if you need to access the manager you can use Find and GetComponent or FindObjectOfType to get a reference to it in the Start function of your monobehviour. And, at the same time, you can drop instance of every manager on every scene so testing levels is easy, but ensuring that those instances are destroyed if there is a previous one, probably containing data from previous levels.

    It's not nice to the garbage collector, as if your are not disciplined enough by deleting the managers from every scene on the final release they will be marked for disposal just after loading the level. But I consider this an acceptable solution with little side effects if you decide to use another dependency injection pattern later.

    Another thing I like about this approach is that you can hide it behind an interface or abstract class, which is not possible (or easy) with singletons using a static field.

    What do you think about it? Do you find any flaw in this approach to persistance?
    Code (CSharp):
    1.  
    2. using System;
    3. using UnityEngine;
    4. using System.Collections;
    5.  
    6. public class Persistant : MonoBehaviour {
    7.  
    8.   private float _lifeTime;
    9.   public float lifeTime {
    10.   get { return _lifeTime; }
    11.   }
    12.  
    13.   virtual protected void Awake() {
    14.   Type managerType = this.GetType();
    15.   _lifeTime = 0f;
    16.   Persistant[] persistantObjects = FindObjectsOfType<Persistant>();
    17.      // Filter per actual type
    18.      ArrayList managers =  new ArrayList();
    19.      foreach (Persistant curObject in persistantObjects) {
    20.        if (curObject.GetType() == managerType) {
    21.          managers.Add(curObject);
    22.        }
    23.      }
    24.      if (managers.Count == 1) {
    25.         GameObject.DontDestroyOnLoad(gameObject);
    26.     } else {
    27.         foreach (Persistant curObject in managers) {
    28.             if (curObject != this && curObject.lifeTime == this._lifeTime) {
    29.                 curObject.beDestroyed();
    30.             } else if (curObject.lifeTime > this._lifeTime) {
    31.                 this.beDestroyed();
    32.                 return;
    33.             }
    34.    
    35.         }
    36.     }
    37.   }
    38.  
    39.   virtual protected void Update() {
    40.     _lifeTime += Time.deltaTime;
    41.   }
    42.  
    43.   public void beDestroyed() {
    44.       GameObject.Destroy(gameObject);
    45.   }
    46. }
    47.  

    Dummy Score Manager example:

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class ScoreManager : Persistant {
    6.  
    7.   public int playTime; // in seconds
    8.  
    9.    override protected void Start () {
    10.   base.Start();
    11.    }
    12.  
    13.   override protected void Update () {
    14.   base.Start();
    15.   playTime = (int) Time.time;
    16.   }
    17. }
    18.  
    [EDIT]: I fixed a couple on bugs in the code and changed the name of the class from APersistant to Persistant. It's not an absttract class.
     
    Last edited: Nov 27, 2015
  2. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    When a singleton is the right tool for a job, then use one. Placing it in a convoluted structure like this hides the what it is (Its still behaving as a singleton).

    Global state on its own isn't evil. While mutable global state is often considered evil, games by nature often have mutable global state. Its perfectly find to have if you handle it with care.
     
  3. Myhijim

    Myhijim

    Joined:
    Jun 15, 2012
    Posts:
    1,148
    Honestly, singletons have their uses, but I personally prefer the Service-Locator pattern (Which behaves similarly to a singleton with a little less coupling).

    The advice I've been given is to use either method sparingly, that it is not that big of an issue until you start spamming them everywhere.

    Generally I like S-L better just because there isn't a concrete connection to a specific class (Well, except for the Locator) and it provides more flexibility, alas at a readability loss.

    EDIT : It also allows for the addition of 'null' versions of things. So you could have Audio and then nullAudio with the same functions, yet do not produce anything, allowing essentially the 'switching off' of components. I'm still on the fence about this though.
     
  4. glantucan

    glantucan

    Joined:
    Aug 31, 2014
    Posts:
    7
    I know singletons have their uses. And whether or not global access is evil or not has been discussed so many times. I didn't want to raise that discussion here again. It's not productive.

    Yup, I know the thread title doesn't help with that :p Sorry, I couldn't help it :D. Let's say I wanted to get the attention of the people who agree on that. No pun intended.

    I'm just proposing a strategy to address the need of persistent objects that need to be unique in the game, with the premise of not using any static variables, and asking the people for help building up around that idea.

    As I said, I was looking for a simple solution to work this out on your game until you decide whether or not it needs a more sophisticated dependency injection pattern, IoC, Service locator or whatever.

    Typical singletons with static global access require a lot of refactoring if you need to do that.

    @ Myhijim. How do you implement service locator in Unity btw? Do you use any framework?
     
  5. Myhijim

    Myhijim

    Joined:
    Jun 15, 2012
    Posts:
    1,148
    I don't use any real framework, just a self made one using generics and the Service-Locator pattern. Generally at one point you do need to have some sort of static variable, the locator itself, unless you pass it via reference which you could do, but it feels like a waste of memory and also tedious to implement.

    If you want to read more on it :
    https://msdn.microsoft.com/en-us/library/ff648968.aspx

    Or from a more game oriented standpoint (It is in C++ but is still highly useful) :
    http://gameprogrammingpatterns.com/service-locator.html

    As stated previously, it is still to be used sparingly, just like any singleton. Just this can be used for a single global 'locator' of instances instead of 10 or 20 singletons of systems.

    That being, an Audio, Map, Character singleton manager vs Locator.GetService<Audio>() . I developed my service locator in a component like style.

    This is decent for a multitude of reasons, say you wanted to remove Audio singleton from your game, but you have Audio.instance.playSound() in many different classes, it becomes a pain to remove everything. But with service locator, the worst you are going to get is a nullReference, and even that can be moved into a 'nullified' version of a class.

    But again it is personal preference, just this is an attempt at decoupling further. Semantics at it's best :p
     
  6. glantucan

    glantucan

    Joined:
    Aug 31, 2014
    Posts:
    7
    Mmmm
    I don't see the point, maybe it's just my brain going nuts after 3 days of intense coding, but what's the advantage of a service locator against using FindObjectOfType<YourManagerType>(). It's just that you gain the ability to use interfaces for your managers. Am I right or I'm missing something?
     
  7. Myhijim

    Myhijim

    Joined:
    Jun 15, 2012
    Posts:
    1,148
    That essentially is in a way a Service Locator pattern. You are still asking a locator (Being the FindObjectOfType included in Object) to find you a Service (YourManagerType).

    The reason I rolled my own service locator is purely out of lack of need for the rest of Objects' inherited methods and tailor it to my own needs. That way I don't have a heap of functions that aren't achieving what I'm trying to do and also from a sustainable memory standpoint.

    Using interfaces on the Services is also a really good point, providing even more abstraction and decoupling really. Those are the two elements it is really about.

    As you can see, even the built in Unity functions have a lot of static functions so the likelihood of eliminating them entirely and the pursuit to do so is ...... difficult.

    If you wanted to at runtime, you could use the FindObjectOfType to find your managers and 'cache' them. That in itself removes the need for a separate singleton for each system. Happy days! :)
     
  8. glantucan

    glantucan

    Joined:
    Aug 31, 2014
    Posts:
    7
    Thanks for the clarification Myhijim. It's more or less what I thought but it's always good to have others confirmation. :)


    About the internal use of statics of the Unity engine, and just to clarify this point to others looking for a way to justify its overuse...
    (EDIT: it got longer than expected, sorry. Skip it if you want, but read it if you want to know why some of us say statics, and global access in general, are bad)

    Every engine and many frameworks and engines have static functions and even static classes built in.
    That's ok. It's rather rare that you will use any of you application components outside of those environments, though any attempt to do it will fail if you have used those static/global variables in your code.

    It's safe to use those static members because the engine API won't change (hopefully) and because you won't try to use your MonoBehaviour inherited components with other engine or framework. Thus, it's not a problem to use FindObjectOfType() as it's not to use any of the Mathf methods. When you program on Unity your code is always going to depend on Unity being present anyways.

    Using a static variable or method for any other purpose it's ok as long as you don't need to change its type or signature (which unfortunatelly is likely to happen when your project is big enough) and you don't plan to use your components in a project where your class containing the static member is not present (less likely to happen but it would be nice to reuse code like that).

    With the service locator pattern the refactoring required in any of those situations is not that bad, as you only have one static method for locating all the services and, if you are judicious enough, you will use it only on the constructor or the Start function. But, anyway, it's important to understand that it will take work if you want to change the signature of a static method, sometimes a lot, many times just too much.

    Another warning (this one also debatable) concerning people worrying too much about the memory waste of unused inherited methods or any other memory or performance loss...
    Optimization is the last thing you should do in any software project.

    If you are starting to learn Object Oriented Programming, stop looking desperately for optimization tips and tricks (yup, I can see you are doing it) and start learning about the ideas behind inheritance, composition, interfaces and design patterns.

    OOP is all about good planning and good practices to reuse code and make future changes easy, that was the idea behind changing from the procedural paradigm to this one. This usually comes at a cost: higher memory footprint and some performance overhead and that can be ok for your purposes. (Memory and performance are not usually a problem when building for desktops nowadays, for example, unless you are careless enough to introduce memory leaks).

    I'd rather try to concentrate on making it work by building a good architecture at first. Then, when all functionality is finished and tested (and the client if any has approved it), I would start optimizing where and if needed, but only at the end and at the bottlenecks pointed out by the Unity profiler.
    If you mess up, hit a dead end, or get into a spaghetti nightmare in the latter process, you can always go back to the unoptimized but easy to change version, and rethink how to do it.
    The other strategy, writing optimized code from the beginning usually mess up your code, making it impossible to change without introducing bugs, producing cascades of changes that never end (spaghetti code) and other developer nightmares, and may it all be to gain a milisecond where it's not actually needed.

    Finally, I want to emphasize that I'm not saying not to use the service locator pattern, actually this is one of the dependency injection solutions I would consider later in the development process. Inversion of control is another one, nicer in OOP terms as it introduces weaker dependencies, uglier because it requires more wiring. There is a good explanation of it for the java language here (I didn't use it in Unity yet but IOC Unity frameworks are available in the Asset Store)

    Oh S***! I think I made obvious I'm one of those tiresome teachers... ;P
     
    Last edited: Nov 9, 2015
    AsianSamurai and phobos2077 like this.