Singleton · Design Patterns Revisited · Game Programming Patterns (original) (raw)

| ← Previous Chapter | | ≡ About The Book | | § Contents | | Next Chapter → | | ------------------------------------ | | --------------------- | | ---------------------------- | | ---------------------------- |

Game Programming PatternsDesign Patterns Revisited

This chapter is an anomaly. Every other chapter in this book shows you how to use a design pattern. This chapter shows you how not to use one.

Despite noble intentions, the Singleton pattern described by the Gang of Four usually does more harm than good. They stress that the pattern should be used sparingly, but that message was often lost in translation to the game industry.

Like any pattern, using Singleton where it doesn’t belong is about as helpful as treating a bullet wound with a splint. Since it’s so overused, most of this chapter will be about avoiding singletons, but first, let’s go over the pattern itself.

The Singleton Pattern

Design Patterns summarizes Singleton like this:

Ensure a class has one instance, and provide a global point of access to it.

We’ll split that at “and” and consider each half separately.

Restricting a class to one instance

There are times when a class cannot perform correctly if there is more than one instance of it. The common case is when the class interacts with an external system that maintains its own global state.

Consider a class that wraps an underlying file system API. Because file operations can take a while to complete, our class performs operations asynchronously. This means multiple operations can be running concurrently, so they must be coordinated with each other. If we start one call to create a file and another one to delete that same file, our wrapper needs to be aware of both to make sure they don’t interfere with each other.

To do this, a call into our wrapper needs to have access to every previous operation. If users could freely create instances of our class, one instance would have no way of knowing about operations that other instances started. Enter the singleton. It provides a way for a class to ensure at compile time that there is only a single instance of the class.

Providing a global point of access

Several different systems in the game will use our file system wrapper: logging, content loading, game state saving, etc. If those systems can’t create their own instances of our file system wrapper, how can they get ahold of one?

Singleton provides a solution to this too. In addition to creating the single instance, it also provides a globally available method to get it. This way, anyone anywhere can get their paws on our blessed instance. All together, the classic implementation looks like this:

`class FileSystem { public: static FileSystem& instance() { // Lazy initialize. if (instance_ == NULL) instance_ = new FileSystem(); return *instance_; }

private: FileSystem() {}

static FileSystem* instance_; }; `

The static instance_ member holds an instance of the class, and the private constructor ensures that it is the only one. The public static instance()method grants access to the instance from anywhere in the codebase. It is also responsible for instantiating the singleton instance lazily the first time someone asks for it.

A modern take looks like this:

`class FileSystem { public: static FileSystem& instance() { static FileSystem *instance = new FileSystem(); return *instance; }

private: FileSystem() {} }; `

C++11 mandates that the initializer for a local static variable is only run once, even in the presence of concurrency. So, assuming you’ve got a modern C++ compiler, this code is thread-safe where the first example is not.

Why We Use It

It seems we have a winner. Our file system wrapper is available wherever we need it without the tedium of passing it around everywhere. The class itself cleverly ensures we won’t make a mess of things by instantiating a couple of instances. It’s got some other nice features too:

This takes us about as far as most of us go when it comes to solving a problem like this. We’ve got a file system wrapper. It works reliably. It’s available globally so every place that needs it can get to it. It’s time to check in the code and celebrate with a tasty beverage.

Why We Regret Using It

In the short term, the Singleton pattern is relatively benign. Like many design choices, we pay the cost in the long term. Once we’ve cast a few unnecessary singletons into cold hard code, here’s the trouble we’ve bought ourselves:

It’s a global variable

When games were still written by a couple of guys in a garage, pushing the hardware was more important than ivory-tower software engineering principles. Old-school C and assembly coders used globals and statics without any trouble and shipped good games. As games got bigger and more complex, architecture and maintainability started to become the bottleneck. We struggled to ship games not because of hardware limitations, but because of productivity limitations.

So we moved to languages like C++ and started applying some of the hard-earned wisdom of our software engineer forebears. One lesson we learned is that global variables are bad for a variety of reasons:

Issues like these are enough to scare us away from declaring a global variable, and thus the Singleton pattern too, but that still doesn’t tell us how we_should_ design the game. How do you architect a game without global state?

