Async Loading SoftObjectPtr in Primary Data Asset

Hello,

I’ve been wrestling with async loading of assets for some time now, and I am 80% of the way there. I declared the following data asset class:

UCLASS(BlueprintType)
class UCharacterDescriptor : public UPrimaryDataAsset
{
	GENERATED_BODY()
public:

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
    FName NameId;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
    FText DisplayName;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (AssetBundles = "Scene"))
    TSoftObjectPtr<class USkeletalMesh> BaseMesh;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (AssetBundles = "Scene"))
    TSoftClassPtr<class UAnimInstance> AnimClass;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (AssetBundles = "UI"))
    TSoftObjectPtr<class UTexture2D> PortraitThumbnail;
};

Somewhere in code, I hold a TSoftObjectPtr<UCharacterDescriptor> property. I successfully async load that using:

	auto primaryAssetId = assetMgr.GetPrimaryAssetIdForPath(charDescriptor.ToSoftObjectPath());
	if(primaryAssetId.IsValid())
	{
		TArray<FName> bundlesToLoad; // Load everything, since scene will ultimately need both UI and Scene display
		m_characterAssetsHandle = assetMgr.LoadPrimaryAsset(primaryAssetId, bundlesToLoad, FStreamableDelegate::CreateUObject(this, &AMyPawn::OnCharacterAssetsLoaded));
	}

In the callback, I do get a valid UCharacterDescriptor, however I was under the impression that async loading a primary asset will also load all secondary assets within. My TSoftObjectPtrs for the mesh and anim resource are still null.

My question is: Should the data asset be holding a raw pointers to the properties - if so, will they still be loaded asynchronously, or will they force some kind of flush that leads to a sync load?

Or should I hold a raw pointer to the data asset instead of a TSoftObjectPtr, and keep the properties within as soft references.

Facing this limitation, I am struggling to see why would I use the UAssetManager as opposed to the FStreamableManager.

Have you just snipped out code that didn’t seem relevant? Your ‘bundlesToLoad’ is empty which will load “no bundles”, not “all bundles”. If you add the strings “Scene” and “UI” to that array it should start doing what you’re expecting.

It’s really hard to give advice here since only you really know the access patterns to your data, but:

Yes, your data asset could hold raw pointers to the properties. If it did they should be loaded asynchronously if you load the asset asynchronously. The downside is that you would be unable to load a CharacterDescriptor without also loading those referenced assets.

Yes, you could hold a raw pointer to the data asset. It would then be loaded along with whatever was referencing it (which may or may not be desirable). You would then have to use UAssetManager::ChangeBundleStateForPrimaryAssets when you wanted wanted to load or unload the Descriptors soft references.

I just want to follow up on this one for the sake of clarity. After passing the appropriate bundle array, I had no problems with it since then. There is a new case popping up however. I added a nested data asset inside the former one.

class UCharacterDescriptor : public UPrimaryDataAsset
{
 ....
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (AssetBundles = "Scene"))
    TSoftObjectPtr<class UVocalizationDataAsset> VocalizationData;
 ....
};

It really is just a glorified array of a custom struct that has the actual soft object ptr.

USTRUCT()
struct FVocalizeEntry
{
	GENERATED_BODY()
	
	UPROPERTY(EditDefaultsOnly, meta = (AssetBundles = "Scene"))
	TSoftObjectPtr<USoundWave> Sound;

    etc ....
};

I designated it to be part of the bundle just in case, but the TArray property containing this struct does not have the asset bundle meta, because my thought is that it is only relevant for asset properties.

In this setup, the sound wave does not actually load. So it sounds to me like I need to explicitly request the load of each PrimaryDataAsset - i.e, nesting them doesn’t recursively load them.

Not implying that it should, but just making sure. In this example, I can just use a raw pointer because I expect no scenario where it shouldn’t be loaded as part of the CharacterDescriptor asset. But I’d like to iron out this detail in case a more demanding case pops up.

Awesome! Glad to hear it!

That is correct, for better or worse bundle loading is not a recursive process. At work we have written a custom process for requesting bundles in this way. It definitely feels more intuitive, though I can understand the desire for a bundle request to be a single async request instead of: request A, load A, search A, request B, load B, search B, request C, etc, etc. We only get away with it because the things we request bundles of are kept permanently loaded for the whole game so we can do that search all at once before doing the bundle request.

The other alternative would be to keep the bundle-based reference to the VocalizationDataAsset and let that one have the raw pointers. I don’t know if that works for your use case, but it is an alternative.

Exactly what I needed, thanks for clarifying!