Singletons and GameState Managers: Good or Evil?

I have heard a lot of mixed views on creating a Singleton-based GameState manager for games made in Unity.

One person says it’s the best way to handle persistent data across levels, another says to just use “DontDestroyOnLoad” between scene changes. Another person says that “Singletons are evil”, and another says that while they’re a great way to handle data, they’re a “slippery slope”, and once you get started, “you’ll end up on the path of no return”.

So my question is, what do you, the Unity community, think? I realize this is probably a very opinion-based discussion, but I’d like to hear what everyone has to say. I, personally, think that a Singleton GameState manager is a great way to handle persistent data across levels, and this weekend I’ll be updating my project to use one (I have to learn a bit more about them first!). “DontDestroyOnLoad” is pretty terrible because it ends up creating multiple instances of the same objects, or at least that’s been my experience.

I’d also like to know if there are other ways to store persistent data across levels. I haven’t heard of any, but one of you may surprise me! :slight_smile:

A lot of people have pretty strong opinions, but honestly there’s a time and place for everything. It’s not evil to use Static classes and it’s not evil to use Singletons… just don’t get too carried away with it as it can make things harder to debug. You may have a call on your singleton that throws an exception but you might not know what GameObject made that call so it can be hard to track down how the error occurred. Realistically though, it’s just about coding smart and making sure you can figure out that information if you need.

Many times I use a mix. If it’s something that absolutely needs to persist, I tend to use a Static class as the actual “Manager” and it creates a Singleton Instance of the object I need to access. You could make it all static, but if you need to persist state between sessions, you may want to be able to serialize and deserialize so it should be instanceable. You can either allow the manager or the Singleton handle the deserialization process to restore the settings.

Ah, that must be why some people have said they’re evil - harder debugging. I’m still very new to the concept of them, I can certainly see that occurring though!

To me, they just seem like the easiest way to store data across levels/sessions. I should probably do a bit more studying on scripting with C# before I tackle one though, eh?

Singleton, like anything, is a tool for a specific purpose.

If

  • Class must exist only in 1 instance. Multiple instance is not wanted and may even break the expected behaviour.
  • Anybody can access that instance from everywhere. There is no point in attempting to retrieve that instance by searching existing object.
  • Usually assume that specific class will behave as if it was sealed. Unless some very special case, inheritance stops at this class.

and if you wonder if you should choose singleton over static;

  • May be useful for this class to not exist in memory unless called for. The instance may be destructible.
  • The class may derive from another. (Ex.: MonoBehaviour)

I sometimes use singletons, sometimes it’s static, and sometimes it’s both. There’s a time and a place for everything.

As for DontDestroyOnLoad, it’s usually paired with something similar to this;

    public void OnEnable()
    {
        if (instance == null)
            instance = this;
        else if (instance != this) 
        {
            Destroy(gameObject);
            return;
        }
    }

I prefer a game manager with DontDestroyOnLoad. To avoid any multible instances just create a launcher scene. I use to make my launcher scene after my splash, but now I even carry around a scene camera as a child of my GM that get activated for cut scenes, credit scene, splash and all menus.

It’s neither black, nor white. It’s gray!

You can use it in a meaningful way, but you can also abuse it.

I’ve seen that mentioned before, but never saw an actual code snippet. That could help me a lot!

I thought Singletons used static classes, though?

A singleton maintains a static reference to itself (typically called Instance). The class is not static.

public sealed class MySingleton
{
    private static MySingleton instance;
    public static MySingleton Instance
    {
        get
        {
            if (instance == null) instance = new MySingleton();
            return instance;
        }
    }

    private MySingleton() { }
}

Oh, I see now! That makes sense.

The only thing you have to watch out for is concurrency if you are doing anything multithreaded… In this case it actually would make sense to use a lock, but thread locks are a bit on the expensive side, so you avoid locking unnecessarily by using two condition checks… The first checks to see if the instance is null… now it’s possible that two threads could get through this check before one gets the instance created, so if it’s null you want to lock it to make sure only one thread gets in at once, the

public sealed class MySingleton
{
    private static object _lockObj = new object();
    private static MySingleton instance;
    public static MySingleton Instance
    {
        get
        {
            //only request the access lock if it's null to begin with
            if (instance == null) 
            {
                //obtain the access lock to ensure only one thread gets in at a time
                lock(_lockObj)
                {
                    //check again to see if it's null so if a previous thread created it, don't do it again
                    if(instance == null)
                    {
                        instance = new MySingleton();
                    }
                }
            }
            
            return instance;
        }
    }

    private MySingleton() { }
}

Now, in the above you see that if there’s an instance, it skips right down and returns it. If not, then it obtains a lock… then checks again. This way if a thread is waiting on the lock and gets inside of it, the instance will have already been created by a previous thread. If it’s still null, it creates it. And finally it cascades to the return. This will make the creation of your singleton thread safe.

True, but I don’t think this issue can actually happen in Unity, right?

1 Like

Excellent point Dustin.

@LightStriker - it can depending on what the calling code and the Singleton are doing (ie - not anything that’s interacting with the game world).

I’m fairly sure Unity’s update system is mono-threaded. Yes, you can start multiple sub-threads (on some platform), but they must never interact directly with the main thread.

So in case that singleton derives from MonoBehaviour or ScriptableObject, that thread collision can never happens - you can’t instantiate them outside the main thread anyway.

I guess it could happen with something deriving from System.Object, starting background working thread that would request that instance, while you didn’t make sure it was initialized first.

public void DoSomething()
{
     var instance = MySingleton.Instance;
}


public void CallDoSomethingInThreads()
{
     var t1 = new Thread(new ThreadStart(DoSomething));
     var t2 = new Thread(new ThreadStart(DoSomething));

     t1.Start();
     t2.Start();
     
}

Even in this case it’s unlikely but you get the point.

I know what you mean; in your example the dispatcher could decide to be an ass, delay their execution one cycle and start both at the same time. I could argue that starting background working thread without making sure the data they need in the first place is initialized is a good way to seek trouble too.

Doing;

public void CallDoSomethingInThreads()
{
     object instance = MySingleton.Instance;

     var t1 = new Thread(new ThreadStart(DoSomething));
     var t2 = new Thread(new ThreadStart(DoSomething));

     t1.Start();
     t2.Start();
}

Would prevent the need for a lock and the instance would be initialized before any thread start going around. Frankly, I never encounter that specific issue in Unity, and I’m not sure which design I would prefer.

Completely agree… the example was just for simplicity to understand how it could happen. You’re right, you should make sure it’s instantiated prior to accessing from background worker threads but better safe than sorry. :wink: Also, it wouldn’t even need to be a dispatcher delay… if the singleton takes some time to instantiate because it’s doing something in the constructor, that would be sufficient to correlate the execution between the two threads.

I’ve finished quite a few apps and games, and I’m still not wholly happy with how I do things, so it’s an evolving thing. Something that might work for someone may change in a few years. Where are you, and what gets you results? That’s more important than trying to find the best pencil to work with.

1 Like

I would agree if the Singleton I wrote derived from MonoBeaviour - but it doesn’t :slight_smile: (and for the record - I despise those implementations) It’s certainly less of concern inside the context of Unity.

Lots of good information to think on. Thanks for the responses everyone!

I think for now, since I can’t find much on writing Singletons, I’ll just revamp some of my scripts in my current project to handle things a bit better, instead of trying to rewrite everything into something as huge as a Singleton Gamestate manager (plus, it’s a small game, so it would be kind of overkill!).