Bunch of questions -UnityPhysics with Burst/Parallel

Hello :slight_smile:

Right now I’m working on a small car game in the dots + physics world. But my profiler showed, that my vehicleMechanic job is taking a lot of time ingame.

So far I did it in a pure ECS way, related to this approach of a vehicleMechanic job of the UnityPhysicsSamples RaycastCar, just a bit simplified.

Now I wanted to implement parallel execution & Burst to improve the performance, but then a lot of questions stacked up:

  • Solved: In the RaycastJob, it does a JobHandle.Schedule() for a IJobParallelFor job. Does it mean that it can be executed in parallel? Because in the Entities.ForEach() you have to write ScheduleParallel() instead of Schedule() to run it parallel, right!?
  • Open: If I execute the vehicleMechanic job with Burst, it will fail with
    “Burst error: The managed class type System.IntPtr is not supported. Loading from a non-readonly static field is not supported” for
JobHandle rayJobHandle = ScheduleBatchRayCast(world.CollisionWorld, rayInputs, rayResults);
rayJobHandle.Complete();

(this specific job is also mentioned here & in the previous linked UnityPhysicsSample job)
I get the point, that I’m not allowed to get the results via pointer/reference on rayResults with Burst. But which options I have? Since Schedule() does not return something or so… I have to go the way with a pointer I guess, so how do I fix it? I could create a extra job without Burst, just for the execution… then save the results in a dynamicBuffer & access it again in the “vehicleMechanic” job… would this be the right way?

  • Solved: Another thing is parallism. I have multiple cars in the scene, so it would be perfect if I could run the vehicleMechanic job in parallel. But it will already fail in this line:
int ceIdx = world.GetRigidBodyIndex(ce);

“The previously scheduled job VehicleMechanicSystem […] writes to the NativeArray […] CollisionWorld.DynamicTree.BranchCount. You are trying to schedule a new job […] which reads from the same NativeArray. To guarantee safety, you must include Vehicl” (it does not display the full error).
This ceIdx is from the chassis, which is the physicsEntityBlock of the car, so all calculations of all the wheels are depending on this. Does it mean physics logic in general is not possible with parallism or how can I solve this problem?

I appreciate your help :slight_smile:

  1. Schedule() for IJobParallelFor is the same as ScheduleParallel for other kinds of jobs you’re used to. Not intuitive! See here:
    IJobFor.ScheduleParallel() vs IJobParallelFor.Schedule() - differences? use cases?

So the RaycastJob from ScheduleBatchRayCast is already parallel.

  1. I’m not sure why you’re using a ptr here, I admit. You normally should be able to just iterate over the NativeArray. I’ll leave others to answer this and possibly help you more D:

  2. Putting the whole foreach in parallel has a number of possible traps, because the job may run longer than you expect it will - depending on the dependencies you use, it may run into the next frame. That is what the error you are getting suggests to me.
    This part in particular: The previously scheduled job VehicleMechanicSystem […] - did you rename the system to VehicleMechanicSystem from the VehicleMechanicsSystem in the sample, by any chance? If so, what’s happening is probably that your job is still running, then you’re scheduling it again next frame, and it therefore has a clash.

I’d recommend switching the VehicleMechanicSystem from ComponentSystem to SystemBase if you didn’t already. You can then pass “Dependency” as the dependency for the Foreach, like so:

        Dependency = Entities
            .WithBurst()
            .ForEach((  VehicleMechanics mechanics  ) => {
//code
            }).ScheduleParallel(Dependency);

[You don’t need to declare Dependency, it’s from SystemBase itself]

It might solve your problem already, because it makes sure you’re scheduling your job after the dependencies of this system, and ensures that following jobs wait for this one.

Again, I’*m just assuming you’re still using ComponentSystem because the sample uses it. If you don’t and already manage dependencies, then sorry for my suggestion D:

1 Like

Hey Zeffi, thank you so much for your time & effort!

  • Thanks for this one! :slight_smile:

  • Normally it looks like this:

