views:

978

answers:

11

I know my gut reaction to global variables is "badd!" but in the two game development courses I've taken at my college globals were used extensively, and now in the DirectX 9 game programming tutorial I am using (www.directxtutorial.com) I'm being told globals are okay in game programming ...? The site also recommends using only structs if you can when doing game programming to help keep things simple.

I'm really confused on this issue, and all the research I've been trying to do is very confusing. I realize there are issues when using global variables (threading issues, they make code harder to maintain, the state of them is hard to track etc) but also there is a cost associated with not using globals, I'd have to pass a loooot of information around very often which can be confusing and I imagine time-costing, although I guess pointers would speed the process up (this is my first time writing a game in C++.) Anyway, I realize there is probably no "right" or "wrong" answer here since both ways work, but I want my code to be as proper as I can so any input would be good, thank you very much!

+6  A: 

In this respect, there's no difference between games and other programs. While arguably OK in small examples given in elementary courses, global variables are strongly discouraged in real programs.

So if you want to write correct, readable and maintainable code, stay away from global variables as much as possible.

Eli Bendersky
+26  A: 

The trouble with games and globals is that games (nowadays) are threaded at engine level. Game developers using an engine use the engine's abstractions rather than directly programming concurrency (IIRC). In many of the highlevel languages such as C++, threads sharing state is complex. When many concurrent processes share a common resource they have to make sure they don't tread on eachother's toes.

To get around this, you use concurrency control such as mutex and various locks. This in effect makes asynchronous critical sections of code access shared state in a synchronous manner for writing. The topic of concurrency control is too much to explain fully here.

Suffice to say, if threads run with global variables, it makes debugging very hard, as concurrency bugs are a nightmare (think, "which thread wrote that? Who holds that lock?").

There are exceptions in games programming API such as OpenGL and DX. If your shared data/globals are pointers to DX or OpenGL graphics contexts then typically this maps down to GPU operations which don't suffer so much from the same trouble.

Just be careful. Keeping objects representing 'player' or 'zombie' or whatever, and sharing them between threads can be tricky. Spawn 'player' threads and 'zombie group' threads instead and have a robust concurrency abstraction between them based on message passing rather than accessing those object's state across the thread/critical section boundary.

Saying all that, I do agree with the "Say no to globals" point made below.

For more on the complexities of threads and shared state see:

1 POSIX Threads API - I know it is POSIX, but provides a good idea that translates to other API
2 Wikipedia's excellent range of articles on concurrency control mechanisms
3 The Dining Philosopher's problem (and many others)
4 ThreadMentor tutorials and articles on threading
5 Another Intel article, but more of a marketing thing.
6 An ACM article on building multi-threaded game engines

Aiden Bell
+1 - @Aiden - I like your reply.
ChrisBD
Thank you. It will take me a while to read through all of those links but your message makes sense and it seems like a much more robust system than what I had originally planned anyway. So, thanks again!
Joe.F
+2  A: 

Most games aren't multi-threaded, although newer titles are going down that route, and so they've managed to get away with it so far. Saying that globals are okay in games is like not bothering to fix the brakes on your car because you only drive at 10mph! It's bad practice which ever way you look at it. You only have to look at the number of bugs in games to see examples of this.

ChrisBD
Xbox360 has 3 dual-core processors and the PS3 has a main processor and 6 vector processors. Both also have additional dedicated graphics processors. In what sense are "most" games on these platforms not multithreaded?
dash-tom-bang
@dash-tom-bang: To be fair, he didn't say "most games on the latest generation of hardware", nor even "most AAA games", he just said "most games", and therefore I think the statement holds true, even if you only count commercial games of the last 10 years, for example.
Kylotan
+16  A: 

Have worked on AAA game titles, I can tell you that globals should be eradicated immediately before they spread like a cancer. I've seen them corrupt an I/O subsystem so completely that it had to be wholly thrown out to be rewritten.

Say no to globals. Always.

