Completing raycast commands before processing them

In a projectile system I build a native array of RaycastCommands by looping through all the chunks of my Entities on the main thread, then execute them in a batch, wait for completion, then process the results and move projectiles that haven’t hit anything.

I can see that the batch appears to be running across multiple threads in ECS 0.17 (Jobs 0.8.0) in U2020.3.

Is there a better way or more efficient way to collecting Raycast hit data or is RaycastCommand.ScheduleBatch the “correct” way of doing things in ECS 0.17?

raycastCommands = new NativeArray<RaycastCommand>(nProjectiles, Allocator.TempJob);
raycastResults = new NativeArray<RaycastHit>(nProjectiles, Allocator.TempJob);

// populate my array of RaycastCommands...

// Schedule the batch of raycasts
JobHandle handle = RaycastCommand.ScheduleBatch(raycastCommands, raycastResults, 1, default(JobHandle));

// Wait for the batch processing job to complete
handle.Complete();

// Create a nativelist of entities to destroy..

// Schedule a parallel IJobChunk job to move projectiles that aren't destroyed..
ProjectileMoveJob projectileMoveJob = new ProjectileMoveJob {... }
this.Dependency = projectileMoveJob.ScheduleParallel(projectileEntityQuery, this.Dependency);

Ideally, you don’t explicitly Complete() the first job. Instead, setup job 1, setup job 2 and make job 1 a dependency of job 2 . Then you Complete() job 2, if necessary.

What you want to avoid here is scheduling and completing multiple jobs in isolation. If possible, you want the entire thing to be a single dependency chain of jobs with a single Complete() at the end.

I don’t know your setup, so I can’t give too detailed instructions, but it might look something like this:

  • Create all the buffers for all the inputs and outputs up-front (RaycastCommands, RaycastHits, Entities to destroy, etc.)

  • Setup job 1, which fills the RaycastCommand buffer

  • Setup job 2, which runs the raycasts with the buffer filled by job 1 as input (job 1 as dependency)

  • Setup two more jobs, which both have job 2 as dependency

  • Job 3, which moves the projectiles with the result from job 2 as input

  • Job 4, which fills a list with the entities to destroy (I assume it doesn’t need the new projectile position as input)

  • Complete() jobs 3 and 4 (there’s a way to combine job handles into a single handle, I think)

  • Delete the entities and free the buffers

You may have noticed that I don’t determine which entities need moving before moving them. I just move all of them and destroy the old ones later.

It’s impossible to tell without profiling, but I reckon that’s faster. The reason for that is that it allows the movement job (which I assume does very simple calculations) to work without having to wait for a filtered entity list.
I assume you determine which projectiles can be deleted by whether they hit something and perhaps by age. If that’s the case, you can run the movement and “FindDeadProjectiles” jobs in parallel.

2 Likes