SiO2 EventBus Events

Hey,
I am currently designing a few events and ask myself which would be the best design:

If I have a CreatureEvent for example, which has the “subevents” Spawn and Death, then I want to know where the new Creature has been spawned or who killed the creature. Ignoring the fact that you can get the spawn position using components:

Would it be better to have a CreatureSpawnedEvent class and a CreatureDeathEvent class which store the Spawn Position or the Attacker Entity, or should I have one CreatureEvent with a field killer which might be null depending on the “subevent”?

Hard to say… since in an ES-based game I wouldn’t have any of these events at all.

Events are an OOP thing… so useful in the visualization/application lifecycle space… and maybe for meta-events like “player joined” in the server space but not really a game object management thing.

1 Like

But how would I handle such logic events?
For example death: I can’t add a “KilledByComponent” since the entity will be removed (I could add that component for one frame and then have a system remove the entity, but it feels wrong).

Another way of having a Component where you can register “onDeathHandlers” is technically the same as an EventBus but worse in that you have thousands of decentral lists for the same thing.

Actually I even need it for changes to the HealthComponent. The AI needs to know when it was hit to react appropriately (turn from friendly to hostile)

The AI may internally use some kind of event system, I guess… but I’m not familiar with those kinds of AI architectures. Usually they are in some state until the world state changes in some way to put them into a different state. At any rate, this is all internal to the AI system so a public event bus isn’t really needed. The “actor” object in your AI system can accumulate these events and you process them as needed.

Otherwise, I don’t understand what a KilledByComponent would be for… but the very idea implies that the game entity needs to stay around until that’s been processed. Otherwise, what are you notifying things about?

instead of removing the entities when they die, you could set a ‘dead’ component with a ‘killed by’ field on ? Then you could process all those in a DeadState and remove the entities there.

And likewise, instead of a ‘position’ component, you could do a ‘spawning’ component and process those in a SpawnState and replace the spawning component with a position component ?

If you need more systems to process a given information, just remember the order of the services you add to the game and change who ‘removes’ it ?

I have to say that i also struggle with the es logic. I always start adding FlagComponents which feels like abusing the ES as Eventbus.

Like for what?

My whole damaging system is basically a eventbus on top of the ES. Every damage dealer that interacts with a damage receiver creates an entity with the components:
DamageSource
DamageTarget
DamageType
DamageAmount
RemoveAfterFrame

Edit:
Actually any relations between entities where you require interacting with components from the other side of the relationship is quite strange in the ES world.
N-M relationships are just a nightmare.

Could you give an example of the nightmares?

The spell “United defense”:
Spawns an AOE circle.
Gives each friend inside the circle +5% health per second, +5% armor and -10% movementspeed.
Each enemy inside the area takes 5% more damage.
For each friend in the area the bonuses are increased by 10%.

The effect should stack if casted by different units.

That would be one of the more complex spells i have on my paper.

At the end you have to create a system that polls other entitie’s components and an if cascade. + a series of entities that only manage the relationships.

So it turns out to be a rather complex and opinion based topic. Given the fact that I already rewrote much of the codebase, I am not afraid of doing it again :smiley: (Even though a EventBus isn’t that different from invoking System methods, which is what I’d use it for, I guess).

The KilledByComponent, It would have to stay for at least 2 frames so every system can process it, but how about that little mod which adds a comic sound each time someone dies? It can’t hook anything, it would have to start up a system just for that “state change” information.

If you proceed to the damage topic: You would need a list<> inside the component or something since you can be hit multiple times. But how do you ensure that each system had access to that list exactly once? It’s technically possible but it’s not so pretty.

I feel these aren’t actually part of a “game object” but rather “messages” as a hint to other systems. Technically speaking, it doesn’t matter for the health of 45% what caused it. As such it probably wouldn’t need network transmission either, it’s just the systems in between them.

Now why Events? Compared to my previous “architecture” where I always called a method onBeingHit(), Events allow for much less coupling. You as sender don’t even care who processes it, and you don’t need such “Callback Lists” on every possible sender.

@zzuegg How about solving this by having a system which simply adds/removes “invisible” buffs to anyone in the circle? Then, in your armor calculation, you see if you are affected by that buff (giant if switch though) and then apply 5% gain.

That last part, I don’t understand. Which components are being polled?

The very idea that you’d want to process something because “this entity was killed by some other entity” means that you need to keep the entity around. Else, you are telling things “hey this thing that doesn’t exist anymore that you can’t access anything about anymore was killed by this other thing”. Which is not useful information.

