3
\$\begingroup\$

I'm working on a game which utilises a variant of the entity component system pattern. In my current code, I have been communicating between components using code of the form:

_entity.GetComponent<MovementComponent>().TargetPosition = targetPosition;

I wanted to remove this very strong coupling which my components have (as a result of specifying exactly which component should receive the message), and transition towards an event / message system.

As a result, I've written an implementation which seems ideal, however it uses run-time dynamic typing which has me concerned that there may be a better way to achieve my goals.


My code is as follows, currently:

Inside Entity:

public void SendMessage<T>(T message) {
    _components.ForEach(x => (x as dynamic).Message(message));
}

Inside Abstract Component class:

public virtual void Message(IComponentMessage componentMessage) { }

Inside an example component (MovementComponent) that receives a move message:

public void Message(MoveMessage message) {
    Console.Write("Received message");
}

Dispatching a command to move:

private void IssueMovement(TileCoordinate targetPosition) {
    Console.Write("Dispatching move message");
    _entity.SendMessage(new MoveMessage(targetPosition));
}

Example Message class - MoveMessage:

public class MoveMessage : IComponentMessage {
    public readonly TileCoordinate MoveTo;

    public MoveMessage(TileCoordinate coordinates) {
        MoveTo = coordinates;
    }
}

NB: IComponentInterface is just an empty interface at the current moment in time.


The way I see it, my current approach has some big advantages:

  1. Adding a new message type does not require any modification elsewhere to be supported.
  2. Subscribing to an event is as easy as creating a method with the correct event class.
  3. There is practically no overhead for listening to a specific event - no need to instantiate a class, inherit some event-specific interface, register interest at run-time with a subscribe method, etc.
  4. As messages are represented by classes (like a MoveMessage), they can contain all the relevant data without needing to be cast or coerced.

However, as mentioned at the beginning of my question, I am concerned about using run-time dynamic typing in this fashion (See my dispatch code in Entity - the second snippet in this post). This system will probably be handling a lot of messages, and I have some concerns about the overhead incurred (it also feels like a code smell in general)

I would really appreciate feedback on this approach; particularly whether it seems like an abuse of dynamic to more experienced C# developers than I, but would also appreciate alternative suggestions.

Alternative approaches I've considered, and why I did not go with them initially:

  1. Exhaustive set of event properties which components can subscribe to manually - adds extra code overhead to each message type, which I'd like to avoid
  2. Dictionary from event Type to handler in each component - have to subscribe at run-time, which feels undesirable.
  3. Switching dynamically inside a non-overloaded handler - more viable than it sounds since most components will not listen to more than a few message types, but still has unpleasant code overhead

I am quite sure this code is okay, outside of the overhead induced by the run-time typing - my apologies if this makes it a bad fit. I am just hoping for a once-over review by someone more experienced to reassure me that this is a good design.

\$\endgroup\$
3
\$\begingroup\$

You have one big problem with this design. It's easiest to explain the problem with the example.

So, let's say you add a new feature where you can issue a speak message. You'll need to add SpeakMessage and SpeakComponent classes. SpeakComponent would have public void Message(SpeakMessage message) method.

In this scenario, when someone calls your IssueMovement method, it will call SendMessage method which in turn will iterate over _components (which now contains MoveComponent and SpeakComponent) and try to call their Message method that has one argument of type MovementMessage. Since SpeakComponent doesn't have that method, it will throw an exception. The only way to avoid that with your current design is to have your component classes each handle every message in the system which is not what you would want to do.

So, to improve your design, your entity will need to know which component handles which message. The best way to do it, IMO, is to have an interface IMessageComponent<T> where T: IComponentMessage with a single method void Message(T componentMessage). MoveComponent would then implement IMessageComponent<MoveMessage> and SpeakComponent would implement IMessageComponent<SpeakMessage>.

Your SendMessage method inside the entity would then become:

public void SendMessage<T>(T message) {
  _components.OfType<IMessageComponent<T>>().ForEach(x => x.Message(message));
}

If you want one component to handle more than one message type (although, that would violate SRP), your component could implement multiple IMessageComponent<T> interfaces with different message classes.

\$\endgroup\$
  • 1
    \$\begingroup\$ I believe that in that case the MovementMessage call would be sent to a method I showed in AbstractComponent which takes any IComponentMessage and does nothing - it seems to be demonstrating that behaviour right now. Regardless - this is a really nice improvement, gets rid of the use of dynamic I was concerned about and saves calling into every component unnecessarily - thank you very much! \$\endgroup\$ – Ashley Davies Jul 11 '17 at 1:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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