Is it possible to safely spawn actors off the game thread?

I’m trying to prepopulate a pool of actors at runtime so when I need them, they go live fast.

Of course, actors in the pool are disabled i.e. no collision, no ticking, hidden etc.

The assets for said actors are already loaded!

Hi,

In short, the answer is no as spawning actors in unreal must be done on the game thread.

As denoted here, in the actor lifecycle, when spawning an actor it calls UWorld::SpawnActor which is tied with systems that are not thread safe.

Even creating actor objects should happen on the game thread: https://forums.unrealengine.com/t/can-i-create-an-actor-object-in-a-thread-other-than-the-gamethread-thread/626922/3

The most you can do is do prep work off the game thread and then you can do deferred spawning which can queue up spawn requests and be spawned on the game thread later safely when needed. (You can also do regular actor spawning after all the prep work if it doesn’t need anymore configuration)

Regards

“prep work off the game thread” I’m not sure what you mean, loading assets the actor relies on?

“deferred spawning” do you mean SpawnActorDeferred?

Spawning on one frame and finishing the spawn on the next one. This could definitely help.

Can FinishSpawning be safely called off the game thread?

It is possible to partially spawn actors asynchronously, removing most of the cost from the game thread, but it requires a small engine change.

I’ve pasted our async actor spawning functions below - the first function can be called asynchronously, the second must be called on the game thread in the same frame (to not have garbage collection issues). They’re mostly a copy and paste from the regular actor spawning functions with a few asserts to make sure they’re used correctly.

Regarding the engine change… EInternalObjectFlags::Async is normally set on any UObjects created off the game thread. This is done so async loaded UObjects don’t get garbage collected before they’re processed on the game thread. To asynchronously spawn actors and not have this flag set, we need to alter this to only happen on the AsyncLoading thread. One of the asserts gives details of the required change.

I think it would be possible to avoid this EInternalObjectFlags::Async engine change and instead clear EInternalObjectFlags::Async on the actor and all sub-objects on the game thread afterwards. That would also remove the requirement to complete the actor spawn on the same frame. But we find it useful in other contexts to be able to spawn other UObjects asynchronously and not worry about tracking them all to clear the flag so we went with this implementation.

Hope it helps! It would be great to have this officially supported by the engine.

`AActor* StartSpawnActorThreadSafe(UClass* Class, ULevel* LevelToSpawnIn)
{
check(Class);
checkf(!IsInAsyncLoadingThread(), TEXT(“Not safe to spawn actors in async loading thread”)); // see EInternalObjectFlags::Async checkf below for reason

AActor* Template = Class->GetDefaultObject();

// If we are using a template object and haven’t specified a name, create a name relative to the template, otherwise let the default object naming behavior in Stat
const FName BaseName = Template->HasAnyFlags(RF_ClassDefaultObject) ? Class->GetFName() : *Template->GetFName().GetPlainNameString();

FName NewActorName = FActorSpawnUtils::MakeUniqueActorName(LevelToSpawnIn, Template->GetClass(), BaseName, /bNeedGloballyUniqueName/false);

AActor* Actor = NewObject(LevelToSpawnIn, Class, NewActorName, RF_NoFlags, Template);

checkf(!Actor->HasAnyInternalFlags(EInternalObjectFlags::Async), TEXT(“UObjectBase::AddObject needs to be modified to only set EInternalObjectFlags::Async when IsInAsyncLoadingThread() && !IsInGameThread() (i.e. not just !IsInGameThread())”));

#if DO_CHECK
// Set CreationTime early so we can check CompleteSpawnActor is called on the same frame
UWorld* World = Actor->GetWorld();
Actor->CreationTime = (World ? World->GetTimeSeconds() : 0.f);
#endif

return Actor;
}

AActor* CompleteSpawnActorGameThread(AActor* Actor, const FTransform& Transform, APawn* Instigator)
{
check(IsInGameThread());
check(Actor);

ULevel* LevelToSpawnIn = Actor->GetLevel();

LevelToSpawnIn->Actors.Add(Actor);
LevelToSpawnIn->ActorsForGC.Add(Actor);

#if WITH_EDITOR
UEngineElementsLibrary::CreateEditorActorElement(Actor); // From AActor::PostInitProperties()

Actor->ClearActorLabel(); // Clear label on newly spawned actors
#endif

#if DO_CHECK
UWorld* World = Actor->GetWorld();
checkf(Actor->CreationTime == float(World ? World->GetTimeSeconds() : 0.f), TEXT(“%s - CompleteSpawnActor must be called on same frame as SpawnActorTask (and before GC)”), *Actor->GetPathName());
#endif

Actor->PostSpawnInitialize(Transform, nullptr, Instigator, /bRemoteOwned/false, /bNoFail/false, /bDeferConstruction/false, ESpawnActorScaleMethod::MultiplyWithRoot);

if (!IsValid(Actor))
{
return nullptr;
}

Actor->CheckDefaultSubobjects();

#if WITH_EDITOR
if (GIsEditor)
{
if (Actor->IsAsset() && Actor->GetPackage()->HasAnyPackageFlags(PKG_NewlyCreated))
{
FAssetRegistryModule::AssetCreated(Actor);
}

GEngine->BroadcastLevelActorAdded(Actor);
}
#endif

Actor->GetWorld()->AddNetworkActor(Actor);

return Actor;
}`

