Jump to content
  • Advertisement
Sign in to follow this  
Akusei

Singletons, Globals and Object communication

This topic is 2583 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I have 2 questions in this post, one is about singletons and the other about exceptions.

This is all C++ BTW.

1. I know everyone hates Singletons but in certain circumstances it makes things easier. The problem I find is that you start to want to use them for a great deal of things and I understand that good code design shouldn't really need them; and there's the problem, I've not found "good code design" yet!

How do you make all subsystems work together in your engine and not use Singletons and avoid passing the same objects all over the place and a ton of nested calls to get what you need, for example:

Getting access to the asset cache subsystem from within a deeply buried object (which is currently the only way I know how to do it), would look something like this
[source lang="cpp"]
void State::Load()
{
CAssetCache &cache = this->Game.GetAssetCache();
}
[/source]

In the above example I'm only really having to call 1 level of functions to get the asset cache but sometimes it can get pretty hairy! Plus, I need to pass the CGame object all over the place. This problem exists for many objects that need to use various subsystems, how do I go about cleanly doing this without Singletons or globals?

2. In C++, do exceptions and exception handling introduce performance issues? I ask because I know when using the .net languages, there is a hit for using try and catch.

Share this post


Link to post
Share on other sites
Advertisement
Firstly the fear of 'all over the place' is, unless you are writing very bad code, never a realised one.

Once you start looking at what your classes and subsystems do you'll quickly see that most of them need access to a limited number of other systems to do their work and, if they need access to a system, you pass them access because it is a dependancy.

To take your 'State' example; 'State' has a dependancy on the 'AssetCache' which means either when the state is constructed or when load is invoked you pass that dependancy in. This way your code becomes 'self documenting' as you can see what your state depends on rather than having it hidden away behind some other type.

There is nothing messy about passing objects around and giving functions parameters; I sometimes wonder where on earth this perception came from in general; parameters are a good thing! OK, maybe if you are passing a large number in then it might be an issue, however at that point you probably want to consider refactoring the function to better express what is should be doing or, as we do in our system where I work, wrap a few parameters in a struct and pass it into the function to use, however this generally happens for complex object constructors (iirc our asset factories do this) and not function calls on instances.

Share this post


Link to post
Share on other sites
Well, the first question to ask yourself is, why do you need to access so many things from so many places? Without more detail about your particular situation it's hard to be specific, but speaking generally, there's almost always a way to minimize the number of systems that have to talk to each other widely. This is the first place to look when trying to clean up a design.

As for exceptions and performance - it depends on the compiler, target platform, optimization settings, and a few other factors. Chances are you shouldn't worry about exception overhead though, for the most part. As long as you're not abusing them all over the place, exceptions are not terribly slow.

Share this post


Link to post
Share on other sites
Ok, this all makes sense, but what about something like this...

I have the following classes
  • CAssetCache
  • CGame
  • CStateSystem
  • CState
    CAssetCache is used by the CGame and CState objects. All of the states (CState objects) are handled by the CStateSystem object but the CStateSystem object doesn't have a need to access CAssetCache. So in this situation I would pass CAssetCache to CStateManager and hold it in a member variable to later pass to each CState object? Is this bad design because it sounds bad to me and is currently how I'm handling it.

    Does my example make sense? I'm trying to illustrate that CStateSystem doesn't ever need to use CAssetCache but is required to hold a pointer or reference to it.

    Thanks

Share this post


Link to post
Share on other sites

Why do you think that's "bad"?


The fact that CStateSystem has a member variable which is assigned a pointer or reference to CAssetCache for the sole purpose of passing it to all CState objects that are instantiated. Am I wrong in thinking this is bad?

Share this post


Link to post
Share on other sites
It makes your dependencies explicit, which is good. If you don't like having the dependency, the flippant answer would be to reorganize the way you handle asset caching ;-)

Share this post


Link to post
Share on other sites
You are correct, it is bad. It cascades the nunber of classes that need to change if the construction of CState changes - this violates OpenClosed principle. Solution is to use a factory for CState. It is responsible for aggregating the set of domain services CState requires such that it can construct them. CAssetCache then takes this factory, which it uses.

Constructor injection is always preferable where possible and is good design. Passing dependencies through an object graph for no reason other than as initialisation arguments to other types means your object model is broken, not the technique. Creating instances is a full time job.

Why do you prepend C to your type names?

Share this post