There are some extensive answers to that question (most of this book in many ways is an answer to just that), but they aren’t apparent or easy to come by. In the meantime, we have to get games out the door. The Singleton pattern looks like a panacea. It’s in a book on object-oriented design patterns, so it _must_be architecturally sound, right? And it lets us design software the way we have been doing for years.

Unfortunately, it’s more placebo than cure. If you scan the list of problems that globals cause, you’ll notice that the Singleton pattern doesn’t solve any of them. That’s because a singleton is global state — it’s just encapsulated in a class.

It solves two problems even when you just have one

The word “and” in the Gang of Four’s description of Singleton is a bit strange. Is this pattern a solution to one problem or two? What if we have only one of those? Ensuring a single instance is useful, but who says we want to let_everyone_ poke at it? Likewise, global access is convenient, but that’s true even for a class that allows multiple instances.

The latter of those two problems, convenient access, is almost always why we turn to the Singleton pattern. Consider a logging class. Most modules in the game can benefit from being able to log diagnostic information. However, passing an instance of our Log class to every single function clutters the method signature and distracts from the intent of the code.

The obvious fix is to make our Log class a singleton. Every function can then go straight to the class itself to get an instance. But when we do that, we inadvertently acquire a strange little restriction. All of a sudden, we can no longer create more than one logger.

At first, this isn’t a problem. We’re writing only a single log file, so we only need one instance anyway. Then, deep in the development cycle, we run into trouble. Everyone on the team has been using the logger for their own diagnostics, and the log file has become a massive dumping ground. Programmers have to wade through pages of text just to find the one entry they care about.

We’d like to fix this by partitioning the logging into multiple files. To do this, we’ll have separate loggers for different game domains: online, UI, audio, gameplay. But we can’t. Not only does our Log class no longer allow us to create multiple instances, that design limitation is entrenched in every single call site that uses it:

Log::instance().write("Some event.");

In order to make our Log class support multiple instantiation (like it originally did), we’ll have to fix both the class itself and every line of code that mentions it. Our convenient access isn’t so convenient anymore.

Lazy initialization takes control away from you

In the desktop PC world of virtual memory and soft performance requirements, lazy initialization is a smart trick. Games are a different animal. Initializing a system can take time: allocating memory, loading resources, etc. If initializing the audio system takes a few hundred milliseconds, we need to control when that’s going to happen. If we let it lazy-initialize itself the first time a sound plays, that could be in the middle of an action-packed part of the game, causing visibly dropped frames and stuttering gameplay.

Likewise, games generally need to closely control how memory is laid out in the heap to avoid fragmentation. If our audio system allocates a chunk of heap when it initializes, we want to know _when_that initialization is going to happen, so that we can control where in the heap that memory will live.

Because of these two problems, most games I’ve seen don’t rely on lazy initialization. Instead, they implement the Singleton pattern like this:

`class FileSystem { public: static FileSystem& instance() { return instance_; }

private: FileSystem() {}

static FileSystem instance_; }; `

That solves the lazy initialization problem, but at the expense of discarding several singleton features that do make it better than a raw global variable. With a static instance, we can no longer use polymorphism, and the class must be constructible at static initialization time. Nor can we free the memory that the instance is using when not needed.

Instead of creating a singleton, what we really have here is a simple static class. That isn’t necessarily a bad thing, but if a static class is all you need, why not get rid of the instance() method entirely and use static functions instead? Calling Foo::bar() is simpler thanFoo::instance().bar(), and also makes it clear that you really are dealing with static memory.

What We Can Do Instead

If I’ve accomplished my goal so far, you’ll think twice before you pull Singleton out of your toolbox the next time you have a problem. But you still have a problem that needs solving. What tool should you pull out? Depending on what you’re trying to do, I have a few options for you to consider, but first…

See if you need the class at all

Many of the singleton classes I see in games are “managers” — those nebulous classes that exist just to babysit other objects. I’ve seen codebases where it seems like every class has a manager: Monster, MonsterManager, Particle, ParticleManager, Sound, SoundManager, ManagerManager. Sometimes, for variety, they’ll throw a “System” or “Engine” in there, but it’s still the same idea.