The very idea that you need to do something with a dead entity means death != removal and/or you have some sort of “corpse” entity that spawns after. Either way, death is a part of the entities lifecycle and removal comes later.

At that point, a spawned sound effect when someone dies is no different than a loot drop. And it could use a system or hook into the existing sound effect system… but yes, in general, you’d use a system for stuff like that. It’s the entire point of an ES.

1 Like

You do not necessarily need to get all entities, in your case you have two groups: enemies and friends (I guess the friends group is smaller equal to enemy group). This is one segregation you can do. Also you can exclude enemies and friends which are far away with a simple cheap check. I think that it is not more expensive done right than any OO approach…
Not sure if I answered your question.

A good one, really. The dedicated system (SpellHandler or whatever) can’t just spread that boosts/impacts as entities once, because it also has to keep track of who of your affected friends in proximity just died (while the spell is still lasting) and reduce the boosts back by -10%. So it has to have something like active spells list that it will check every frame for consistency, and regenerate impact entities as needed. Those, however, can then probably be processed separately in different systems (like HealthControl, MovementControl etc).

We should probably be careful with terminology. When I see “HealthControl”, I think of JME controls… which don’t really play a part here.

That’s the fun part to me. The rest is pretty standard ES stuff.

Some system manages AOE membership.

Some system manages application of spell effects to AOE members.

Rather than static spell effects, they are based on the number of friends in the AOE. So the main spell entity that all of the spell effects link back to needs to be updated. It’s like a reverse buff, really. Conceptually: for each new buff entity you give out, you buff the spell itself.

From there, it’s not much different than another other spell whose effects would potentially change over time. Like, imagine a fire spell that does less damage over time, etc… The spell effects need to adjust their amount from the main spell entity.

I know sometimes it feels like events but it really isn’t. It’s still just set membership operations. When you think of this like massive lists of source + process + destination then it’s different. The event producer/consumer model is more expensive in comparison… and certainly harder to manage in a decoupled way.

Now I am really uncertain on how to proceed even for such simple things like “notifying the AI when it has been hit”.
I mean we are essentially talking about cross-system communication (at least when it’s about reacting to damage), so having a component with another type (A List of “Damages” and a boolean hasBeenProcessedBySystemX and a Decay of 2 frames) feels too far away from notifying a system so it runs some logic.

Actually it appears to multiple systems: Sound, Visuals, w.e.

Where do you guys see the issues with events vs. “event like components”?

that’s [quote=“pspeed, post:15, topic:38498”]
We should probably be careful with terminology. When I see “HealthControl”, I think of JME controls… which don’t really play a part here.
[/quote]
Sorry for that, I didn’t mean anything from JME ofc.

So here you base your 1:M broadcasting amount on the info you have from that AOE membership handler. And if M has reduced - you broadcast reduced amount…

Hmm this is kinda vague phrase, in which context? “AI has to be aware that it’s now low on health” - that’s one case, it can just check actual health level against a threshold. “AI has to know who exactly is firing at it” - that’s the other case, this can be done, for instance, by putting originator’s id into freshly born damage entity, and whatever then processes that entity (health system or something) can put that id to some “targeters” component dedicated to contain attackers, that can be examined by AI to see who is currently who. Well, at least I’m trying to manage it this way :slight_smile:

Sound/visuals are actually one-way systems that just process something given (you can consider this “event”, but anyway these systems don’t give anything back, so you don’t need two-way logical link when dealing with them).

If your decay system always runs last then I don’t see why this is an issue. And certainly a hasBeenProcessedBySystemX is a sign of a totally broken design.

I have trouble talking hypothetically because it’s too easy to keep adding “no, by what if”.

So someone needs to lay out a very specific example and I can tell them maybe ways to fix it. Else it’s too hard to guess what we’ll be hit with next.

One question I haven’t seen answered: why is a dead entity getting removed right away but you still want to do stuff with it? I suspect equating death with removal is a big fundamental problem.

What is an example of an event like component? I’ve never seen these in any of my code, really.

And that is the way i have implemented it.

One point why it might feel wrong is the reverse lookup: “EntityData.findEntities(Affects.cass,caster=current)”
If you would not have choosen the ComponentHandler based collections it would probably imply iterating over all entities.
It is really an implementation detail that makes a big difference here. Kudos for that!

I have some other issues, but thats for a different time/topic