[BurstCompile]
    public struct RaycastJob : IJobParallelFor
    {
        [ReadOnly] public CollisionWorld world;
        [ReadOnly] public NativeArray<RaycastInput> inputs;
        public NativeArray<RaycastHit> results;

        public unsafe void Execute(int index)
        {
            RaycastHit hit;
            world.CastRay(inputs[index], out hit);
            results[index] = hit;
        }
    }

    public static JobHandle ScheduleBatchRayCast(CollisionWorld world,
        NativeArray<RaycastInput> inputs, NativeArray<RaycastHit> results)
    {
        JobHandle rcj = new RaycastJob
        {
            inputs = inputs,
            results = results,
            world = world

        }.Schedule(inputs.Length, 4);
        return rcj;
    }

You see? It writes the result of world.CastRay to hit, which we save in results, which is a pointer to the outer world. If I try to call this in my vehicleMechanicSystem job with enabled Burst:

 var rayCommands = new NativeArray<Physics.RaycastInput>(4, Allocator.TempJob);
 var rayResults = new NativeArray<Physics.RaycastHit>(4, Allocator.TempJob);
 [...]
 var handle = ScheduleBatchRayCast(world, rayCommands, rayResults);
 handle.Complete();

It will lead to: “Burst error: The managed class type System.IntPtr is not supported. Loading from a non-readonly static field is not supported”.
So I don’t know exactly how I should do it. I cannot schedule it via Burst -so what would be the best workaround for this? Like creating a new Burst-less job and save the results via DynamicBuffer for the upcoming VehicleMechanicSystem job?
Sure, I could run “world.CastRay()” directly in my VehicleMechanicSystem job, but it would not be parallel anymore :frowning:

  • First, you are right: VehicleMechanicSystem == VehicleMechanicsSystem :sweat_smile::slight_smile:
    And yes, I used SystemBase already :slight_smile:
    Your idea makes perfectly sense for me, but it did not helped. I get still the same error. I broke it down to:
[UpdateAfter(typeof(BuildPhysicsWorld)), UpdateBefore(typeof(StepPhysicsWorld))]
public class VehicleMechanicSystem : SystemBase
{
    BuildPhysicsWorld CreatePhysicsWorldSystem;

    protected override void OnCreate() {
        this.CreatePhysicsWorldSystem = this.World.GetOrCreateSystem<BuildPhysicsWorld>();
    }
   
    protected override void OnUpdate() {
        this.CreatePhysicsWorldSystem.GetOutputDependency().Complete();
        PhysicsWorld world = this.CreatePhysicsWorldSystem.PhysicsWorld;

        this.Entities.ForEach((
            int entityInQueryIndex,
            Entity entity,
            in MechanicComponent mechanics
        ) => {
            Entity ce = mechanics.chassisEntity;
            int ceIdx = world.GetRigidBodyIndex(ce); // Not working with Schedule() or ScheduleParallel()

        }).WithoutBurst().ScheduleParallel(this.Dependency);
    }
}

I renamed the class name to “Z” to see more of the error:
"InvalidOperationException:
The previously scheduled job Z:<>c__DisplayClass_OnUpdate_LambdaJob0
writes to the Unity.Collections.NativeArray`1[System.Int32] <>c__DisplayClass_OnUpdate_LambdaJob0.JobData.world.CollisionWorld.Broadphase.m_DynamicTree.BranchCount.

You are trying to schedule a new job Broadphase:AllocateDynamicVsStaticNodePairs,
which reads from the same Unity.Collections.NativeArray`1[System.Int32]
(via AllocateDynamicVsStaticNodePairs.dynamicBranchCount).
To guarantee safety, you must include Z:<>c__DisplayClass_OnUpda"

Without the UpdateAfter/UpdateBefore it gives me an additional error:
"InvalidOperationException:
The previously scheduled job Z:<>c__DisplayClass_OnUpdate_LambdaJob0
writes to the Unity.Collections.NativeArray`1[Unity.Physics.RigidBody] <>c__DisplayClass_OnUpdate_LambdaJob0.JobData.world.CollisionWorld.m_Bodies.

You must call JobHandle.Complete() on the job Z:<>c__DisplayClass_OnUpdate_LambdaJob0,
before you can deallocate the Unity.Collections.NativeArray`1[Unity.Physics.RigidBody] safely."

So since it wants that I “complete” Z (=VehicleMechanicSystem) I assume parallism is not working with dots physics?

Jumping in here. For 1., the answer is correct, not action required from me. :slight_smile:
Number 2., your Burst job is fine, the error you linked in the second post is related to the fact you are using a static member somewhere I assume. It looks to me like it’s not related to this job in particular.
Number 3., make sure you add your job as an input dependency for StepPhysicsWorld system (call AddInputDependency() on it and provide the handle). Also, you don’t need to complete BuildPhysicsWorld output dependency, just provide it as an input for your ScheduleParallel call (combined with Dependency).

