Skip to main content
GameDev.net gamedev.net
Using GameDev.net for your class this semester?
Learn more →
🔒 Locked

Engine design, global interfaces

Started by Funkymunky Jun 5, 2015 at 3:56 PM 53 replies 9.9k views
Original Post
Funkymunky
Funkymunky

I am currently looking to redesign my "engine" (which is mostly just a wrapper for several graphics APIs). I'm re-thinking the problem of how to have certain pieces of functionality that can be used anywhere in the code. Currently I pass around pointers to the manager classes, to access things like the Graphics API interface that makes draw calls, or the Resource Loader interface that allows for a thread-streamed read of a resource from the hard drive.

What are some other ways of doing this? So far I've thought of / read about the following methods:

  • Singleton classes
  • A class with all static member functions visible through a global instance.
  • Globally visible member functions, C-style
  • Passing around pointers to anyone interested.

Are there other ways to do this? Are there any benefits to one over another? Does anyone have any personal experience or insight into which way they think is the best? I generally shy away from globals, I don't particularly like singletons, and passing around the pointers is getting a bit cumbersome...

jpetrie
jpetrie
Singleton classes

...are strictly inferior to any of the other options you've listed. They're just extra boilerplate around a global. Usually boilerplate that's harmful or poorly implemented.

A class with all static member functions visible through a global instance

Another pointless abstraction; just use your third option (free, C-style functions). The only real motivating reason to use a static class is if you have a language like C# which does not permit global free functions.

Globally visible member functions, C-style

This is what I would suggest if you aren't going to use your forth option (passing the objects around) which I think is the best.

Passing around pointers to anyone interested

This is what you should use. It sounds like it's basically what you're doing, so that's good. One potential improvement you may consider is breaking up the interfaces you pass around, so you have more granularity in them. The typical objection to "pass all the things around" is "but then I just have to have a FooManager and BarManager and QuuxManager in every function!"

This is good. This tells you that your design is shit; having to pass the dependencies shows you how many dependencies you have (in a painful, hard-to-ignore fashion) and so you should focus on reorganizing things to reduce dependencies. One way to help this along is to break up the functionality of larger monolithic "manager" classes so you don't need to pass "huge" interfaces to things that only need a small subset of that functionality.

The "making your dependencies explicit" thing is a compelling advantage of the passing-the-pointers approach. Another one is that these interfaces are encapsulated in instances, allowing you the possibility of having more than one (perhaps for testing or for running two simulations concurrently but independently). A third is the lack of global accessibility: the fewer possibilities you have for a project to access globally-available systems and state, the easier it is to reason about changes and potential bug fixes. As recently as last week I spent a few hours tracking down a bug that was frustrating because the inputs to the function calls entering the code were exactly bitwise equal, but the behavior was massively different in two different contexts. The crux of the issue turned out to be access to a global state tucked away in some tiny, easy-to-overlook utility class function.

If you really, really don't want to pass your dependencies around, I'd only fall back as far as your third option: freestanding C-style interfaces. Ideally you never do this if the interface has any kind of state, because as soon as you introduce state to that API you run into the massive downsides of the freestanding interface approach (which are all shared in various forms by your earlier suggestions): it's bad for testing and it's bad for concurrency.

