Download

ActorComponent crashes editor, unless I log something in BeginPlay()

The editor runs the ActorComponent fine the first time I hit play, but if I hit play a second time the editor crashes. I have BeginPlay implemented on the abstract Base class, and subclasses since if I print to the output log the editor doesn’t crash. (Code shown doesn’t have it)

The base class:



typedef TFunction<FName(void)> Condition;
typedef TArray<FName> BranchValues;
typedef TUnion<BranchValues, Condition> BranchTypes;

#define LOCTEXT_NAMESPACE "Dialogue"

USTRUCT()
struct FDialogueNodes
{
    GENERATED_BODY()

    FText DialogueFragment;
	
    BranchTypes branch;

    FDialogueNodes() {}
	
    FDialogueNodes(FText dialogueFragment, TArray<FName> nextNode)
        : DialogueFragment(dialogueFragment)
        {
            branch.SetSubtype<BranchValues>(nextNode);
        }

    FDialogueNodes(Condition condition)
    {
        branch.SetSubtype<Condition>(condition);
    }

    FDialogueNodes(FText dialogueFragment)
        : DialogueFragment(dialogueFragment)
    {
        branch.SetSubtype<BranchValues>(BranchValues());
    }
};

UCLASS(abstract)
class DIALOGUES_API UBaseDialogue : public UActorComponent
{
    GENERATED_BODY()

public:
    UBaseDialogue();
    virtual void BeginPlay() override;

    virtual void TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction ) override;
	
//... BlueprintCallable methods

protected:
    FName id;
};

Child class header:



UCLASS(ClassGroup=(Dialogue), meta=(BlueprintSpawnableComponent))
class DIALOGUES_API UBanter2afterthespiders : public UBaseDialogue
{
  GENERATED_BODY()

public:
  UBanter2afterthespiders();

virtual void BeginPlay() override;
virtual void OnRegister() override;
	
	virtual void TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction ) override;
private:
	IGameTime *gameTime;
};


Child class Cpp:



UBanter2afterthespiders::UBanter2afterthespiders()
{
}

void UBanter2afterthespiders::OnRegister()
{
	Super::OnRegister();
	id = TEXT("0x01000001000000DC");
	gameTime = NewObject<AGameTimeImplementation>();
	Condition c_0 = [this](void)->FName{ return (gameTime->Hour() < 19) ? TEXT("0x01000001000000D4") : TEXT("0x01000001000000E2"); };
       // Emplace 24 elements in dialogues TMap.
}

void UBanter2afterthespiders::BeginPlay()
{
	Super::BeginPlay();
}

void UBanter2afterthespiders::TickComponent(float DeltaTime, ELevelTick TickType,
                                            FActorComponentTickFunction *ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
}


I don’t think there’s really any reason to have TickComponent, or BeginPlay on these classes. (Works fine the first time I hit play without crashing) I’ve tried tons of things from manually deleting Interfaces, to migrating my Interfaces to Unreal’s implementation of interfaces. I also tried changing where Banter2afterthespiders intializes it’s values from the Constructor to OnRegister.

memreport shows memory usage growing slightly bewteen the first play and second play, I have a feeling that my c_0 TFunction isn’t being garbage collected, but I’m not sure how to either delete a TFunction manually or allow for a Union to be garbage collected. If anyone is able to help me figure out what’s causing the crash I’d appreciate it greatly.

Crash Log The Crash Log isn’t always the same.

It is probably indeed garbage collection related. If you’re saving pointers to a UObject in a member variable, you have to remember to mark those variables with UPROPERTY(). UE uses reference counting for its garbage collection so if you’re storing a UObject pointer in a field that isn’t marked UPROPERTY, it won’t count as a reference and that object may be garbage collected too early. In your case try making ‘gameTime’ or ‘branch’ as UPROPERTY and see if it makes a difference.

I just find it odd that if I print a random string to the console it prevents the crash.

Can’t comment on that without the code.

