#Structural Change or IEnableabledComponent ?

1 messages · Page 1 of 1 (latest)

open rock
#

Hi, hope you are doing well.

I have been learning Unity DOTS recently and I am struggling with a concept (Structural Change). I also attended the DOTS bootcamp recently held, but still i didn't find the answer I was looking for.

Imagine a game where I have lots and lots (1000+) of enemies, and i have a player who can shoot these enemies down. Now, in a DOTS setup, lets say when the player kills the enemies, should I "Destroy" the enemy or should i just "Hide" it?

I know the question seems very trivial, but if you let me explain quickly. If I destroy the enemies entities, (and it will happen very frequently)....it will be a "structural change" which can be heavy and should be avoided, right? But if I just "Hide" the enemies, now that's a lot of hidden enemies in the chunk which serves very little purpose. Which one should i opt for?

I come from a OOP background, and I have been developing games for almost 6 years now. Very rarely I "Destroyed Gameobjects" because I know thats a significant thing to do in a game and should be avoided. I always tried to hide it (like pooling) in case i need it later. But for this particular problem, where I have lots and lots of enemies, I am very confused what to do.

I am trying my best to avoid structural changes, but its becoming really tedious at some points to hide something that I wont need at a later point in the game.

jaunty estuary
#

"Where and when to make structural changes?" is a better question. Because structural changes are not something to be avoided in ECS. The cost of destroying and instantiating entities is actually much smaller than that in the GameObject world.

#

FYI, even hiding or disabling entities is also a structural change. Because a tag component will be added to the entities thus change their archetype.

open rock
#

i know adding / removing will cause a structural change. i am just "setting" the flag true/false in terms of need

#

"Where and when to make structural changes?" is a better question
I agree with you. I am actually trying to find the answer myself.

jaunty estuary
#

The answer is you should plan your structural changes so they won't obstruct the running jobs.

#

Means, whenever a structural change happens, no job is running or have to be forced to complete.

#

EntityCommandBuffer is a way to defer your structural changes to the later time. Such ECB should be retrieved from where no job would be running.

#

Another way is using IEnableableComponent to mark entities then have a system at the beginning of initialization group to destroy them all at once.

jaunty estuary
open rock
#

in a way, you are creating a clean up system which cleans its entities in initialisation group.

#

i am thinking of doing the same when I show UI or i know when the player is idle (looking at a ui or something)

severe atlas
#

dont change archetypes at runtime, or if you do dont do it on a frequent operation(certainly not on the simple destruction of an enemy). creating and destroying entities is kind of an essential part of a game that has some sort of mechanic revolving around such things, while structural changes are ideally avoided when possible, sometimes its not possible(like using lookups is also basically a requirement for much gameplay logic). i dont think you should sweat creating and destroying entities en masse, best to do it in batch arrays if possible though

#

and things that are "heavy" are relative terms, like compared to oop creating and destroying in ecs is not heavy. just always profile

jaunty estuary
#

Whatever solution you choose, I suggest you to frequently look at the profiler.

mossy hazel
#

adding/removing components is generally bad (but there are exception for cleanup components for example)
creating/destroying entities is perfectly fine

Adding/removing leads to creation of unique archetypes (which sometimes may never be reused), which slow downs app the more you have them (as they are never garbage collected).
Creating/Destroying entities is reusing existing archetypes and mostly is just memory copying, which is fast (allthough, must be done on main thread).

severe atlas
#

also in your case is it literally just 1000 entities? if it is, thats not a lot tbh. now if enemies have a hierarchy of sub entities, or linkedentitygroup where there are dozens or hundreds of connected entities to each main enemy entity, then perhaps there is more reason to be concerned

mossy hazel
#

oh yeah, overhead of 1000 entities is nothing compared to overhead of 1000 game objects. Mostly it's just specific logic about entities that will be noticable at high counts (graphics or physics for example)

open rock
#

