Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

Of course it depends on the situation. But when a lower lever object or system communicate with an higher level system, should callbacks or events be preferred to keeping a pointer to higher level object?

For example, we have a world class that has a member variable vector<monster> monsters. When the monster class communicates with the world, should I prefer using a callback function then or should I have a pointer to the world class inside the monster class?

share|improve this question
    
Aside from the spelling in the question title, it isn't actually phrased in the form of a question. I think rephrasing it might help solidify what you're asking here, since I don't think you can get a useful answer to this question in its current form. And it's also not a game-design question, it's a question on the programming structure (not sure if that means the design tag is or isn't appropriate, can't remember where we came down on 'software design' and tags) –  MrCranky Feb 22 '11 at 11:56

7 Answers 7

up vote 9 down vote accepted

There's three main ways that one class can talk to another without being tightly coupled to it:

  1. Through a callback function.
  2. Through an event system.
  3. Through an interface.

The three are closely related to each other. An event system in many ways is just a list of callbacks. A callback is more or less an interface with a single method.

In C++, I rarely use callbacks:

  1. C++ doesn't have good support for callbacks that retain their this pointer, so it's hard to use callbacks in object-oriented code.

  2. A callback is basically a non-extensible one-method interface. Over time, I find that I almost always end up needing more than one method to define that interface and a single callback is rarely enough.

In this case, I'd probably do an interface. In your question, you don't actually spell out what monster actually needs to communicate to world. Taking a guess, I'd do something like:

class IWorld {
public:
  virtual Monster* getNearbyMonster(const Position & position) = 0;
  virtual Item*    getItemAt(const Position & position) = 0;
};

class Monster {
public:
  void update(IWorld * world) {
    // Do stuff...
  }
}

class World : public IWorld {
public:
  virtual Monster* getNearbyMonster(const Position & position) {
    // ...
  }

  virtual Item*    getItemAt(const Position & position) {
    // ...
  }

  // Lots of other stuff that Monster should not have access to...
}

The idea here is that you only put in IWorld (which is a crappy name) the bare minimum that Monster needs to access. Its view of the world should be as narrow as possible.

share|improve this answer
1  
+1 Delegates (callbacks) usually become more numerous as time goes on. Giving an interface to the monsters so they can get at stuff is a good way to go in my opinion. –  Michael Coleman Feb 23 '11 at 3:13

Personally? I just use a singleton.

Yeah, okay, bad design, not object-oriented, etc. You know what? I don't care. I'm writing a game, not a technology showcase. Nobody's going to be grading me on the code. The purpose is to make a game that is fun, and everything that gets in my way is going to result in a game that is less fun.

Will you ever have two worlds running at once? Maybe! Maybe you will. But unless you can think of that situation right now, you probably won't.

So, my solution: make a World singleton. Call functions on it. Be done with the entire mess. You could pass in an extra parameter to every single function - and make no mistake, that's where this leads. Or you could just write code that works.

