Sign in to follow this  
Nairou

Alternative to global classes?

Recommended Posts

Alright, I'm sure this is a common topic of discussion, but I've been searching the forums a while and haven't found anything that directly answers the issue I'm having. Right now, I have a lot of global classes in my engine, implemented as global functions returning references to static class instances. The idea being that I can access these classes from anywhere, and not have to worry about initialization order. However, I'm also aware of the problems with having lots of globals, and I'm having to deal with a lot of compiler dependencies as well. I'd like to redo this so that each class is created at run time and each can exist on its own, with specific ties to certain parts of the engine rather than being global. The typical "best practice" that I keep reading about. However, the problem I'm running into is with those classes that really are needed by a lot of other engine systems. For example, the logging class, or the settings class. Any part of the engine may need to output to the log or read settings. Other systems (like, say, the sound system) may have a small number of classes that interact with it, but those classes still need a way to access it to begin with (and passing numerous references to a class constructor just feels messy). Does this mean I'm forced to make some or all of my classes global just so they are accessible to classes that aren't directly related to it? The only other idea I read about here in the forums was creating a single global class instance, and creating all of these other "global" classes within that. I can see the merits of doing this, but it is still basically global, the only difference being that you have to go through another class to get to it rather than accessing it directly. I'm assuming that, since games are fairly interconnected systems, there must be some best-practice for allowing certain engine classes to interact with certain other engine classes without making it global for everyone to touch. I'd appreciate any ideas people have on this.

Share this post


Link to post
Share on other sites
Quote:
Original post by Nairou
(and passing numerous references to a class constructor just feels messy).


Your feelers are off. Plus, there shouldn't be (and you haven't described) 'numerous' instances, just a few.


One thing that I tend towards is allowing parameterization of the logger, settings, etc. but using a global by default if none is specified. This allows flexibility to change if needs be, ease of use if the complexity is not necessary, but you're not *just* using the globals so you get the pressure to not include too many into the class.

You'll still need to be wary of what *really* needs a logger/settings to keep coupling/dependencies down, but it seems to be a good pragmatic middle ground in my (admittedly limited) experience.

Share this post


Link to post
Share on other sites
Quote:
Original post by Nairou
I can access these classes from anywhere, and not have to worry about initialization order.

Since when do using globals and not worrying about initialisation order go together? [grin]

Quote:
I'd like to redo this so that each class is created at run time

Well globals are still instantiated at run-time, by using globals you weren't avoiding this.

Quote:
However, the problem I'm running into is with those classes that really are needed by a lot of other engine systems.

This isn't really true, it is however what many people find after they're global or singleton mind-ified.

Quote:
For example, the logging class, or the settings class. Any part of the engine may need to output to the log or read settings. Other systems (like, say, the sound system) may have a small number of classes that interact with it, but those classes still need a way to access it to begin with

The logger has always been a debatable exception to prove the rule. Never the less, globals are becoming more unnacceptable as we move into the massively concurrent processing era.

Just pass references to these into the constructor at startup.

Quote:
(and passing numerous references to a class constructor just feels messy).

Why does it?
That's what constructor arguments are for after all; there shouldn't be that many different things any given class needs to interact with anyway considering that each class should have one a single responsibility.

Any class that require access to, say, the rendering and physics subsystem at the same time is likely taking on too much responsibility and should be split into separate classes.

Quote:
The only other idea I read about here in the forums was creating a single global class instance, and creating all of these other "global" classes within that. I can see the merits of doing this, but it is still basically global, the only difference being that you have to go through another class to get to it rather than accessing it directly.

It's better, but no substitute for doing it right.

Quote:
I'm assuming that, since games are fairly interconnected systems, there must be some best-practice for allowing certain engine classes to interact with certain other engine classes without making it global for everyone to touch.

There are ways of promoting loose coupling, yes.
There are existing design patterns to help with this, for example you could look into the observer and mediator patterns.

Share this post


Link to post
Share on other sites
Quote:
Original post by dmatter
Since when do using globals and not worrying about initialisation order go together? [grin]


Heh, well what I meant was that I didn't have to worry about what order they were initialized in. Using the global functions meant that the classes would be automatically initialized before the first time they were used.

Quote:
Original post by dmatter
Well globals are still instantiated at run-time, by using globals you weren't avoiding this.


Very true, I worded that badly and was referring to the process of initializing the classes manually, in a particular order, instead of just floating around omnipotently in global space.

