Updating TerrainCollider using ForEach().ScheduleParallel() works, fails when using IJobEntityBatch

I am having an issue where I can use Entities.ForEach().ScheduleParallel() to update terrain colliders in a multi-threaded fashion (when I add a large DynamicBuffer to entities to break up the chunk), but I cannot get IJobEntityBatch to work due to “The BlobAssetReference is not valid.” errors.

I have attached a repro project. Here is the system code directly:

using System.Diagnostics;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Physics.Systems;

[UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
[UpdateBefore(typeof(BuildPhysicsWorld))]
public class TerrainColliderUpdateSystem : SystemBase
{
   EntityQuery query;

   private struct UpdateTerrainColliderJob : IJobEntityBatch
   {
       [ReadOnly] public ComponentTypeHandle<PhysicsCollider> PhysicsColliderTypeHandle;
       public BufferTypeHandle<HeightElement> HeightsTypeHandle;

       [BurstCompile]
       public void Execute(ArchetypeChunk batchInChunk, int batchIndex)
       {
           var physicsColliders = batchInChunk.GetNativeArray(PhysicsColliderTypeHandle);
           var heightsBuffers = batchInChunk.GetBufferAccessor(HeightsTypeHandle);

           for (int i = 0; i < batchInChunk.Count; i++)
           {
               var collider = physicsColliders[i];
               var heightsBuffer = heightsBuffers[i];

               var colliders = Unity.Physics.TerrainCollider.Create(
                       heightsBuffer.Reinterpret<float>().AsNativeArray(),
                       new int2(ReproSetup.Resolution),
                       new float3(1),
                       Unity.Physics.TerrainCollider.CollisionMethod.Triangles
               );

               collider.Value.Dispose();
               collider.Value = colliders;
           }
       }
   }

   protected override void OnCreate()
   {
       query = GetEntityQuery(typeof(PhysicsCollider), typeof(HeightElement));
   }

   protected override void OnUpdate()
   {
       var mode = ReproSetup.Mode;
       Dependency.Complete();
       var stopWatch = new Stopwatch();
       stopWatch.Start();


       if (mode == SystemMode.ForEach)
       {
           Entities
                   .WithBurst()
                   .ForEach((ref PhysicsCollider collider,
                           ref DynamicBuffer<HeightElement> heightsBuffer) =>
                   {
                       var colliders = Unity.Physics.TerrainCollider.Create(
                                           heightsBuffer.Reinterpret<float>().AsNativeArray(),
                                           new int2(ReproSetup.Resolution),
                                           new float3(1),
                                           Unity.Physics.TerrainCollider.CollisionMethod.Triangles
                                   );

                       collider.Value.Dispose();
                       collider.Value = colliders;
                   }).ScheduleParallel();
       }
       else
       {
           var job = new UpdateTerrainColliderJob
           {
               PhysicsColliderTypeHandle = GetComponentTypeHandle<PhysicsCollider>(),
               HeightsTypeHandle = GetBufferTypeHandle<HeightElement>(),
           };

           // Split the chunk up for parallel processing
           Dependency = job.ScheduleParallel(query, 4, Dependency);
       }


       Dependency.Complete();
       stopWatch.Stop();
       UnityEngine.Debug.Log($"System Execution Time: {stopWatch.ElapsedTicks / 10000.0f}");
   }
}

If using the repro project, in the SampleScene, the “ReproSetup” game object has a selector to run as “For Each” or “Job”. With “For Each” selected, the workload runs successfully. You can even see that the frametime reduces significantly if you set the InternalCapacity attribute on the DummyElement.cs to 3000 (just breaks up the chunk of entities). So multi-threaded shape changes do appear to work. However, with “Job” selected, we get the following error:

InvalidOperationException: The BlobAssetReference is not valid. Likely it has already been unloaded or released.
Unity.Entities.BlobAssetReferenceData.ValidateNonBurst () (at Library/PackageCache/com.unity.entities@0.17.0-preview.42/Unity.Entities/Blobs.cs:260)
Unity.Entities.BlobAssetReferenceData.ValidateNotNull () (at Library/PackageCache/com.unity.entities@0.17.0-preview.42/Unity.Entities/Blobs.cs:277)
Unity.Entities.BlobAssetReference`1[T].get_Value () (at Library/PackageCache/com.unity.entities@0.17.0-preview.42/Unity.Entities/Blobs.cs:365)
Unity.Physics.Broadphase+PrepareStaticBodyDataJob.ExecuteImpl (System.Int32 index, System.Single aabbMargin, Unity.Collections.NativeArray`1[T] rigidBodies, Unity.Collections.NativeArray`1[T] aabbs, Unity.Collections.NativeArray`1[T] points, Unity.Collections.NativeArray`1[T] filtersOut, Unity.Collections.NativeArray`1[T] respondsToCollisionOut) (at Library/PackageCache/com.unity.physics@0.6.0-preview.3/Unity.Physics/Collision/World/Broadphase.cs:831)
Unity.Physics.Broadphase+PrepareStaticBodyDataJob.Execute (System.Int32 index) (at Library/PackageCache/com.unity.physics@0.6.0-preview.3/Unity.Physics/Collision/World/Broadphase.cs:819)
Unity.Jobs.IJobParallelForDeferExtensions+JobParallelForDeferProducer`1[T].Execute (T& jobData, System.IntPtr additionalPtr, System.IntPtr bufferRangePatchData, Unity.Jobs.LowLevel.Unsafe.JobRanges& ranges, System.Int32 jobIndex) (at Library/PackageCache/com.unity.jobs@0.8.0-preview.23/Unity.Jobs/IJobParallelForDefer.cs:62)

This is the first error that appears in the log, there are many others afterwards, but I assume they are a result of this error. The errors keep coming when using Unity Physics. When using Havok Physics, the editor just crashes.

Any thoughts on how I can parallelize this workload other than by loading the entities up with useless data to get the chunks broken up?

7585135–940336–Project.zip (30 KB)

I figured it out. I just needed to add

physicsColliders[i] = collider;

below line 40 in the system code above. I guess the ForEach uses a reference whereas the Job uses a copy.