Editor Crash on interface call "Do not directly call Event functions in Interfaces."

anonymous_user_1ed1c3df
Tue, 21 Feb 2017 00:28:40 +0000
UI

I need some help debugging this crash. I have a Pickup actor that is calling a C++ function to dynamically create an Buff Component actor component and give it to the overlapping actor. When that component is created, there is an Initialize function that is being called (because spawn variables are totally not useful [here][1] due to engine limitations). The Buff Component is defined in C++ and my main player actor is defined in blueprint, so for reasons why blueprints cannot be referenced in C++ I have defined an interface in C++, implemented it on my main player actor, and now my C++ objects can indirectly interact with blueprint objects. The interface has simple getter and setter methods so the Buff Components can do things with the actor’s properties without having to know what the actor type is. I went to test the pickup functional and the editor crashed and I’m not sure where and why it did. I mean I know why in the assertion error, but why is that a why?

Pickup Distributor: the thing that the player is overlapping.

The interface that the player implements
BuffableInterface.h

//
#pragma once

#include "BuffableInterface.generated.h"

UINTERFACE(Blueprintable, MinimalAPI)
class UBuffableInterface : public UInterface
{
	GENERATED_UINTERFACE_BODY()
};

class IBuffableInterface{
	GENERATED_IINTERFACE_BODY()

public:
	UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category="BuffableInterface")
	TArray<class UBuffComponent*> GetActiveBuffs();

	UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category="BuffableInterface")
	float GetHealth();

	UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category="BuffableInterface")
	void SetHealth(float health);

};

The Buff Component that is being created.
BuffComponent.h

//
#pragma once

#include "Components/ActorComponent.h"
#include "BuffComponent.generated.h"

//* Base class for which all buff effects will be derived from.
UCLASS( Blueprintable, ClassGroup=(BuffComponent), meta=(BlueprintSpawnableComponent) )
class PASTRYPANZERPANIC_API UBuffComponent : public UActorComponent
{
	GENERATED_BODY()

public:

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Buff")
	FString text;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Buff")
	float timeCreated;

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Buff")
	float timeActive;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Buff")
	float lifeSpan;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Buff")
	float tickRate;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Buff")
	bool bIsExpirable;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Buff")
	class TScriptInterface<class IBuffableInterface> owner;

	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="DEBUG")
	bool DEBUG = true;

	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="DEBUG")
	float DEBUGTIME = 5.f;


public:
	UBuffComponent();

	virtual void BeginPlay() override;

	virtual void TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction ) override;

	//* Event that is called when this buff needs to be removed form play.
	UFUNCTION(BlueprintNativeEvent, Category="BuffComponent")
	void OnDestroy();

	//* Constructor for this class. Needs to be called immediately after spawning.
	UFUNCTION(BlueprintCallable, Category="BuffComponent")
	virtual void Initialize(
		TScriptInterface<class IBuffableInterface> _owner/* = nullptr*/,
		float _timeCreated/* = 0.0*/,
		float _lifeSpan/* = 6.0*/,
		float _tickRate/* = 1.0*/,
		bool _bIsExpirable/* = true*/
	);

	//* Prints out all the buffs on a given tank refernce. Debug info.
	UFUNCTION(BlueprintCallable, Category="BuffComponentUtil")
	static void PrintAllTankBuffs(
		TScriptInterface<class IBuffableInterface> tankRef/* = nullptr*/,
		float _debugTime/* = 5.0*/
	);

	//* Clears all buffs on the given tank reference.
	UFUNCTION(BlueprintCallable, Category="BuffComponentUtil")
	static void ClearAllTankBuffs(
		TScriptInterface<class IBuffableInterface> tankRef/* = nullptr*/,
		bool _debug/* = true*/,
		float _debugTime/* = 5.0*/
	);

	//* Clears N number of the newest buffs from the given tank reference.
	UFUNCTION(BlueprintCallable, Category="BuffComponentUtil")
	static void ClearNTankBuffs(
		TScriptInterface<class IBuffableInterface> tankRef/* = nullptr*/,
		int32 numToClear/* = 1*/,
		bool _debug/* = true*/,
		float _debugtime/* = 5.0*/
	);

};

BuffComponent.cpp
EDIT:

Fixed some miss-matched line number to reflect stack traces in the above assert screenshot.

//
#include "PastryPanzerPanic.h"
#include "BuffComponent.h"
#include "BuffableInterface.h"
#include "../Tank.h"

UBuffComponent::UBuffComponent()
{
	PrimaryComponentTick.bCanEverTick = true;

}

