#Is there an alternative to DynamicBuffer for a single int?
1 messages · Page 1 of 1 (latest)
Can't you just use normal IComponentData?
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.
DynamicBuffers are a way to have lists on entities
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
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...
you can't
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
now that you mentioned it, I don't understand at all what you meant in the first place
NativeContainer?
+1 to understanding your use case though. Having multiple threads increment a value in parallel does not sound safe.
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.
what's stopping you from just having component data with int as value?
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()
What you described is my understanding too. My question was really about if there was an alternative to DynamicBuffers. Or is ecb.AppendToBuffer meant to be the hammer for all 'context aware mutations' that don't fall under the Add/Remove/Set methods on ParallelWriters?
What is your use case for DynamicBuffers?
store dead entities or store one integer per buffer?
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)
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!
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
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
This is perfectly fine
that's what it's meant for anyway
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
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;
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
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.
that will never be done, that's for sure
BUT
what can be done
is that you can pass function pointers
What makes you so sure?
lambda is managed and is not burst compatible
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.
well, maybe in 5-10 years then 😅
besides
that would be unusuable in jobs, unless you invent system api for jobs as well
which sounds like far too much work
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.