We make use of a good number of traces in a standard frame of gameplay in Megalith, so taking this out of the performance picture isn’t a huge gain but it does help remove offenders from the game thread. Something you must watch out for is that it isn’t free. Those cycles go somewhere else, which appears to be kicked off to TaskGraph threads. TaskGraph threads are UE4’s generalized task threads that it can kick work to. So you’re still potentially in contention with there being enough computation power to complete that work under a specific time threshold. Like anything, your mileage may vary. For us, it should give back a couple milliseconds on bad frame and help stabilize framerate.

The basic premise here is you’re taking an operation that will give you some result instantly about the state of the physical nature of your game and you’re asking to get the result at a later time. In this case, that later time is always at the very beginning of the next frame (before any Tick groups have occurred).

So lets just look at a comparison right now between the familiar World trace functions, many of which are exposed to blueprints for ease of access, and and the async trace functions. Async trace functions are still part of the UWorld object, so they’re right there for the picking, you just need to create some glue to generally make them easier to use.

Trace functions:

bool UWorld::LineTraceSingleByChannel(

struct FHitResult& OutHit,

const FVector& Start,const FVector& End,

ECollisionChannel TraceChannel,

const FCollisionQueryParams& Params,

const FCollisionResponseParams& ResponseParam) const

FTraceHandle UWorld::AsyncLineTraceByChannel(

EAsyncTraceType InTraceType,

const FVector& Start, const FVector& End,

ECollisionChannel TraceChannel,

const FCollisionQueryParams& Params,

const FCollisionResponseParams& ResponseParam,

FTraceDelegate * InDelegate, uint32 UserData)

The top function, LineTraceSingleByChannel , is the synchronous version which immediately does a RaycastSingle call into PhysX. The bottom function, AsyncLineTraceByChannel , is the asynchronous version which returns an FTraceHandle which will be used later to get the results.

There is also:

AsyncLineTraceByObjectType

AsyncSweepByChannel

AsyncSweepByObjectType

AsyncOverlapByChannel

AsyncOverlapByObjectType

We’re only going to look at the AsyncLineTraceByChannel here for brevity, but they all function very much the same way. They take a set of arguments similar to the synchronous version and then return FTraceHandles .

Notice above I’ve bolded two things:

FTraceHandle FTraceDelegate

FTraceHandle

This is effectively your “ticket” to the work. A simple analogy is a dry-cleaning ticket. You drop off your clothing, you get a ticket and are told to come back at a later timer. When you return, you must present your ticket to get your items returned. Without a ticket, your clothing will just be burned at the end of the working day! This just uniquely tracks (for a given frame of requests) your individual async work request.

FTraceDelegate

If the FTraceHandle is a ticket then the FTraceDelegate is a delivery service. You drop off your clothing, and at a later date, a delivery service returns your cleaned clothes to your doorstep. It is simply a non-dynamic delegate you can bind to your trace request to be called when your work is done. This delegate takes two arguments and no return values. DECLARE_DELEGATE_TwoParams( FTraceDelegate, const FTraceHandle&, FTraceDatum &);

FTraceDatum

This is where the results of your work is stored on the World AsyncTraceState object and how it is returned to you through either a provided FTraceDelegate firing OR by querying values with your FTraceHandle . It is important to note that there are a few structures here. FTraceDatum derives FBaseTraceDatum , which simply holds commonalities such as the World, CollisionParams, and UserData. FTraceDatum has:

Start / End line positions in world space of request TArray<FHitResult> OutHits array containing the results EAsyncTraceType to determine single, multi or test for the request

FOverlapDatum

This structure is how asynchronous overlap work results is returned to you either by querying or delegate firing. I’m mentioning it briefly so that readers are aware there are a few slightly different paths depending on what you’re asking of the physics scene. It has:

Position / Rotation of the request. TArray<FOverlapResult> Results array of results for tasks. Shape information for overlaps is in the base class FBaseTraceDatum

Some interesting things to note: