OO approach to reduce/avoid reliance on global variables / Singletons / redundant passing by reference?

Started by
7 comments, last by rip-off 12 years, 10 months ago
I've read a few threads on this topic, however they always end in the same way, stay away from singletons and global variabels, create loosely coupled classes. Obviously thats what everyone should strive for, but has anyone figured out a practical way of acheiving that for the core classes of a game project? For example im using a GameLoopManager, PhysicsManager, InputManager, GraphicsManager, CameraManager, PlayerManager etc. etc. but I find that its impossible to obtain loose coupling between these classes. I reach a point where everything must have access to everything else, and might as well make everything a Singleton for global access.

I've heard some suggest to simply supply whichever functions with a reference to any needed class as an argument, but in the end im basically giving a pointer of every function to every function, which in my opinion leaves all the problems with global variables (universal access along with messy/ hard to interpret code/ butterflyeffect code.

Obviously i need to reduce the reliance of classes upon eachother, but no matter how many times i go over the code, i just get stuck at the same points. For example the inputManager needs access to the camera and player manager, the graphics manager needs access to the player and camera managers, the physics manager needs access to players, npc, enemy and environment managers, etc etc.

I've been reading Game Engine Architecture by Jason Gregory and he suggests using Singletons.

Has anyone found a decent apprach to have a loosely coupled class breakdown for a game project? Is it possible?
Advertisement
Firstly the usage of a 'manager' class often indicates a design problem as they generally end up doing too much.

Secondly; really? those things all need access to them?

Input needs no access to the camera system or player system; they might well want access to them in a pull model, or to subscribe to events to react to them but there is no deep linkage required.
Same deal with the 'graphics manager' (whatever that means); it doesn't need to know about 'players' all it needs to know about is view points and objects in the world that can be rendered. No direct link is required here and view points can be passed in.
The physics system has the same deal; why does it need to know about NPCs? All it cares about are physics meshes in the world which it is simulating and any call backs required to be executed when events happen in the world (such as collisions).

In general subsystems can be setup with very little information, and can be further decoupled with things like event systems which pass messages about between unrelated systems.

As for singletons; they are never the answer. If you want to make something a global then make it a damned global. Wrapping things behind singletons is just asking for trouble and hiding the problem away.

So, yes it IS possible to do.. it can be hard but it's certainly doable to decouple these things, you just have to think long and hard about why things need access; chances are you'll find things you thought needed access really didn't.
I'm guessing currently you do something like this:


class GraphicsManager
{
std::vector<Player> players;
//...
void takeCareOfGraphicsForNPC(Player *player);
void drawAll();
//...
}


This is illogical, since the player relies on your graphics device to function appropriately, not the other way around.

So instead, do it like this:

class Drawable
{
//...
}

class GraphicsManager
{
//...
void draw(const Drawable &d);
//...
}
class Player : public Drawable
{
//...
void draw(const GraphicManager &graphics);
//...
}

class PlayerManager
{
std::vector<Player*> players;
void takeCareOfPlayer(Player *p)
}



and so forth. This is just a rough idea of course. Player wouldn't just be a drawable, it would maybe inherit from an Actor class first, and you wouldn't make an entire PlayerManager just to take care of your Player objects, you'd do some kind of generic Actor manager. Of course there are alternative approaches: A Player might not know how to draw itself at all and just contain its data, and the PlayerManager would be responsible to extract that data, interpret it, put it into a drawable and hand it over to the GraphicsManager. But that's personal taste imo, it doesn't really change anything in terms of effort of maintenance etc.

I can't go into more specifics since this is specific to what you're trying to do, but I think you should get my point.
Is a Context class collecting pointers to existing Managers not advisable?
For me, this has worked well so far and makes easy state changes having a Stack of Contexts that can be pushed on-to or cleared etc.



Also remember to use static functions if you are using your Manager class as an interface to manipulate other objects so you're not always implicitly including all the Manager's own members.


Brushing up on some Functional Programming concepts is advisable too (at least it was for me) in making your code more compositional and less spaghetti-fied
I find that this article does a very good job of explaining why singletons should be avoided and several alternatives that can be used for different circumstances.

I've heard some suggest to simply supply whichever functions with a reference to any needed class as an argument, but in the end im basically giving a pointer of every function to every function... For example the inputManager needs access to the camera and player manager, the graphics manager needs access to the player and camera managers, the physics manager needs access to players, npc, enemy and environment managers, etc etc.
.... I reach a point where everything must have access to everything else, and might as well make everything a Singleton for global access.
It sounds like your classes do too many things. Each class should have a single responsibility -- if a class has two jobs, split it into two classes.

Once you've done that, the number of things that each class actually relies on becomes clearer.

For example, it sounds like your input-manager currently accepts user input, and then uses that input to control a camera -- that's two jobs.
You can break this into an input device, a camera, and a camera controller. The input device and the camera have zero dependencies. The camera controller depends on the input device and the camera.

