Engine: allow modules to access eachother (singleton?)

Started by
24 comments, last by Subotron 16 years, 2 months ago
Quote:Original post by MJP
For your constructor initialization problem...I'm not sure if it's necessarily the best way to solve it, but you could use some sort of factory function for instantiation of your Model class. This would allow you hide the details of your error/exception handling from the code that's using your framework, and give you an opportunity to pass it references to modules that are internal to your frame work (texture managers, error handlers). This approach also gives you some other interesting options, such as using a custom memory allocator to allocate memory for the models. Or you can have your function return an abstract interface class, if you want to completely hide the public interface from the implementation.

Just trying to give you some ideas to work with, at any rate. [smile]

thanks for the suggestion. This might be a good idea actually. I'm not sure if I understand what you mean with "factory function" (reading up on design patterns right now due to the suggestions in this thread, I found out I missed out on quite a lot of theory which I should've known before starting this project, exception handling included) but as far as I get it you mean I'd just write a function that encapsulates creating a model instance and adding it to the manager, and possibly other stuff like error handling, etc.

Just to check if I got it right (keeping in mind that a scene manager might not be the best place to do this), I could do something like this:

CustomInit(){    SceneMgr->Insert ( file );}SceneMgr::Insert( const std::string& file ){    try    {        boost::shared_ptr<Model> mdl = new Model ( file );        Insert( mdl );    }    catch ( Exception )    {        localErrorHandlerPtr->Notify( Exception );    }}
Advertisement
Ahh this old problem. I have recently been in your position of trying to understand how all this different parts can possibly communicate with each other. You might want to consider an event based system whereby an object listens for events ocurring in another object and will respond to ones relevant to it. Using this system I would have your main engine class listen for "Error Events" so it can shutdown the engine or do whatever. This is along with the error handling within the object itself as well. Sorry I can't contribute more but I'm very bad at communicating my ideas in text form.
It's not a bug... it's a feature!
I suggest taking a look at the XNA framework (it's C#, but bear with me), in particular, the Game, GameComponent, and GameServices classes.

Here's an article that explains it.
http://www.nuclex.org/articles/xna-gamecomponents-and-gameservices

Relating it to your example, Game would be your EngineApp, while the various manager classes would be derivatives of GameComponents. GameComponents' constructors are passed a reference to the Game class on creation, which they use to register themselves through a Listener pattern. Game keeps a list of GameComponents and calls their overloaded Initialize and Update members. So, a component could technically access other components through this list by keeping a reference to the Game class. However, this would cause more problems down the road because it doesn't eliminate the tight coupling and dependencies between components. It also introduces a problem with order, depending on which components are registered first.

So, XNA introduces the GameServices class. Any class, even a GameComponent/manager, that provides services to other components inherits from GameService and registers itself with Game, using a unique key (in XNA, this is the class type) for identification. A component can then check with the Game class whether a Service with a given key has been registered and if so, get a reference to it. In your example, ErrorHandler could be such a Service. The TextureManager GameComponent could get and keep a reference to the ErrorHandler service and call its logging member functions (e.g., Write) when required.

This polymorphism and indirection allows you (or someone else) to create derived error handlers with different behavior, such as writing to a log file asynchronously, logging to a remote database, etc simply by registering the new class as a service with the right key. No change necessary in the other components that use this service class, as long as they use the same interface functions (Write).

However, there's still a coupling problem with this because you still access the Service by casting it to a derived class and using its interface directly. e.g., Write(...).

Instead of using Services, we've implemented our inter-component communications through an event messaging system, essentially like the signal & slots (Listener pattern) system recommended in rip-off's post. Game, or a class derived from it, would either maintain an event queue that it processes every frame, or provide a sort of TriggerEvent() function that immediately dispatches events/messages. Components register themselves with Game, again using the Listener pattern, to be notified when events that they are interested in are processed.

Using the error handler example again, a ResourceMissing event is either queued or triggered immediately. When this event is processed, any registered listeners can do whatever it needs to. For example, you could have an error logger listen for this event and log a message, AND you could also have a ResourceManager listen for this event and deal with the problem by creating a placeholder texture, stream the texture from DVD, or a remote server, etc.

Therefore, the events that a component listens to (understands), and generates in return (sends), becomes a nice, extensible API for that component. You can now control how the component interacts with the outside world, instead of giving strangers full access using a raw pointer. If you add new events to the component, it won't affect older components (they don't know about the new event types) and vice versa (you won't have to modify your component because of a new component's event types that you aren't interested in).

Of course, with all this indirection, you pay for it in performance, compared to directly calling functions across modules (spaghetti!). You have to pick what is better for your needs. What you gain is better decoupling, modularity, and extensibility, which is great for prototyping and upgrading components.

You might also extend this framework by allowing GameComponents and Services to be registered from DLL libraries to create a plugin system. I would add a Shutdown callback from Game to allow proper clean up though.

NOTE: in XNA, which uses C#, multiple inheritance isn't really a problem, but it's something I prefer to avoid in C++. So, our GameComponents that also provide services actually implement the Service interface through the PIMPL idiom, instead of inheriting from both GameComponent and Service.
Yes that's pretty much what I meant, except that you probably want to return a reference to the Model so that the code using the framework has something to manipulate. If you're using interface classes, it would look something like this:

class IModel{public:     virtual void DoSomething () = 0;     virtual void DoSomethingElse () = 0;};class CModel{public:     virtual void DoSomething ();     virtual void DoSomethingElse ();private:     virtual void PrivateFunction ();};...IModel* SceneMgr::Insert( const std::string& file ){    try    {        boost::shared_ptr<Model> mdl = new Model ( file );        Insert( mdl );        return mdl.get();    }    catch ( Exception )    {        localErrorHandlerPtr->Notify( Exception );          return NULL;    }}




I've always handled my engines the same way. Make one main engine class then have the components of the engine as public members and in their constructor just pass them a pointer to the engine. Voila components can access components. Just gotta make sure you use it correctly. Also I have begun relying on an event system to handle the more complex things in my engine. Never had a problem with this design.

Each component for me is a manager class (i.e. EntityManager, GUIManager, EventManager, etc).

public EngineName
{
EventManager eventManager = new eventManager(this);
}
you get the idea. Then in the Manager classes you can do e.errorManager... (where e is the public pointer that points to the engine).
To all of you, thanks a lot for all the great help. I really learned a lot from this topic, and have already rewritten some parts of my framework in this light. I think an event-based (listener) system would be great in the long run, so I'm going to try and use this. I do hope this doesn't mean I have to be working with seperate threads, because this is something I have never worked with before and seems complicated (keeping stuff in sync and all that) but I guess I can manage without. I also re-checked modules on how dependant they were on other modules, and was able to remove some of those dependencies. The whole thing is much better now!

This topic is closed to new replies.

Advertisement