Skip to main content
GameDev.net gamedev.net
🔒 Locked

Design Issues: Making systems know about eachother (engine)

Started by klette Apr 20, 2006 at 10:18 AM 25 replies 4.3k views
Original Post
klette
klette
I know this is a pretty frequent question, but id like to know how you design your code, so that the different systems of your engine know about eachother. My current system: Core |-->Rendererer |-->ResourceHandler |-->Sound etc.. As you can see, the core can access everything neatly, but whats the best way of making the subsystems talk to eachother. One option is to make the core a singleton and fetch its instance and go from there. Is this the kind of situation that a singleton might be useful? Seems to be a lot of different opinions about them.. Any thoughts?
Mattman
Mattman
You could try using Dependency Injection. Basically pass the object reference as a parameter to one of the other objects for it to be used.

class DisplayManager {    private SoundManager soundManager = null;    ...    public void setSoundManager(const SoundManager &sm) {        this->soundManager = sm;    }}
Enselic
Enselic
In my engine, I have all the core components as static classes (SpriteRenderer, SoundManager, GameTimer, KeyboardInput, etc). They are not encapsulated by a Core. I've tested a bunch of different small-scale engine designs, and I think this works well.

For instance (the engine is a sprite based 2D C#/MDX engine btw) the rendering is handled by each renderable object itself.

So the the Ship class has a method like this
  public void Render()  {      // ...      SpriteRenderer.AddSprite(shipSprite);  }

and the Background class something like
  public void Render()  {      // ...      SpriteRenderer.AddSprite(backgroundSprite1);      SpriteRenderer.AddSprite(backgroundSprite2);  }

I.e. all classes can access the renderer, and tell the renderer which sprites to render.

It works the same for sound and input, these are static classes, so every class has access to these everywhere. Note that it is only the core classes that are static, all other gameobjects are instanciated.

I know some people dislikes having objects static and therefore global, but for game engine components I think it makes sense.

The regular updating (i.e. update input and render all added sprites etc), is handled by so called tasks. So I have a task that regularly updates input and regularly renders the scene etc. This idea is from superpigs Enguinity series (Part III) and it works great.

Btw, I listened to a keynote by Tim Sweeny at Epic Games recorded at the GDC 2006, where he told about the Unreal Engine 3.0 and its architecture. It's really intresting, you can find it at http://www.gdcradio.net/.
Ravuya
Ravuya
Have you considered message passing? Have all the modules accept an enumerated message type with two integer values (which you can use as pointers or whatever).
klette
klette
Thanks for the fast response :)

Im going to try the dependency injection method first, as I kinda liked the concept.

This message passing, how does that really work? Do you have any articles or some other docs on that method?
klette
klette
Got Dependency Injection working on all systems just now :) Works great. Thanks again everybody ;)
Ravuya
Ravuya
Quote:
Original post by klette
This message passing, how does that really work? Do you have any articles or some other docs on that method?


I'm sure I could find an article quickly, but I know it was covered well in Game Programming Gems 4.
tstrimp
tstrimp
Quote:
Original post by Ravuya
I'm sure I could find an article quickly, but I know it was covered well in Game Programming Gems 4.


I found this one. Do you know of a bettter resource explaining it? I'm very interested as this is what I'm currently dealing with.
JBourrie
JBourrie
Quote:
Original post by Enselic
It works the same for sound and input, these are static classes, so every class has access to these everywhere. Note that it is only the core classes that are static, all other gameobjects are instanciated.

I know some people dislikes having objects static and therefore global, but for game engine components I think it makes sense.


I do things the same way. Peoples' hatred of global variables is a bit harsh... you just have to know when to use them. If you have an object that is used everywhere in the program, will be created when the program starts, and lasts until the program ends, then static/global objects are the most straightforward/flexible/readable way to do it.

Ravuya
Ravuya
Quote:
Original post by tstrimp
Quote:
Original post by Ravuya
I'm sure I could find an article quickly, but I know it was covered well in Game Programming Gems 4.