void UBuffComponent::BeginPlay()
{
	Super::BeginPlay();

	PrimaryComponentTick.SetTickFunctionEnable(false);
	PrimaryComponentTick.bStartWithTickEnabled = false;

}

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

	timeActive += tickRate;

	//* Debugging.
	if(DEBUG){
		FString msg = UKismetSystemLibrary::GetDisplayName(this) += FString(TEXT(": Buff Tick"));
		GEngine->AddOnScreenDebugMessage(-1, DEBUGTIME, FColor::Cyan, msg);
	}

	//* Call Destroy when life span is reached.
	if(timeActive >= lifeSpan && bIsExpirable){
		this->OnDestroy();
	}
}

void UBuffComponent::OnDestroy_Implementation()
{
	//* Debugging.
	if(DEBUG){
		FString msg = UKismetSystemLibrary::GetDisplayName(this)
			+= FString(TEXT(" buff destroyed and removed from "))
			+= UKismetSystemLibrary::GetDisplayName(Cast<AActor>(owner.GetObject()));
		GEngine->AddOnScreenDebugMessage(-1, DEBUGTIME, FColor::Yellow, msg);
	}

	//* Remove form buff array and destroy component.
	//owner->activeBuffs.Remove(this);
	owner->GetActiveBuffs().Remove(this);
	this->DestroyComponent(false);
}

void UBuffComponent::Initialize(
	TScriptInterface<IBuffableInterface> _owner,
	float _timeCreated,
	float _lifeSpan,
	float _tickRate,
	bool _bIsExpirable)
{
	//* Constructor setup.
	this->timeCreated = _timeCreated;
	this->lifeSpan = _lifeSpan;
	this->tickRate = _tickRate;
	this->bIsExpirable = _bIsExpirable;
	this->owner = _owner;

	//* Establish tick rate if any.
	if(tickRate > 0.f){
		PrimaryComponentTick.TickInterval = tickRate;
		PrimaryComponentTick.SetTickFunctionEnable(true);
		PrimaryComponentTick.bStartWithTickEnabled = true;
	}

	//* Add to the buff array of this buff's owner.
	//owner->activeBuffs.Add(this);
	owner->GetActiveBuffs().Add(this);

	//* Debug info.
	if(DEBUG){
		FString msg = UKismetSystemLibrary::GetDisplayName(this)
			+= FString(TEXT(" Buff created for "))
			+= UKismetSystemLibrary::GetDisplayName(Cast<AActor>(owner.GetObject()));
		GEngine->AddOnScreenDebugMessage(-1, DEBUGTIME, FColor::Yellow, msg);
	}
}

void UBuffComponent::PrintAllTankBuffs(
	TScriptInterface<IBuffableInterface> tankRef,
	float _debugTime)
{
	//* This function is for debugging purposes.

	FString msg;

	//* Error checking for null pointers.
	if(tankRef == nullptr){
		msg = FString(TEXT("(BuffComponent::PrintAllTankBuffs) tankRef is not a valid type."));
		GEngine->AddOnScreenDebugMessage(-1, _debugTime, FColor::Red, msg);

		return;
	}

	//if(tankRef->activeBuffs.Num() == 0){
	if(tankRef->GetActiveBuffs().Num() == 0){
		msg = FString(TEXT("Active buff list for "))
			+= UKismetSystemLibrary::GetDisplayName(Cast<AActor>(tankRef.GetObject()))
			+= FString(TEXT(" Contains no items."));
		GEngine->AddOnScreenDebugMessage(-1, _debugTime, FColor::Magenta, msg);

		return;
	}

	//msg = FString(TEXT("Active buff list for ")) += UKismetSystemLibrary::GetDisplayName(tankRef) += FString(TEXT(" contains ")) += FString::FromInt(tankRef->activeBuffs.Num()) += FString(TEXT(" items."));
	msg = FString(TEXT("Active buff list for "))
		+= UKismetSystemLibrary::GetDisplayName(Cast<AActor>(tankRef.GetObject()))
		+= FString(TEXT(" contains ")) += FString::FromInt(tankRef->GetActiveBuffs().Num())
		+= FString(TEXT(" items."));
	GEngine->AddOnScreenDebugMessage(-1, _debugTime, FColor::Yellow, msg);

	//* Get every buff and print to screen.
	//for(int i = 0; i < tankRef->activeBuffs.Num(); i++){
	for(int i = 0; i < tankRef->GetActiveBuffs().Num(); i++){
		//msg = FString::FromInt(i) += FString(TEXT(" : ")) += UKismetSystemLibrary::GetDisplayName(tankRef->activeBuffs[i]);
		msg = FString::FromInt(i)
			+= FString(TEXT(" : "))
			+= UKismetSystemLibrary::GetDisplayName(tankRef->GetActiveBuffs()[i]);
		GEngine->AddOnScreenDebugMessage(-1, _debugTime, FColor::Cyan, msg);
	}
}

