5
\$\begingroup\$

I'm looking at implementing a messaging system in my entity component system. I've deduced that I can use an event / queue for passing messages, but right now, I just use a generic object and cast out the data I want. I also considered using a dictionary. I see a lot of information on this, but they all involve a lot of casting and guessing. Is there any way to do this elegantly and keep strong typing on my messages?

\$\endgroup\$
5
  • \$\begingroup\$ If you are using Java then Google's Guava library has a great event system that may solve your issue. \$\endgroup\$
    – mobo
    Commented Jul 1, 2013 at 15:02
  • 2
    \$\begingroup\$ You almost certainly will need to do casting, but you can hide it via the messaging API. However, the language you're using will have a pretty big impact on the techniques available -- which language are you doing this in? \$\endgroup\$
    – user1430
    Commented Jul 1, 2013 at 15:18
  • \$\begingroup\$ C# is the language of choice. I'd take Java answers, too for curiosity. \$\endgroup\$ Commented Jul 1, 2013 at 19:39
  • \$\begingroup\$ Sounds like you want to use Generics. I'd post a lengthy explanation of how to design a system but there is already a pretty awesome system designed for use in Unity (but you can actually use it anywhere) wiki.unity3d.com/index.php?title=CSharpMessenger_Extended \$\endgroup\$ Commented Jul 18, 2013 at 22:10

3 Answers 3

4
\$\begingroup\$

Create a separate message class for each type of message (to a degree).

In your entities, make a different method for each type of message. eg.

class CollisionMessage;
class DamageMessage;

class Entity {
public:
  virtual void onCollision(CollisionMessage&);
  virtual void onDamage(DamageMessage&);
};

You can now use a form of double-dispatch to select the right version. Have a deliver method on your Message types that delivers them to the right handler:

class Message {
public:
  virtual void deliver(Entity& e) = 0;
};

class CollisionMessage : public Message {
public:
  virtual void deliver(Entity& e) { e.onCollision(*this); }
};

class DamageMessage : public Message {
public:
  virtual void deliver(Entity& e) { e.onDamage(*this); }
};

You can extend this to an observer system by registering message handlers. You can also use this to allow multiple independent messages to share the same type using a little metadata, e.g, ensuring that the "EnvironmentDamage" and "GunDamage" messages both use the DamageMessage type.

The use of components or ECS really doesn't change this much. With components you're just delivering to individual components instead of a main game object class. With ECS you're delivering them to systems and including a "target entity ID" as part of the message. Nice and simple.

If you're going down the ECS route and want it to actually be useful, you might consider a more heavily data-oriented approach to messaging. See the BitSquid blog on managing coupling (actually, read all their articles; they're quite fantastic, even if I don't agree with all of it).

\$\endgroup\$
1
\$\begingroup\$

Separate queues per data type to end casting. (I like this one better as it is simple, flexible, and allows for explicit ordering of types of operations)

class SpawnEntityEvent {
public:
    std::string resource;
    double x,y,z;
};
class MoveEntityEvent {
public:
    Entity* ent;
    double x,y,z;
};
class ChangeEntityPropEvent {
public:
    Entity* ent;
    std::string key;
    std::string value;
};
std::queue<SpawnEntityEvent> spawnQueue;
std::queue<MoveEntityEvent> moveQueue;
std::queue<ChangeEntityPropEvent> propQueue;

    /*** And if one needs to maintain interleaved order... ***/
std::queue<int> order;
enum {
    ENT_SPAWN,
    ENT_MOVE,
    ENT_PROP
};
void nextSpawnEvent() {
    SpawnEntityEvent& evt=spawnQueue.front();
    // spawn your entity with evt data now
    spawnQueue.pop();
}
void nextMoveEvent();
void nextPropChangeEvent();

void nextEvent() {
    switch (order.front()) {
        case ENT_SPAWN: nextSpawnEvent();break;
        case ENT_MOVE: nextMoveEvent();break;
        case ENT_PROP: nextPropChangeEvent();break;
    }
    order.pop();
}

Or tag structures with metadata to end guessing. (This feels a bit cumbersome to me but does allow to keep a single event queue, also creating message codes should be re-usable for networking)

class EntityEvent {
public:
    EntityEvent(int t) {type=t;}
    unsigned char type;
};
enum {
    ENT_SPAWN,
    ENT_MOVE,
    ENT_PROP
};
class SpawnEntityEvent : public EntityEvent {
public:
    SpawnEntityEvent() : EntityEvent(ENT_SPAWN) {};
    std::string resource;
    double x,y,z;
};
class MoveEntityEvent : public EntityEvent {
public:
    MoveEntityEvent() : EntityEvent(ENT_MOVE) {};
    Entity* ent;
    double x,y,z;
};
class ChangeEntityPropEvent : public EntityEvent {
public:
    ChangeEntityPropEvent() : EntityEvent(ENT_PROP) {};
    Entity* ent;
    std::string key;
    std::string value;
};
std::queue<EntityEvent*> eventQueue;
\$\endgroup\$
5
  • \$\begingroup\$ Welcome to the site MickLH. We typically like our answers to be a bit more complete. You've got the start of an answer here, but it really needs a bit more explanation to be a complete answer. If you could add more information you could make this a decent answer. \$\endgroup\$
    – House
    Commented Jul 1, 2013 at 17:09
  • \$\begingroup\$ is it preferred that i make a quick edit with example code, or write an essay? \$\endgroup\$
    – MickLH
    Commented Jul 1, 2013 at 18:54
  • \$\begingroup\$ However you think it best to answer the question. Everyone has their own style and standards. \$\endgroup\$
    – House
    Commented Jul 1, 2013 at 19:21
  • \$\begingroup\$ Some description would be nice. How would your solution work? The first sentences is pretty much clear (at least to me). But what do you mean by tag structures with metadata to end guessing. Some kind of event type attribute? \$\endgroup\$
    – Stephen
    Commented Jul 1, 2013 at 21:16
  • \$\begingroup\$ Thanks for the heads up, I've written some code that I think explains the concept a little better. \$\endgroup\$
    – MickLH
    Commented Jul 18, 2013 at 21:36
0
\$\begingroup\$

I have spent a while thinking about this issue myself and it seems that it's a static vs dynamic programming type of choice. You can't have both advantages unfortunately.

When speaking about a language like C# it comes down to a choice of where to put casting + reflection logic or alternatively whether to use dynamic objects.

The best sample of two approaches would be NServiceBus and SignalR distributed messaging frameworks:

NServiceBus (casting somewhere "above"):

public class PlaceOrderHandler : IHandleMessages<PlaceOrder>
{
    public void Handle(PlaceOrder message)
    {
        Console.Out.WriteLine(@"Order for ""{0}"" placed.", message.Product);
    }
}

SignalR (using dynamics in-place):

stockTickerHubProxy.On("UpdateStockPrice", stock => 
    // Context is a reference to SynchronizationContext.Current
    Context.Post(delegate
    {
        textBox.Text += string.Format("Stock update for {0} new price {1}\n", stock.Symbol, stock.Price);
    }, null)
);
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .