Async physics creation locks the main thread

With the implementation of asynchronous body creation, its possible to fairly regularly lock the main thread -- essentially defeating the purpose of all this work.

Here we have 63ms of async physicsbody creation happening on a background task. The problem is that there’s a fairly non-granular lock applied here so if the main thread attempts to do anything with the chaos it simply stalls:

[Image Removed]

And here our mainthread takes almost 60ms as opposed ti its normal ~16ms because every physics related call is inflated by locks

[Image Removed]

This seems to specifically be the problem. [Image Removed]

I’m playing around with a local change, basic idea is to break the list into chunks in AddActorsToScene_AssumesLocked. Lock chaos, take x number of actors, process them, release the lock and yield so that other threads can process if necessary.

Thanks,

Brenden

void FChaosScene::AddActorsToScene_AssumesLocked(TArray<FPhysicsActorHandle>& InHandles, const bool bImmediate /*= true*/)
{
	TRACE_CPUPROFILER_EVENT_SCOPE(FChaosScene::AddActorsToScene_AssumesLocked);
 
	Chaos::FPhysicsSolver* Solver = GetSolver();
	Chaos::ISpatialAcceleration<Chaos::FAccelerationStructureHandle, Chaos::FReal, 3>* SpatialAcceleration = GetSpacialAcceleration();
 
	const int32 BatchSize = (GChaos_AddActorsBatchSize > 0) ? GChaos_AddActorsBatchSize : InHandles.Num();
 
	for (int32 Start = 0; Start < InHandles.Num(); Start += BatchSize)
	{
		const int32 End = FMath::Min(Start + BatchSize, InHandles.Num());
 
		{
			UE_CHAOS_ASYNC_INITBODY_PHYSICSSCENE_WRITESCOPELOCK(Solver->GetExternalDataLock_External());
 
			for (int32 Index = Start; Index < End; ++Index)
			{
				FPhysicsActorHandle& Handle = InHandles[Index];
				FChaosEngineInterface::AddActorToSolver(Handle, Solver);
 
				if (bImmediate && SpatialAcceleration)
				{
					const Chaos::FRigidBodyHandle_External& Body_External = Handle->GetGameThreadAPI();
					const bool bHasBounds = Body_External.GetGeometry()->HasBoundingBox();
 
					Chaos::FAABB3 WorldBounds;
					if (bHasBounds)
					{
						const Chaos::FAABB3 LocalBounds = Body_External.GetGeometry()->BoundingBox();
						WorldBounds = LocalBounds.TransformedAABB(Chaos::FRigidTransform3(Body_External.X(), Body_External.R()));
					}
 
					Chaos::FAccelerationStructureHandle AccelHandle(Handle->GetParticle_LowLevel());
					SpatialAcceleration->UpdateElementIn(AccelHandle, WorldBounds, bHasBounds, Body_External.SpatialIdx());
				}
			}
		}
 
		// Allow other threads a chance at the lock between batches.
		FPlatformProcess::YieldThread();
	}
}

After quite a bit of testing. This proved to be very successful. Instead of locking for the entire addition, we break it into batches and yield to other threads in between iterations. Main thread runs buttery smooth now.

Hi Brenden,

Could you attach the trace file for this please? I’d like to take a look through it

Geoff Stacey

Developer Relations

EPIC Games

Thanks Brenden,

One of my colleagues has also just published an article which may help (depending on how large each ISM component actually is).

Best

Geoff

Unfortunately the traces have been cleared since we made the above change and at this stage the issue is resolved so it doesn’t really show up on new traces.