Is Async Physics Tick really async?

I enabled Tick Physics Async, also experimented with Substepping, Substepping Async (in all combinations), but still callback is called from game thread. (Code below)

My output is:

LogTemp: Warning: UAsyncPhysicsActorComponent::BeginPlay() on Thread: 2680 (GameThread)
PIE: Server logged in
PIE: Play in editor total start time 0.059 seconds.
LogTemp: Warning: UAsyncPhysicsActorComponent::TickComponent 0.00833 on Thread: 2680 (GameThread)
LogTemp: Warning: UAsyncPhysicsActorComponent::AsyncPhysicsTickComponent 0.00400 on Thread: 2680 (GameThread)
LogTemp: Warning: UAsyncPhysicsActorComponent::AsyncPhysicsTickComponent 0.00400 on Thread: 2680 (GameThread)
LogTemp: Warning: UAsyncPhysicsActorComponent::AsyncPhysicsTickComponent 0.00400 on Thread: 2680 (GameThread)
LogTemp: Warning: UAsyncPhysicsActorComponent::TickComponent 0.07302 on Thread: 2680 (GameThread)
LogTemp: Warning: UAsyncPhysicsActorComponent::AsyncPhysicsTickComponent 0.00400 on Thread: 2680 (GameThread)
LogTemp: Warning: UAsyncPhysicsActorComponent::AsyncPhysicsTickComponent 0.00400 on Thread: 2680 (GameThread)
LogTemp: Warning: UAsyncPhysicsActorComponent::AsyncPhysicsTickComponent 0.00400 on Thread: 2680 (GameThread)

above log is for config:

[/Script/Engine.PhysicsSettings]
bTickPhysicsAsync=True
AsyncFixedTimeStepSize=0.004000
bSubstepping=False
bSubsteppingAsync=False
MaxSubsteps=16
MaxSubstepDeltaTime=0.002500

Chaos settings are:

Enabling bSubstepping with (or without) bSubsteppingAsync gives same result.
All threads are same GameThread.
So it looks it is not very “async” when called from one thread.
Shouldn’t it be called from set dedicated threads for physical simulation?

// Called when the game starts
void UAsyncPhysicsActorComponent::BeginPlay()
{
	Super::BeginPlay();
	uint32 ThreadID = FPlatformTLS::GetCurrentThreadId();
	FString ThreadName = FThreadManager::Get().GetThreadName(ThreadID);
	UE_LOG(LogTemp, Warning, TEXT("UAsyncPhysicsActorComponent::BeginPlay() on Thread: %u (%s)"), ThreadID, *ThreadName);
	// ...

	SetAsyncPhysicsTickEnabled(true);
}


// Called every frame
void UAsyncPhysicsActorComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
	uint32 ThreadID = FPlatformTLS::GetCurrentThreadId();
	FString ThreadName = FThreadManager::Get().GetThreadName(ThreadID);
	UE_LOG(LogTemp, Warning, TEXT("UAsyncPhysicsActorComponent::TickComponent %.5f on Thread: %u (%s)"), DeltaTime, ThreadID, *ThreadName);
	// ...
}

void UAsyncPhysicsActorComponent::AsyncPhysicsTickComponent(float DeltaTime, float SimTime)
{
	Super::AsyncPhysicsTickComponent(DeltaTime, SimTime);

	uint32 ThreadID = FPlatformTLS::GetCurrentThreadId();
	FString ThreadName = FThreadManager::Get().GetThreadName(ThreadID);
	UE_LOG(LogTemp, Warning, TEXT("UAsyncPhysicsActorComponent::AsyncPhysicsTickComponent %.5f on Thread: %u (%s)"), DeltaTime, ThreadID, *ThreadName);
}

Hi,

Yes, async physics is actually async, but that specific callback still runs on the Game Thread each time the physics simulation is advanced one step.

See FPhysicsSolverFrozenGTPreSimCallbacks and FAsyncPhysicsTickCallback::OnPreSimulate_Internal

It works in this way:

Non-Async physics (default)

We have two representations of the state of the physics simulation. One is the game thread state (this is what you access when you use any of the existing APIs that do not talk directly to the physics solver), and the other is the Physics thread state (although in practice there is not a dedicated single thread for physics, we go wide across multiple threads at different stages of a single physics step).

At the beginning of the frame, from the Game Thread we push all the dirty states into a marshaling object and then kick off a task to be executed in another thread, which is what advances the physics simulation.

At this point we also schedule a task to be executed in the game thread that ends up executing the AsyncPhysicsTickComponent callback.

The task in charge of advancing the simulation one step, will be executed outside the GT (for short) and process data the GT pushed to the marshaling manager object, and then advance the simulation one step (using multiple threads). Lastly, when that work is complete, the physics solver advance task will push the results to the marshalling manager, so then the Game thread can consume them at the end of its frame.

In Sync physics (default mode), the game thread will wait until physics finishes its work, before moving to the next frame and try to consume any results.

Async Physics

The difference between Async physics and Sync Physics, is that with Async physics the the game thread will no longer wait for physics work to be completed.

Lets say you have your physics delta time set to 33ms and your Game delta time set to 16ms.

In normal operation that means you will have one physics step per 2 game frames.

But if for some reason the game thread hitches and it takes 100 ms, in the next frame instead of executing one single physics step task, 3 tasks will be issued to “catch up”. BUT, the game thread will not wait for these 3 tasks to be completed before moving to the next frame.

Everything is eventually synced (and interpolated if needed) using timestamps.

I talked a bit about this in Unreal Fest (although the presentation is mostly about the Chaos Visual Debugger) https://youtu.be/_DKKztvMd2o?t=1227

That said, here are the relevant slides from that presentation.

1 Like

That said, it is possible to have callbacks from the thread that is advancing the physics simulation. But I don’t know if we have them fully documented yet, explaining all the options and caveats.

You need to implement an object of type Chaos::TSimCallbackObject, and then register it with the physics solver like this.

if (UWorld* World = GetWorld())
{
	if (FPhysScene* PhysScene = World->GetPhysicsScene())
	{
		AsyncCallback = PhysScene->GetSolver()->CreateAndRegisterSimCallbackObject_External<FYourAsyncCallbackObjectType>();
	}
}