While caretaker classes are sometimes useful, often they just reflect unfamiliarity with OOP. Consider these two contrived classes:

`class Bullet { public: int getX() const { return x_; } int getY() const { return y_; }

void setX(int x) { x_ = x; } void setY(int y) { y_ = y; }

private: int x_, y_; };

class BulletManager { public: Bullet* create(int x, int y) { Bullet* bullet = new Bullet(); bullet->setX(x); bullet->setY(y);

return bullet;

}

bool isOnScreen(Bullet& bullet) { return bullet.getX() >= 0 && bullet.getX() < SCREEN_WIDTH && bullet.getY() >= 0 && bullet.getY() < SCREEN_HEIGHT; }

void move(Bullet& bullet) { bullet.setX(bullet.getX() + 5); } }; `

Maybe this example is a bit dumb, but I’ve seen plenty of code that reveals a design just like this after you scrape away the crusty details. If you look at this code, it’s natural to think that BulletManager should be a singleton. After all, anything that has a Bullet will need the manager too, and how many instances of BulletManager do you need?

The answer here is zero, actually. Here’s how we solve the “singleton” problem for our manager class:

`class Bullet { public: Bullet(int x, int y) : x_(x), y_(y) {}

bool isOnScreen() { return x_ >= 0 && x_ < SCREEN_WIDTH && y_ >= 0 && y_ < SCREEN_HEIGHT; }

void move() { x_ += 5; }

private: int x_, y_; }; `

There we go. No manager, no problem. Poorly designed singletons are often “helpers” that add functionality to another class. If you can, just move all of that behavior into the class it helps. After all, OOP is about letting objects take care of themselves.

Outside of managers, though, there are other problems where we’d reach to Singleton for a solution. For each of those problems, there are some alternative solutions to consider.

To limit a class to a single instance

This is one half of what the Singleton pattern gives you. As in our file system example, it can be critical to ensure there’s only a single instance of a class. However, that doesn’t necessarily mean we also want to provide public,global access to that instance. We may want to restrict access to certain areas of the code or even make it private to a single class. In those cases, providing a public global point of access weakens the architecture.

We want a way to ensure single instantiation without providing global access. There are a couple of ways to accomplish this. Here’s one:

`class FileSystem { public: FileSystem() { assert(!instantiated_); instantiated_ = true; }

~FileSystem() { instantiated_ = false; }

private: static bool instantiated_; };

bool FileSystem::instantiated_ = false; `

This class allows anyone to construct it, but it will assert and fail if you try to construct more than one instance. As long as the right code creates the instance first, then we’ve ensured no other code can either get at that instance or create their own. The class ensures the single instantiation requirement it cares about, but it doesn’t dictate how the class should be used.

The downside with this implementation is that the check to prevent multiple instantiation is only done at runtime. The Singleton pattern, in contrast, guarantees a single instance at compile time by the very nature of the class’s structure.

To provide convenient access to an instance

Convenient access is the main reason we reach for singletons. They make it easy to get our hands on an object we need to use in a lot of different places. That ease comes at a cost, though — it becomes equally easy to get our hands on the object in places where we don’t want it being used.

The general rule is that we want variables to be as narrowly scoped as possible while still getting the job done. The smaller the scope an object has, the fewer places we need to keep in our head while we’re working with it. Before we take the shotgun approach of a singleton object with global scope, let’s consider other ways our codebase can get access to an object:

What’s Left for Singleton

The question remains, where should we use the real Singleton pattern? Honestly, I’ve never used the full Gang of Four implementation in a game. To ensure single instantiation, I usually simply use a static class. If that doesn’t work, I’ll use a static flag to check at runtime that only one instance of the class is constructed.

There are a couple of other chapters in this book that can also help here. TheSubclass Sandbox pattern gives instances of a class access to some shared state without making it globally available. The Service Locator pattern does make an object globally available, but it gives you more flexibility with how that object is configured.

| ← Previous Chapter | | ≡ About The Book | | § Contents | | Next Chapter → | | ------------------------------------ | | --------------------- | | ---------------------------- | | ---------------------------- |