Fundamentally the choice is simple: do you want global state, or do you want instanced state? Instanced state is generally better, as it's nice and siloed for testing, concurrency and isolation. Instanced state trivially degenerates to global state (if you want only one, make only one). Instanced state makes your dependencies explicit, but does require a little more typing to pass instances around. Global state prevents you from "accidentally" making more copies of the state than you expect, but in my experience this either never happens or happens with inexperienced/incompetent engineers who need to be taught that it's probably not a good idea to instantiate a new RenderDevice object for every new function they write (or perhaps the fallout of a bad API design that doesn't make it clear what the side-effects of the API are).

Everything beyond that choice is various ways to hang window decoration on one approach or the other. Most of that window decoration is, in my experience, dumb boilerplate crap and consequently of little redeeming value.

That said...

I think you'll get the most bang-for-your-buck by trying to direct your refactoring efforts to redesigning systems and subsystems such that they don't need the cumbersome dependencies you're currently having to carry around all over. This is a hard task, and one that's especially difficult to describe a general process for beyond the common sense (look at dependency, look at why dependency is needed, remove that need or move it elsewhere). But I think it's the better approach. If you provide more concrete examples of the some of the dependencies you don't like carrying and why you need them where you need them, maybe more specific advice could be given.

thatguyfromthething
thatguyfromthething
Singleton classes A class with all static member functions visible through a global instance. [/quote] A combination of these two. Most engines have a [fill in blank manager]. Why? Like why would you possibly need a TimeManager class, for example? I think that people just make a class "just because" then they create an initialization problem for themselves. For sounds for example, why should you want to be creating or working with it during initialization? Yet this can easily happen if you use singleton class. Singletons are OK to use, but I never have a whole manager class in a singleton, it makes no sense. If you much make some manager classes or factory classes, then have the first thing you do be to initialize them yourself in the order you want.


Globally visible member functions, C-style
This one is like the last option, but it is less limited.
This is my thread. There are many threads like it, but this one is mine.
Aardvajk
Aardvajk
I would listen to Josh and ignore the other guy here. No offence, but anyone who thinks singletons are OK has never had to maintain a large scale project for any amount of time.
Oberon_Command
Oberon_Command

Singletons are OK to use

In which cases are the use of a singleton both justifiable and preferable to stateless free functions or a regular global variable? The rest of your post excludes pretty much all the common (mis)use cases for singletons.

cozzie
cozzie
Keep it nice guys, let's assume everyone's here to help by giving their opinion and thoughts.

My share; this might soudn stupid, but would a singleton be the same as a regular class for which you just create one object? If so, I'd say there's no problem to have classes like these, for example my engine has 1 shadermanager class object, same for the renderer and scenemanager class.

On design, I agree with josh. My engine is basically set up like this:

- namespaces per main "part" of the engine/ framework (renderer, d3drenderer, IO, audio, input etc.)
- within these namespaces classes are defined
- I have one main app base class which has one instance of the renderer, scenemanager, input class etc. (the members of this class could also have been globals)
- per application that uses the engine, I inherit a class from the base bame class and go from there
- when one of the classes needs to use one of the others, I first try to get away with getting a const ref to the object and if really needed, a pointer (with write access)
- when designing, I try to figure out in which class another class could be a member, looking at "who" has to be able to change things and who shouldn't. For example, the d3drenderer class should never modify the scenemanager, just use it to retrieve what should be rendered, etc.

So basically also passing around pointers here (and const ref preferably if possible)
Crealysm game & engine development: http://www.crealysm.com

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

I think Josh articulated the big elephant in the room wonderfully. Singletons, and globals for that matter hide dependacies, couple code, and pave the way for poor design to go unnoticed.

However, I'm sure just like any tool, it has a purpose. But like a chainsaw it can be (and usually is) horribly misused.

Marcus

Oberon_Command
Oberon_Command

My share; this might soudn stupid, but would a singleton be the same as a regular class for which you just create one object? If so, I'd say there's no problem to have classes like these, for example my engine has 1 shadermanager class object, same for the renderer and scenemanager class.

It's more than that. A singleton is a class which has boilerplate explicitly preventing more than a single instance from being instantiated or used.

Juliean
Juliean




Singletons are OK to use

Singleton are OK to use in smallscale throwaway projects that you are done with in like 4 months or so. You know, the kind of project where everything is "OK". That doesn't make singleton OK, that means "you don't have to care about anything". I don't know if this is the case for the OP, it seems that this project is rather medium/large scale (as far as hobby projects are concerned).

And yeah, there are larger projects like Ogre which get away with using Singletons. Mind my wording, there is nothing right about using them, and using alternate methods would only further those projects (an affiliate Ogre developer admitted to us in class that they weren't really happy with the singletons in the end). There is enough reasoning to be found on the net, but just to get the point straight.

cozzie
cozzie


My share; this might soudn stupid, but would a singleton be the same as a regular class for which you just create one object? If so, I'd say there's no problem to have classes like these, for example my engine has 1 shadermanager class object, same for the renderer and scenemanager class.


It's more than that. A singleton is a class which has boilerplate explicitly preventing more than a single instance from being instantiated or used.

Thanks, that clears things up for me. In that case I don't advice to use singletons based on that I've never used them and have no experience with them.
Crealysm game & engine development: http://www.crealysm.com

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

Another option that has not been mentioned is using signals and slots. It allows different parts of your system to stay fairly independent. When some event happens they can issue a signal to inform whoever is interested. There is some part of the code that instantiates all the objects and hooks all the signals to the appropriate slots, and that's the only part of the code that needs to know about the connection between the objects. One of the obvious advantages is that the objects themselves can be tested in isolation to the rest of the system, by hooking the signals and the slots to dummies in the test program.

Funkymunky
Funkymunky

This all falls in line with my existing outlook, but I'm a little surprised to hear the static class method derided as "Another pointless abstraction." I've read L. Spiro's blog post about her engine design, and that's the method she seems to have gone with. Her reasoning seems sound, although a lot of it could be emulated by just using global functions in a namespace.

Zipster
Zipster

This all falls in line with my existing outlook, but I'm a little surprised to hear the static class method derided as "Another pointless abstraction." I've read L. Spiro's blog post about her engine design, and that's the method she seems to have gone with. Her reasoning seems sound, although a lot of it could be emulated by just using global functions in a namespace.

As you said, free functions in a namespace would serve the same purpose, but she's considering consistency with the rest of her codebase (which is important) and personal preference, so her decision is swayed towards static class methods. However the visibility issue ("private free functions") can be solved with unnamed namespaces, so that's not a distinct feature of classes.

Funkymunky
Funkymunky

To be honest, I think I prefer static class methods to global variables for the same reasons she outlined. I'm thinking I'll continue passing around pointers to my manager-abstractions, however.

Hodgman
Hodgman
FWIW, the noun "manager" is often considered a code-smell, where it implies that the responsibility of the class is vague and over-reaching (often like real life managers :lol:). "Manager" classes very often violate the Single Responsibility Principle and other core OOP teachings.

The consequence of this is, because the manager is so complex and is overburdened with responsibilities, disparate parts of the code base all need access to the same manager, so you feel the burden of "having to pass too much stuff everywhere".
Often, this can be just as bad as global variables in all but name. If every part of the code has a pointer to a shared structure, then that structure is global state.

Passing around pointers to anyone interested.

IMHO, this is the right default choice.
But, work on following OO rules properly and focus on the data-flows and data-dependencies withing your game and you can minimize the amount of pointer-passing that's required in the first place.
LorenzoGatti
LorenzoGatti

A nice advantage of passing around pointers is that you can improve encapsulation by using pointers to the right thing instead of giving excessive access to global data structures and unneeded dangerous functions. For example:

  • A piece of a scene graph, without access to its parent(s), rather than the whole world.
  • When visiting game entities in order to render them you can pass around, and append to, passive collections of things to draw (e.g. managed vertex buffers) rather than a "Graphics API interface that makes draw calls"
  • When loading resources, you might be able to use objects that represent one resource and bury in their inaccessible implementation references to global variables representing a registry of what resources are loaded or not and other delicate mechanisms like spawning threads to load resources and concurrency control.
Omae Wa Mou Shindeiru
thatguyfromthething
thatguyfromthething

Singletons are OK to use

 
Singleton are OK to use in smallscale throwaway projects that you are done with in like 4 months or so. You know, the kind of project where everything is "OK". That doesn't make singleton OK, that means "you don't have to care about anything". I don't know if this is the case for the OP, it seems that this project is rather medium/large scale (as far as hobby projects are concerned).
 
And yeah, there are larger projects like Ogre which get away with using Singletons. Mind my wording, there is nothing right about using them, and using alternate methods would only further those projects (an affiliate Ogre developer admitted to us in class that they weren't really happy with the singletons in the end). There is enough reasoning to be found on the net, but just to get the point straight.

You just make some big assumptions. You never used singletons properly, so you think they have no use.

The real problem in projects like Ogre is a bad object model and too many dependencies, not singletons. Many game engines have the same issue. That's because people arbitrarily come up with an object model without much thought then they are still stuck with it ten years later.

Singletons can provide a great convenience but they are not really something to use at a high level or to resolve your basic initialization order issues.

In some other thread I pointed out how you can use header files to immediately include initialization code and from there to initialize all your most important stuff.

This is my thread. There are many threads like it, but this one is mine.
Oberon_Command
Oberon_Command

You never used singletons properly, so you think they have no use.


I reiterate and rephrase my earlier question, which you appear to have ignored: what constitutes using singletons "properly" and what makes them superior to simple globals and free functions in those cases?
thatguyfromthething
thatguyfromthething
Initialization order is nothing to do with singletons, people who try to resolve that issue with them are in for despair.

Solving initialization issues? Put this in a your header files. This will get immediately called.
namespace{	int initDummy = Application::initializeEventhingInProperOrder();};

BAD use of singleton:


class SoundManager: public Singleton{public:  blah blah blah};
There's probably exceptions, but I notice once you make a specific singleton class, usually it means you are into design pattern tunnel vision and making a mistake. You definitely don't want a bunch of manager or factory classes that are all singletons.

I would also question do you really need a 'sound manager' that's a discreet object? I doubt it. Probably its whole interface could be static.



GOOD use (or at least convenient use):
OptionsMenu* OptionsMenu::getInstance(){	static OptionsMenu* optionsMenu = new OptionsMenu();	return optionsMenu;}
Something that you will only have one of for which doesn't have any dependencies on initialization. You don't have to have a singleton for anything, but it can be very convenient.
This is my thread. There are many threads like it, but this one is mine.
Oberon_Command
Oberon_Command

Solving initialization issues? Put this in a your header files. This will get immediately called.

namespace{	int initDummy = Application::initializeEventhingInProperOrder();};



I'm not seeing how that would even compile (since it's a statement outside a function), unless Application::initializeEventhingInProperOrder is a constructor, in which case this just looks like a hack. Why is this better than putting a call to Application::initializeEventhingInProperOrder() in main()?

GOOD use (or at least convenient use):

OptionsMenu* OptionsMenu::getInstance(){	static OptionsMenu* optionsMenu = new OptionsMenu();	return optionsMenu;}
Something that you will only have one of for which doesn't have any dependencies on initialization. You don't have to have a singleton for anything, but it can be very convenient.



How is this more convenient than defining a single, global instance of OptionsMenu? What if I want a second OptionsMenu instance? Suppose I want an options menu for graphics options and a menu for sound options?

You're not making a very strong case, here. "Convenient" is not "good," as you yourself imply.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.