It can be a good idea to map out your dependencies like this -- in this case, camera and input-device would be at the bottom of a graph, with camera-controller above them.
As a rule of thumb, the lower-down something appears in your graph, the more reusable it is. The higher up something appears, the more specialised it is.

At work, we actually run a python script over our code-base to generate a picture of our dependency graph. We try and keep 'engine' level stuff as far down the graph as possible, with as few inter-dependencies as possible, and 'game' level code generally floats upwards (as it depends on engine level code).
For example im using a GameLoopManager, PhysicsManager, InputManager, GraphicsManager, CameraManager, PlayerManager etc. etc. but I find that its impossible to obtain loose coupling between these classes.[/quote]Going back to the dependency graph, it's pretty common for the "game loop manager" to be right at the top of this graph, because it's responsible for creating all of the game systems, executing them, and shutting them down.

However, a few layers down this graph, your physics/graphics/input etc managers probably don't need to know about each other. As said above, the tendency to name everything "manager" also suggests to us that these classes have too many responsibilities at the moment -- splitting them up may make the actual dependencies more clear.

As in the above example, you can often remove a dependency between two classes by adding a 3rd class that knows about both of them.
A 'player' might actually be an example of one of these classes - a player might own a graphical object and also own a physics object. Physics/graphics don't have to know about each other if this 3rd class handles the interactions between them.
Has anyone found a decent apprach to have a loosely coupled class breakdown for a game project? Is it possible?[/quote]Yes, it's possible. Keep studying software engineering and you'll get there eventually.

As said above, the tendency to name everything "manager" also suggests to us that these classes have too many responsibilities at the moment

What I noticed is that 'manager' classes are often named this way for the lack of a better term, not necessarily because they do too much. Often they are merely small collectors or dispatchers connecting unrelated subsystems in some way. I've also noticed a trend of calling classes 'managers' if they encapsulate some kind of register mechanism to which other classes can connect as listeners or providers.

Difficult to say if this applies to the OP, of course.
Pretty much the only good use for globals are compile time constant variables that can properly be initialized without calling any code before main. Basically integers, floats, strings, and simd vector intrinsics if you are using those. Basically if it would work in plain C it will be well behaved as a global, and ideally all of this data is const so it can be loaded as 'data' and not need code to execute to construct the objects before main (bloating your exe size and slowing down your startup time).

C++ gets messy, since globals with constructors get their constructors called before main, and each .obj file is initialized in an undefined order (without special work by you at the link step), making the initialization of globals that rely on other globals possible and dangerous. If you absolutely must make a class based object a global, make it a pointer and create a function to initialize it and place a call to that function at an appropriate place as you are starting up.

Things can also get really messy when new is called before main, especially when you are hooking new/delete yourself for your own memory management, and some random thing calls new before your allocator is initialized. This matters if you want your allocator to not have to test on every single malloc call if it has been initialized already or not.

The real big last thing to consider is that non-const globals are treated as if they had the word volatile in front of them. For example, if you allocate a global renderer interface, and use that, every single virtual function call re-loads the interface pointer before jumping into the vtable, as does every single piece of code that dereferences the global directly. You can alleviate this problem most of the time by making a local copy of the pointer, but that takes additional coding which isn't very nice.

My other huge big rule is no static data in functions. Ever. They are actually a pair of globals: the data you made, and a hidden boolean variable to indicate if the variable has been initialized yet. This is big trouble in a threaded environment if two threads are hitting a function containing this construct at the same time. In addition, every single execution of that function will incur a boolean test and potential performance hit from a branch misprediction, while testing if the static is initialized.

There is an exception for avoiding globals though: If you are working with SIMD code (__m128) etc do not ever use the _mm_set_XXX functions (except for setzero), make a const global for all your constants instead for the code to use.
http://www.gearboxsoftware.com/

What I noticed is that 'manager' classes are often named this way for the lack of a better term, not necessarily because they do too much. Often they are merely small collectors or dispatchers connecting unrelated subsystems in some way. I've also noticed a trend of calling classes 'managers' if they encapsulate some kind of register mechanism to which other classes can connect as listeners or providers.

The FooManager might start out as a small class that does something with Foos, but next time there is some Foo related functionality that needs to be added it can often get thrown in with the FooManager, as the name "Manager" doesn't really exclude any type of functionality.

If the class had been named something else (e.g. TextureCache rather than TextureManager or something) this new functionality might be more obviously unrelated, and a new class created for it instead of adding it somewhere that was convenient.

Essentially I think if you're having difficulty coming up with a good name for the class then maybe its purpose isn't well defined, and I think this could cause problems down the road. It might not have too many responsibilities, but because they are ill defined it could easily end up with too many. Of course in any moving codebase the problem of dealing with additional functionality requires discipline. It isn't just vaguely named classes that can end up with unrelated responsibilities.

This topic is closed to new replies.

Advertisement