#Are there any best practices for events under the unity ecs architecture?

1 messages · Page 1 of 1 (latest)

faint lark
#

I want to use c# events to handle some business logic under the dots architecture. I would like to ask about the best practices for events under dots.

worthy egret
#

With dots - your events is data. List of structs for example. To create event - you add to that list. Then any system can read it. Afterwards you clear it

#

C# events themselves won't even work

faint lark
#

oes it mean that the event is a component?

#

does it mean that the event is a component?

#

When an event occurs, I mount the component on the entity and uninstall it from the entity when the event ends?

worthy egret
#

And you don't add/remove components, instead you add to it

#

And once read - remove from it

#

Basically, if entity is supposed to be "listening" to certain event - it just contains DynamicBuffer for it

#

And invoking logic adds to it

#

It's inverted compared to c# events

#

Example: DamageEvent

#

If entity can be hit with damage - you simply add damage event to it's buffer

#

And then other systems read it: apply damage to health, spawn vfsx on damage and etc

#

Something you can also do - modify your events before they are read by other systems

#

This can be used for example to calculate damage reductions for complex combat systems

#

So armor system just reads events, checks if they match something and reduce value in them accordingly

#

Next system already has it processed

stable venture
worthy egret
#

so you can optimize it really well

#

especially when you enable usage of change filters

#

so systems that go over those buffers will skip chunks when there is no chance of write in it

#

so in the end it's highly parallelizable

#

and very modular

#

and you can over optimize individual systems without affecting others

stable venture
#

Yeah, I just figured if you split a damage calculation into 7 steps and it doesn't happen frequently, if the change filter checks adds up to more cost than just letting one system handle it. I do get all other benefits though^^

worthy egret
#

as well as add as many new ones as you want

#

7 steps sounds overkill

#
  1. Some system detects damage and writes it in some internal buffer
  2. System goes over that buffer and to each damagable entity in it - adds damage event
  3. As many systems as you want query damagable entity and do their thing based of damage event
  4. System clears damage event buffer
#

that's it

#

in the simplest example it's just 4

#

if you combine it into one system

#

you'll just save some time on main thread

#

that will be spent on scheduling additional jobs

#

on threads, it's likely that performance will be very similiar

#

allthough depends highly on "god" job

#

if you'll manage to make it parallel, it might be better

#

but with modular case - one very good benefit is that while jobs are small and doing atomic thing, they also access much less component types

#

which means that while they are not parallel themselves, they run parallel to each other

#

unless ofc they access same components

abstract fossil
worthy egret
#

buffer should have in operator

#

then this job will run only when this component was accessed with RW for chunk

#

meaning if you write to this buffer only via lookup

#

then it will only trigger then

abstract fossil
#

ohh this is amazing, thanks!

#

now I know why the attribute wasn't working before

worthy egret
#

it doesn't actually check if data changed

#

and it's per chunk

#

not per entity

#

so only use case - optimizations and that's all

abstract fossil
#

hmm I see, but it is still a good improvement to keep in mind
I have tested it with IJE and IJC and it seems to be working fine, just as you said

worthy egret
#

even if it doesn't write

abstract fossil
#

so every time any job gets RW access our WithChangeFilter job will have to run?

worthy egret
abstract fossil
#

okay that is very unfortunate then, but I can see why they did it like that

worthy egret
#

because it's cheap

#

just 1 int per chunk

#

practically free

#

but provides so much opportunities

abstract fossil
#

for something like damage you will most likely always have some RW access, so the attribute doesn't do much then

abstract fossil
worthy egret
#

there is a way though

abstract fossil
#

but WithChangeFilter most likely doesn't have any big overhead?

worthy egret
#

via IJobChunk

#

without IJE codegen

#

you can access RO pointer

#

do writes

#

and if you actually did write

#

update change filter

#

job boilerplate will grow massively

#

but for hot paths it's worth it

#

Another way to have it

abstract fossil
#

but then every time you use the DynamicBuffer you would have to use IJC and not IJE

worthy egret
#

is if your ref job runs using enabled component filters

worthy egret
#

btw, it's not just buffers

#

same works for any component

worthy egret
#

that would mean, when it's disabled

#

job won't run for those chunks

#

and won't trigger change filters

#

now this is a pretty good opportunity to have chains of logic execution via iteration, that fully utilizes change filters

#

but this is like cutting edge of data design

abstract fossil
#

it's really cool, since you can even tell the System not to Update using the RequireForUpdate

worthy egret
#

enablables can only be read with a sync

#

so either you need a sync point to read on main thread, or you read inside of job

#

meaning job must be scheduled as long as at least 1 entity exists

abstract fossil
worthy egret
#

what I said applies to RequireForUpdate

#

this requirments only checks for entity existance

#

ingoring filters

abstract fossil
#

ohh only for RequireForUpdate

worthy egret
#

change filters, enablable filters

abstract fossil
#

okay I misunderstood because you mentioned read 👍

#

I thought generally about read access

worthy egret
#

well, that's true too

#

if you try to read component on main thread

#

you need to sync

#

because other jobs might writing to it atm

#

this applies to enablable and change filters too

abstract fossil
dire totem
#

is there any example code that I can learn?

storm ridge
# dire totem is there any example code that I can learn?

if you want to learn something about events in dots you can read this topic: https://forum.unity.com/threads/comparing-different-approaches-for-events-in-dots.1267775/
and it's implementations here: https://github.com/PhilSA/DOTSEventsStressTest/tree/master/Assets/StressTest/TestEvents
this is pretty old (uses foreach somewhere in project) but you can learn how to deal with "events"

GitHub

Contribute to PhilSA/DOTSEventsStressTest development by creating an account on GitHub.