Unloading Assets to Free RAM?

Hi. I know how to load assets dynamically or “asynchronously”, but what about unloading assets that are no longer needed? I made a character morphing system. When all morph-data is loaded, it takes up many GBs of memory. So how can I unload this data/assets from RAM?

Any help please? I’m trying to figure this out, but still without success.

I am using RemoveFromRoot() on everything I can find, but the Windows Task-Manager shows that RAM-usage only increases with loading stuff, unloading doesn’t seem to happen.

I don’t even understand what exactly gets loaded into memory: the package itself, the assets or objects created from assets?..

If I use LoadObject< USkeletalMesh >(…), is it only the SkeletalMesh that takes up memory or also the package/asset it is loaded from?

Ok, please correct me if I’m wrong, but I’m getting the impression that garbage collection is what I need to utilize here.

I was thinking that UE handles GC automatically, but I haven’t seen a single asset “unload” so far. So I just tried calling CollectGarbage(EObjectFlags::RF_NoFlags). One statement after that I use FindObject(…) to see if the asset I am trying to unload is still there. And it’s not! The problem is, one frame later the editor crashes because of some missing object…

I also tried calling ConditionalBeginDestroy() on the loaded object now, but that seems to delete the entire asset, because I can’t reload it afterwards or open it in the editor.

Am I the only person who needs to manage memory and unload unused assets?..

It seems there is no way to do it, closest i find is this:

But even that describes it only deletes reference,

So it’s same as with UObjects, you can’t delete asset from memory, insted you delete any reference to the asset and GC will unload it after a while, possibly force this process when more memory is needed. UE4 works this way for safety, so some code somewhere don’t crash because something suddenly disappeared from memory.

Thanks for reply. This is really getting annoying though… I can’t believe a game engine doesn’t allow you to unload and make room for other content.

What am I supposed to do?? Display a message to the player: “Please restart the game, too many assets have been loaded.” ??

I used RetrieveReferencers(…) on a SkeletalMesh I loaded with LoadObject(…) for testing. It turns out the only referencers to the mesh I am trying to un-load are its own MorphTargets. I manually set all their pointers to the mesh to NULL. Retrieving referencers again afterwards shows there aren’t any left, internal or external. And STILL the fricking mesh won’t unload. I can’t call CollectGarbage “by hand”, because it causes the whole editor to crash.

Is this a joke? An engine that has been around for almost 20 years and can’t free up memory for unused resources?

1 Like

It should free up unused resources, did you wait a minute after you relesed the refrences?

It should free up unused resources and think it should free them up when it really needed, UE4 was designed in a way that you don’t need to think about memory. Did you wait a minute after you relesed the references?

Yes I did.

Today, I’m gonna rewrite my MorphingDataAsset, not knowing if it will work or not. Instead of using pointers to the individual Morph-Meshes and other DataAssets that are used and referenced by my “MorphCollection”, I will only store the paths to them and load them dynamically when needed. I can only hope that, if I keep no other references to the DataAssets, they will eventually be garbage collected.

But to be honest, I expect I will be in for some more nasty surprises when it comes to trying to understand how references are tracked inside UE.
For example, last night I experimented with UObject::RetrieveReferencers(…) and IsReferenced(UObject).

Two things already gave me a headache:

  • An object that returns no referencers when calling RetrieveReferencers still returned true when calling IsReferenced on it.
  • And when I declared a UObject* as a UPROPERTY() inside some TestActorComponent and set its value to the asset I was trying to unload somehow, that reference/referencer did not appear when calling RetrieveReferencers on the asset.

… so I don’t expect I’m gonna get this working anytime soon …

EDIT:

Do you know if it’s necessary to reset ALL references to an object? I mean, if there is an object A that references objects B and C. B and C only reference (and are referenced by) A, B or C and nothing “outside”. If object A is no longer referenced by anything other than B or C, will that be enough for garbage collection to collect all three objects or not?

And thank you again for sticking with me.

As i heard GC don’t handle circular references, so you need to break circle if you want object to be deleted. Insted of trying to delete those so badly, code your classes in the way that if they don’t need something you cut a reference. What exactly do you trying to delete, what other object keeping track of it when, why

#Yes

It is very possible

