#IJobParallelJob spawner system.

1 messages · Page 1 of 1 (latest)

raw sphinx
#

I've been trying to create a spawn job that uses parralism to achieve maximum performence. I'm using EntityCommandBuffer.ParallelWriter to instantiate entities and then set their component data.

The problem is that once I playback the ECB, I get an error saying "InvalidOperationException: Structural changes are not allowed while iterating over entities. Please use EntityCommandBuffer instead.". This is called inside the ECB.Playback() function.

What am I doing wrong here?
https://pastebin.com/ygr065ar

dusky merlin
#

as it is literally doing what the error says Structural changes are not allowed while iterating over entities.

raw sphinx
#

Sure, but isn't it weird that it then tells me to use EntityCommandBuffer?

dusky merlin
#

the error text probably wasnt imagined for this case

raw sphinx
#

This just makes it a bit unclear. Is it impossible to create entities in a parallel job?

dusky merlin
#

no

#

just use a system command buffer

#

and set the dependency handles properly

#

then you no longer need any of the completes

#
var ecbSystem = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>();

foreach (var spawnerRO in SystemAPI.Query<RefRO<SpawnerComponent>>())
{
      var spawner = spawnerRO.ValueRO;
      var commandBuffer = ecbSystem.CreateCommandBuffer(World.Unmanaged);

      var initializeJob = new InitializeSpawnedEntitiesJob
      {
          spawner = spawner,
          parallelWriter = commandBuffer.AsParallelWriter(),
          minRot = new float3(-1, -1, -1),
          maxRot = new float3(1, 1, 1),
          random = random
      };
 
      state.Dependency = initializeJob.Schedule(spawner.spawnDimension.x * spawner.spawnDimension.y, 100, state.Dependency);
}
#

uh discord formatting (Open in full view it looks better)

raw sphinx
#

What is a system command buffer?

#

Or more importantly, why is it able to handle structural changes while a regular parallel ECB can't?

dusky merlin
raw sphinx
#

OOOhhhh

#

I misunderstood you earlier

storm canopy
#

Basically, ECB creation and playback must happen outside of any foreach query. There's a few different ways to solve this in your case

raw sphinx
#

Gonna try moving that outside the foreach

dusky merlin
raw sphinx
#

Yep, this kinda works now.

var commandBuffer = new EntityCommandBuffer(Allocator.TempJob);
var parallelWriter = commandBuffer.AsParallelWriter();

    foreach (var spawnerRO in SystemAPI.Query<RefRO<SpawnerComponent>>())
    {
        var spawner = spawnerRO.ValueRO;

        var initializeJob = new InitializeSpawnedEntitiesJob
        {
            spawner = spawner,
            parallelWriter = parallelWriter,
            minRot = new float3(-1, -1, -1),
            maxRot = new float3(1, 1, 1),
            random = random
        };

        var handle = initializeJob.Schedule(spawner.spawnDimension.x * spawner.spawnDimension.y, 500);
        handle.Complete();
    }

    commandBuffer.Playback(state.EntityManager);
    commandBuffer.Dispose();
storm canopy
#

Actually no you're right; creation is fine

raw sphinx
#

It seems like a lot less cubes spawn than when it was not jobified, but not related to the issue in this thread

dusky merlin
#

tbh with that amount of entities you are better off pre-spawning then setting the data in a job

raw sphinx
#

Prespawning? Like, manually placing them in a subscene?

dusky merlin
#

sorry batch spawning would be the better term

raw sphinx
#

Ah

#

I guess for that I should use "IJobParallelForBatch"?

dusky merlin
#

more like var createdEntities = state.EntityManager.CreateEntity(archetype, entityCount, Allocator.TempJob);

#

then set the data in the jobs

raw sphinx
#

Wouldn't it be slower since it doesn't create them in parallel?

storm canopy
#

An ECB is just something that records intentions to spawn stuff at a later point on the main thread. The actual playback is never parallel

#

I believe you could simplify this code if you replaced your foreach + your InitializeSpawnedEntitiesJob with just one IJobEntity:

  • Have a IJobEntity that iterates on the SpawnerComponent
  • for each one, spawn spawner.spawnDimension.x * spawner.spawnDimension.y entities with ECB

That way, you'd have one parallel job using one ECB

#

@dusky merlin 's suggestion would give best spawning performance though. When spawning with EntityManager, you can spawn things more efficiently by "batches" instead of one by one like an ECB does (see his code above) . It'll make things a little more complicated because then you need to store the created entities in a native array and come up with a strategy to understand how to initialize them in a job later. The implementation will be very specific to what you're trying to do

raw sphinx
#

I see. I initially spawned all my entities in a batch before jobifying it. I thought spawning them one by one would be faster if it was multithreaded

#

Thank you both very much for the help!

#

I'd like to ask one more question since it seems somewhat relevant to parallel for.
The job sets a random position for each spawned prefab. When done in a single thread (or in a single batch while multithreaded) the results look like this

#

Which is the way it's intended

#

But

#

When I use multiple batches, the result end up looking a lot more empty. I suspect some of the cubes are getting the same positions and are stacked inside each other

#

Is Unity.Mathematics.Random() not meant to be used in parallel?

dusky merlin
#

might not be the best way, depends whether you want it to be deterministic or not

raw sphinx
#

That makes sense

#

Thank you

naive plover
#

Since all you are doing is instantiating entities, it's probably going to be faster to just spawn them in your system update directly. This is what I do:

                // Instantiating an entity creates copy entities with the same component types and values.
                var instances = state.EntityManager.Instantiate(spawner.ValueRW.Prefab, difference, Allocator.Temp);

                var random = Random.CreateFromIndex(updateCounter++);

                foreach (var instance in instances)
                {
                    var position = random.NextFloat2Direction() * spawner.ValueRW.Radius;

                    SystemAPI.SetComponent<LocalTransform>(instance, new LocalTransform
                    {
                        Position = new float3(position.x, 0, position.y),
                        Rotation = quaternion.identity,
                        Scale = 1.0f
                    });
                }
#

also add [UpdateInGroup(typeof(InitializationSystemGroup))] so the system runs early in the frame before any jobs start (because it will complete all running jobs)