Assets Streaming

Overview

In the Unreal Engine, when a UObject references other Assets, they are loaded in memory by default.

For example, when a UPROPERTY property is defined in a UObject, the reference is loaded in memory when the owner itself is being loaded.

UPROPERTY(EditAnywhere)
UMyAsset * MyAsset;

In some cases, we want to define an Asset dependency, but we need to defer the loading of these Assets when they are actually needed.

TSoftObjectPtr

Instead of storing a pointer to an Asset directly (hard pointer), we store it inside a TSoftObjectPtr object (soft pointer or lazy pointer).

TSoftObjectPtr is a template class that is declared with an asset type and can be stored as a UPROPERTY property.

ℹ️
TSoftObjectPtr was originally named TAssetPtr.

TSoftObjectPtr contains a reference to an Asset if it is loaded in memory, and additional information to load it on demand.

UPROPERTY(EditAnywhere)
TSoftObjectPtr<UMyAsset> MyAssetPtr;

Usage

  1. To check if a TSoftObjectPtr contains a valid object, call the IsValid function.
  1. If there is a live UObject, call the Get function to dereference the TSoftObjectPtr.
  1. If there is not live UObject, we need to stream it into memory.

The name of the Asset can be obtained by calling the GetAssetName function.

TAssetSubclassOf

To store a reference to a subclass, use the TSoftClassPtr object instead.

TSoftClassPtr works like a TSubclassOf.

TSoftClassPtr is a template class that is declared with a base asset type and can be stored in a UPROPERTY property.

ℹ️
TSoftClassPtr was originally named TAssetSubclassOf.
UPROPERTY(EditAnywhere)
TSoftClassPtr<UMyAsset> MyAssetPtr;

Usage

  1. To check if a TSoftClassPtr contains a valid object, call the IsValid function.
  1. If there is a live UObject, call the Get function to dereference the TSoftClassPtr.
    1. The Get method returns an instance to a UClass instead of a UObject.
    1. Call the GetDefaultObject function from the UClass object to get a pointer to the derived UObject instance.

Loading

Asynchronous

To load an asset asynchronously, we need to use FStreamableManager.

The FStreamableManager object streams assets in and keeps them in memory.

A good place to store it, is in the GameInstance.

FStreamableManager StreamableManager;

FStreamableManager loads Assets from a string reference represented by FSoftObjectPath (also known as soft reference).

Internally, FSoftObjectPath stores a FName pointing to a top level asset and optionally, a subobject path.

ℹ️
FSoftObjectPath was originally named FStringAssetReference.

To obtain a string reference from a TSoftObjectPtr call the ToSoftObjectPath function.

ℹ️
ToSoftObjectPath was originally named ToStringReference.
FSoftObjectPath SoftObjectPath = MyAssetPtr.ToSoftObjectPath();

To load an asset from a string reference, call the FStreamableManager::RequestAsyncLoad function.

The RequestAsyncLoad function has several overloads:

Streaming Handle

The function returns a FStreamableHandle handle.

As long as the handle is active, the loaded assets will stay in memory.

The handle should be stored in the owner of the loaded asset.

TSharedPtr<FStreamableHandle> Handle;

To load an asset using the FStreamableHandle:

FStreamableHandle Handle = StreamableManager.RequestAsyncLoad(SoftObjectPath);

if (!Handle.IsValid())
{
    return;
}

To obtain the asset object, the handle need to be polled to check if the loading has completed.

if (Handle->IsValid && Handle->HasLoadCompleted())
{
    // Use the loaded asset
    UObject* Obj = Handle->GetLoadedAsset();
    ...
}

Lambda

To load an asset with a lambda function, call RequestAsyncLoad with a lambda.

The lambda will be called when the loading is complete.

StreamableManager.RequestAsyncLoad(
    SoftObjectPath, []()
    {
        // Use the loaded asset
        ...
    });

Delegate

To load an asset with a delegate:

For example, if the owner is named UAssetLoader:

UAssetLoader::Load()
{
    StreamableManager.RequestAsyncLoad(
        SoftObjectPath,
        FStreamableDelegate::CreateUObject(this, &UAssetLoader::OnLoadComplete)
    );
}

UAssetLoader::OnLoadComplete()
{
    // Use the loaded asset
    ...
}

Calling RequestAsyncLoad with bManageActiveHandle set to true indicates that the manager should keep the handle alive.

ℹ️
This operation wad originally implemented in FStreamableManager::SimpleAsyncLoad (deprecated).

Synchronous

FStreamableManager can also load Assets synchronously.

To load an asset synchronously, call the RequestSyncLoad function.

The function returns a FStreamableHandle.

Handle = StreamableManager.RequestSyncLoad(SoftObjectPath);

The handle must be released when the asset is not needed, or when the owner is destroyed.

To release a handle, check that it is valid with IsValid and call ReleaseHandle.

if (Handle.IsValid())
{
    Handle->ReleaseHandle();
}

Another way to load an asset synchronously is to call the LoadSynchronous function either from the FStreamableManager or from the TSoftObjectPtr.

The function loads the asset, and returns the asset object represented by the asset pointer.

UMyAsset* MyAsset = MyAssetPtr.LoadSynchronous();