#Is there an alternative to DynamicBuffer for a single int?

1 messages · Page 1 of 1 (latest)

sand dew
#

Is there anything like a DynamicBuffer which offers thread safe mutation on a single value type? Feels a little strange to append to a buffer just to read the buffer length on processing and ignore the contents. Thanks!

fresh vault
sand dew
#

Would I run into issues with 100k entities trying to increment or decrement an int value in an icomponent? I thought dynamic buffers were required for that type of operation.

fresh vault
#

they are fully managed by Unity

#

thus are easy to work with

#

if you are trying to use them in any other way - something is wrong

sand dew
#

Interesting. What I took away from https://youtu.be/IO6_6Y_YUdE was that in order to accumulate changes from a large number of entities (like damage) on a single piece of data, a thread safe container was required. I didn't realize I could just pass a RefRW to an Icomponent into a parallel job without causing issues. I'll try that out. Thanks!

📌 Download the full project files: https://www.tmg.dev/entities-1-0 📌
👨‍💻 Code/Scripts from this video: https://www.tmg.dev/entities-1-0-code 👨‍💻
💬 Come chat with other DOTS/ECS devs: https://tmg.dev/Discord 💬

🚧 Resources Mentioned 🚧

Unity ECS Documentation - https://docs.unity3d.com/Packages/com.unity.entities@1.0/manual/index.html

My Videos...

▶ Play video
nimble maple
#

Could you point us to the part of the video you're referring to?

One single element of a DynamicBuffer isn't meant to be written to by multiple threads. But a typical approach to handling damage in parallel jobs would be:

  • the damageable entity has a DynamicBuffer of damage "events"
  • Each (parallel) job appends a damage "event" to that buffer, using ecb.AppendToBuffer (this will later append these damage events on the main thread)
  • a single-thread job finally applies all the damage events to the health
fresh vault
#

now that you mentioned it, I don't understand at all what you meant in the first place

fervent dirge
#

NativeContainer?

#

+1 to understanding your use case though. Having multiple threads increment a value in parallel does not sound safe.

sand dew
#

Sorry I didn't word my question successfully. My use case is that I want to accumulate the number of dead entities so a single spawner can decrement a counter. The way I have it currently implemented is a parallel job looks for a deadeventtag on entities, and appends to a buffer the spawner uses to decrement. Right now I'm forced to populate the buffer with a "empty" ibufferelement, that is forced to have at least one member which goes unused. In fact I dont look at the members and just look at the buffer length before clearing it. So my question is if there is a container that affords the thread safety of dynamicbuffers, but allows for non blocking concurrent write access to a single value type.

fresh vault
#

limitations are same for both

#

and no

#

you can't write to same value

#

in parallel

#

also

#

to count number of entities

#

you might want to just use query.CalculateEntityCount()

sand dew
fresh vault
#

store dead entities or store one integer per buffer?

nimble maple
# sand dew Sorry I didn't word my question successfully. My use case is that I want to accu...

Based on this description, something like @fresh vault 's CalculateEntityCount idea above would sound like a good solution:

  • Create an EntityQuery for your dead entities (those with a DeadEventTag)
  • Calculate the count of these entities using deadEntitiesQuery.CalculateEntityCount()
  • Subtract/Add that count from your singleton component that has the counter
  • (later in the frame) destroy all dead entities efficiently using EntityManager.DestroyEntity(deadEntitiesQuery)
sand dew
# nimble maple Based on this description, something like <@331022224779771915> 's `CalculateEnt...

That sounds like it will work. I'm probably taking an ill advised approach for implementing everything as IJobEntities using IAspects as the methodology to define both logic and the implied query within them (i.e. I haven't used a manually constructed query in the project... yet). I'll probably stick with parallel jobs + buffer so I can also do additional per dead entity management/logic at the same time processing the DeadEventTag.

Thanks for the clarification!

nimble maple
#

It sounds like you could avoid the buffer entirely for this use case. ECB operations are relatively costly since they must happen one by one on the main thread, and so it's always a good idea to minimize them when possible

If you need to do additional processing per dead entity, simply add systems that iterate on entities with a DeadEventTag after it was added, but before they get removed/destroyed in the frame

Or if that processing needs to happen globally, you already have access to the number of dead entities with deadEntitiesQuery.CalculateEntityCount(), so you can just use this instead of the buffer approach

fresh vault
#

that's what it's meant for anyway

nimble maple
#

also when I say "ECB operations are relatively costly", maybe take this with a grain of salt. It's costly in the context of massive scale, but don't be afraid to use them

sand dew
# nimble maple It sounds like you could avoid the buffer entirely for this use case. ECB operat...

One thing I am doing in the HandleDeadEntity parallel job is also retrieving an option death data component that can be a sibling to the DeadEventTag, which is then used to make sure specific spawners are only considering dead entities which they spawned. In order to do this as a query, it would need to limit the query based on data inside this optional component, rather than just the component type.

Naive question: Is there a way to do that? Or would the expected approach be to query all DeadEventTag entities in a system's OnUpdate, then loop all of them, checking for all DeathData optional siblings, at which point I would wouldn't count dead entities who's DeathData.Uid != Spawner.Entity;

nimble maple
#

oh I see, so you have multiple spawners and each one must only care about the number of dead entities that they spawned? In that case, the buffer approach is starting to make sense.

If there will be tons of dead entities per frame and you're feeling motivated, there might be more efficient alternative solutions that involve NativeStream or parallel MultiHashMaps instead of appending to buffers using ECBs, but this might be overkill for your use case.

Shared Components could also perhaps be part of an alternative solution, but I doubt I could give more details on that solution without trying to implement it in practice first (each spawned entity would have a SharedComponent corresponding to their spawner, so you could write a query that returns all entities spawned by a specific spawner)
https://docs.unity3d.com/Packages/com.unity.entities@1.0/manual/components-shared.html

sand dew
#

One final thought. It would be fantastic if someday there was a way to execute methods on aspects in a parallel safe way, i.e. ecb.EnqueueAction(entity, BurstAction<aspect> => { aspect.DoSomething(); }); Would save a lot of extra juggling work needed with some DynamicBuffer use cases.

Where ecb is a parallel writer and ecb.EnqueueAction is being called from a Burst compiled parallel IJobEntity.

fresh vault
#

BUT

#

what can be done

#

is that you can pass function pointers

sand dew
fresh vault
sand dew
#

I'd imagine a little code gen magic could generate burst code. It wouldn't need to be C# Actions to provide that type of functionality. Auto value 'capture' would require some clever thinking.

fresh vault
#

besides

#

that would be unusuable in jobs, unless you invent system api for jobs as well

#

which sounds like far too much work

sand dew
#

Hopefully sooner than later. They need a mechanism to enact distant mutations from parallel jobs that doesn't go through DynamicBuffers, as it requires quite a bit of boilerplate, which ECS already has too much of.

fresh vault
#

nah

#

I just realized

#

that this will require declaration of struct as managed

#

thus my first statement is valid - never

#

ecb.EnqueueAction(entity, Action<aspect> => { aspect.DoSomething(); });
Something has to accept managed value here

#

overall passing custom code is not for ECS