11
\$\begingroup\$

I know that having global variables or singleton classes creates cases that can be difficult to test/manage and I have been busted in using those patterns in code but often times you gotta ship.

So are there cases where global variables or singletons are actually useful in game development?

\$\endgroup\$

12 Answers 12

30
\$\begingroup\$

These things can always be useful. Whether or not it's the prettiest or safest solution is another matter, but I think game development involves a certain degree of pragmatism.

\$\endgroup\$
1
  • \$\begingroup\$ Very close to my mantra. \$\endgroup\$ Commented Aug 14, 2010 at 21:49
16
\$\begingroup\$

Definitely a non-exhaustive list, but here goes:

Cons

  • Lifetime management. Singletons and globals may like to start up before key systems (e.g. heap) are initialized, depending on how you set them up. If you ever want to tear them down (useful if you're doing leak tracking, for instance) you have to be careful about the teardown order or start getting into things like phoenix singletons. (See static initialization order fiasco.)
  • Access control. Should rendering only have const access to game data, while update gets non-const? This may be harder to enforce with singletons and globals.
  • State. As Kaj pointed out, it's harder to functionally decompose and transform singletons to work at all in a non-shared-memory architecture. It also has potential performance implications for other types of NUMA systems (accessing memory that isn't local). Singletons usually represent centralized state, and thus are the antithesis of transforms that are made easier by purity.

Either pro or con

  • Concurrency. In a concurrent environment, singletons can either be a pain (you must consider data race / reentrancy issues) or a blessing (it's easier to centralize and reason about locking and resource management). Clever use of things like thread local storage can mitigate potential issues somewhat, but it is not usually an easy problem.
  • Codegen (depending on compiler, architecture, etc): For a naive create-on-first-use singleton implementation, you may be evaluating an extra conditional branch for each access. (This can add up.) Multiple globals used in a function may bloat the literal pool. A "struct of all globals" approach may save space in the literal pool: only one entry to the base of the struct, and then offsets encoded in the load instructions. Finally, if you're avoiding globals and singletons at all costs, you generally need to use at least a little extra memory (registers, stack, or heap) to pass pointers, references, or even copies around.

Pros

  • Simplicity. If you know the cons listed above don't affect you as much (e.g. you're working in a single-threaded environment for a single CPU handheld platform), you avoid some architecture (such as the aforementioned argument-passing). Singletons and globals may be easier to understand for less experienced coders (although it may be easier to use them incorrectly).
  • Lifetime management (again). If you use a "struct of globals" or some mechanism other than a create-on-first-request, you can have easy to read and fine-grained control over initialization and destruction order. You automate this to some degree, or manually manage it (having to manage it may be a pro or a con, depending on the number of globals/singletons and their inter-dependencies).

We use a "struct of global singletons" a lot in our handheld titles. PC and console titles tend to rely on them less; we'll switch more toward an event-driven/messaging architecture. That having been said, the pc/console titles still often use a central TextureManager; since it usually wraps a single shared resource (the texture memory) this has made sense for us.

If you keep your API relatively clean, it might not be too terribly hard to refactor out of (or into!) a singleton pattern when you need...

\$\endgroup\$
2
  • \$\begingroup\$ Great list. Personally I find only negative value in singletons and globals because of the problems that come from shared (likely mutable) state. I do not find the "codegen" problem to be an issue though; you either need to pass the pointers through registers or you need to do some sequence of operations to get it, I think it changes the code vs. data size sightly, but the sum will be about the same. \$\endgroup\$ Commented Aug 20, 2010 at 20:27
  • \$\begingroup\$ Yep, re the codegen, the only thing we've actually seen make a significant difference was going from individual globals to a globaltable: it shrunk the literal pools, and hence the .text section size, by several percent. Which is a very nice thing on small systems. =) The performance benefits of not hitting the dcache quite so much for literals was just a nice (albeit tiny in most cases) side benefit. One other advantage of moving to a global table was the ability to really easily move it to a section that resided in faster memory... \$\endgroup\$
    – leander
    Commented Aug 21, 2010 at 3:54
4
\$\begingroup\$

They can be very useful, especially during prototyping or experimental implementation, but in general we prefer passing references around for manager like structures, that way you at least have some control over where they are accessed from. The biggest problem with globals and singletons (in my opinion) is that they are not very multithread friendly, and code that uses them is much harder to port to non shared memory, like SPU's.

\$\endgroup\$
1
  • \$\begingroup\$ How does that help with threading though? If they are all references to the same class, it's the same thing as a singleton in terms of threading. \$\endgroup\$
    – Augs
    Commented Apr 19, 2022 at 20:37
2
\$\begingroup\$

I would say that the singleton design itself isn't useful at all. Global variables can definetly be useful but I'd rather see them hidden behind a well-written interface so that you aren't aware of their existence. With singletons you are definetly aware of their existence.

I often use global variables for things that need access all through out an engine. My performance tool is one good example which I use all over the engine to call. The calls are simple; ProbeRegister(), ProbeHit() and ProbeScoped(). Their real access is a little bit more tricky and uses global variables for some of their things.

\$\endgroup\$
2
\$\begingroup\$

The main problem with globals, and poorly implemented singletons is obscure construction & deconstruction bugs.

