Proper Method for Unloading DataAssets at runtime?

Firstly, what I’ve tried:

  • TSoftObjectPtr<> = nullptr;/// The loaded PrimaryDataAsset is held by a UObject. Eventually the UObject is MarkAsGarbage(), however even when the object shows as having been GC’d, the PrimaryDataAsset still shows as valid (I used a test TWeakObjectPtr<> to verify on EndPlay() )
  • Because of the above I created a second test PrimaryDataAsset and TWeakObjectPtr<>. Both PrimaryDataAssets ( A and B ) are found via GetPrimaryAssetDataList(), then loaded via FAssetData::GetAsset(). Only TweakObjectPtr<>A is passed to a UObject, TWeakObjectPtr<>B is never passed to a hard reference. However, on EndPlay() they both show up as valid.
  • I made a third PrimaryDataAsset and TWeakObjectPtr<>C. It is stored in Editor via a UPROPERTY() TSoftObjectPtr<>. OnPlay TWeakObjectPtr<>C = TSoftObjectPtr<>C.LoadSynchronous. If I then TSoftObjectPtr<>C.Reset() and = nullptr; OnEndPlay TWeakObjectPtr<>C is still valid.
  • I briefly attempted UAssetManager::AssetManager().UnloadPrimaryAsset(PrimaryAssetData->GetPrimaryAssetId()); however it was unable to find the asset, which I haven’t yet looked deeper into.

It’s my understanding that as long as a TSoftObjectPtr<> no longer has hard references it will be GC’d, yet it does not appear to work for PrimaryDataAssets.

Currently I’m able to load PrimaryDataAssets as needed, but I want to avoid forcibly unloading a specific PrimaryDataAsset in case it is still referenced by another UObject.

So as far as UAssetManager::AssetManager().UnloadPrimaryAsset(goal->Purpose()->GetPrimaryAssetId()) is concerned, it appears the that (*FoundType)->AssetMap.Find(PrimaryAssetId.PrimaryAssetName); or AssetTypeMap.Find(PrimaryAssetId.PrimaryAssetType); from GetNameData() is failing.

There are currently two PrimaryDataAssets I’m trying to unload, A and B. A doesn’t throw an error, so I assume the NameData is found. However the TWeakObjectPtr<> I made to track it’s state still shows valid after called unload and a GC. B is the one which isn’t being found.

B is loaded via TSoftObjectPtr::LoadSynchronous rather than GetPrimaryAssetList() so I assume it is never added to the AssetTypeMap or AssetMap. I can’t use MarkAsGarbage(), otherwise the I can’t find or load the data asset until the session is closed and reopened.

Currently can’t step into UnloadPrimaryAsset() in vs debug, downloading the symbols which I assume I need for that.

Now that I have the symbols, I traced execution through to UAssetManager::UnloadPrimaryAssets(). It appears that FPrimaryAssetData:: CurrentState and FPrimaryAssetData::PendingState are both invalid. VS showed (I believe it was called) their streamer handles as nullptr.

To test further, I loaded an asset via UAssetManager::GetPrimaryAssetDataList(), which showed the asset correctly loaded in AssetMap (which requires the AssetType and AssetName both be held by AssetManager). Then I called UAssetManager::UnloadPrimaryAsset(). The type and asset path are found, it’s just the same issue where FPrimaryAssetData:: CurrentState and FPrimaryAssetData::PendingState are both invalid.

Places that FPrimaryAssetLoadState CurrentState are set:

  • OnAssetStateChangeCompleted() NameData->CurrentState.Handle = NameData->PendingState.Handle;
  • ChangeBundleStateForPrimaryAssets() NameData->CurrentState.Handle = NewHandle;

Places that FPrimaryAssetLoadState PendingState are set:

  • ChangeBundleStateForPrimaryAssets() NameData->PendingState.Handle = NewHandle;

Well it appears part of the issue was that I was using GetAssetDataList() then a for loop for all the FAssetData found. Then to load I used FAssetData::GetAsset(), which did not run through ChangeBundleStateForPrimaryAssets().

I switched to LoadPrimaryAsset() which does. It will load the asset, and when I unload it says it successfully unloads, and the TWeakObjectPTr<> does show as invalid, however a TSoftObjectPtr<> says it stilll is valid and pressing play for a new PIE states the asset was already loaded.

Need to do further testing.

Hm. So I load and store two DataAssets (A, B).

UPROPERTY(EditAnywhere)
TSoftObjectPtr<UPrimaryDataAsset> A;

TWeakObjectPtr<UPrimaryDataAsset> B;

After loading them via UAssetManager::LoadPrimaryAssets(), in the callback I unload them.

The unload returns true, and stepping through it in VS debug I see that FPrimaryAssetLoadState PendingState is valid and thus proceeds to unload, but FPrimaryAssetLoadState CurrentState is invalid. The unload requires only one be valid.

Where I store them, on BeginDestroy() which is called on end PIE, both A and B are valid, and on the next PIE, they are still seen as valid. I have to close the editor to completely unload them.

I’m going to test this with a fresh project to see if it’s reproduceable.

Not that it’s a huge deal, these data assets are only about 11kb in memory, but it should still function as expected.

I also just discovered and tried FStreamableHandle::ReleaseHandle(), returned from LoadPrimaryAssets(), which in it’s description states: “Release this handle. This can be called from normal gameplay code to indicate that the loaded assets are no longer needed”.

Unfortunately the asset still remained loaded between PIE sessions until editor was closed and reopened.

Long coming update, this may be a PIE issue, not packaged (with how PIE loads and holds assets), which someone suggested about loading regular TSoftObjectPtr into memory. I haven’t yet tested as I’m working on other features currently. But will next time I package probably. Just posting for my own memory.

2 Likes

any updates? cause i have same problem

The issue was a PIE issue. A DataAsset will unload in a build and possibly standalone. The editor just holds assets in memory differently