« Globals are underrated | Main | Globals underrated continued »

July 24, 2009

Comments

>It sounds like in Noel's perfect world,
>we have to copy our game state every frame -
>our update function would take the old game
>state as its input and spit out the new game
>state.

It's not a dream anymore. Multicore requires it, because one is typically working on several consecutive frames at once across the cores. A truly high performance game already requires zero globals, except a single ring buffer of frame-local data:

struct gApp
{
Frame m_frame[kFramesInFlight];
};

It's not about the difference between

gGraphics->DrawMesh(myMesh);
and
Graphics::Inst()->DrawMesh(myMesh);

It's about constructing objects with the interfaces they need to do their work. So whatever objects you have in your codebase that need to do graphics work, you construct them with a Graphics* (or IGraphics*) and they use that throughout their lives. That reduces coupling in your client objects, and also restricts the knowledge that your Graphics thing is in fact an awful singleton/global.

Interestingly enough, if you take the following statement: "Speaking of ideals, even if you have globals, you don't want any global to be accessed from anywhere. You do want proper layering." and follow it through, you get a proliferation of pointers (which you kind of decried before), which is really what you want. If something is intrinsically messy, you don't hide it behind globals and global-enabling accessors. Well, you do if you don't want to understand what's going on.

The ServiceLocator thing sounds complex but in actual use it's a joy and not complex at all.

It's not even complex to implement if you look at the code. It's rather short and elegant. I was skeptical myself before I was exposed to it.

Before we basically had gApp and one way or another almost every module would eventually need access to something in gApp. Then it was broken into services and now each module only needs access to the services it uses. Usually that's about 1/10th to 1/15th of what used to be in gApp.

It's helped not only with globals but also with dependencies so that changing one service means only modules that use that service get effected or recompiled. Before, since all the stuff was combined in gApp that meant changing almost anything ended up recompiling everything since gApp both included a ton of stuff and a ton of stuff included gApp.

We've mostly achieved "the ideal" - our renderer only pulls data from the game simulation, and the latter knows nothing about meshes, shaders, textures etc. The renderer itself is split in two parts, and what you'd call the "lower level" doesn't know about the game, it just submits render calls to DirectX.

An additional layer of separation comes from the fact that we use heavily Lua for all game logic and many "engine" parts; for example, our last few games were city building strategies, and the C++ code knows nothing about the economy - has no notion of money, buildings, units, upgrades etc; it also has no notion of dialogs, buttons, scroll bars etc.

And it feels less dirty when you access globals in Lua :-) Not to mention the language+runtime gives you the tools to e.g. inspect all globals used, automatically serialize some of them in savegames etc.

Verify your Comment

Previewing your Comment

This is only a preview. Your comment has not yet been posted.

Working...
Your comment could not be posted. Error type:
Your comment has been saved. Comments are moderated and will not appear until approved by the author. Post another comment

The letters and numbers you entered did not match the image. Please try again.

As a final step before posting your comment, enter the letters and numbers you see in the image below. This prevents automated programs from posting comments.

Having trouble reading this image? View an alternate.

Working...

Post a comment

Comments are moderated, and will not appear until the author has approved them.