I found this one. Do you know of a bettter resource explaining it? I'm very interested as this is what I'm currently dealing with.


This page is always my first stop when constructing a complex system. I'm away from my home machines so I can't find other pages on it, but I'm sure others exist.
msn12b
msn12b
Quote:
Original post by JBourrie
If you have an object that is used everywhere in the program, will be created when the program starts, and lasts until the program ends, then static/global objects are the most straightforward/flexible/readable way to do it.


Straightforward: Yes.
Flexible: Hardly.

MSN
Oluseyi
Oluseyi
Quote:
Original post by Ravuya
Have you considered message passing?

I'm for message passing, too, because it is hands-down the most flexible and extensible of the systems suggested here. With a robust messaging framework in place, one subsystem doesn't need to know anything about any other subsystem - it doesn't even need to know if that subsystem exists! All it needs is to be able to send messages to the system queue, and handle messages off the system queue directed to it.

With message passing, you can actually "stub" out functionality in full, sending as-yet unhandled messages to the queue, and when you implement the handler the functionality comes online without any modification to the sending/originating subsystem.
lubby
lubby
Quote:
u can do the same with empty functions


That does not make a lot of sense. Assuming you mean empty methods... By doing this you are forced to implement for each class that is capable of recv messages stubs for each possible message. Or derive from a common base class which implements all possible methods, and then override these stubs when a message should be handled.

It Much simpler to make each message capable class implement a single Message method and implement required message handling with a switch case combination. You only need to case messages then that this type will support. Others can be default ignored.

[Edited by - lubby on April 21, 2006 12:43:21 AM]
Spoonbender
Spoonbender
Quote:
Original post by JBourrie
Quote:
Original post by Enselic
It works the same for sound and input, these are static classes, so every class has access to these everywhere. Note that it is only the core classes that are static, all other gameobjects are instanciated.

I know some people dislikes having objects static and therefore global, but for game engine components I think it makes sense.


I do things the same way. Peoples' hatred of global variables is a bit harsh... you just have to know when to use them. If you have an object that is used everywhere in the program, will be created when the program starts, and lasts until the program ends, then static/global objects are the most straightforward/flexible/readable way to do it.


If you really want them to be global, consider using singletons instead. That way you get to ensure it's initialized exactly once, and it'll be a bit easier to deal with if you later on decide that it shouldn't have had global scope after all, since you're already dealing with an instance of a class, rather than a static class.

And I'm having a hard time seeing why you think this usage of globals is particularly good.
BLiTZWiNG
BLiTZWiNG
I struggled with all of these concepts for a long time. I finally settled on making each subsystem do only one thing, what the function says, and making the engine itself combine the smaller blocks of code into larger blocks of functionality.

If I didn't do it this way, I kept finding that hunting down where a variable gets changed could be a nightmare. Making entities load meshes was not elegant. My current system is at least procedural, but I'm not about to claim that it is fast or efficient, even though it may be.

I also use a message system for handling incoming network messages and being able to send debug text from anywhere.

It works for me.
Enselic
Enselic
Quote:
Original post by Spoonbender
Quote:
Original post by JBourrie
Quote:
Original post by Enselic
It works the same for sound and input, these are static classes, so every class has access to these everywhere. Note that it is only the core classes that are static, all other gameobjects are instanciated.

I know some people dislikes having objects static and therefore global, but for game engine components I think it makes sense.


I do things the same way. Peoples' hatred of global variables is a bit harsh... you just have to know when to use them. If you have an object that is used everywhere in the program, will be created when the program starts, and lasts until the program ends, then static/global objects are the most straightforward/flexible/readable way to do it.


If you really want them to be global, consider using singletons instead. That way you get to ensure it's initialized exactly once, and it'll be a bit easier to deal with if you later on decide that it shouldn't have had global scope after all, since you're already dealing with an instance of a class, rather than a static class.

Personally for me, it is a tradeoff.

It takes less time to write
  SpriteRenderer.AddSprite(sprite);

than
  SpriteRenderer.Instance.AddSprite(sprite);

