#How do you do simple events

1 messages · Page 1 of 1 (latest)

mint escarp
#

I am trying to understand how to do a simple event system in ECS that isn't general purpose. I have a component that I want to be able to 'send events', like when it goes from 'stage A' to 'stage B'.

My initial thought was to add an event struct to a dynamic buffer on the entity with the component. And then have a system in late update that gets all entities with that component and clears the event buffer. And maybe a system group(?) before it that systems can go in to, to get the 'events'?

But this feels like it could be a rather naive approach? I did some looking around and found event systems, but they tend to go for high performance and end up being a bit obtuse to sort out what exactly they are doing and how my own version would look. Especially when I am still familiarizing my self with the ECS APIs and patterns.

silent lark
mint escarp
# silent lark if you need some simple rare event - create entities to raise it it's not recomm...

Oh really? Why entities? That seems like it would be worse than the buffer approach.

The events won't happen very often, but it is likely that a lot could happen in close succession. And that there would be a lot of entities with the main component.

I did take a it of a read over that, admittedly I got a bit overwhelmed by the options combined with the number new patterns and concepts to me. Guess I should just buckle down and read through it better.

silent lark
vapid silo
# mint escarp I am trying to understand how to do a simple event system in ECS that *isn't* ge...

At my company we are currently using the buffer approach you mentioned. For example we have a Health component, whenever it changes we modify a HealthChanged buffer as well. The next frame the Health system resets the HealthChanged buffer by clearing it before doing any new health related calculations.
Any job that needs to listen for changes can utilize a ChangeFilter on the HealthChanged buffer to ensure that they do minimal work when there is no change. Using a buffer also means you can quickly check the length of the buffer to deal with false-dirtying on a ChangeFilter (which happens cause when a job tries to write to a component it will dirty all (of the same) component in the chunk that contains them)

We haven't been using it for very long but it is much better than other solutions we used before. Such storing events in a singleton container.

#

It is not a "general purpose" solution in that it's automatically managed for you, but that pattern is applicable and works quite well on a lot of different issues

mint escarp
lament knoll
#

If I had to give a very quick summary of pros and cons of different approaches:

1- A global NativeList/Queue of events
Pros: Simple and decently efficient.
Cons: Not as efficient as approach #2 if you need to create events in parallel, and not as efficient as approach #3 if your events need to look up more than 1 component on their target entity. Also, you'll have to deal with dependency management manually.

2- NativeStreams of events
Pros: Best parallel write performance.
Cons: They can be a bit of a pain to manage, because each parallel job that wants to create events in parallel might need to create and manage its own NativeStream. And like approach #1, it may still not be as efficient as approach #3 if your events need to look up more than 1 component on their target entity.

3- DynamicBuffer of events on entities that "receive" these events
Pros: Pretty simple, and might have the best performance if your events need multiple component data access on their target entity (avoids component lookups).
Cons: Can't safely create events in parallel, unless they are produced by the same entity

4- Singleton DynamicBuffer of events
Pros: Similar to approach #1 but the dependency management is automatic.
Cons: Unless you go through the trouble of getting the buffer of events once per chunk instead of once per iterated entity, they might be expensive (need to get buffer from lookup every time).

5- Entities as events
Pros: Probably the simplest and most versatile, due to being able to use composition for events.
Cons: Probably the worst-performing of the approaches.

6- Enableable components as events
Pros: Great performance and simplicity if your use case allows using this approach.
Cons: They're only viable if you'll have at most 1 event per frame, or if you may have many events but you don't need per-event data and just need to know if at least one event happened.

#

When it comes to performance, something to keep in mind is that every events approach that involves checking for changes on many entities (like approaches 3 and 6) will add a little bit of cost to each update whether or not events happen, and that cost may grow with entity counts. This is because these approaches rely on having jobs that iterate these entities constantly and check for changes. This is done very efficiently when change filtering is used, but it still costs something, and if you have many different event types that work this way, your performance can have a death by a thousand cuts. Approaches like this can be especially inefficient if you have many possible event types and many entities that can receive these events, but events happen relatively rarely.

So for example, let's say you decide to use approach #3, but you have 30 different possible event types. That would mean adding 30 different DynamicBuffers to your entities that can receive these events, and probably 30 jobs that are constantly checking if any new events are present in these buffers. At this point, I would start to reconsider this approach. I would either pivot to global NativeLists of events (we still have to check 30 lists, but at least it's not 30 lists per entity), or I would come up with some scheme to use only one DynamicBuffer for all of my 30 event types (events would have a type id and we'd do a switch statement on that id when processing the events)

This is why I said the "Entities-as-events" approach was "probably" the worst-performing. It always depends. If you have 100,000 entities that can receive EventA, but EventA will only happen to 0-20 entities per frame on average, then the entities-as-events approach might perform better than the DynamicBuffer approach. The former costs a bit more when events DO happen, but it doesn't have the relatively high constant cost that the latter has for checking every frame if events happened on 100,000 entities with change-filter jobs

mint escarp
#

Oh that all helps a lot in understanding the different options and their pros and cons. Thank you very much @lament knoll 😄

summer rose
# lament knoll When it comes to performance, something to keep in mind is that every events app...

I did a quick look through on the old post and it seems it only does one read & write on target entity. Did you evaluate the different methods in consideration to if you need to have more reads on the target entity? Top of my head I would probably payload the event with data from the sender, and read some data from the receiver & do some calculation to the write. I can imagine that 3 and 6 would outperform the others in some middle ground as well if you need to do X reads on target entity as well? But maybe I'm wrong, I haven't profiled 😅

dusty mural