If you only load an asset using FStreamableManager, then you can call unload, and then force garbage collection, and then that asset will be unloaded.

This will only work if you dont make any other references to the object after loading it, such as loading a static mesh and making it the asset for a static mesh component. That component must also set its mesh to null for this to work, or that component needs to be destroyed.

Check out StreamableManager.h

USTRUCT()
struct ENGINE_API FStreamableManager
{

/** 
	 * Releases a reference to an asynchonously loaded object, that was loaded by SimpleAsyncLoad
	 */
	void Unload(FStringAssetReference const& Target);

You can also use RequestAsyncLoad, making sure to do something with the delegate to retain the asset so it is not loaded and then unloaded again on the next run of GC:

/** 
	 * Request streaming of one or more target objects, and call a delegate on completion. 
	 * Objects will be strongly referenced until the delegate is called, then Unload is called automatically 
	 */
	void RequestAsyncLoad(const TArray<FStringAssetReference>& TargetsToStream, FStreamableDelegate DelegateToCall, TAsyncLoadPriority Priority = DefaultAsyncLoadPriority);

This will also only work if the asset only got loaded due to being loaded via the StreamableManager, ie, there were no hard references to the asset from .umaps or blueprints

To ensure an asset gets packaged, store FStringAssetReference as a UPROPERTY() and point to the asset that you want to then load using the FStreamableManager.

#:heart:

3 Likes

It does handle circular references, that’s the reason to use a GC rather than relying on smart pointers. Any UObjects which aren’t reachable from the root set can be deleted by the GC.

My StreamableManagerActor for this topic
Note: OnTextureLoaded is not working, im not cpp expert , i just used ChatGPT :smiley:
I’m not sure if unload is working either :smiley: But when I load the pictures (3GB) like this, nothing increased in Ram. When I load normally (AsyncLoadAsset), the ram fills up with GBs.
I Used SyncLoad when testing

HEADER FILE
HEADER FILE
HEADER FILE

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Engine/StreamableManager.h"
#include "GameFramework/Actor.h"
#include "StreamableManagerActor.generated.h"

UCLASS()
class POPSLIBRARY_API AStreamableManagerActor : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AStreamableManagerActor();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

public:
	UFUNCTION(BlueprintCallable, Category = "SoftReference")
	void StreamableAsyncLoad(TArray<FSoftObjectPath> TargetPaths);
public:
	UFUNCTION(BlueprintCallable, Category = "SoftReference")
	void StreamableSyncLoad(TArray<FSoftObjectPath> TargetPaths);

public:
	UFUNCTION(BlueprintCallable,BlueprintNativeEvent, Category = "SoftReference")
	void OnTextureLoaded();

public:
	UFUNCTION(BlueprintCallable, Category = "SoftReference")
	void UnloadAsset(FSoftObjectPath TargetPaths);
	

	FStreamableDelegate OnTextureLoadedDelegate;
	
};

CPP FILE
CPP FILE
CPP FILE

// Fill out your copyright notice in the Description page of Project Settings.


#include "StreamableManagerActor.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "Engine/StreamableManager.h"
#include "Engine/AssetManager.h"


// Sets default values
AStreamableManagerActor::AStreamableManagerActor()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you dont need it.
	PrimaryActorTick.bCanEverTick = true;

}

// Called when the game starts or when spawned
void AStreamableManagerActor::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void AStreamableManagerActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

void AStreamableManagerActor::StreamableAsyncLoad(TArray<FSoftObjectPath> TargetPaths)
{
	OnTextureLoadedDelegate.BindUObject(this, &AStreamableManagerActor::OnTextureLoaded);
	UAssetManager::GetStreamableManager().RequestAsyncLoad(TargetPaths);
}

void AStreamableManagerActor::StreamableSyncLoad(TArray<FSoftObjectPath> TargetPaths)
{

	OnTextureLoadedDelegate.BindUObject(this, &AStreamableManagerActor::OnTextureLoaded);
	UAssetManager::GetStreamableManager().RequestSyncLoad(TargetPaths);
}

void AStreamableManagerActor::UnloadAsset(FSoftObjectPath TargetPath)
{
	UAssetManager::GetStreamableManager().Unload(TargetPath);
}

void AStreamableManagerActor::OnTextureLoaded_Implementation()
{
	return;
}