Alternatives to global variables / passing down references through deep "call-trees"?

Started by
16 comments, last by swiftcoder 9 years ago

When I started programming one of the biggest mysteries to me was how to provide access to things like SoundPlayer, Renderer, ErrorLog, StateMachine etc to very deeply "nested" objects, like the player.

Let's say I create my StateMachine in main(), inside the constructor of StateMachine I initialize it's member: GameState, which in turn initializes it's member Level which in turn initializes it's member Player.

Player wants to play a sound when he jumps, so he would need to call soundPlayer.playSound(SOUND_JUMP). But Player doesn't have a reference to a SoundPlayer object. He also wants to draw himself at the center of the screen, so he needs a reference to a Renderer and the Window, to get it's dimensions. What do?

After a lot of inconclusive articles (or failure to understand them), I decided to just use a static global class, since I wanted to generate results as fast as possible and I kinda got stuck with it.

Actually, I used a mixture of dependency injection through constructors, dependecy injection through methods and global variables...

Here is an example (not actual code):


class Player
{
    private:
        SoundPlayer& _soundPlayer;
        Sprite _sprite;
    public:
    Player(SoundPlayer& soundPlayer) :_soundPlayer(soundPlayer) {} // Dependency injection, storing the reference inside the object
    void Render()
    {
        Renderer* renderer = Renderer::GetInstance(); // Global static horrible singleton!

        renderer->Render(_sprite);
    }
    void Shoot(ParticleSpawner& particleSpawner) // Dependency injection through function argument, not stored
    {
        particleSpawner.Spawn(PARTICLE_BULLET);
    }
};

What I really don't like, are the static GetInstance() function calls everywhere. It just isn't clear that this class depends on the other, it just hides somewhere inside of a random method. At least in the constructor it's clear, you can't even create an instance if you don't pass it's dependency in.

Inside the methods parameters it's already a little more awkward, because you're happy you could create an instance of the object, but then it suddenly needs an instance of another class.

The worst part is when I have a deep "component hierarchy" or whatever it's called. I read the term "call-tree" before and think it fits quite well.

Player needs a reference to an instance of SoundPlayer, otherwise it can't be created, so it's owner ALSO needs that reference to pass it in, but it itself DOESN'T need it, except for creating the player, so it also puts it into its constructor as a required parameter, so now THIS class ALSO needs a reference to SoundPlayer, even though it has no intention of ever using it, but one of its components needs it, to in turn construct one of its components, which in turn needs it to construct one of its components, which in turn... feels kind of wrong to me :|

I got so tired of typing it all out that My state machine just has a #define to pass it all in.

Kind of like this:


class StateMachine
{
    private:
        Renderer& _renderer;
        SoundPlayer& _soundPlayer;
        ErrorLog& _errorLog;
        FileSystem& _fileSystem;
        NetworkSystem& _networkSystem;
        InputDevice& _inputDevice;
        BreadMakingSystem& _breadMakingSystem;
        SystemsManager& _systemsManager;
        // ...
        State* _gameState;
    public:
        StateMachine(Renderer& renderer, SoundPlayer& soundPlayer, ErrorLog& errorLog, FileSystem& fileSystem, NetworkSystem& networkSystem, InputDevice& inputDevice, BreadMakingSystem& breadMakingSystem, SystemsManager& systemsManager):
            _renderer(renderer),
            _soundPlayer(soundPlayer),
            _errorLog(errorLog),
            _fileSystem(fileSystem),
            _networkSystem(networkSystem),
            _inputDevice(inputDevice),
            _breadMakingSystem(breadMakingSystem),
            _systemsManager(systemsManager)
        {
        }
        enum StateType
        {
            STATE_GAME,
            STATE_INTRO,
            STATE_TITLE,
            STATE_OPTIONS
        };
        void SwitchState(StateType state)
        {
#define STUFF_TO_PASS renderer, soundPlayer, errorLog, fileSystem, networkSystem, inputDevice, breadMakingSystem, systemsManager
            switch (state)
            {
                case STATE_GAME:
                    _state = new GameState(STUFF_TO_PASS);
                    break;
                case STATE_INTRO:
                    _state = new IntroState(STUFF_TO_PASS);
                    break;
                case STATE_TITLE:
                    _state = new TitleState(STUFF_TO_PASS);
                    break;
                case STATE_OPTIONS:
                    _state = new OptionsState(STUFF_TO_PASS);
                    break;
            }
        }
};

