Jobs aren't always the fastest for all operations. EntityManager operation mostly have to cause structural change on the main thread. Let's see how we could speed this up.

Bad : Schedule a job just to queue EntityCommandBuffer

It is true that worker threads are great but the mistake is that EntityCommandBuffer.

Actually EntityCommandBuffer is for remembering what to do, then work on them later. When "played back", it would be like EntityManager doing DestroyEntity one by one in the main thread. It is not like they are being destroyed in a job. (Pay attention to the word "command buffer". The commands are not being executed just yet.)

No, EntityCommandBuffer.Concurrent won't help you much either, you are just remembering what to do in parallel. (And Concurrent blocks the other thread on write if they clashed.)

The purpose of EntityCommandBuffer is to defer EntityManager commands, not to speed up EntityManager commands. And therefore this job is a complete waste of time if you schedule a job just to add/remove/delete entities via EntityCommandBuffer. You better off just do it without the job.

So it does not matter if you scheduled IJobChunk, or anything highly parallelized. Command buffer make them happen later. And now is better than later. (Performance-wise, sometimes you better do it in a job where something else happened)

Good : Just use EntityManager right there

Even iterate destroying with EntityManager directly, naively, will be faster because you didn’t schedule a job!

Be careful of invalidating the thing you are iterating on. Use PostUpdateCommand and iterate destroy should be faster on low number of entities. Or use EntityQuery and ToEntityArray to make a copy of things you want to work on to avoid invalidation.

Better : Use NativeArray overload

The strength of this method is that you can put whatever Entity in NativeArray<Entity> that you may allocate by yourself and handpicked Entity in them, from ToEntityArray of EntityQuery, or from NativeSlice subset of NativeArray<Entity.

public void AddComponent(NativeArray<Entity> entities, ComponentType componentType);

public void CreateEntity(EntityArchetype archetype, NativeArray<Entity> entities);

public void DestroyEntity(NativeArray<Entity> entities);

public void Instantiate(Entity srcEntity, NativeArray<Entity> outputEntities);

public void RemoveComponent(NativeArray<Entity> entities, ComponentType type);

Out of 5 methods :

Add/Remove : This is just an iteration over each one and add/remove. (not much better)

Instantiate/Create/Destroy : Looks like it can really batch create and destroy to save time!

Maybe even better : Use ExclusiveEntityTransaction

But if you wanna destroy (or other EM operations) in the job for real, then use ExclusiveEntityTransaction.

ExclusiveEntityTransaction is like an inverse of what normally occurs. Normally to do things to EntityManager we have to “come back” to the main thread for a moment (at EntityCommandBufferSystem, at ComponentSystem, etc.)

With ExclusiveEntityTransaction, we can “lock the EntityManager” for one thread to work on and prevent the main thread from using it. At the same time the main thread can go on and do other things which do not touch EntityManager.

So the use of ExclusiveEntityTransaction is heavily geared towards having multiple worlds. It renders 1 World near unusable (EntityManager became busy in-job) but your other worlds may still work on with their own EntityManager and the remaining worker threads. Now you see something that only multiple worlds can achieve! Worker threads do work stealing automatically and is a shared resource for all World.

But to make multiple worlds useful to each other it would requires more careful planning how to communicate. (Which is probably by EntityManager.MoveEntitiesFrom ?)

Setting up for ExclusiveEntityTransaction with other world doing EntityManager.MoveEntitiesFrom from your main world

This method has EntityQuery overload which from roughly looking, runs a job that cuts off queried chunk pointers and hand it to the 2nd EntityManager . No copy or anything. This way you could "get rid" of entities quickly, or just setting up for ExclusiveEntityTransaction.

Remember that EntityQuery must be made from the world owning things you want to move! EntityQuery is not interchangable between worlds.

Also this overload asks for entity remapper NativeArray working space from the caller, which you can get from EntityManager.CreateEntityRemapArray . You will get just enough size for the method to work. (Please make it from EntityManager of the world you are moving FROM) As a bonus you get the remap result back, but it is safe to just dispose it immediately or just use using on the EntityManager.CreateEntityRemapArray .

Finally when your 1st world run out of things to do unless it has the processed components, move them back. This step should be a bit difficult to determine “when”, as you don’t have JobComponentSystem’s dep chain to manage this dependency for you. Possibly, position a ComponentSystem conditioned with UpdateAfter all systems which does not require the thing processing in the 2nd world. This system then MoveEntitiesFrom , and all other systems which requires the processed entities should be conditioned with UpdateAfter this world sync system.

The most awesome : Use the EntityQuery overload of EntityManager

The ExclusiveEntityTransaction doesn’t even have this ability, so all hail main thread.

Any time you use the EntityManager with EntityQuery instead of Entity, you are doing it to everything in a whole chunk at the same time instead of to each entities one by one. Since EntityQuery represent a chunk! Multiple chunks even, if you have so many Entity that it spans several chunks on one EntityQuery.

Here’s all of them you can use.

public void RemoveComponent(EntityQuery entityQuery, ComponentType componentType);

public void AddComponent(EntityQuery entityQuery, ComponentType componentType);

public void AddSharedComponentData<T>(EntityQuery entityQuery, T componentData) where T : struct, ISharedComponentData;

public void DestroyEntity(EntityQuery entityQueryFilter);

VS NativeArray<Entity> overload? These are much better, since things like add and remove is really happening on the whole chunk and no one is moving, and NativeArray way couldn't add/remove.

  • Add/RemoveComponent : If this add is a normal component, the chunk data layout need to be reformed to be the shape of new archetype (but at the same time, still faster than one by one). If this is a tag component, the chunk data don't need to be reformed. Almost free, just setting a new archetype.

    Notice the lack of Data on the name AddComponent. This is unlike the Entity overload where we could specify what data. This one will default the value for everything in the chunk.

    *Doing AddComponent to the everything in a chunk is not the same as "chunk component", a different feature that allows you to add component to the chunk itself. (With AddChunkComponentData)
  • AddSharedComponentData : I think this is almost as fast as adding a tag component, since for shared component data no actual chunk space is needed.
  • DestroyEntity : You are just throwing away chunks. Should be very fast.

Together with ISharedComponentData filter

it even works when the EntityQuery has been .SetFilter -ed too. So you can even do selective mass operation based on your SharedComponentData or your .SetFilterChanged criteria. Just don’t forget to .ResetFilter when you want to make the query go back to normal.

As an example, I have 10000 of things to show but only a subset (1000) of them is visible+processed at any given time (governed by Process tag component) and this subset move forward from 0,1000, 2000, 3000, ... until the end.

So for each 1000 entities I add Group ISharedComponentData with integer 1~10, when it is time to remove all Process of the previous 1000 entities and add to the next 1000 at once, I could achieve that with 1 RemoveComponent and 1 AddComponent with filtered EntityQuery (add filter, do remove, add new filter, do add, reset the filter if needed) instead of 2000 iterations.

Main article about SCD and its filter ability :