Link to post
Share on other sites
I know everyone hates Singletons but in certain circumstances it makes things easier ... and there's the problem, I've not found "good code design" yet!
How do you make all subsystems work together in your engine and not use Singletons and avoid passing the same objects all over the place and a ton of nested calls to get what you need, for example:

Getting access to the asset cache subsystem from within a deeply buried object (which is currently the only way I know how to do it), would look something like this
[font="Courier New"]void State::Load() [/font]{
[font="Courier New"] CAssetCache &cache = this->Game.GetAssetCache();[/font]
Having a chain of getters (e.g. this.GetGame().GetCache().GetWidget().GetFooBar()) can be considered a code smell - a sign that the design is a bit unclear.

Likewise, the use of singletons should be considered a sign that the design is spaghetti (ok, I exaggerate, but it's not a good smell laugh.gif)

In this case, in order to [font="Courier New"]Load[/font] a [font="Courier New"]State[/font], you require an asset cache. At the moment this detail (which asset cache to use) is hard-coded and hidden in the function. It seems much more appropriate for the asset cache to be passed as an argument to [font="Courier New"]Load[/font] (e.g. [font="Courier New"]void State::Load(CAssetCache& cache)[/font])

Plus, I need to pass the CGame object all over the place.[/quote]No, you should only pass around the parts of [font="Courier New"]CGame[/font] that are needed.

You should think about the dependencies between different classes in your game, and try to think of these classes as fitting into a dependency pyramid somehow.

e.g. Say we've got a Game object, which owns a TextureSystem and an AssetCache. The TextureSystem owns a bunch of Textures and the AssetCache owns a bunch of assets.
We can decompose this into layers of dependencies:
4) Game has a Texture System
3) Texture System has many Texture
2a) Asset Cache has many Assets
2b) Texture is a Asset
1) Asset

At the bottom, we've got the Asset class, which has no dependencies (it doesn't even know what an AssetCache is). Items like this, down at the bottom of the dependency graph are the most re-usable classes, because they're the most isolated from other code.
Above asset, on the 2nd level, we've got the classes that have a dependency on the Asset class. The Texture class inheritrs from it, and the AssetCache class contains a collection of pointers to Assets.
Above that, the 3rd layer has the TextureSystem, which makes use of an AssetCache in order to load Textures, so it depends on all the layers below it.
Finally, the Game object is at the top, and depends on everything.

When writing a class, you should only interact with other classes that are on the same dependency level or lower -- therefore, nothing should ever be passed a Game class! The Game class should act more like a puppet-master, orchestrating the actions of the classes on the lower levels, without any of them ever being aware that the Game class exists.

e.g. m_Texture = m_TextureSystem.Load( "foo", m_AssetSystem );

Usually when you've got a dependency between two classes that you need to eliminate, the answer is to create a 3rd class, which exists at one level higher in the dependency graph, and use this new class to orchestrate the communication between the other two.
2. In C++, do exceptions and exception handling introduce performance issues? I ask because I know when using the .net languages, there is a hit for using try and catch.[/quote]Whether or not exceptions are useful in C++ is a very controvertial topic. Like any C++ feature, there is an impact on performance for using them, which you should be mindful of.
If you were to use plain old C and implement the exact same mechanisms (e.g. stack unwinding, destruction), you would have the same performance impacts. So, I'd read up on how exception handling is typically implemented in C++ so that you're familiar with the (hidden) code you're actually writing when you type try/throw.

Many game-related libraries choose not to use exceptions because they are not supported well on the compilers used for game consoles.
Why do you prepend C to your type names?
It used to be a Microsoft-encouraged convention, making it extremely widespread, and thus is still very common in a lot of places.

Share this post


Link to post
Share on other sites


2. In C++, do exceptions and exception handling introduce performance issues? I ask because I know when using the .net languages, there is a hit for using try and catch.


"If you can't make it right, make it fast".

Apples to apples - if same type of exceptional conditions are handled in same way, the difference between using built-in exceptions or custom mechanism will be negligible.

Experience shows that for real-time tasks, there often is nothing to handle. If something goes wrong, you either exit the process completely, being unable to meet the real-time constraint or you ignore the error, perhaps by doing a default action.

What tends to be too common is that instead of using exceptions, which do provide somewhat deeper coverage and implicit error handling behavior, alternatives are chosen but not implemented fully. So instead of having a fairly general error handling one trades unknowingly broken code for minimal performance gains.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!