Shaggy Frog
+1 "Say no to globals" -- There is a t-shirt there.
Aiden Bell
C++ isn't fully OO. Trying to force everything into OO doesn't really work. Same kind of goes for Win32, I guess. Finding globals scattered all over the place is bad design, but that's not the same thing.
John
+1, I cannot agree more, globals always lead to bad habbits and cluttering, not matter what the project is. @John: you can easyily combat copius amounts of globals using singlton objects/static methods to delegate over any global data you might need(such as a DC), this keeps stuff centralized, clean and prevents duplication, C++ 'OO enough' to handle that just fine :P
Necrolis
@John, I don't see the relevance here - you don't need OO to remove global variables. You can write a C app with everything in functions and structs and not a global in sight.
Kylotan
@Necrolis: wrapping globals in a singleton is just pretending you fixed the problem. Forcing it OO doesn't make it better.
John
@Kylotan - you mean like a Haskell-style way of C-programming? I don't think you can write a Windows C program without having to store cross-function data, since you aren't calling all the functions - Windows calls callbacks and you need the data.
John
@John I agree, Singletons are at least as bad as globals.
FredOverflow
Hum... but what's the difference between a Singleton and a global ??? globals are not bad because they are labelled "globals" they are bad because they introduce a globally shared state and I fail to see what's the Singleton is bringing to the table for that particular problem (disregarding initialization/destruction issues).
Matthieu M.
@Matthieu M. there isn't much difference between the two. One just has a fancy name.
Shaggy Frog
@John - I have no idea exactly what you're referring to I'm afraid. I've written many Windows programs without extensive use of callbacks, and the one or two callbacks I did use typically had some user-supplied context that you can use, typically via an LPVOID or the like.
Kylotan
singletons are better than globals because they prevent namespace cluttering, and they allow a lot more checking on the global they wrap(ie: prevent bad/invalid assignments), with things like __declspec(property) in MSVC. you can also group related globals a lot more efficiently, and lock them for synchronicity. I'm by no means saying you should create singltons all willy nilly just to replace globals, but in situations when you really must provide a global level state(such as Lua script state), then they provide the needed security
Necrolis
@Necrolis make no mistake, a Singleton *is* a global.
Shaggy Frog
oh, I'm not saying it is, i'm just saying its a little safer than a raw extern'd variable, I don't even use singletons for this, just thought I'd explore the avenue :)
Necrolis
Why must a Lua script state be a singleton? There's nothing intrinsic to a lua_State that means you must always have one, only one, and that it should be accessible from all parts of your program and all threads. Lots of people I know use multiple lua_States, for example.
Kylotan
+4  A: 

All answers until now deal with the globals/threads issue, but I will add my 2 cents to the struct/class (understood as all public attributes/private attributes + methods) discussion. Preferring structs over classes on the grounds of that being simpler is in the same line of thought of preferring assembler over C++ on the grounds of that being a simpler language.

The fact that you have to think on how your entities are going to be used and provide methods for it makes the concrete entity a little more complex, but greatly simplifies the rest of the code and maintainability. The whole point of encapsulation is that it simplifies the program by providing clear ways in that your data can be modified while maintaining your objects invariants. You control the entry points and what can happen there. Having all attributes public imply that any part of the code can have a small innocent error (forgot to check condition X) and break your invariants completely (health below 0, but no 'death' processing being triggered)

The other common discussion is performance: If I just need to update a datum, then having to call a method will impact my performance. Not really. If methods are simple and you provide them in the header as inlines (inside the class body or outside with the inline keyword), the compiler will be able to copy those instructions to each use place. You get the guarantee that the compiler will not leave out any check by mistake, and no impact in performance.