1 Like

petarmHavok, feel you hugged, it finally worked with Burst & ScheduleParallel! :):slight_smile: Thank you so much!

I was actual completely unaware of these dependencies & how to work with them, so I was reading a bit about it first.
The only thing left is actual number 2. I called now world.CastRay() directly in a loop within VehicleMechanicSystem job, so this works at least.

But doing this does not work neither with Burst nor ScheduleParallel():

public class VehicleMechanicSystem : SystemBase
{
    private BuildPhysicsWorld CreatePhysicsWorldSystem;

    protected override void OnCreate() {
        this.CreatePhysicsWorldSystem = this.World.GetOrCreateSystem<BuildPhysicsWorld>();
    }

    [BurstCompile]
    public struct RaycastJob : IJobParallelFor
    {
        [ReadOnly] public CollisionWorld world;
        [ReadOnly] public NativeArray<RaycastInput> inputs;
        public NativeArray<RaycastHit> results;

        public void Execute(int index) {
            RaycastHit hit;
            this.world.CastRay(this.inputs[index], out hit);
            this.results[index] = hit;
        }
    }

    public static JobHandle ScheduleBatchRayCast(
        CollisionWorld world,
        NativeArray<RaycastInput> inputs,
        NativeArray<RaycastHit> results
    ) {
        return new RaycastJob {
            world = world,
            inputs = inputs,
            results = results
        }.Schedule(inputs.Length, 5);
    }

    protected override void OnUpdate() {
        this.CreatePhysicsWorldSystem.GetOutputDependency().Complete();
        PhysicsWorld world = this.CreatePhysicsWorldSystem.PhysicsWorld;

        this.Entities.ForEach((
            Entity entity
        ) => {
            NativeArray<RaycastHit> rayResults = new NativeArray<RaycastHit>(4, Allocator.TempJob);
            NativeArray<RaycastInput> rayInputs = new NativeArray<RaycastInput>(4, Allocator.TempJob);

            //  [...]

            JobHandle rayJobHandle = ScheduleBatchRayCast(world.CollisionWorld, rayInputs, rayResults);
            rayJobHandle.Complete();

            rayInputs.Dispose();
            rayResults.Dispose();
        }).WithoutBurst().Run();               // Works fine!
        // .WithoutBurst().ScheduleParallel()  // Does not work!
        // .Run()                              // Does not work!
    }
}

One thing is parallism,
calling it without Burst & with ScheduleParallel() it will throw “UnityException: CreateJobReflectionData can only be called from the main thread.”

The other thing is Burst,
calling it with Burst & Run() it will throw:
Burst error BC1042: The managed class type System.IntPtr is not supported. Loading from a non-readonly static field Unity.Jobs.IJobParallelForExtensions.ParallelForJobStruct1<VehicleMechanicSystem.RaycastJob>.jobReflectionData` is not supported

at Unity.Jobs.IJobParallelForExtensions.ParallelForJobStruct1<VehicleMechanicSystem.RaycastJob>.Initialize() at Unity.Jobs.IJobParallelForExtensions.Schedule( VehicleMechanicSystem.RaycastJob* jobData, int arrayLength, int innerloopBatchCount, Unity.Jobs.JobHandle dependsOn ) at VehicleMechanicSystem.ScheduleBatchRayCast( Unity.Physics.CollisionWorld* world, Unity.Collections.NativeArray1<Unity.Physics.RaycastInput>* inputs,
Unity.Collections.NativeArray`1<Unity.Physics.RaycastHit>* results
) (at VehicleMechanicSystem.cs:28)

So how do I call now a job within a parallel-scheduled burst job? :eyes:
Or which workaround should I use?

I am glad you are making progress. We’ll get to the bottom of this one too.

Never spawn jobs from jobs, it won’t work. Having an Entities.Foreach and then scheduling a job from that won’t work for sure. Use Entities.Foreach to collect the data into a NativeArray, and then spawn raycast jobs in parallel that will work on that data. This type of nesting is not supported.

Also, make sure you add similar logic to your Vehicle system as in number 3 (UpdateAfter, UpdateBefore and proper input/output dependencies).

1 Like

Hey petarmHavok, thanks again for your reply!
I followed your advise and it took me a while to transform the VehicleMechanicSystem, but somehow I got it to work.

I have now 3 IJobParallelFor BurstCompiled Jobs, used by the system:

  • The first one collecting the rayInputs NativeArray for the 2nd job.
  • The 2nd one RaycastJob, making the ray casts for each wheel & adding the results to rayResults.
  • The 3rd one is using the rayResults for the main vehicle mechanic part.

For 4 cars in a scene, with each 4 wheels, I would have 4 threads for the 1. job, 16 threads for 2. job and 4 threads for the 3. job.
Since they are depending on each other, I think it makes not much sense to put them each in a own system (& transforming the arrays for the rays to dynamicBuffers).

The hardest part were actual the NativeArrays: rayInputs & rayResults.
I found no way to dissolve them at the right time with the Entity.Foreach() Pattern.
So I read about [DeallocateOnJobCompletion] for properties in a Job, which dissolves the NativeArray when the job is completed, which was working perfect, but I could not use Entity.Foreach() anymore. Maybe there is a workaround existing?

It basically looks now like this (very abstract, not all lines included):

[UpdateAfter(typeof(BuildPhysicsWorld)), UpdateBefore(typeof(StepPhysicsWorld))]
public class VehicleMechanicSystem : SystemBase
{
     BuildPhysicsWorld CreatePhysicsWorldSystem;
     StepPhysicsWorld stepPhysicsWorld;

     protected override void OnCreate() {
         this.CreatePhysicsWorldSystem = this.World.GetOrCreateSystem<BuildPhysicsWorld>();
         this.stepPhysicsWorld = this.World.GetOrCreateSystem<StepPhysicsWorld>();
     }


    [BurstCompile]
    public struct RayParameterJob : IJobParallelFor
    {
        [NativeDisableParallelForRestriction] // Was needed to fill rayInputs for 16 wheels of the 4 cars -each of the car = 1 thread/index
        public NativeArray<RaycastInput> rayInputs;
    }

    [BurstCompile]
    public struct RaycastJob: IJobParallelFor
    {
        [ReadOnly][DeallocateOnJobCompletion]
        public NativeArray<RaycastInput> rayInputs;

        public NativeArray<RaycastHit> rayResults;
    }

    [BurstCompile]
    public struct VehicleMechanicJob: IJobParallelFor
    {
         [ReadOnly][DeallocateOnJobCompletion]
         public NativeArray<RaycastHit> rayResults;
    }

    protected override void OnUpdate() {
        JobHandle buildPhysicsWorldHandle = this.buildPhysicsWorld.GetOutputDependency();
        JobHandle jobsBefore = JobHandle.CombineDependencies(buildPhysicsWorldHandle, this.Dependency);

        // All car entities found by query
        NativeArray<Entity> carEntities= this.query.ToEntityArray(Allocator.TempJob);

        // rayResults & rayInputs holding data for each wheel of the cars
        NativeArray<RaycastHit> rayResults = new NativeArray<RaycastHit>(carEntities.Length * 4, Allocator.TempJob);
        NativeArray<RaycastInput> rayInputs = new NativeArray<RaycastInput>(carEntities.Length * 4, Allocator.TempJob);

        // 1. Job
        RayParameterJob rayParameterJob = new RayParameterJob {
               rayInputs = rayInputs
        };
        this.Dependency = rayParameterJob.Schedule(carEntities.Length, 32, jobsBefore);

        // 2. Job
        RaycastJob raycastJob = new RaycastJob {
               rayInputs = rayInputs
               rayResults = rayResults,
        }
        this.Dependency = raycastJob.Schedule(rayInputs.Length, 32, this.Dependency);

        // 3. Job
        VehicleMechanicJob vehicleMechanicJob = new VehicleMechanicJob {
               rayResults = rayResults,
        }
        this.Dependency = vehicleMechanicJob.Schedule(carEntities.Length, 32, this.Dependency);

        this.m_EntityCommandBufferSystem.AddJobHandleForProducer(this.Dependency);
        this.stepPhysicsWorld.AddInputDependency(this.Dependency);
    }
}

…But not sure, what this “32” is for, at least its working ^^

32 is batch size, basically helps the scheduler divide work into jobs (and threads). If you have a lot of rays 32 is a good choice, but you might want to reduce the number if you have 10 or so raycasts.

1 Like