So if you work with primitives which don't have these issues or are very aware of the issue with a pointer. Then they can be used safely.

Globals have their place, same as gotos and should not be dismissed out of hand but instead used with care.

There is a good explanation of it in the Google C++ Style Guide

\$\endgroup\$
2
  • \$\begingroup\$ I disagree that that's the main problem; "don't use globals" goes back further than the existence constructors. I would say there are two major problems with them. First, they complicate reasoning about a program. If I have a function that accesses a global, that function's behavior depends not just on its own arguments, but also on all other functions that access the global. That's a lot more to think about. Second, even if you can hold it all in your head (you can't), they still result in more complicated concurrency problems. \$\endgroup\$
    – user744
    Commented Aug 17, 2010 at 16:47
  • \$\begingroup\$ @Joe the key word is "dependencies." A function that accesses globals (or singletons or any other shared state) has implicit dependencies on all of those things. It is much easier to reason about a bit of code if all of its dependencies are explicit which is what you get when the complete list of dependencies are documented in the argument list. \$\endgroup\$ Commented Aug 20, 2010 at 20:28
1
\$\begingroup\$

Globals are useful while quickly prototyping a system that requires some state between function calls. Once you've established that the system works, move the state into a class and make the functions into methods of that class.

Singletons are useful for causing problems to yourself and others. The more global state you introduce, the more issues you will have with code correctness, maintenance, extensibility, concurrency, etc. Just don't do it.

\$\endgroup\$
1
\$\begingroup\$

I tend to recommend using some sort of DI/IoC container with custom lifetime management instead of singletons (even if you use a "single instance" lifetime manager). At least then it's easy to swap out the implementation to facilitate testing.

\$\endgroup\$
1
  • \$\begingroup\$ For those of you who don't know, DI is "Dependency Injection", and IoC is "Inversion of Control". Link: martinfowler.com/articles/injection.html (I had read about these before but never seen the acronym, so it took me a bit of searching.) +1 for mentioning this excellent transformation, though. \$\endgroup\$
    – leander
    Commented Aug 17, 2010 at 18:07
0
\$\begingroup\$

If you want the memory saving features of a singleton perhaps you could try the flyweight design pattern?

http://en.wikipedia.org/wiki/Flyweight_pattern

As far as multi-thread issues mentioned above it should be fairly simply with some foresight to implement a lock mechanism for resources that may be shared between threads. http://en.wikipedia.org/wiki/Read/write_lock_pattern

\$\endgroup\$
0
\$\begingroup\$

Singletons are a great way to store a shared state in a early prototype.

They are not a silver bullet and come with some problems, but they are a very useful pattern for certain UI/Logic states.

For example in the iOS, you use singletons for getting the [UIApplication sharedApplication], in cocos2d you can use it to get references to certain objects like [CCNotifications sharedManager] and personally I usually start out with a [Game sharedGame] singleton where I can store state that is shared between a lot of different components.

\$\endgroup\$
0
\$\begingroup\$

Wow, this is interesting to me, as I've never personally had a problem with the singleton pattern. My current project is a C++ game engine for the Nintendo DS, and I'm implementing a lot of the hardware access utilities (ie. VRAM Banks, Wifi, the two graphics engines) as singleton instances, because they are intended to wrap global C functions in the underlying library.

\$\endgroup\$
1
  • \$\begingroup\$ My question would be then, why use singletons at all? I see that people like to wrap APIs with singletons or classes that consist only of static functions, but why bother with the class at all? It seems even easier to me to just have the function calls (wrapped as you wish), but then internal to them access the "global" state. E.g. Log::GetInstance()->LogError(...) could just as well be LogError(...) which internally accesses any global state without requiring the client code to know about it. \$\endgroup\$ Commented Aug 20, 2010 at 20:31
0
\$\begingroup\$

Only when you have an item that only has one controller but is handled by several modules.

Such as, for example, the mouse interface. Or joystick interface. Or music player. Or sound player. Or the screen. Or the save files manager.

\$\endgroup\$
-1
\$\begingroup\$

Globals are much faster! So it would fit perfectly for a performance intensive application, like a game.

Singletons are a better global, IMO, and thus the right tool.

Use it sparingly!

\$\endgroup\$
3
  • \$\begingroup\$ Much faster than what? Globals are going to be in some random memory page if they're a pointer, and some fixed but probably distant memory page if they're a value. The compiler can't use any useful peephole optimizations on them. By any measure I can think of, globals are slow. \$\endgroup\$
    – user744
    Commented Aug 17, 2010 at 16:40
  • \$\begingroup\$ Faster than getters and setters and passing references around. But not by much. It does help to keep sizes down, so it would help in some systems. I probably exaggerated when I said 'much', but I am very sceptical of anyone saying that you shouldn't use something. You just need to use your common sense. After all, singletons are nothing more than a static class member, and those are globals. \$\endgroup\$
    – jacmoe
    Commented Aug 17, 2010 at 17:05
  • \$\begingroup\$ But to solve any synchronization problems with globals you need getters/setters for them, so that's not really relevant. The problem with (and rare benefit of) global state is that it's global state, not any concerns about speed or (effectively) syntax aspects of the interface. \$\endgroup\$
    – user744
    Commented Aug 17, 2010 at 17:27

Not the answer you're looking for? Browse other questions tagged .