I've tried my hand at writing component systems and entity systems.
What I haven't managed to solve is the problem of event collision.
Say, we have an components A,B and C. A triggers 'kill B' event. When B recieves it, it erases the message (doesn't propagate it further). When C recieves it, it erases B. The order of which component will get the event first is not defined.
When the order is 'A,B,C', B stays alive but with 'A,C,B' B is erased. In other words, side effects of the event are undefined. A,B and C need to know about each other (which I don't want) to handle the event properly.
I intend to use components for games so for a more substantial example: we have an arrow (Projectile component) which fires of a collision event upon striking Link (Collidable component). Link has shield (also Collidable) which stops damage from affecting Link's Health component.
Arrows and health were written without the shield in mind and my component architecture is supposed to be encapsulated, so Health processes the collision event without knowing about the arrow or shield. Whether or not the damage is stopped depends on the order of components, which is random.
The best I've came up with is keeping a 'cache' of actions the event will do at the end of processing but still this solution has the same problems, only delayed. I could ditch events and try entity component system with storing 'mutations' in systems but that's not much different from just using global data for everything.
Okay, my communication skills are terrible. Here's what I've done to make components working without knowing about each other:
- Use 'pure' entity system (components are pure data, all logic in systems)
- Components do not talk to each other. The systems talk to each other by adding system-exclusive components to entities
- Systems for above situation are: Movement, HandleCollision, ApplyArrowDamage. Movemen t appends a Collision component to the 'reciever' of the collision (Link) when calculating movements. HandleCollision reacts to Collision component, appends ArrowDamage component to Link entity and removes Collision component from Link. ApplyArrowDamage does just that and removes ArrowDamage component and maybe appends Dead component to be handled elswere.
- Now, to create the situation when the shield blocks arrows, I create an CheckShield system and place it between HandleCollision and ApplyArrowDamage. CheckShield works with ApplyArrowDamage, checks whenether the reciever (Link in this case) has a shield equiped and removes ArrowDamage component if so.
- This way, my old code doesn't need to account for new code to work properly and vice versa.
For the 'ABC' situation I do the same: If I want C to erase B, I write a new system that works with C and fires off before the system that works with B. The only sane way to solve this problems with event raising is to keep rethrowing the event whenether it's state changed, and keep rethrowing it until it doesn't changed in the last iteration.