Actor delete stalls (WP): FAssetRenameManager::PopulateAssetReferencers unconditionally adds all dirty packages as soft-reference candidates

We investigated a recurring stall when deleting actors in a large World Partition level (Polaris project). Users report the editor becomes stuck on “Computing References” for several seconds (6–10s typical, but we’ve seen reports of much longer stalls on other large WP projects) when deleting actors — even actors with zero actual references pointing to them.

Root Cause

We profiled the stall (CPU trace attached) and traced the hot path to:

UUnrealEdEngine::DeleteActors →

FAssetRenameManager::FindSoftReferencesToObjects →

FAssetRenameManager::PopulateAssetReferencers() →

FAssetRenameManager::GatherReferencingObjects()

We reviewed the engine source for

PopulateAssetReferencers()(Engine/Source/Editor/UnrealEd/Private/AssetRenameManager.cpp) and found the likely cause of the disproportionate cost:

if (AssetToRename.bOnlyFixSoftReferences)
{
    AssetToRename.NotRenamedReferencingPackageNames.Add(OldPackageName);
    AssetToRename.NotRenamedReferencingPackageNames.Add(AssetToRename.NewObjectPath.GetLongPackageFName());
 
    // Add dirty packages and the package that owns the reference. They will get filtered out in LoadReferencingPackages if they aren't valid
    for (UPackage* Package : ExtraPackagesToCheckForSoftReferences)
    {
        AssetToRename.NotRenamedReferencingPackageNames.Add(Package->GetFName());
    }
}

The genuine Asset Registry query

AssetRegistryModule.Get().GetReferencers(...)is cheap and indexed. However, this function unconditionally appends every currently dirty (unsaved) world and content package in the editor session as an additional soft-reference candidate — regardless of whether the Asset Registry indicated any relationship to the object being deleted.

GatherReferencingObjects()then has to load/inspect each of these candidates to rule them out as false positives.

We’d like guidance on whether

PopulateAssetReferencers()could be changed to only include dirty packages as candidates when there’s a plausible reason to suspect a relationship (e.g., a lightweight in-memory check) rather than unconditionally including every dirty world/content package in the session. Please let us know if a minimal repro project would help, or if there’s an existing UE-JIRA tracking this behavior.

Steps to Reproduce

  1. Open a large World Partition map with a substantial amount of streamed/loaded content (in our case, set-dressing actors across many WP cells).
  2. Work in the editor for a period of time performing normal edits — moving actors, adjusting properties, placing/adjusting set dressing — without saving. This causes multiple world and content packages to become “dirty” (unsaved).
  3. Select any actor (does not need to have any actual references pointing to it) and delete it (Del key, or right-click → Delete).
  4. Observe the “Computing References” progress dialog. On our test machines this consistently takes ~6–10 seconds, scaling with how much unsaved/dirty content is present in the session, and independent of whether the deleted actor actually has any referencers.

Hi,

There likely isn’t a simple solution here that doesn’t risk breaking references throughout your project, though I’d recommend checking to see if “Validate Unloaded Soft Actor References” is enabled in your project settings. If so, disabling this might improve performance at the risk of potentially having some soft actor references break when saved. I don’t think the CPU trace attachment came through, but the setting may or may not help depending on how much of the time is being spent in LoadPackage calls. We’ll still need to scan all dirtied packages and call CheckPackageForSoftObjectReferences on the ones that are already loaded, but we’ll skip loading additional packages based on asset registry referencers.

However, this function unconditionally appends every currently dirty (unsaved) world and content package in the editor session as an additional soft-reference candidate — regardless of whether the Asset Registry indicated any relationship to the object being deleted.

This is going to be necessary since the asset registry and it’s list of references/referencers only updates on save; for a dirty package, we can’t say for certain if some references may have been added, so when we are deleting something we need to manually check these. We do cache dirty packages in CachedSoftReferences so that future asset deletions can reference that instead of rescanning the package, but as soon as it’s dirtied again it will be removed from the cache.

Assuming the majority of the work here is based on dirtied package count, the best recommendation is going to be saving dirited packages more often, especially before going through and deleting a lot of content. Running a SaveAll right before would totally remove those from the list of things that need to be unconditionally scanned and bake all those references into the asset registry where they can be looked up more easily. If you wanted to take that approach, you could add a popup warning in UUnrealEdEngine::DeleteActors when the dirty package count exceeds some threshold to suggest running a SaveAll before proceeding. You could also avoid engine changes and implement this as your own combined “save all and delete actor” button that could be added to a toolbar/menu via a plugin.

The riskier option is exposing your own delete function that sets bWarnAboutSoftReferences to false and lets things potentially break. The “Check References on Delete” option in editor preferences is probably overkill (as it includes hard references as well), but you could manually call GUnrealEd->DeleteActors with just the soft references flag flipped to false since breaking soft references is less risky. The scenario you’d potentially be breaking would be an unsaved actor with a new soft reference to the thing you’re about to delete, but at least the failure wouldn’t be catastrophic (the soft reference would just fail to resolve).

Best,

Cody