Doing it this way requires a bit of discipline to clean things up when it gets messy (that's "when", not "if") but there's no way you can prevent code from getting messy - either you have the spaghetti problem, or the thousands-layers-of-abstraction problem. At least this way you're not writing large amounts of unnecessary code.

And if you decide you don't want a singleton anymore, it's usually pretty simple to get rid of it. Takes some work, takes passing a billion parameters around, but those are parameters you'd need to pass around anyway.

share|improve this answer
2  
I'd probably phrase it a bit more succinctly: "Don't be afraid to refactor". –  Tetrad Feb 23 '11 at 6:52
    
Singletons are evil! Globals are much better. All of the singleton virtues you listed are the same for a global. I use global pointers (actually global functions returning references) to my various subsystem and initialize/destruct them them in my main function. Globals pointers avoid initialization order issues, dangling singletons during tear down, non-trivial construction of singletons, etc. I repeat singletons are evil. –  deft_code Feb 24 '11 at 0:08
    
@Tetrad, highly agreed. It's one of the best skills you can have. –  ZorbaTHut Feb 24 '11 at 9:53

Whenever I can, I try to restrict communication between objects to a request-and-respond model. There is an implied partial ordering on the objects in my program such that between any two objects A and B, there may be a way for A to directly or indirectly call a method of B or for B to directly or indirectly call a method of A, but it is never possible for A and B to mutually call each others' methods. Sometimes, of course, you want to have backward communication to the caller of a method. There are a couple ways I like to do this, and neither of them are callbacks.

One way is to include more information in the return value of the method call, which means the client code gets to decide what to do with it after the procedure returns control to it.

The other way is to call out to a mutual child object. That is, if A calls a method on B, and B needs to communicate some information to A, B calls a method on C, where A and B can both call C, but C cannot call A or B. Object A would then be responsible for getting the information from C after B returns control to A. Note that this isn't really fundamentally different from the first way I proposed. Object A can still only retrieve the information from a return value; none of object A's methods are invoked by B or C. A variation of this trick is to pass C as a parameter to the method, but the restrictions on the relationship of C to A and B still apply.

Now, the important question is why I insist on doing things this way. There are three main reasons:

  • It keeps my objects more loosely coupled. My objects may encapsulate other objects, but they will never depend on the context of the caller, and the context will never depend on the encapsulated objects.
  • It keeps my control flow easy to reason about. It's nice to be able to assume that the only code that can change the internal state of self while a method executing is that one method and no other. This is the same kind of reasoning that might lead one to put mutexes on concurrent objects.
  • It protects invariants on the encapsulated data of my objects. Public methods are allowed to depend on invariants, and those invariants may be violated if one method can be called externally while another is already executing.

I'm not against all uses of callbacks. In keeping with my policy on never "calling the caller," if an object A invokes a method on B and passes a callback to it, the callback may not change the internal state of A, and that includes the objects encapsulated by A and the objects in A's context. In other words, the callback may only invoke methods on objects given to it by B. The callback, in effect, is under the same restrictions that B is.

One last loose end to tie up is that I will allow the invocation of any pure function, regardless of this partial ordering I've been talking about. Pure functions are a bit different from methods in that they can't change or depend on mutable state or side effects, so there is no worry about them confusing matters.

share|improve this answer

Generally I try and avoid bidirectional links, but if I must have them, I make absolutely sure that there is one method to make them and one to break them, so that you never get inconsistencies.

Often you can avoid the bidirectional link entirely by passing in data as it is necessary. A trivial refactoring is to make it so that instead of having the monster keeping a link to the world, you pass the world in by reference to the monster methods that need it. Better still is to only pass in an interface for the bits of the world that the monster strictly needs, meaning the monster doesn't come to rely on the world's concrete implementation. This corresponds to the Interface Segregation Principle and the Dependency Inversion Principle, but doesn't start to introduce the excess abstraction you can sometimes get with events, signals+slots, etc.

In a way, you can argue that using a callback is a very specialised mini-interface, and that is fine. You have to decide whether you can more meaningfully achieve your goals via one collection of methods in an interface object or several assorted ones in different callbacks.

share|improve this answer

I try to avoid having contained objects call their container because I find it leads to confusion, it becomes too easy to justify, it'll become overused and it creates dependencies that can't be managed.

In my opinion, the ideal solution is for the higher level classes to be smart enough to manage the lower level classes. For example, the world knowing to determine if a collision between a monster and a knight occurred without either knowing about the other is better too me than the monster asking the world if it collided with a knight.

Another option in your case, I would probably figure out why the monster class needs to know about the world class and you'll most likely find that there's something in the world class that can be broken out into a class of its own that it makes sense for the monster class to know about.

share|improve this answer

Don't use a callback function to hide what function you're calling. If you were to put in a callback function, and that callback function will have one and only one function assigned to it, then you're not really breaking the coupling at all. You're just masking it with another layer of abstraction. You're not gaining anything (other than maybe compilation time), but you're losing clarity.

I wouldn't exactly call it a best practice, but it's a common pattern to have entities that are contained by something to have a pointer to their parent.

That being said, it might be worth your while to use the interface pattern to give your monsters a limited subset of functionality they can call on the world.

share|improve this answer
    
+1 Giving the monster's a limited way to call on the parent is a nice middle way in my opinion. –  Michael Coleman Feb 22 '11 at 18:33

You won't go very far without events, obviously, but before even begin to write (and design) an event system, you should ask you the real question : why would the monster communicate with the world class ? Should it really ?

Let's take a "classical" situation, a monster attacking a player.

Monster is attacking : the world can very well identify the situation where a hero is next to a monster, and tell the monster to attack. So the function in monster would be :

void Monster::attack(LivingCreature l)
{
  // Call to combat system
}

But the world (who already knows the monster) doesn't not need to be knowed by the monster. Actually, the monster can ignore the very existence of the class World, which is probably better.

Same thing when monster is moving (I let sub-systems get the creature and handle move computation / intention for it, the monster being just a bag of data, but lots of people would say this is not real OOP).

My point being : events (or callback) are great, of course, but they are not the only answer to every problem you'll face.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.