So, I guess I need to manually clear structs first. I don’t see a way to do that in the documentation. Do I just need to run the destructor manually? branch can’t be set as a UPROPERTY(), since TUnion doesn’t support that attribute yet.

Are you sure you can’t mark it with UPROPERTY without any specifiers? I haven’t worked with TUnion so I believe you, just want to make sure you didn’t try it with any specifiers like VisibleAnywhere. One workaround for making sure objects that the struct stores are reference counted is by keeping an extra TArray marked UPROPERTY() that simply stores UObject pointers that are in use in that TUnion. I used that when TMaps couldn’t be marked with UPROPERTY yet (they can nowadays).

You don’t need to run the struct’s destructor manually. I don’t think your crash is caused by something not being garbage collected. Rather, I think something is being garbage collected too soon. Whatever you did in that log operation may have triggered the object to live just a bit longer.

Found the issue. Turns out it was UMG widgets not being removed from the scene correctly causing the leak and eventual crash.

Actually, it seems to be happening again. Odd.

If I’m using a raw pointer pointing to a class that isn’t a UObject does Unreal try to garbage collect that?

No, Unreal’s garbage collection only affects UObjects. Raw pointers are your own responsibility. Did you have any luck by keeping an extra TArray to reference UObjects that are stored in your TUnion? By having them in the TArray marked UPROPERTY() it guarantees them not being garbage collected, whereas your TUnion isn’t reference counted.

TArray most likely isn’t the issue. I have sublclasses of BaseDialogue, with just TArrays in the union that don’t cause the crash. What seems to be the problem is my subclasses that have TFunctions stored in the union.

I have a post up on Unreal Answers with the currently relevant code. Proper way to deal with destroying a TFunction? - UE4 AnswerHub

So, I’ve changed the FDialogueNode struct a bit to see if I can find the cause of the crash. It still happens, but sometimes I get a useful callstack now.



USTRUCT(NotBlueprintable)
struct FDialogueNode
{
    GENERATED_BODY()


    UPROPERTY()
    FText DialogueFragment;
    
    UPROPERTY()
    TArray<FName> bv;
    
    BranchTypes branch;


    FDialogueNode() {}


    FDialogueNode(FText dialogueFragment, TArray<FName> nextNode)
        : DialogueFragment(dialogueFragment)
        {
            bv.Append(nextNode);
            branch.SetSubtype<BranchValues>(MoveTemp(bv));
        }


    FDialogueNode(Condition condition)
    {
        DialogueFragment = FText::GetEmpty();
        bv.Append(BranchValues());
        branch.SetSubtype<Condition>(condition);
    }


    FDialogueNode(FText dialogueFragment)
        : DialogueFragment(dialogueFragment)
    {
        bv.Append(BranchValues());
        branch.SetSubtype<BranchValues>(MoveTemp(bv));
    }


     void Reset()     {
         // Try using ResetSubtype
         if(branch.HasSubtype<BranchValues>())
        {
            branch.GetSubtype<BranchValues>().Reset();
        }
        if(branch.HasSubtype<Condition>())
       {
           branch.GetSubtype<Condition>().Unset();
       }
       branch.Reset();
    }
};


This is how the DialogueNode gets added and removed from the TMap respectively in OnRegister and OnUnregister:



void UBanter2afterthespiders::OnRegister()
{
    Super::OnRegister();
    gameTime = new GameTime();
   dialogues.Emplace(TEXT("0x01000001000000DC"), FDialogueNode([this](void)->FName{ return (gameTime->Hour() < 19) ? TEXT("0x01000001000000D4") : TEXT("0x01000001000000E2"); }));
}


void UBanter2afterthespiders::OnUnregister()
{
    //delete gameTime;
    //gameTime = nullptr;
    for(auto it = dialogues.CreateIterator(); it; ++it)
    {
        it.Value().Reset();
    }
    delete gameTime;
    //dialogues.Reset();
    Super::OnUnregister();
}




If I change OnRegister to skip passing the TFunction to FDialogueNode the crash doesn’t happen.
Could this be a bug with TUnion?

Here’s the call stack in case it is helpful.