Quote:
Original post by dmatter
Quote:
(and passing numerous references to a class constructor just feels messy).

Why does it?
That's what constructor arguments are for after all; there shouldn't be that many different things any given class needs to interact with anyway considering that each class should have one a single responsibility.


Well, lets take the renderer for instance. It needs access to the logger, to report any problems it encounters or just to make note of what it is doing. It needs access to the settings, so it knows, for example, what API to use or which display devices to initialize. It also needs a reference to the material manager, to request shaders or textures. That's three classes I can think of off-hand that it will need access to. So you're saying you would put all three of those as references in the renderer's constructor?

Quote:
Original post by dmatter
The logger has always been a debatable exception to prove the rule. Never the less, globals are becoming more unnacceptable as we move into the massively concurrent processing era.

Just pass references to these into the constructor at startup.


I guess I'll have to look into this more then. Other than the fact that it still feels messy to be passing strings of references to every class I initialize, it does solve the main problems I was having.

It almost makes me want to create a common base class which automatically pulls in things like logging and settings, and have the other game components inherit from that. But that is probably just overkill. [grin]

Share this post


Link to post
Share on other sites
Quote:
Heh, well what I meant was that I didn't have to worry about what order they were initialized in. Using the global functions meant that the classes would be automatically initialized before the first time they were used.


A loud kaboom was heard in the server room. According to early reports, there were no survivors.

It seems someone assumed variables were properly initialized when that wasn't the case.

Share this post


Link to post
Share on other sites
Quote:
Original post by Nairou
[It almost makes me want to create a common base class which automatically pulls in things like logging and settings, and have the other game components inherit from that. But that is probably just overkill. [grin]


Maybe you should split your settings up. after all why does the renderer need the physics settings or ai settings?

Share this post


Link to post
Share on other sites
Quote:
Original post by Antheus
A loud kaboom was heard in the server room. According to early reports, there were no survivors.

It seems someone assumed variables were properly initialized when that wasn't the case.


Correct me if I'm wrong, but I don't think that applies to this:

// Global access point
ixSettings& Settings()
{
static ixSettings Settings_;

return Settings_;
}


Quote:
Original post by stonemetal
Maybe you should split your settings up. after all why does the renderer need the physics settings or ai settings?


Very true, but what is the alternative? Having every class open it's own settings? Depending on how settings are stored, that might mean reprocessing the same file multiple times or maintaining multiple copies of settings in memory.

Although, now that I think about it, it would be kinda nice if there was a way for each class to initialize it's own settings or logging object if/when it needed it, and have that object automatically tie into the log or settings used by the rest of the system. Not sure how that would work though, and again it may just be overkill.

Share this post


Link to post
Share on other sites
Quote:
Original post by Nairou
Correct me if I'm wrong, but I don't think that applies to this:

// Global access point
ixSettings& Settings()
{
static ixSettings Settings_;

return Settings_;
}


Yes, this is a typical C++ workaround (other languages have different ways of approaching the subject). However, it does not solve the order-of-destruction issue.

Share this post


Link to post
Share on other sites
Quote:
Well, lets take the renderer for instance. It needs access to the logger, to report any problems it encounters or just to make note of what it is doing. It needs access to the settings, so it knows, for example, what API to use or which display devices to initialize. It also needs a reference to the material manager, to request shaders or textures. That's three classes I can think of off-hand that it will need access to. So you're saying you would put all three of those as references in the renderer's constructor?


This is a common question and different people have different opinions. I can tell you my thoughts.

If I need some subsystem (such as the renderer) to communicate with another subsystem (such as a settings class), I couple them with a higher level system. So, just taking the example of a RENDERER subsystem and a SETTINGS subsystem, I'd have a higher level system (perhaps a GAME system) that tells the SETTINGS subsystem to go read its settings file. It's then the GAME system's job to ask the SETTINGS class what settings it read, and then go set up the RENDERER subsystem with the relevant settings. So for this example, the GAME system would call the SETTINGS class telling it to go read the configuration file. It then asks the SETTINGS class "what's the display resolution supposed to be? how many bits per pixel? etc". Then, the GAME system goes and makes its calls to the RENDERER saying "okay, set up a display with this resolution, this many bits per pixel, etc". The big advantage when you do things like this is now your RENDERER doesn't need to know anything about a SETTINGS subsystem (and vice versa). If you later completely rewrite your SETTINGS subsystem, it doesn't (and, in my opinion, shouldn't ever) cause you to touch any code in your RENDERER. Your RENDERER is now decoupled from your SETTINGS subsystem, and can now be re-used in a different project with a completely different SETTINGS subsystem.

You can decouple almost all of your subsystems using this same method.

For the logger, that's a tougher question. That's a case where I just bite the bullet and pass in a LOGGER subsystem when I create my other subsystems (RENDERER, etc). That method doesn't decouple them as much as I'd like, unfortunately. I think there are more elegant solutions for this out there, though.

Share this post


Link to post
Share on other sites
Quote:
Original post by Nairou
Quote:
Original post by Antheus
A loud kaboom was heard in the server room. According to early reports, there were no survivors.

It seems someone assumed variables were properly initialized when that wasn't the case.


Correct me if I'm wrong, but I don't think that applies to this:


... read the 10.14 section of the link I provided, only one page further down down..

Actually, that entire site is a very good read.

Share this post


Link to post
Share on other sites
Quote:
Original post by Nairou
Quote:
Original post by stonemetal
Maybe you should split your settings up. after all why does the renderer need the physics settings or ai settings?


Very true, but what is the alternative? Having every class open it's own settings? Depending on how settings are stored, that might mean reprocessing the same file multiple times or maintaining multiple copies of settings in memory.


The details of how settings are kept in persistent storage shouldn't be the concern of the sub systems, that concern should belong to a SettingsParser which accepts a stream and reads in the system settings.

It might work like this:
// Settings are distributed into appropriate classes and
// instances of these classes are what get returned by the SettingsParser.
class LoggerSettings;
class VideoSettings;
class PhysicsSettings;
// ...

// Some higher level system constructor
void Engine::Engine()
{
// Load in settings
std::ifstream settingsFile( SETTINGS_PATH );
SettingsParser settings( settingsFile );

// Initialise sub-systems
this->logger = new Logger( settings.getLoggerSettings() );
this->renderer = new Renderer( settings.getVideoSettings(), this->logger );
this->physicsController = new PhysicsController( settings.getPhysicsSettings(), this->logger );
// ...
}

Share this post


Link to post
Share on other sites
You should havea more tree like class structure. If too many class pointers are being passed into your renderer then group some of them appropriately. It's all about abstraction (OO-design). You really shouldn't need any global variables. I probably shouldnt say this but if you really absolutely insist on globals then I'd at least use singletons not global functions (but don't do it there are only very rare cases where this is ok).

