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

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.

I found the generated source file where the assert is being thrown.

// PastryPanzerPanic.generated.cpp

TArray<UBuffComponent*> IBuffableInterface::GetActiveBuffs()
	{
		check(0 && "Do not directly call Event functions in Interfaces. Call Execute_GetActiveBuffs instead.");
		BuffableInterface_eventGetActiveBuffs_Parms Parms;
		return Parms.ReturnValue;
	}

So why is this a thing? Am I not calling interface methods correctly?

Okay this post kind of answers what I’m doing wrong.

I had no idea the engine api is so draconian about interfaces (C# ftw) but it seems I have to use an interface call instead of just calling the method defined by the interface. So what’s the difference here in C++? The assert being thrown says to use Execute_MethodName instead, but I have no clue in what context to use that.

I tried replacing every instance of MethodName with Execute_MethodName like the assert says, but it doesn’t compile.

You have to call Execute_ instead of the method itself. In addition to the parameters you specify in the interface, you also need to provide the UObject which is calling the method.

For example, if I had a function in an interface ICoolInterface

bool DoStuff(bool bWorkProperly)

When I need to call the interface:

UObject* SomeObject;
if (SomeObject->GetClass()->ImplementsInterface(UCoolInterface::StaticClass())) // Note I'm using the UInterface here, not the IInterface!
{
    ICoolInterface* SomeObjectInterface = Cast<ICoolInterface>(SomeObject);
    SomeObjectInterface->Execute_DoStuff(SomeObject, true);	
}
2 Likes

This Kinda Works agaionst the fact that we want our Interfaces to be generic(the need to have a reference to the object we want to call from. Does not work when having an Array of Interfaces tbh.) You can also just the TScriptInterface<> Container. It Containes Functions to get the Interface and to get the Object.
So You can just write:

TScriptInterface<IMyRandInterface> Interface = ...;
Interface.GetInterface()->Execute_Foo(Interface.GetObject());
2 Likes