TArray of UObjects getting garbage collected

We have an inventory system in our game that uses a TArray of UObjects to represent slots in a container.
The declaration for the TArray uses the UPROPERTY specifier to prevent the objects from being GC’d.


The inventory container is created within our derived UGameInstance object at startup in the function UGameInstance::StartGameInstance().

When changing maps, the container persists, but all of its slots get garbage collected from its TArray, causing the game to crash.

We assume that having a TArray of UObjects is compliant with the documentation on Garbage Collection:
“The only container that is safe to have UObject or UObject-derived pointers in is a TArray”

1 Like

Update:
The problem seems to have been solved by manually assigning names to the UObjects.

2 Likes

In the latest version 5.2, I have experienced a similar problem. Thanks to this post, I was able to confirm the issue after a few hours scrambling.

My context was a TArray<UMyData *> where UMyData was derived form UObject.
When I initialized the TArray, I named each entry, but I was using indexes (x and y), which caused some UMyData to have the same name.

These UMyData became corrupted as the program advanced in runtime. This was a very problematic issue to diagnose because some UMyData were fine and others became slowly corrupted. The first UMyData to become corrupted were near the start and end indices of the TArray.

Anyway, I thought I would share so that google can help other who run in the same issue as I did.

This was my first nasty GC bug.

1 Like

I had a similar problem in 5.2.1, related to a blueprint asset.
I have a quest asset (which inherits from a blueprint), and all related quest steps are tracked within it using:

UPROPERTY()
TMap<FString, UQuesStepBase*> QuestSteps;

It works perfectly in editor mode/standalone mode. However, it causes a crash in the IOS development package. All UQuesStepBase objects become null after the quest asset is packaged. I spent around 10 hours trying to figure it out, and it turns out that you have to guarantee the Outer of the UObject to be valid to prevent it from being garbage collected.

In the end, what I did was duplicate all the UObjects I need to store and set their Outer to the UYourBlueprintGeneratedClass you get from:

FYourBlueprintCompilerContext::FinishCompilingClass(UClass* Class) {
UYourBlueprintGeneratedClass* BPGClass = Cast<UYourBlueprintGeneratedClass>(Class);
}

Somehow, it just works.