And inside of the GameStates constructor, again a #define to keep passing it on to it's children.

I was asking myself what the best way to do all of this would be, while refactoring Renderer::GetInstance() out and instead rewriting 100 constructors to accept a reference to Renderer instead. I just thought "What the hell am I doing? Is this really better?!"

Before I go and rewrite everything I want to make sure I do it correctly this time.

This is something that really irritates me about programming, there never seems to be a definitive correct way and I'm only satisfied with perfection, even though I'm nowhere good enough to even achieve anything close to it :P

Should I just leave it like this or is there a better way?

Advertisement
I would say it depend on your goals. If you want to get a game done, leave it like this. If you want to learn new techniques you might want to look in to messaging systems (someone else probably going to give more detailed advice on that). Or in some way do it the other way around, it's not the player that wants to play a sound. It's the 'soundmanager' that wants to play a sound when someones jumping.
That said, since i stopped worrying about things that didn't 'feel right' i actually started finishing projects :) I use globals for almost all subsystems as long as they dont carry state (and even then sometimes). I've had no code-reuse problems, i reuse my globals class and do a bit of copy pasteing :) If i actually find something to be a problem, i try to fix it then, or if the game's almost complete i leave it be and fix it in my next project instead.
As MartinMM said, if you want to make a game, make a game. If it's not broken, don't fix it. You'll waste a lot of valuable time trying to perfect these things (I know this personally.)

That being said, your thought process seems backwards to me. A player wouldn't tell the audio system to play a sound, the engine should do that when it tells the player to jump.

Digressions aside, store your subsystems in a variable and pass that instead of passing each one separately.
There is an in-between zone between the extremes.

It goes by various names, including "service finder" and "well known objects".


The reason globals are generally referred to as 'evil' is that they are often a hidden dependency, and often are shared state.

The shared state problem can usually be addressed by making access immutable -- that is, systems can read from it, but not modify it.

There was a recent post in here about a week ago where someone was bit by a global value like this. It was something akin to "SetGlobalState ( STATE_UNICODE )" that modified the state of every UI element. The person was setting the state in one location, but later in the program discovered it had been reset to single-byte characters.

With an immutable shared state, there may still be a way that the system can change, but that change is heavily controlled. Individual subsystems do not modify the state, but they can use it to read from and discover the current status of things.


Coupling the two, a service finder or well known object that also exhibits immutable state is a common compromise solution.

For example, while the game is running I may have a global pointer that contains pointers to the current services. These are all pointers rather than static objects, created and initialized at an earlier point, and cleaned up nicely at a later point. By the time the main game gets to them, it looks like ::simulator->clocks->elapsedTime or ::simulator->audio->.... or ::simulator->physics->... or whatever else the simulator contains.

It still has problems, it still requires thought. It is still a hidden dependency, and it is still something that unit tests will need to build and configure for tests to work. It is still something that needs to be initialized at some point early on, and if something is wrong the bad pointers can cause a crash. It is still something that can be misused and abused. But as long as these are treated as read-only sources, effectively as a directory of the well-known services provided by the game, and rules are established to decide when values are safe to use and when they are not, it tends to be a good compromise. It is not a singleton, other objects can be created and destroyed as needed. It is not as nice for testing as passing around specific parameters, although you can write testable code by having some object look up the well-known service and then pass the object or values as parameters to the function doing the real work. Common rules include things like not preserving pointers between calls to your function, and not holding the functions over an update; the top-most systems in the game is free to use those time segments to modify and update and replace values, briefly mutating them as changes are needed, then returning to an immutable state when processing resumes.

