Alternative to global classes?

Started by
14 comments, last by Nairou 16 years, 1 month ago
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.
Advertisement
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.

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.
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]
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.
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?
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 pointixSettings& 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.
Quote:Original post by Nairou
Correct me if I'm wrong, but I don't think that applies to this:

// Global access pointixSettings& 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.
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.
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.

This topic is closed to new replies.

Advertisement