void UBuffComponent::ClearAllTankBuffs(
	TScriptInterface<IBuffableInterface> tankRef,
	bool _debug,
	float _debugTime)
{

	FString msg;

	//* Error checking for null pointers.
	if(tankRef == nullptr){
		msg = FString(TEXT("(BuffComponent::ClearAllTankBuffs) tankRef is not a valid type."));
		GEngine->AddOnScreenDebugMessage(-1, _debugTime, FColor::Red, msg);
		return;
	}


	//* Debug info.
	if(_debug){
		msg = FString(TEXT("Clearing all buffs on "))
			+= UKismetSystemLibrary::GetDisplayName(Cast<AActor>(tankRef.GetObject()));
		GEngine->AddOnScreenDebugMessage(-1, _debugTime, FColor::Yellow, msg);

		//msg = FString(TEXT("Buffs found: ")) += FString::FromInt(tankRef->activeBuffs.Num());
		msg = FString(TEXT("Buffs found: "))
			+= FString::FromInt(tankRef->GetActiveBuffs().Num());
		GEngine->AddOnScreenDebugMessage(-1, _debugTime, FColor::Cyan, msg);
	}

	//* Get every buff and call their Destroy event.
	//int32 buffLen = tankRef->activeBuffs.Num();
	int32 buffLen = tankRef->GetActiveBuffs().Num();
	for(int i = 0; i < buffLen; i++){
		//* All array elements will be shuffled down as they are remove, so we
		//* need to stay at index 0.
		//tankRef->activeBuffs[0]->OnDestroy();
		tankRef->GetActiveBuffs()[0]->OnDestroy();
	}

	// Debug info.
	if(_debug) GEngine->AddOnScreenDebugMessage(-1, _debugTime, FColor::Green, FString(TEXT("Buff clear complete.")));
}

void UBuffComponent::ClearNTankBuffs(
	TScriptInterface<IBuffableInterface> tankRef,
	int32 numToClear,
	bool _debug,
	float _debugTime)
{

	FString msg;

	//* Error checking for null pointers.
	if(tankRef == nullptr){
		msg = FString(TEXT("(BuffComponent::ClearNTankBuffs) tankRef is not a valid type."));
		GEngine->AddOnScreenDebugMessage(-1, _debugTime, FColor::Red, msg);
		return;
	}

	//* Debug info.
	if(_debug){
		//msg = FString(TEXT("Clearing last ")) += FString::FromInt(tankRef->activeBuffs.Num()) += FString(TEXT(" buffs on ")) += UKismetSystemLibrary::GetDisplayName(tankRef);
		msg = FString(TEXT("Clearing last "))
			+= FString::FromInt(tankRef->GetActiveBuffs().Num())
			+= FString(TEXT(" buffs on "))
			+= UKismetSystemLibrary::GetDisplayName(Cast<AActor>(tankRef.GetObject()));
		GEngine->AddOnScreenDebugMessage(-1, _debugTime, FColor::Yellow, msg);

		//msg = FString(TEXT("Total buffs found: ")) += FString::FromInt(tankRef->activeBuffs.Num());
		msg = FString(TEXT("Total buffs found: "))
			+= FString::FromInt(tankRef->GetActiveBuffs().Num());
		GEngine->AddOnScreenDebugMessage(-1, _debugTime, FColor::Cyan, msg);
	}

	//if(numToClear > tankRef->activeBuffs.Num()) numToClear = tankRef->activeBuffs.Num();
	if(numToClear > tankRef->GetActiveBuffs().Num()) numToClear = tankRef->GetActiveBuffs().Num();

	//* Get last N buffs and call their Destroy event.
	//int32 buffLen = tankRef->activeBuffs.Num();
	int32 buffLen = tankRef->GetActiveBuffs().Num();
	for(int i = buffLen; i > buffLen - numToClear; i--){
		GEngine->AddOnScreenDebugMessage(-1, _debugTime, FColor::Yellow, FString(TEXT("Item: ")) += FString::FromInt(i));
		//tankRef->activeBuffs[buffLen - numToClear]->OnDestroy();
		tankRef->GetActiveBuffs()[buffLen - numToClear]->OnDestroy();
	}
}

How the buff component is being created.
ApolloLibrary.cpp
EDIT:

This actually crashes the editor by itself, so this method call has been removed in favor of Add Buff Component

//
UActorComponent* UApolloLibrary::ConstructActorComponent(UClass *actorComponent, class AActor *owner)
{
	UActorComponent *newComp = NewObject<UActorComponent>(owner, actorComponent);

	newComp->RegisterComponent();

	return newComp;
}

The offending interface method call in the player blueprint.

6 posts - 3 participants