yeah, i am keeping an eye on the profile time to time making sure i am not doing anything stupid. i am just tying to figure and plan out the best thing ahead of time. its difficult in the beginning of the project because the game is still evolving and things are not fixed yet (like powerups, balance etc.)

one thing i found out when working with ECS, you spend most of the time designing and profiling the systems rather than making new systems. 😂
on the bright side, I am learning a lot and learning the new ways. and the best thing is, those systems are very modular and re-usable if it is designed ideally.

open rock
severe atlas
#

so 1k physics rigidbodies? still not a big deal imo

mossy hazel
#

if all dynamic - may be pretty slow

#

on mobile especially

#

but you know

#

it's same everywhere

open rock
#

yeah, i am making it for mobile.

mossy hazel
#

but unlike game objects, it's not game object overhead that matters here

#

it's specifically physics overhead

#

because for entities - 100000 entities is nothing

#

it handles them just fine

open rock
#

thats why i am trying to optimise all the things i can, and put as much budget (frame time) as i can in the physics simulation

mossy hazel
#

you can create and destroy 100 entities per frame

#

and unlikely you'll run into performance issues because of that

#

(but it's certainly seems more like very extreme case)

open rock
#

my enemy count will be at most 1k to 5 k

severe atlas
#

did you profile?

open rock
#

i am keeping and profiling all the time. Right now, its about 100 fps

#

but its very early stage. No ui etc.

#

just wanted to know the norms and stuff, hence asking what you guys think

#

i am trying to get 60+ FPS on android,

mossy hazel
#

fun fact, what I had in editor at 70fps turned out into 300fps on il2cpp 😅

#

(not saying exact thing will happen to you!)

open rock
#

xD

#

🤞

#

i will test it on mobile within 1-2 days

#

making the levels now. anyways, thanks for you input guys.

#

i will keep an eye out on the profiling and stuff.

#

i guess i wont know until i stress test it myself and see.

mossy hazel
#

tbf, I'd rather focus more on code maintainability and ease to extend and refactor

#

you can always optimize later with ECS

open rock
mossy hazel
open rock
#

i am going for a hybrid model for now, like UI, Analytics and few other things are handled on the mono side

severe atlas
#

might end up being something like ui hogging all performance in the end

mossy hazel
#

just a couple of "newbie" tips:

  1. Don't use SystemAPI.Query if you aim to use threading (jobs) in game. Those are usually not compatible together (unless you know what you are doing).
  2. Never use ECB to set component data (unless it's part of instantiation process).
  3. Never access ECS from mono. It's always better for ECS to access mono instead (keeps project architecture simpler, as only systems can be sources of logic)
severe atlas
#

found out having multiple uidocuments is pretty heavy and had to rework them to be a single one

mossy hazel
open rock
#

i got the other 2

mossy hazel
open rock
#

why

mossy hazel
#

SystemAPI.Query runs things on main thread and automatically creates sync point when necessary

#

so if say you have systems ordered like this:
[Query] [Job] [Query] {Job] [Query]
You'll end up running everything on main thread if there are dependency collisions between jobs and queries.
Or more precise - main thread will have to wait for all dependent jobs to finish in order to proceed (this is what called sync point).

#

the example above is 100% as explained, if all systems access same component

#

LocalTransform for example

#

and since systems usually access many components - mostly sync points just complete almost all scheduled jobs in project

open rock
#

in that case, ECB is a better way to go, right?

#

like the local-transform example

mossy hazel
#

that's absolutely unrelated

#

and no

#

that's point 2

#

never use ECB to set component data

#

ECB equivalent in oop code would be something like new Task(() => entity.SetComponent(myTransform));

#

and this "lambda" will be called on main thread

severe atlas
#

honestly the first two points issue makes are very simple fixes later on down the road if you mistakenly do them so i wouldnt worry about those two

mossy hazel
#

so not only you are doing the work on main thread in a slow way (because ECB code cannot get optimized well enough like system code can)

#

but you are also wasting time just scheduling those changes on ECB

mossy hazel
#

the 3rd is project killer

#

😅

#

refactoring that is usually - scrap all affected code and start again.

open rock
#

ohk. sorry, i thought sync point is only created when there's a structural change.

severe atlas
#

spaghettio code with no.3 😄

open rock
mossy hazel
#

usually that's when main thread wants to do smth

open rock
#

ohk

mossy hazel
#

so best place for main thread to do smth - before any jobs are scheduled

#

structural changes cause all jobs to sync (because entities layout changes and that may affect any job)

#

but even simply accessing LocalTransform on mian thread, will cause all transform related jobs to sync

#

which most likely will sync 90% of project

#

since physics are affected

#

and that fact usually creates ironic situations where it's better to schedule job to do something stupidly simple vs doing it on main thread. And simply scheduling it is slower than doing that code, but you avoid sync point

open rock
#

hmm, I am currently grinding through the documentation and anything I can find where the "best-practices" are mentioned.

mossy hazel
#

oh don't trust unity on best practices

#

I don't think they know them 😅

open rock
#

some of the concepts are new to me, will check it out as i go through.

mossy hazel
#

Majority of samples are not showing any good practices (in fact, what they do is usually awful). So I'd only use samples for ONLY context of that sample (if it's about showing how to do ragdoll, I won't care for any patterns they used to make it work, but only how exactly they create it and what APi they use)

open rock
#

hmm. i agree with you

#

and the API changes so frequently now-a-days

mossy hazel
#

been stable mostly for a year

#

only transforms API changed early after 1.0 exp release and that's it

open rock
#

no, what I am saying is, most of the documentation you find

#

those are done with older API than 1.0

#

even, most of the youtube videos and stuff

mossy hazel
#

ah, what I said about samples is also valid for manual

#

never use Entities.ForEach for one

open rock
#

hmm. so its very easy to get confused there is what i was trying to say

mossy hazel
#

yeah, dots has so many "little details"

#

that are not written anywhere

open rock
#

anyways thanks for the info. i will check those through. specially thanks for the sync point advice

mossy hazel
#

which sucks

#

and majority of discord convos that expose them are not stored anywhere

#

for public wiki for example

open rock
#

yeah. i agree.

#

thanks nevertheless 🙂

#

helps a lot

open rock
#

Woah!! Unity actually has a dedicated section in the unity profiler called Structural changes which monitors such changes!!

#

this is really helpful. This will help me narrow down structural changes that might cause an issue. I didn't even know it existed!

jaunty estuary
#

wow, me too 😮😂

mossy hazel
#

Haven't been able to see anything valuable in this window

open rock
midnight shard
mossy hazel
#

you should not rely on them in any way

open rock
# midnight shard one useful case: you can profile your project and show us "ideal" ratio between ...

since you asked, I hope it will help you.
In my case, destroying the entities was much better than hiding it.
I guess I can explain why now. The entities that I was trying to hide has physics components in it. I managed to "hide" it by moving the entity outside my map, But doing this was a bad idea because, this clean-up system had to wait now for other jobs to who can "move" the entities (i.e. has write access to LocalTransforms.) So it was clogging up the cleaning system due to the dependency. The average wait-time was about 1-2 ms (for context, I have about 1000 rigidbodies in the scene, all has simple pathfinding and very simple AI which is responsible for moving the bodies.)

When I removed the pooling and used DestroyEntity instead, the wait time became almost negligible, and after analysing the profiler window, I now have an exact number of how much time it needed for that structural change (see screenshot). It was about 0.005ms (5.33us). It was really helpful in my case to narrow down and see how much of an affect destroying entity has.

Only gotcha I would say that, if you are doing structural change, make sure to do it using an ECB, even better, if u use an existing ECB if u can.

Lastly, every game is different, so the use cases are different and there might be 100s of things that i don't know and yet to learn, where maybe a there's a better solution than this. but for my case, it was sufficient enough. So always profile and see. I found the structural change section in the profiler window very useful in my case in solving the mystery of "structural changes". Hope you do too. At least it will give you some numbers to compare. rest is up to you :).
Happy coding 🙂