David Rodríguez - dribeas
Using inlines can still affect performance. If you inline so much that the critical section of your code no longer fits in cache, you may find speed losses due to cache misses.
Shaggy Frog
@Shaggy Frog: +1 (+10 if I could): It's a horrible trap to fall into too; very difficult to dig yourself out, especially as colleagues tend to try to “reoptimize” things back into the local optimum while you're getting out of the hole. Sometimes, there's no alternative but to read the damn disassembled code, realize that you're in trouble, and start telling the compiler to stop inlining.
Donal Fellows
@David structs and classes are basically equivalent in C++
FredOverflow
@FredOverflow: I know they are the same, that is what the comment in the parenthesis meant to say: in the rest of the answer the struct will be used as 'all public attributes' while class as 'private attributes + methods'. I guess it was not clear enough.
David Rodríguez - dribeas
@Shaggy Frog: I know about the problem of excessive inlining. The fact is that in C++ having the methods marked as inline does not mean that they will actually be inlined. The compiler will decide when to inline and when not to, and the compiler will most probably make a better choice --for sure, it knows better than I do.
David Rodríguez - dribeas
@Shaggy Frog: if you're manually copying the method instead of having the compiler inline it, you're not earning anything in term of performance but a whole load of hurt in term of maintenance... anyway `inline` is a hint, hell even VS `__forceinline` is a hint ;)
Matthieu M.
Re: comments about inlining being a hint. Yes, of course. That doesn't change my comment, though; if the compiler decides to inline so much that the critical section grows too big, you'll have problems.
Shaggy Frog
+1  A: 

I would say that the advice about globals and 'keeping things simple' is probably a mixture of being easier to learn and old fashioned thinking about performance. When I was being taught about game programming I remember being told that C++ wasn't advised for games as it would be too slow but I've worked on multiple games using every facet of C++ which proves that isn't true.

I would add to everyone's answers here that globals are to be avoided where possible, I wouldn't be afraid to use whatever you need from C++ to make your code understandable and easy to use. If you come up against a performance problem then profile that specific issue and I'll bet that most of the time you won't need to remove use of a C++ feature but just think about your problem better. There may still be some platforms around that require pure C but I don't really have experience of them, even the Gameboy Advance seemed to deal with C++ quite nicely.

identitycrisisuk
+2  A: 

If in class A you need to access data D, instead of setting D global, you'd better put into A a reference to D.

Lo'oris
+1  A: 

Globals are NOT intrinsically bad. In C for instance they are part of the language's normal use... and since C++ builds on C they still have a place.

On an aesthetic level, it's better to avoid them where you can sensibly make them part of a class, but if all you do is wrap a bunch of globals into a singleton, you made things worse because at least with globals it's obvious what the point is.

Be careful, but for some things it makes less sense to force OO concepts on what is actually a global value.

John
Especially in C++, gobals are a **huge** problem (think order of initialization and destruction!). Furthermore, better solutions exist in C++. There are a few notable exceptions (`cin`, `cout`) but apart from these, globals should be avoided completely in C++.
Konrad Rudolph
C++ is NOT a pure OO language and you are using it wrong trying to force this approach. It offers a mix of procedural/OO. If you want a proper OO language, use one instead.
John
@John C++ also supports functional programming (for example via `boost::bind`, `boost::lambda` and now anonymous functions and closures in C++0x) and template metaprogramming, among others :)
FredOverflow
+1  A: 

Having read a bit more what you posted though:

I'm being told globals are okay in game programming ...? The site also recommends using only structs if you can when doing game programming to help keep things simple.

Games code is no different from other code really. Gratuitous use of globals is bad regardless. And as for 'only use structs', that is just a load of crap. Approach game development on the same principles as any other software - you may find places where you need to bend this but they should be the exception, typically when dealing with low-level hardware issues.

John
Game code has much different priorities than other types of code...
Inverse
That's just an excuse game programmers give to a)make themselves feel special b)excuse bad design. I should know, I used to _be_ one.
John
+1  A: 

The metaissue here is that of state. How much state does any given function depend on, and how much does it change? Then consider how much state is implicit versus explicit, and cross that with the inspect vs. change.