Even so, it tends to be a reasonable compromise that avoids enormous collections of parameters and enables quick access to external systems, without too much collateral damage from mutable state, shared state, static object lifetimes, or singleton demands.

The best approach is to avoid dependencies, but obviously that can be done only within limits. In the end ever pieces of software work together to implement a solver for a problem. So in reality one simply tries to get closer to the minimum of dependencies, and to make the dependencies explicit.

A first step is to define modules, each one with a well defined API by which clients can call the services of the module. A module need not be a single object, but it can be beneficial if the API is provided by a single object (see e.g. the facade pattern, to some extent also the strategy pattern). The usual name for such modules is "sub-system" here in the forums. The code snippets in the OP show that such modularization seems to be used.

Now if enough sub-systems are available, the problem of disorganization appears again. This can be lowered by using a layered architecture. Sub-systems collaborate with sub-systems in the same layer, they utilize sub-systems of the next lower layer, but they MUST NOT call sub-systems of upper layers (although they MAY respond to them), and they SHOULD NOT utilize sub-systems from the layer after next. Of course, in reality this is more a rule thumb than a strict law.

So far, the above gives a kind of top level organization, but it does not actually solve OP's problem. However, it gives a hint to us: When a lower level sub-system MUST NOT call a higher level one, then the lower level sub-system also has no clue what kind of structures the higher levels are using. That means that top down communication should be done with data structures belonging to the lower levels.

The next thought is about the game loop. The game loop is actually a sequence of update calls on high level sub-systems. It is sequential, because we want clear and stable (as far as possible) world states (i.e. not an oscillation between particular sub-systems, so that the frame is delayed unpredictably!). Also from an efficiency point of view, it is beneficial to run a particular update step on all entities before passing on to the next kind of step.

So in such a scenario we actually have a sub-system (on the game loop level) that runs an update on all of its active entities, and for each prepares a data structure for passing the updated data, and … that's it. Because all interested sub-systems using that same pool of data structures, they already know where to find it. Of course, such a high level sub-system will utilize other sub-systems and this may mean to pass pointers around, also often only at initialization time. But the amount of pointers in each particular case is low, especially since you couple sub-systems instead of (data) objects.

That being said, your thought process seems backwards to me. A player wouldn't tell the audio system to play a sound, the engine should do that when it tells the player to jump.

I agree and I have a hard time thinking outside the box.

My current approach is to have all the logic related to the player inside one class: Player.

In Player::Update() I check if the jump button has been pressed, using a reference to InputDevice that got passed into the constructor and saved as a member, then play the sound the same way, but instead with a SoundPlayer. If I had more things for whoknowswhat I would also put it there smile.png

I don't really know how to really do it from "outside" since I would have to expose a million different things like position, velocity, state etc (which would be the hardest since the Player class itself uses kind of a state machine pattern instead of a variable).

There is an in-between zone between the extremes.

It goes by various names, including "service finder" and "well known objects".

Reminds me of this one:

http://gameprogrammingpatterns.com/service-locator.html

But I don't really see how it is much different from a static singleton. It just goes through one more class to actually fetch the service. The only upside I see is, it can dynamically return different derived classes. (Like the NullSound example).

Now if enough sub-systems are available, the problem of disorganization appears again. This can be lowered by using a layered architecture. Sub-systems collaborate with sub-systems in the same layer, they utilize sub-systems of the next lower layer, but they MUST NOT call sub-systems of upper layers (although they MAY respond to them), and they SHOULD NOT utilize sub-systems from the layer after next. Of course, in reality this is more a rule thumb than a strict law.

Let's make sure we are talking about the same things here, could you explain what exactly you mean by layers and levels, up and down?

The way I see it is, a layer is a scope, like the function body of main() or the scope of a class like GameState. Up is main() and down is something like statemanager.gamestate.level.player, right?

