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

Observer pattern

Started by XezekielX Jun 8, 2008 at 6:28 PM 4 replies 6.3k views
Original Post
XezekielX
XezekielX
I would like to use a component-based entity system for my next project. Let's say I have a "Ship" entity with the following components: - RenderComponent - FrameComponent (size and position) - CollisionComponent - InputComponent etc. I thought of using the Observer design pattern for communication between the components of the game's entities. Each component would be an observer and a subject at the same time; components would notify all other components of the entity (and possibly components of other entities as well, like a health bar in the HUD for instance). The FrameComponent would use the InputComponent's notifications to update the ship's position. The CollisionComponent would then use the position update to check for collisions against enemy bullets. The RenderComponent would also need the position update notification to draw a sprite at the correct location on the screen, etc. You get the point. Now, there's something that has always bugged me with the Observer pattern, that is, when a single observer wants to be notified of updates by multiple subjects (RenderComponent needs to be notified when the position of the ship changes [FrameComponent] and when the ship explodes after a collision [CollisionComponent]). Most implementations of this pattern simply pass a pointer to the subject to the observers' notify() method :
foreach(observer in observers)
  observer->notify(this);
The "problem", if it is one, it what happens at the other end, in an observer's notify() method. Of course, I could use a giant switch case using something like a ComponentType (or something like a dynamic_cast<> in C++) to determine what type of subject just sent a notification. That solution doesn't seem very flexible nor elegant to me. What would be a better solution? Am I even using the right approach to the problem? Thank you for your suggestions and ideas.
it seems rational thought has been killed and buried
Sneftel
Sneftel
Quote:
Original post by XezekielX
I thought of using the Observer design pattern for communication between the components of the game's entities. Each component would be an observer and a subject at the same time; components would notify all other components of the entity (and possibly components of other entities as well, like a health bar in the HUD for instance).
Notify them... of what? The Observer pattern should only be considered in the context of actual events, not "notification" in general. I think that's where much of your confusion arises. For instance:

foreach(observer in observers)  observer->notify(this);


This snippet is a little silly. It's a little like calling someone (or many someones), yelling "hello, it's me" and hanging up. There's a couple of ways this code can be made less silly.

foreach(observer in observers)  observer->notify(this, EVENT_POSITION_CHANGED);


Here, an enumeration of messages is used. There's plenty of variations on it, but the basic concept is that the notify() method is used as a demultiplexer. It's ugly and Java 1.1ish and don't do it. Don't do the equivalent thing with object hierarchies, either... that is ever so much uglier.

Next up:

foreach(positionObserver in positionObservers)  observer->notifyPositionChanged(this);


Here a specific function is being used. Note that we get type-safe parameter passing by doing it this way.

Or, finally:

PositionChanged();


Look! I just used boost::signals and boost::bind to cut through all that boilerplate crap, avoid the disgusting mess that is "listener interfaces" in C++, and get all the bookkeeping done for me. Highly recommended.
XezekielX
XezekielX
Quote:
Original post by Sneftel
Notify them... of what? The Observer pattern should only be considered in the context of actual events, not "notification" in general. I think that's where much of your confusion arises.

I was thinking of the "Pull" model, where observers query the subject about the reasons for sending a notification.

I had thought of using a sort of "event type" parameter, like you suggested, but as was already said, the notify() method just becomes a sort of message router.

Quote:
Original post by Sneftel
Next up:

foreach(positionObserver in positionObservers)  observer->notifyPositionChanged(this);


Here a specific function is being used. Note that we get type-safe parameter passing by doing it this way.

But whenever I would create a new type of component, I would need to modify Observer's interface to add a new notify[SpecificEventOccured]() method; that kind of defeats the purpose of components being reusable, no?

Quote:
Original post by Sneftel
Look! I just used boost::signals and boost::bind to cut through all that boilerplate crap, avoid the disgusting mess that is "listener interfaces" in C++, and get all the bookkeeping done for me. Highly recommended.

Could you please elaborate? I'm (unfortunately) not very familiar with the boost library.

it seems rational thought has been killed and buried
Sneftel
Sneftel
Quote:
Original post by XezekielX
I was thinking of the "Pull" model, where observers query the subject about the reasons for sending a notification.

Yikes. I haven't heard of this particular approach before. It's more complex, obviously, because it means the object needs to temporarily store information about the event it's generating just in case it's interrogated. It has the side effect of not allowing recursive event invocations on a given object. Why complicate things in this way?

Quote:
Quote:
Original post by Sneftel
foreach(positionObserver in positionObservers)  observer->notifyPositionChanged(this);


But whenever I would create a new type of component, I would need to modify Observer's interface to add a new notify[SpecificEventOccured]() method;

Yes.
Quote:
that kind of defeats the purpose of components being reusable, no?
No! Look, regardless of whether you explicitly define them as separate functions, or try to hide them behind a behemoth of a switch statement, morally speaking they are separate functions. That sort of thing--where you decide not to use a feature of your programming language, only to reimplement it in a way that lets you pretend you aren't using it--is rarely a good idea.


Quote:
Quote:
Original post by Sneftel
Look! I just used boost::signals and boost::bind to cut through all that boilerplate crap, avoid the disgusting mess that is "listener interfaces" in C++, and get all the bookkeeping done for me. Highly recommended.

Could you please elaborate? I'm (unfortunately) not very familiar with the boost library.

clicky. There's also this, but don't use it unless you know you need it.
XezekielX
XezekielX
Quote:
Original post by Sneftel
Yikes. I haven't heard of this particular approach before. It's more complex, obviously, because it means the object needs to temporarily store information about the event it's generating just in case it's interrogated. It has the side effect of not allowing recursive event invocations on a given object. Why complicate things in this way?

That comes directly from the Design Patterns book by the way, in the middle of page 298 (yes, I do understand that "design patterns" are just that, patterns, and are not necessarily meant to be used exactly as written in the book). I guess this approach was proposed to avoid modifying the Observer interface everytime a new Subject subclass is created.

Quote:
Original post by Sneftel
That sort of thing--where you decide not to use a feature of your programming language, only to reimplement it in a way that lets you pretend you aren't using it--is rarely a good idea.

Good point.

Quote:
Original post by Sneftel
clicky. There's also this, but don't use it unless you know you need it.

I'll look into that, thank you.

it seems rational thought has been killed and buried
Trefall
Trefall
I've used the Observer pattern for the same purpose as this, and so far it's worked quite well.

In my approach, communication is blind. This means that components emit events to the void (or the event manager), while subscribers gets events from the event manager. So it's a one way communication. So if a component emits an event, but needs information back, then the receiver component would need to return an event.

I'm kind of in the middle of a rewrite in my entity system at the moment, making it more safe and also work with LUA scripting (as I'm going to define most/all my components in scripts), but the svn version I have should show a working example of my framework.

Framework

Topic Locked

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

Sign in to reply to this topic.