If you have a function/method/whatever that is called DoStuff(), you have no idea from the outside what it depends on, what it needs, and what's going to happen to the shared state. If this is a class member, you also have no idea how that object's state is going to mutate. This is bad.

Contrast to something like cosf(2), this function is understood not to change any global state, and any state that it requires (lookup tables for example) are hidden from view and have no effect on your program-at-large. This is a function that computes a value based on what you give it and it returns that value. It changes no state.

Class member functions then have the opportunity to step up some problems. There's a huge difference between

myObject.hitpoints -= 4;
myObject.UpdateHealth();

and

myObject.TakeDamage(4);

In the first example, an external operation is changing some state that one of its member functions implicitly depends upon. The fact is that these two lines can be separated by many other lines of code begins to make it non-obvious what's going to happen in the UpdateHealth call, even if outside of the subtraction it is the same as the TakeDamage call. Encapsulating the state changes (in the second example) implies that the specifics of the state changes aren't important to the outside world, and hopefully they're not. In the first example, the state changes are explicitly important to the outside world, and this is really no different than setting some globals and calling a function that uses those globals. E.g. hopefully you'd never see

extern float value_to_sqrt;
value_to_sqrt = 2.0f;
sqrt();  // reads the global value_to_sqrt
extern float sqrt_value; // has the results of the sqrt.

And yet, how many people do exactly this sort of thing in other contexts? (Considering especially that class instance state is "global" in regards to that particular instance.)

So- prefer giving explicit instruction to your function calls, and prefer that they return the results directly rather than having to explicitly set state before calling a function and then checking other state after it returns.

The more state dependencies a bit of code has, the harder it'll be to make it multithread safe, but that has already been covered above. The point I want to make is that the problem isn't so much globals but more the visibility of the collection of state that is required for a bit of code to operate (and subsequently how much other code also depends on that state).

dash-tom-bang
+1  A: 

Two specific issues that I've encountered in the past:

First: If you're attempting to separate e.g. render phase (const access to most game state) from logic phase (non-const access to most game state) for whatever reason (decoupling render rate from logic rate, synchronizing game state across a network, recording and playback of gameplay at a fixed point in the frame, etc), globals make it very hard to enforce that.

Problems tend to creep in and become hard to debug and eradicate. This also has implications for threaded renderers separate from game logic, or the like (the other answers cover this topic thoroughly).

Second: The presence of many globals tends to bloat the literal pool, which the compiler typically places after each function.

If you get to your state through either a single "struct GlobalTable" which holds globals or a collection of methods on an object or the like, your literal pool tends to be a lot smaller, decreasing the size of the .text section in your executable.

This is mostly a concern for instruction set architectures that can't embed load targets directly into instructions (see e.g. fixed-width ARM or Thumb version 1 instruction encoding on ARM processors). Even on modern processors I'd wager you'll get slightly smaller codegen.

It also hurts doubly when your instruction and data caches are separate (again, as on some ARM processors); you'll tend to get two cachelines loaded where you might only need one with a unified cache. Since the literal pool may count as data, and won't always start on a cacheline boundary, the same cacheline might have to be loaded both into the i-cache and d-cache.

This may count as a "microoptimization", but it's a transform that's relatively easily applied to an entire codebase (e.g. extern struct GlobalTable { /* ... */ } the; and then replace all g_foo with the.foo)... And we've seen code size decrease between 5 and 10 percent in some cases, with a corresponding boost to performance due to keeping the caches cleaner.

leander
I had no idea about any of that, thank you very much for sharing it! I'm always interested in learning how to optimize code, and this sounds like a great trick :D
Joe.F
My pleasure. Remember, though, as with anything that looks like a "trick" you should always make sure you need it before applying it -- use a profiler, or check disasm before and after. It's worthwhile to try things like this as experiments (if you're not on a deadline), to gain better understanding, but not always worth keeping the results. =) Stuff like this tends to vary so much from compiler to compiler and from CPU to cpu that the "show me the profile" mantra is always worth keeping in mind.
leander