So if the Player wants to be drawn, it should not interact with the Renderer but instead the Renderer should "collect" all Renderables "below" it, or the class that owns Renderer should feed the renderer the Sprites/Renderables?

So something like:


void GameState::Render()
{
    _renderer.Render(_level.GetPlayer().GetSprite());
    _renderer.Render(_level.GetEnemy().GetSprite());
    _renderer.Render(_level.GetPickup().GetSprite());
}

Of course that would need to be made more general, maybe through an Interface with a recursive function like GetAllRenderables() or whatever :)

I don't know it's just an idea.

The way I do it is kind of the opposite, I call stateManager.Render() which calls gameState.Render() which calls level.Render() which calls player.Render() which then gets a pointer to the DirectX device directly and does all its drawing directly in low level code hahaha :) But once I wanted different graphic-layers, for UI and debug output, it became clear that this is not a very good way to do it.

I think you might be violating the principle of a class having 1 sole purpose.
Some food for thought, how about:

Class player
Class input
Class scene/ level or world
Class audio

Class game, with update/ render/ init or something

Now, game::update
{
if(player does something and scene something is state abc) audio.playfx(id)
Etc.

That way your game class (what you might call main), doesn't have to go to deeper trees.
Depending on the size of the game you might get a big game class, but there you can create helper/ subclasses probably.

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me


But I don't really see how it is much different from a static singleton. It just goes through one more class to actually fetch the service.

It makes all the difference in the world.

A static singleton is initialized at a time that is difficult to control. It may be initialized on first use, which may even happen before main() is called. A static singleton is destroyed when static objects are destroyed, after main() has already completed. A singleton class has a strict rule that there can only be one of them, ever, and it is not permitted to ever create another instance.

Under the service locator pattern there is a global pointer, and you ensure through policy that the pointer is valid between certain times. It is initialized at the time you specify, and it can be cleaned up at the time you specify. While that well-known pointer only has a single instance, and that single instance has pointers to other objects, nothing prevents you from having other instances of the objects, you can have any number of them created for any other purpose.

Thus you can establish by policy a well-known set of interfaces, and with policy you guarantee to the game objects that the interfaces are valid when you call their Update() function. But also through policy you make it known that the pointers can change at any other time, and that the values may be different in between successive calls to Update(). They are valid for a limited time only. You can request a pointer to the current audio subsystem and tell it to play an audio clip, but by policy you are not to modify the pointer to the audio subsystem, nor are you are allowed to store the pointer, it is treated as invalid once the Update() function is complete.

Thus, a system of ::simulator->audio->play() is very different from ::audio.play(), because lifetimes are controlled, access is controlled, and the interfaces can be replaced as needed with different objects, including mock objects and test objects, and they can be swapped out if needed during execution.

It still suffers from the problem of a hidden dependency (you didn't explicitly pass the bundle it belongs to) but since the whole purpose was to introduce a hidden dependency so you don't need to pass the parameters everywhere, that's a known concern that can be accepted and understood. You could resolve that by passing the bundle pointer through the objects, but that's a trade off. Adding a parameter everywhere has a tiny cost of passing the parameter but it gets multiplied thousands or even millions of times per second. Thus you take a penalty in the form of needing to know and understand the policy of the hidden dependency, done in order to save the cost of thousands or millions of parameters per second.

One thing that I think it is worth pointing out is that, in practice, with a well designed system I've never once encountered the 'deep call tree' problem with regards to passing things around. That's not to say I haven't seen people get 'bothered' by having to pass things down two levels, instead butchering designs to add a g_Renderer because they didn't want to do it.. but frankly I put that down to lazy thinking.

I would argue that Cozzie has hit the nail on the head with the mentioning of the SRP with regards to what the classes are doing.

Take the 'player' example given. It is controlling the thing it renders, when it renders, the sound it makes and even has some idea about 'shooting' - maybe fine for a simple application but very quickly things like this become and interconnected nightmare.

Many things can be solved by decoupling and abstraction.
Take rendering, for example, instead of the 'player' telling the renderer 'draw this', as things are created a 'renderable' is created and handed off to the renderer. The higher logic never talks to the renderer again but might touch the handle to the renderable to flip internal state which the render can act upon.

Sound it a similar thing; you might have a handle to a sound but instead of instructing the sound system directly to 'play a sound' instead a message might be broadcast to say 'play this handle' via the message system. This decouples you from knowing if the sound system even exists AND allows other things to 'hook in' to listen for events.

Shooting is another decoupled area; instead of the player 'shooting' the player might well have a weapon to which the 'shoot' logic is handed off to. That in turn could have some effect handles which is sends a message out to say 'hey, do this'.

At best the 'player' in these situations acts as a container and coordinator, it might not even have the logic internally to do things like listen to input; instead an 'input controller' might be assigned which could be a local input, an AI input or even a remote player driving the same logic and actions.

The point I'm trying to drive at is that, in many cases, when people think they 'need everything everywhere' or 'have to pass it down loads of layers' the truth of the matter is the design is often at fault. Be that down to a lack of experience or not is another matter, but across a number of (AAA) games now and a few engines which have avoided globals in favour of locally passed in parameters I've never experienced either of those situations play out with a good design. (And in one case the removal of a 'global' system for one locally passed down even simplified the codebase and made life easier for all concerned. )


Let's make sure we are talking about the same things here, could you explain what exactly you mean by layers and levels, up and down?
The way I see it is, a layer is a scope, like the function body of main() or the scope of a class like GameState. Up is main() and down is something like statemanager.gamestate.level.player, right?
So if the Player wants to be drawn, it should not interact with the Renderer but instead the Renderer should "collect" all Renderables "below" it, or the class that owns Renderer should feed the renderer the Sprites/Renderables?

Well, let me give an example where several sub-systems are mentioned.

The player manipulates its input device, so input is generated. The Input sub-system fetches the raw input, preprocesses / prepares it and stores it into the game's input queue. Notice that the Input sub-system does not call any other sub-system. Somewhere at the beginning of the game loop the PlayerController looks into the input queue and determines whether current input situation matches one or more of its configurations. Notice that this can be understood so that the PlayerController accesses a service of the Input sub-system, namely its input queue, so a higher level system (PlayerController) utilizes a lower level system (Input).

Let's say that the PlayerController determines an input situation and hence generates / updates a motion data structure for "intention for forward walking". Notice that such intentions may also origin from another sub-system, namely AI for NPCs. The difference is that AI usually outputs such intention like "move to location X". However, both these are examples of all possible intentions that address the Movement sub-system. When that sub-system runs its update, it investigates the intentions and checks whether they are "physically" possible. If not then the intention is cleared and ignored. Notice that the lower level sub-system (Movement) does not communicate directly with a higher level one (PlayerController or AI), instead perhaps replying to AI by canceling an intention.

The Movement sub-system updates the movement structure to reflect the new intention (assuming that it has passed the possibility check). It updates the belonging placement data structure accordingly to the current motion.

Later in the game loop the animation sub-system is updated. It iterates the animated sprites and adapts their drawable, i.e. determines the valid slice from the texture and such.

Later on in the game loop, (visual) rendering is invoked. The upper layer of rendering investigates all sprites (notice: no distinction to PC, NPC, or whatever) and does visibility culling. Any sprite that passes is enqueued into a rendering queue, not as sprite but as drawable (this rect, this texture, this texture rect; something like that). Then the lower layer renderer is invoked. This is the renderer that actually knows about D3D, OpenGL, or whatever. It iterates the queue of drawables and generates graphic API calls from it. Notice that the lower layer of rendering is fed with low level data (close to meshes and textures) which also is ready to use (placed where needed, animated if necessary, and so on). Again no need for it to communicate with any higher sub-system.

This topic is closed to new replies.

Advertisement