HI all,

As already covered, we’d not recommend attempting to spawn actors off the game thread at present, although the suggestions Ben Wyatt has posted for partial support are interesting!

However, this is a very timely thread as we’ve been having a number of internal conversations around supporting actor pooling recently, and are in the process of creating prototypes to prove our ideas out. There’s a strong desire at Epic to reduce spawning costs as well as minimize hitching in general to compliment the world partition streaming improvements we made with 5.6, so we’re investigating a few approaches including a generic solution to both actor and component pooling/recycling, providing user controlled amortized batched spawning, and making further improvements around thread safety with a focus on enabling more work to be moved off the game thread, potentially including sections of the spawning workload.

We can’t yet make any guarantees for when this might make its way into the codebase I’m afraid, but I’m hopeful you’ll see the beginnings of these efforts appearing around the 5.7 and 5.8 releases. I’ll follow up with our product management team to see if we can get the actor pooling work added to the UE public roadmap for a bit more visibility.

Regards,

Andy

Yes, by prep work I mean loading assets, and configuring any data needed for the actor.

Yes, I’m referring to SpawnActorDeferred to gradually spawn in actors to populate actors into the pool. As you mentioned, you get to control when to call FinishSpawningActor allowing you to spread spawning over multiple frames to avoid bottle necking the game thread.

Unfortunately no, FinishSpawningActor must also be called on the game thread.

We may be forced to do it. Thanks :slight_smile:

EDIT: Looks like spawning on the fly is very expensive for us across the board.

THANK YOU SO MUCH!

I’d definitely +1 the request for this to be added as an official engine feature as well.

If you don’t mind sharing some more details, what version of the engine did you guys use this with? Do you spin up new threads each time something wants to spawn an actor/have a single long-running background task/some combination? I may look into the feasibility of replacing some of our SpawnActors calls with this flow in our project since there are definitely some cases where the game thread is bottlenecked by actor spawning while we have additional CPU cores underutilized.

Since this turned into a feature request I’ll reassign the case to Epic so they can consider it, thank you for your help!

We’ve used this with UE5.4 and UE5.5 but I’d expect it to work in earlier versions too.

We’re using ConstructAndDispatchWhenReady to create an async graph task on the fly, with game systems later in the frame ensuring task completion and finishing the spawn on the game thread.

One important caveat to know is that this doesn’t cover the async creation of components added in Blueprint - these are still handled on the game thread as these are created by the construction script (which isn’t thread safe!). This would be a great thing for Epic to improve if they’re interested in this approach. Otherwise, to maximize what can be done asynchronously, the creation of those components needs to be moved to a native constructor.

In the meantime, you could try to spawn within unused budget for sth else.

For example, if you had a time budget per frame e.g. for streaming,

then if streamer didn’t do anything, you can use this time for something else.

On previous projects I’ve done the reverse and disabled time sliced systems (such as world and texture streaming) for a frame after expensive operations such as spawning characters. It works great for hiding the hitch but you of course need to be careful not to be doing that too frequently.

That’s great news! We’re definitely interested in taking advantage of that.