DOTS Unity physics - raycasting in parallel jobs extremely slow

Hello all, in my game I have multiple entities with component called BoxComponent.
At the start of every frame I want to find out how many items are around each box and save this information to the BoxComponent.NumOfItemsAround property.
To achieve this I am using PhysicsWorld.CollisionWorld.BoxCastAll code and I was thinking to do this in parallel. I am using following code in my system:

partial struct BoxInfoUpdaterSystem : ISystem
{
    [BurstCompile]
    public void OnCreate(ref SystemState state) {}

    [BurstCompile]
    public void OnDestroy(ref SystemState state) {}

    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        state.Dependency = new BoxesUpdateJob
        {
            CollisionWorld = SystemAPI.GetSingleton<PhysicsWorldSingleton>().PhysicsWorld.CollisionWorld,
        }
        .ScheduleParallel(state.Dependency);
    }

    [BurstCompile]
    public partial struct BoxesUpdateJob : IJobEntity
    {
        [ReadOnly] public CollisionWorld CollisionWorld;

        public void Execute(ref BoxComponent box)
        {
            NativeList<ColliderCastHit> hits = new NativeList<ColliderCastHit>(Allocator.TempJob);
            if (CollisionWorld.BoxCastAll(
                box.Position,
                quaternion.identity,
                new float3(1,1,1),
                float3.zero,
                0,
                ref hits,
                new CollisionFilter()
                {
                    BelongsTo = (uint)CollisionLayers.Boxes,
                    CollidesWith = (uint)CollisionLayers.Items
                }
            ))
            {
                box.NumOfItemsAround = hits.Length;
            }
            else
            {
                box.NumOfItemsAround = 0;
            }
            hits.Dispose();
        }
    }
}

This seemed like it’s working, until I checked the profiler and found out this:

  • it is not running in parallel, only one thread is occupied
  • it is taking EXTREME amount of time to finish - I currently only have like 15 boxes and whole job takes around 1.2ms, which is like 10times more than the lenght of my average job

Can please someone shed some light on this? Probably parallel raycasts should be done in a different way? Thanks in advance!

1 Like

Not using Unity Physics personally, but couple of notes:
1.Allocator.TempJob is incorrect usage inside job, you should use Temp one here.
2. IJobEntity runs per chunk (as it codegened to IJobChunk) which means if all your entities belongs to one chunk - your IJobEntity Execute will be called for all entities in chunk on same worker thread, this is why you see it running on one thread.
3. Have you profiled with all safety checks off? (better profile in build, but with disabled things like below it will give performance as possible close to build as it can in editor.


image

1 Like

Thanks for the answer, I actually spent a few more hours on this issue and have some updates. Regarding your post:

  1. Aah sure, that makes sense, thanks!
  2. I was thinking that it might be the case so I added amount of the BoxComponents in my scene, from like 20 entities I went to around 600, but results were still VERY slow and utilization of threads weird - maximum 2 threads were used in the same time, but like 90% of the time it was still running single threaded. At this point the performance was so bad that it went from original 90FPS in editor to 20. Unusable.
  3. I observed no change in performance when switching off safety checks

What I actually did
I built a bit different solution using IJobParallelFor job according the manual here. That involved creation of two lists and usage of a BoxComponent component lookup:

// Auxilliary struct used as the input to the Raycast in the BoxesUpdateJob
public struct BoxCastInput
{
    public float3 Center;
    public quaternion Orientation;
    public float3 HalfExtents;
    public float3 Direction;
    public float MaxDistance;
    public CollisionFilter CollisionFilter;
}

[RequireMatchingQueriesForUpdate]
partial struct BoxInfoUpdaterSystem : ISystem, ISystemStartStop
{
    // This number is quite low actually, should be higher for high amounts of entities
    private const int BATCH_COUNT = 2;
    private ComponentLookup<BoxComponent> _boxesLookup;
    private NativeList<Entity> _boxesEntitiesLists;
    private NativeList<BoxCastInput> _raycastInputsList;

    [BurstCompile]
    public void OnStartRunning(ref SystemState state)
    {
        _boxesLookup = SystemAPI.GetComponentLookup<BoxComponent>();

        /*
         * The goal in this method is to create two lists where we know that the same index 
         * can be used for both lists to get the data related to one specific entity.
         * As my entities are non-stationary it is ok to do it just once.
         * 
         * Like this: 
         * _boxesEntitiesLists[i] = Entity on index i
         * _raycastInputsList[i] = Raycast input for the entity i
         */
        _boxesEntitiesLists = .....; // => get all entities that contain BoxComponents to one list
        _raycastInputsList = .....; // => create Raycast inputs for each entity
        }
    }
    
    [BurstCompile]
    public void OnDestroy(ref SystemState state)
    {
        _raycastInputsList.Dispose();
        _boxesEntitiesLists.Dispose();
    }

    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        _boxesLookup.Update(ref state);

        state.Dependency = new BoxesUpdateJob
        {
            CollisionWorld = SystemAPI.GetSingleton<PhysicsWorldSingleton>().PhysicsWorld.CollisionWorld,
            Inputs = _raycastInputsList.AsArray(),
            BoxesEntities = _boxesEntitiesLists.AsArray(),
            BoxesLookup = _boxesLookup
        }
        .Schedule(_raycastInputsList.Length, BATCH_COUNT, state.Dependency);
    }

    [BurstCompile]
    public struct BoxesUpdateJob : IJobParallelFor
    {
        [ReadOnly] public CollisionWorld CollisionWorld;
        [ReadOnly] public NativeArray<BoxCastInput> Inputs;
        [ReadOnly] public NativeArray<Entity> BoxesEntities;
        // We can afford to disable safety checks here
        [NativeDisableContainerSafetyRestriction]
        public ComponentLookup<BoxComponent> BoxesLookup;

        public void Execute(int index)
        {
            NativeList<ColliderCastHit> hits = new NativeList<ColliderCastHit>(Allocator.Temp);

            BoxCastInput input = Inputs[index];
            CollisionWorld.BoxCastAll(input.Center, input.Orientation, input.HalfExtents, float3.zero, input.MaxDistance, ref hits, input.CollisionFilter);

            // Use component lookup to write the raycast result to the component
            BoxComponent box = BoxesLookup[BoxesEntities[index]];
            box.NumOfItemsAround = hits.Length;
            BoxesLookup[BoxesEntities[index]] = box;

            hits.Dispose();
        }
    }
}

Using the code above I was able to get full parallelism with nicely distributed workload across the threas and the time went from 1.2ms to 0.4ms in avg. This is still kinda heavy and it can get problematic when amount of boxes goes high. In that case would be the best to use some custom spatial structures hashing in order to make this operation real quick.

Conclusion
Based on my research I found out that:

  • using IJobEntity.ScheduleParallel() in conjunction with Unity Physics raycasting is not working ideally and produces performance very heavy results
  • using IJobParallelFor (or similar alternative) produces performance more friendly code
  • possibly this could even be some bug in Jobs/Unity Physics? Or is it just a problem of inefficient internal code generation?

There’s still a possibility I overlooked something, feel free to elaborate!

PS: I am using Physics 1.3.8, Entities 1.3.8 and Unity 6000.0.31f

EDIT: So I just realized that I can do my checks using OverlapBox which is much faster method than BoxCast. But non-paralellism is still there, so it’s still much better to use IJobParallelFor. Probably this will be a rule of thumb for all wanna-be-parallel shape-checks in Unity.Physics.

1 Like