Skip to main content
gamedev.net
🔒 Locked

C++ signalling, circular-dependency issue

Started by RobotGymnast Sep 19, 2010 at 12:07 PM 11 replies 2.1k views
Original Post
RobotGymnast
RobotGymnast
I'm having a little circular-dependency problem. It works fine, but it makes for ugly-seeming code. It's in the context of a Snake game.

I have a class, Snake, which contains a vector of SnakeSegments, and manages their interaction (for instance moving and growing as a unit, rather than as separate entities).

When a SnakeSegment collides with a Food object, it flips its hasEaten member to true. The Snake routinely queries the SnakeSegments, essentially for this member. If any of the queries return positive (i.e. one has hit food), then the Snake will grow as a unit (i.e. expand the head and shrink the tail). This is all fine and good, but I'd much prefer a more signal-based approach, where, when a SnakeSegment hits food, it sends an alert (signal, interrupt, etc.) to the Snake class, which tells it to grow. This means I wouldn't have ugly code in my Snake's Update function, checking all the segments; and I would instead have an OnEat() function in my Snake class.

However, this leads to circular dependencies; the Snake contains a vector of SnakeSegments, and the SnakeSegments have a Snake& or Snake* member, which tells them whom to alert when they eat. In the code, I essentially just have to pre-declare the Snake class:

class Snake;class SnakeSegment{...Snake* alertOnEat;...};


and my Snake class just works normally

#include "SnakeSegment.hpp"class Snake{...std::vector<SnakeSegment> segments;...void OnEat();...};


Are there any nicer designs to this? Note that it isn't just a problem occuring here; a similar problem occurs in a number of areas (e.g. the GameWorld contains a Snake member, and the Snake alerts the GameWorld when it dies), so a solution specific to Snake and SnakeSegment isn't what I'm looking for.

Thank you!
ApLars
ApLars
Use google and type Callbacks or signal/slots.
I use the boost library for this kind of things (http://www.boost.org/doc/libs/1_44_0/doc/html/signals.html)


rip-off
rip-off
I think you are over-designing snake. Modelling the entire game as a grid is typically simpler.
RobotGymnast
RobotGymnast
Quote:
Original post by ApLars
Use google and type Callbacks or signal/slots.
I use the boost library for this kind of things (http://www.boost.org/doc/libs/1_44_0/doc/html/signals.html)


I'll look into it, thanks.

Quote:
Original post by rip-off
I think you are over-designing snake. Modelling the entire game as a grid is typically simpler.


Been there, done that.
However, this is an issue that will arise across many games with similar design anywhere.
rip-off
rip-off
The only way to break the direct dependency is to introduce some kind of middle layer, the signal/slot system is one choice, another is a interface:
struct Growable {    virtual ~Growable() {}    virtual void grow() = 0;};struct Segment {   Segment(Growable *growable) : growable(growable)   {   }   void onHit(const Food &food)   {       growable->grow();   }private:   Growable *growable;};struct Snake : Growable {    virtual void grow()    {       segments.push_back(...);    }};

The signal/slot is a generalisation of this, it is much more flexible but slightly more complex (boost hides a lot of the complexity with its bind() functions) and might incur extra runtime cost. Whether this matters depends on what you are doing exact, here it probably wouldn't matter.
Zipster
Zipster
You don't necessarily need to use boost::signals either, Sneftel's very very fast event library will probably provide all the functionality you need without the additional overhead.
Echkard
Echkard
Quote:
Original post by rip-off
The only way to break the direct dependency is to introduce some kind of middle layer
True, but in this case, why would you want to hide that dependency? Signal/slot patterns are useful when entities are hooked up in arbitrary manners. But you're not going to be plugging in a SnakeSegment to anything else but a Snake, right? Using an event pattern is poor practice. Each class has a pointer to the other; done properly this is an elegant solution; a predeclaration isn't "ugly" in the least.


rip-off
rip-off
Quote:

True, but in this case, why would you want to hide that dependency?

I don't know why the OP wants to - I think that anything beyond a grid is over-design here. I am guessing that the OP wants to use an existing snake game as a sandbox for implementing a more advanced architecture.
Echkard
Echkard
Quote:
Original post by rip-off
Quote:

True, but in this case, why would you want to hide that dependency?

I don't know why the OP wants to - I think that anything beyond a grid is over-design here.
I'd have to agree. I think the basic approach is wrong. The purpose of OOP is to model reality more naturally. But segments of a snake don't eat -- the entire snake does. This should be implemented by allowing Snake to query its own segments for position, then scan those positions for food (or anything else) and act accordingly. The only reason to take his current approach would be if the segments themselves had some need for autonomous logic.

SeaBourne
SeaBourne
Other way is to use forward declaration for it. But that requires the variables to be pointers also.
Kylotan
Kylotan
I'm surprised that nobody mentioned the Observer pattern. rip-off's answer is essentially the Observer pattern, but it's worth knowing about the pattern because it's usually a good solution to problems of this nature.
Echkard
Echkard
Quote:
Original post by Kylotan
I'm surprised that nobody mentioned the Observer pattern. rip-off's answer is essentially the Observer pattern, but it's worth knowing about the pattern because it's usually a good solution to problems of this nature.
The signal/slot Aplars referred to is a specific way to implement the Observer pattern ... which is itself a subset of the somewhat more general Publish/Subscribe pattern.

Still, I think its being misrecommended in this particular case. Event patterns are really intended for autonomous or semi-autonomous class interaction, not as a general means to disconnect what should be dependent classes.

Topic Locked

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

Sign in to reply to this topic.