Share this post


Link to post
Share on other sites
Another reason why you should not be using globals nor singletons is unit tests! If you want to properly unit test your class, you will need to mock out correctly your logger, settings, etc.

In that regards, functional programming (and referential transparency) has been a real eye opener to me. I think there is nothing wrong in having constructors that take (shared) pointers to various dependent objects. On the contrary, it makes the dependencies much more visible, and that is a winner in the long term.

If the list of objects to be passed becomes too big, you have several solutions:

- If the list is mostly the same all the time, have an object "context" that contains pointers to your subsystems, and pass "context" around
- If the list is really big, or changes dynamically, pass a map of subsystems, and with a bit of template magic / dynamic type, get back your subsystem when you need it.

Share this post


Link to post
Share on other sites
A little more code for a nice and clean code base cannot be under estimated.


void SomeClass::SomeFunction(...)
{
Logger log("logfile.txt");

if (worked)
log << "It worked!";
}



Honestly logging should be simple and basic. You should also have some way of disabling it for speed also.

A lot of people didn't mention internal messaging systems. Then you just pass around this one class to all the others that want to communicate and presto, 99% of your issues solved. You can start off with a very basic one and build into more complex systems.

It seems to be the one topic most people avoid, and when its rarely discussed its at a high level or too complex for most new people to comprehend.

Share this post


Link to post
Share on other sites
I make a CGame class that has all the stuff my game needs to run. Anything that would be a global just ends up being a member variable of the game class. I create an instance of it in my main function, and everything that the game needs is inside, and is taken care of by a simple CGame->Update() call.

Of course, all the little things are still seperated into their own classes. They are all just strung together through CGame.

Share this post


Link to post
Share on other sites
Thanks for all of the excellent suggestions! I think what I'm going to end up doing is like what small_duck and Mike2343 suggest. I've started changing some of the smaller classes (like the logger and the settings) to have static data so that they can be created on the fly when needed without losing everything. And I may create a small structure of the most common class pointers to pass to the constructors of other classes, though this may not end up being necessary now that I'm making the first change.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this