You can try this out yourself – make a new native Actor with the following code, place it in your game, and it will crash in about 1 minute after you start the game (once garbage collection happens)
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CrashyActor.generated.h"
UCLASS()
class UArrayIdx : public UObject
{
GENERATED_BODY()
public:
UArrayIdx() {}
~UArrayIdx() { mArrayIdx = 999999; }
int mArrayIdx;
};
USTRUCT(BlueprintInternalUseOnly)
struct FArrayIdxHolder
{
GENERATED_USTRUCT_BODY()
UPROPERTY(Transient)
TObjectPtr<UArrayIdx> mInner;
};
UCLASS()
class ACrashyActor : public AActor
{
GENERATED_BODY()
public:
ACrashyActor() {
mMyArray.AddDefaulted(100);
PrimaryActorTick.bCanEverTick = true;
}
virtual void Tick(float DeltaTime) override {
if (!mArrayIdx.mInner) {
mArrayIdx.mInner = NewObject<UArrayIdx>();
mpArrayIdx = &mArrayIdx; // this should hold a strong reference to the struct
FObjectPtr(&mArrayIdx).Get()->AddToRoot(); // this protect the struct from garbage collection
mArrayIdx.mInner->mArrayIdx = 10;
}
check(!mArrayIdx.mInner->GetMaskedFlags(RF_BeginDestroyed | RF_FinishDestroyed));
mMyArray[mArrayIdx.mInner->mArrayIdx] += DeltaTime;
}
FArrayIdxHolder mArrayIdx;
UPROPERTY(Transient, VisibleAnywhere)
TArray<float> mMyArray;
TObjectPtr<FArrayIdxHolder> mpArrayIdx;
};
My question is: does anyone know a good way to fix this code, using -only- logic in the AActor class, and not touching any of the code in the UObject or UClass?
Context:
In my case, the USTRUCT is actually FAnimNode_RetargetPoseFromMesh, and the UObject is its member variable, TObjectPtr<UIKRetargetProcessor> Processor. My game is crashing when the Processor object gets garbage collected, even though my AnimNode is still alive and ticking.
As these are both engine classes that I don’t want to fork, I’m looking for a solution that doesn’t involve modifying either of these classes
To further complicate these matters, the AnimNode is stored inside of ANOTHER UStruct(), FAnimInstanceProxy, and I can’t convert my derived class to a UCLASS()
Background:
I’ve been trying to make an AnimInstance (also known as AnimBlueprint) in Native code, so that I can reuse some generic logic across multiple skeletons.
When trying to make my AnimGraph (implemention of AnimNodes) in native code, I followed the pattern of some of the logic in AnimPreviewInstance. (In particular, check out UE_5.0\Engine\Source\Editor\AnimGraph\Public\AnimPreviewInstance.h, line 147)
Here’s some code that still crashes, and is more accurate towards the case I’m looking at with regard to my usage of FAnimNode_RetargetPoseFromMesh.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CrashyActor.generated.h"
// pretend that this is defined in an engine file that you can't modify
UCLASS()
class UMyIdxClass : public UObject
{
GENERATED_BODY()
public:
int mIdx;
};
// pretend that this is defined in an engine file that you can't modify,
// and you have to use it because it implements some complicated code that you don't want to fork
USTRUCT(BlueprintInternalUseOnly)
struct FArrayIdxHelper
{
GENERATED_USTRUCT_BODY()
void FindLastIdx(const TArray<float>& array) { GetLastIdxVal().mIdx = array.Num() - 1; }
int GetLastIdx() { return GetLastIdxVal().mIdx; }
private:
UMyIdxClass& GetLastIdxVal() {
if (!mLastIdxVal) {
mLastIdxVal = NewObject<UMyIdxClass>();
}
check(!mLastIdxVal->GetMaskedFlags(RF_BeginDestroyed | RF_FinishDestroyed));
return *mLastIdxVal;
}
UPROPERTY(Transient)
TObjectPtr<UMyIdxClass> mLastIdxVal;
};
UCLASS()
class ACrashyActor : public AActor
{
GENERATED_BODY()
public:
ACrashyActor() {
mMyArray.AddDefaulted(100);
PrimaryActorTick.bCanEverTick = true;
}
virtual void Tick(float DeltaTime) override {
mArrayHelper.FindLastIdx(mMyArray);
mMyArray[mArrayHelper.GetLastIdx()] += DeltaTime;
}
FArrayIdxHelper mArrayHelper;
UPROPERTY(Transient, VisibleAnywhere)
TArray<float> mMyArray;
};
Here’s one solution that I found, but it would be nice to know if there’s a better one that doesn’t involve using a hardcoded string.
(I can’t use GET_MEMBER_NAME_CHECKED here because the macro requires the variable to be visible from the function where it’s being used)
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CrashyActor.generated.h"
// pretend that this is defined in an engine file that you can't modify
UCLASS()
class UMyIdxClass : public UObject
{
GENERATED_BODY()
public:
int mIdx;
};
// pretend that this is defined in an engine file that you can't modify,
// and you have to use it because it implements some complicated code that you don't want to fork
USTRUCT(BlueprintInternalUseOnly)
struct FArrayIdxHelper
{
GENERATED_USTRUCT_BODY()
void FindLastIdx(const TArray<float>& array) { GetLastIdxVal().mIdx = array.Num() - 1; }
int GetLastIdx() { return GetLastIdxVal().mIdx; }
private:
UMyIdxClass& GetLastIdxVal() {
if (!mLastIdxVal) {
mLastIdxVal = NewObject<UMyIdxClass>();
}
check(!mLastIdxVal->GetMaskedFlags(RF_BeginDestroyed | RF_FinishDestroyed));
return *mLastIdxVal;
}
UPROPERTY(Transient)
TObjectPtr<UMyIdxClass> mLastIdxVal;
};
UCLASS()
class ACrashyActor : public AActor
{
GENERATED_BODY()
public:
ACrashyActor() {
mMyArray.AddDefaulted(100);
PrimaryActorTick.bCanEverTick = true;
HackReferenceHolder = nullptr;
}
virtual void Tick(float DeltaTime) override {
mArrayHelper.FindLastIdx(mMyArray);
mMyArray[mArrayHelper.GetLastIdx()] += DeltaTime;
if (!HackReferenceHolder) {
FProperty* prop = mArrayHelper.StaticStruct()->FindPropertyByName("mLastIdxVal");
if (FObjectPtrProperty* objPtrProp = Cast<FObjectPtrProperty>(prop)) {
HackReferenceHolder = objPtrProp->GetObjectPropertyValue(&mArrayHelper);
}
}
}
FArrayIdxHelper mArrayHelper;
UPROPERTY(Transient, VisibleAnywhere)
TArray<float> mMyArray;
UPROPERTY(Transient, VisibleAnywhere)
TObjectPtr<UObject> HackReferenceHolder;
};