or
  SpriteRenderer::GetInstance().AddSprite(sprite);


And the risk of initializing deinitializing twice is quite low in my engine since I have engine defined tasks that taks care of that, so it is not up to the user of the engine.

To "start" my engine, the code looks like this:
static void Main(string[] args){	// Start the core tasks	CreateAndUpdateGameWindowTask gameWindowTask = new CreateAndUpdateGameWindowTask(10, "Spatra 2", 800, 600);	KeyboardInputTask inputTask = new KeyboardInputTask(20, CreateAndUpdateGameWindowTask.GameWindow);	GameTimerTask timerTask = new GameTimerTask(30);	GameStateManagerTask gameStateManagerTask = new GameStateManagerTask(40);	SpriteRenderingTask spriteRenderingTask = new SpriteRenderingTask(200000, false, 800, 600);	Kernel.AddTask(gameWindowTask);	Kernel.AddTask(inputTask);	Kernel.AddTask(timerTask);	Kernel.AddTask(spriteRenderingTask);	Kernel.AddTask(gameStateManagerTask);	// [...]        Kernel.Execute();}


Initialization and deinitialization follows from the design of the Kernel system.

Besides, converting from static classes to singletons isn't very demanding in case it would be needed.

Quote:

And I'm having a hard time seeing why you think this usage of globals is particularly good.

I think game engine components, which really are needed througout the entire application life time, are ok to have global becuse it simply becomes easier to use without the system getting messy, which other uses of globals easily becomes. I mean we are talking about like 6 globals.

In my early attemts on engine design I used dependencay injection as suggested in this thread. To me the code always ended up as messy, with dependencies being injected everywhere.

Btw, there is another parallell interesting discussion on engine design at the Game Engine Design Questions thread.

[Edited by - Enselic on April 21, 2006 5:06:25 AM]
klette
klette
Correct me if im wrong, but would the message passing work something like this?

-------- ---------
|system1|<==fetch_msg==|CORE|===send_cmd==>|system2|
-------- ---------

The core fetches messages from the subsystem, clears the msglist, then send the message to the correct system?

And if this is the case, how does one implement an effect way of fetching resources from the ResourceManager?
Having the Renderer store pointers to the textures its using is one way, but im a bit worried about the timing. I guess each system runs its own thread ?

[Edited by - klette on April 21, 2006 5:15:40 AM]
Bob Janova
Bob Janova
There are two basic lines of attack to this, as I see it anyway.

If you just want something that works, for this game now, then having static members of your Core class (or just global variables if you're in C++ and therefore allowed such extravagances) for each major component of the game is fine. Then your code everywhere can see and use those components. Advantages: fast to write and code for, and no overheads passing data around; disadvantages: no flexibility, you'll be hard pressed to reuse any of the code in another project.

For a more flexible solution you need to have some form of module registration and a method of requesting actions on the part of other modules. For example:
interface IComponent { object Action(string command, object args);}class Kernel { static Hashtable<string,IComponent> Components; ...}class SomeModule { SomeModule(){  // Registration  Kernel.Components.Add("SomeModule", this); } void SomeMethod(){  // Request an action  Kernel.Components["Renderer"].Action("AddSprite", sprite); }}

(Okay, so you'd probably do it with ints and a list of constants for efficiency's sake, but the basic design would hold.) Advantages: flexible, reusable, extensible; disadvantages: lots of overhead on cross-module activity.
Enselic
Enselic
Quote:
Original post by Bob Janova
disadvantages: [...], you'll be hard pressed to reuse any of the code in another project.

I agree that it isn't the best solution from an flexible point of view, but in what way do you mean that static accessible core engine components prevent code reuse?
Bob Janova
Bob Janova
Just that the code you write would include a lot of stuff specific to the project, so it would be hard to reuse it in another. It's nothing to do with static accessibility (if you notice, in the flexible solution the list of components is static too). That is, almost by definition less flexible code will be harder to reuse.

Topic Locked

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

Sign in to reply to this topic.