K2Node_SwitchObject

Hi, I’ve created a K2Node that switches selected classes to the class of the input object. It works fine in the editor, but in a packaged version it’s a fatal error:

Fatal error!

Unhandled Exception: EXCEPTION_ACCESS_VIOLATION reading address 0x0000000000000020

0x00007ff77c13cce8 MarketPlugins.exe!UObject::execCallMathFunction() []
0x00007ff77c13f90e MarketPlugins.exe!UObject::execLetBool() []
0x00007ff77c1105fb MarketPlugins.exe!ProcessLocalScriptFunction() []
0x00007ff77c0decbd MarketPlugins.exe!ProcessScriptFunction<void (__cdecl*)(UObject * __ptr64,FFrame & __ptr64,void * __ptr64)>() []
0x00007ff77c110099 MarketPlugins.exe!ProcessLocalFunction() []
0x00007ff77c1105fb MarketPlugins.exe!ProcessLocalScriptFunction() []
0x00007ff77c10f801 MarketPlugins.exe!UObject::ProcessInternal() []
0x00007ff77beef2f9 MarketPlugins.exe!UFunction::Invoke() []
0x00007ff77c10ea3e MarketPlugins.exe!UObject::ProcessEvent() []
0x00007ff780194ef5 MarketPlugins.exe!AActor::ProcessEvent() []
0x00007ff780170897 MarketPlugins.exe!AActor::BeginPlay() []
0x00007ff780177cfc MarketPlugins.exe!AActor::DispatchBeginPlay() []
0x00007ff78183c532 MarketPlugins.exe!AWorldSettings::NotifyBeginPlay() []
0x00007ff7809dc431 MarketPlugins.exe!AGameStateBase::HandleBeginPlay() []
0x00007ff781782d82 MarketPlugins.exe!UWorld::BeginPlay() []
0x00007ff781649171 MarketPlugins.exe!UEngine::LoadMap() []
0x00007ff7815cf78d MarketPlugins.exe!UEngine::Browse() []
0x00007ff780971fe2 MarketPlugins.exe!UGameInstance::StartGameInstance() []
0x00007ff780971951 MarketPlugins.exe!UGameEngine::Start() []
0x00007ff779206f56 MarketPlugins.exe!FEngineLoop::Init() []
0x00007ff779226e86 MarketPlugins.exe!GuardedMain() []
0x00007ff77922707a MarketPlugins.exe!GuardedMainWrapper() []
0x00007ff779229f86 MarketPlugins.exe!LaunchWindowsStartup() []
0x00007ff77923ccb4 MarketPlugins.exe!WinMain() []
0x00007ff782bbca7a MarketPlugins.exe!__scrt_common_main_seh() [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288]
0x00007ff9d4c1259d KERNEL32.DLL!UnknownFunction []
0x00007ff9d654af38 ntdll.dll!UnknownFunction []

Here’s my code:

KCHandler_SwitchClass:

#pragma once

#include "BPTerminal.h"
#include "KismetCompiler.h"

class FKCHandler_SwitchClass final : public FNodeHandlingFunctor
{
protected:
	TMap<UEdGraphNode*, FBPTerminal*> BoolTermMap;

public:
	FKCHandler_SwitchClass(FKismetCompilerContext& InCompilerContext) : FNodeHandlingFunctor(InCompilerContext) {}

	virtual void RegisterNets(FKismetFunctionContext& Context, UEdGraphNode* Node) override;

	virtual void RegisterNet(FKismetFunctionContext& Context, UEdGraphPin* Net) override;
	
	// This function is a copy, up to the rewrite of the for (auto PinIt... loop.
	virtual void Compile(FKismetFunctionContext& Context, UEdGraphNode* Node) override;

private:
	FEdGraphPinType ExpectedSelectionPinType;
};
#include "K2Node/KCHandler_SwitchClass.h"
#include "EdGraphUtilities.h"
#include "K2Node/K2Node_SwitchObject.h"

#define LOCTEXT_NAMESPACE "KCHandler_SwitchClass"

void FKCHandler_SwitchClass::RegisterNets(FKismetFunctionContext& Context, UEdGraphNode* Node)
{
	UK2Node_SwitchObject* SwitchNode = Cast<UK2Node_SwitchObject>(Node);
    
	FNodeHandlingFunctor::RegisterNets(Context, Node);
    
	// Create a term to determine if the compare was successful or not
	//@TODO: Ideally we just create one ever, not one per switch
	FBPTerminal* BoolTerm = Context.CreateLocalTerminal();
	BoolTerm->Type.PinCategory = UEdGraphSchema_K2::PC_Boolean;
	BoolTerm->Source = Node;
	BoolTerm->Name = Context.NetNameMap->MakeValidName(Node, TEXT("CmpSuccess"));
	BoolTermMap.Add(Node, BoolTerm);
}

void FKCHandler_SwitchClass::RegisterNet(FKismetFunctionContext& Context, UEdGraphPin* Net)
{
	FBPTerminal* Term = Context.CreateLocalTerminalFromPinAutoChooseScope(Net, Context.NetNameMap->MakeValidName(Net));
	Context.NetMap.Add(Net, Term);
}

void FKCHandler_SwitchClass::Compile(FKismetFunctionContext& Context, UEdGraphNode* Node)
{
	UK2Node_SwitchObject* SwitchNode = CastChecked<UK2Node_SwitchObject>(Node);

	FEdGraphPinType ExpectedExecPinType;
	ExpectedExecPinType.PinCategory = UEdGraphSchema_K2::PC_Exec;

	// Make sure that the input pin is connected and valid for this block
	UEdGraphPin* ExecTriggeringPin = Context.FindRequiredPinByName(SwitchNode, UEdGraphSchema_K2::PN_Execute, EGPD_Input);
	if ((ExecTriggeringPin == NULL) || !Context.ValidatePinType(ExecTriggeringPin, ExpectedExecPinType))
	{
		CompilerContext.MessageLog.Error(*LOCTEXT("NoValidExecutionPinForSwitch_Error", "@@ must have a valid execution pin @@").ToString(), SwitchNode, ExecTriggeringPin);
		return;
	}

	// Make sure that the selection pin is connected and valid for this block
	UEdGraphPin* SelectionPin = SwitchNode->GetSelectionPin();
	if ((SelectionPin == NULL) || !Context.ValidatePinType(SelectionPin, SwitchNode->GetPinType()))
	{
		CompilerContext.MessageLog.Error(*LOCTEXT("NoValidSelectionPinForSwitch_Error", "@@ must have a valid execution pin @@").ToString(), SwitchNode, SelectionPin);
		return;
	}

	// Find the boolean intermediate result term, so we can track whether the compare was successful
	FBPTerminal* BoolTerm = BoolTermMap.FindRef(SwitchNode);

	// Generate the output impulse from this node
	UEdGraphPin* SwitchSelectionNet = FEdGraphUtilities::GetNetFromPin(SelectionPin);
	FBPTerminal* SwitchSelectionTerm = Context.NetMap.FindRef(SwitchSelectionNet);

	if ((BoolTerm != NULL) && (SwitchSelectionTerm != NULL))
	{
		UEdGraphPin* FuncPin = SwitchNode->GetFunctionPin();
		FBPTerminal* FuncContext = Context.NetMap.FindRef(FuncPin);
		UEdGraphPin* DefaultPin = SwitchNode->GetDefaultPin();
			
		// We don't need to generate if checks if there are no connections to it if there is no default pin or if the default pin is not linked 
		// If there is a default pin that is linked then it would fall through to that default if we do not generate the cases
		const bool bCanSkipUnlinkedCase = (DefaultPin == nullptr || DefaultPin->LinkedTo.Num() == 0);

		// Pull out function to use
		const UClass* FuncClass = Cast<UClass>(FuncPin->PinType.PinSubCategoryObject.Get());
		UFunction* FunctionPtr = FindUField<UFunction>(FuncClass, FuncPin->PinName);
		check(FunctionPtr);

		// Run thru all the output pins except for the default label
		for(UEdGraphPin* Pin : SwitchNode->Pins)
		{
			if ((Pin->Direction == EGPD_Output) && (Pin != DefaultPin) && (!bCanSkipUnlinkedCase || Pin->LinkedTo.Num() > 0))
			{
				TSubclassOf<UObject> ClassType = SwitchNode->GetExportClassForPin(Pin);
					
				if(ClassType->IsValidLowLevel())
				{
					// Create a term for the switch case value
					FBPTerminal* CaseValueTerm = new FBPTerminal();
					Context.Literals.Add(CaseValueTerm);
					CaseValueTerm->Name = SwitchNode->GetExportTextForPin(Pin);
					CaseValueTerm->Type = FEdGraphPinType(UEdGraphSchema_K2::PC_Class, TEXT(""), UObject::StaticClass(),EPinContainerType::None, false, FEdGraphTerminalType());
					CaseValueTerm->SourcePin = Pin;
					CaseValueTerm->bIsLiteral = true;
					CaseValueTerm->ObjectLiteral = *ClassType;

					// Call the comparison function associated with this switch node
					FBlueprintCompiledStatement& Statement = Context.AppendStatementForNode(SwitchNode);
					Statement.Type = KCST_CallFunction;
					Statement.FunctionToCall = FunctionPtr;
					Statement.FunctionContext = FuncContext;
					Statement.bIsParentContext = false;

					Statement.LHS = BoolTerm;
					Statement.RHS.Add(SwitchSelectionTerm);
					Statement.RHS.Add(CaseValueTerm);

					// Jump to output if strings are actually equal
					FBlueprintCompiledStatement& IfFailTest_SucceedAtBeingEqualGoto = Context.AppendStatementForNode(SwitchNode);
					IfFailTest_SucceedAtBeingEqualGoto.Type = KCST_GotoIfNot;
					IfFailTest_SucceedAtBeingEqualGoto.LHS = BoolTerm;

					Context.GotoFixupRequestMap.Add(&IfFailTest_SucceedAtBeingEqualGoto, Pin);
				}
			}
		}

		// Finally output default pin
		GenerateSimpleThenGoto(Context, *SwitchNode, DefaultPin);
	}
	else
	{
		CompilerContext.MessageLog.Error(*LOCTEXT("ResolveTermPassed_Error", "Failed to resolve term passed into @@").ToString(), SelectionPin);
	}
}

#undef LOCTEXT_NAMESPACE

K2Node_SwitchObject:

#pragma once

#include "Containers/Array.h"
#include "CoreMinimal.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h"
#include "HAL/Platform.h"
#include "Internationalization/Text.h"
#include "K2Node_Switch.h"
#include "UObject/NameTypes.h"
#include "UObject/ObjectMacros.h"
#include "UObject/UObjectGlobals.h"
#include "EdGraph/EdGraphPin.h"
#include "K2Node_SwitchObject.generated.h"


UCLASS()
class ENHANCEDBLUEPRINTSUNCOOKEDONLY_API UK2Node_SwitchObject : public UK2Node_Switch
{
	GENERATED_BODY()

protected:
	UPROPERTY()
	FName SelectionPinType;
	
public:
	UK2Node_SwitchObject(const FObjectInitializer& ObjectInitializer);

	UPROPERTY(EditAnywhere, Category = PinOptions)
	TSet<TSubclassOf<UObject>> PinClasses;

	UPROPERTY()
	TMap<FName, TSubclassOf<UObject>> PinsClassMap;

	TArray<FName> PinNames;

	UFUNCTION()
	static bool NotEqual_ObjectClass(UObject* A, TSubclassOf<UObject> B);

	// UObject interface
	virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
	// End of UObject interface

	// UEdGraphNode interface
	virtual FText GetTooltipText() const override;
	virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
	virtual bool ShouldShowNodeProperties() const override { return true; }
	// End of UEdGraphNode interface

	// UK2Node interface
	virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override;
	// End of UK2Node interface

	// UK2Node_Switch Interface
	virtual void AddPinToSwitchNode() override;
	virtual FName GetUniquePinName() override;
	virtual FEdGraphPinType GetPinType() const override;
	// End of UK2Node_Switch Interface

	virtual FName GetPinNameGivenIndex(int32 Index) const override;

	TSubclassOf<UObject> GetExportClassForPin(const UEdGraphPin* Pin) const;

	virtual FNodeHandlingFunctor* CreateNodeHandler(FKismetCompilerContext& CompilerContext) const override;

protected:
	virtual void CreateSelectionPin() override;
	virtual void CreateCasePins() override;
	virtual void RemovePin(UEdGraphPin* TargetPin) override;
};
#include "K2Node/K2Node_SwitchObject.h"
#include "BlueprintActionDatabaseRegistrar.h"
#include "BlueprintNodeSpawner.h"
#include "Containers/UnrealString.h"
#include "EdGraph/EdGraph.h"
#include "EdGraphSchema_K2.h"
#include "HAL/PlatformCrt.h"
#include "Internationalization/Internationalization.h"
#include "K2Node/KCHandler_SwitchClass.h"
#include "Misc/AssertionMacros.h"
#include "Templates/SubclassOf.h"
#include "UObject/Class.h"
#include "UObject/UnrealNames.h"
#include "UObject/UnrealType.h"


UK2Node_SwitchObject::UK2Node_SwitchObject(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	FunctionName = TEXT("NotEqual_ObjectClass");
	FunctionClass = UK2Node_SwitchObject::StaticClass();
	SelectionPinType = UEdGraphSchema_K2::PC_Object;
}

bool UK2Node_SwitchObject::NotEqual_ObjectClass(UObject* A, TSubclassOf<UObject> B)
{
	if(A)
	{
		return A->GetClass() != B;
	}
	return false;
}

void UK2Node_SwitchObject::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
	bool bIsDirty = false;
	FName PropertyName = (PropertyChangedEvent.Property != NULL) ? PropertyChangedEvent.Property->GetFName() : NAME_None;
	if (PropertyName == TEXT("PinClasses"))
	{
		bIsDirty = true;
	}

	if (bIsDirty)
	{
		ReconstructNode();
		GetGraph()->NotifyGraphChanged();
	}
	Super::PostEditChangeProperty(PropertyChangedEvent);
}

FText UK2Node_SwitchObject::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
	return FText::FromString("Switch on Object");
}

FText UK2Node_SwitchObject::GetTooltipText() const
{
	return FText::FromString("Selects an output that matches the input value's class");
}

void UK2Node_SwitchObject::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const
{
	// actions get registered under specific object-keys; the idea is that 
	// actions might have to be updated (or deleted) if their object-key is  
	// mutated (or removed)... here we use the node's class (so if the node 
	// type disappears, then the action should go with it)
	UClass* ActionKey = GetClass();
	// to keep from needlessly instantiating a UBlueprintNodeSpawner, first   
	// check to make sure that the registrar is looking for actions of this type
	// (could be regenerating actions for a specific asset, and therefore the 
	// registrar would only accept actions corresponding to that asset)
	if (ActionRegistrar.IsOpenForRegistration(ActionKey))
	{
		UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass());
		check(NodeSpawner != nullptr);

		ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner);
	}
}

void UK2Node_SwitchObject::CreateSelectionPin()
{
	UEdGraphPin* Pin = CreatePin(EGPD_Input, SelectionPinType, UObject::StaticClass(), TEXT("Selection"));
	GetDefault<UEdGraphSchema_K2>()->SetPinAutogeneratedDefaultValueBasedOnType(Pin);
}

FEdGraphPinType UK2Node_SwitchObject::GetPinType() const 
{ 
	FEdGraphPinType PinType;
	PinType.PinCategory = SelectionPinType;
	PinType.PinSubCategoryObject = UObject::StaticClass();
	return PinType;
}

FName UK2Node_SwitchObject::GetPinNameGivenIndex(int32 Index) const
{
	check(Index);
	return PinNames[Index];
}

TSubclassOf<UObject> UK2Node_SwitchObject::GetExportClassForPin(const UEdGraphPin* Pin) const
{
	if(Pin)	
	{
		if(PinsClassMap.Contains(Pin->GetFName()))
		{
			return PinsClassMap[Pin->GetFName()];
		}
	}
	return nullptr;
}

FNodeHandlingFunctor* UK2Node_SwitchObject::CreateNodeHandler(FKismetCompilerContext& CompilerContext) const
{
	return new FKCHandler_SwitchClass(CompilerContext);
}

void UK2Node_SwitchObject::CreateCasePins()
{
	for(const TSubclassOf<UObject> PinClass : PinClasses)
	{
		if(PinClass->IsValidLowLevel())
		{
			const UEdGraphPin* Pin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, PinClass->GetFName());
			PinsClassMap.Add(Pin->GetFName(), PinClass);
			PinNames.Add(Pin->GetFName());
		}
		else
		{
			const FName PinName = GetUniquePinName();
			PinNames.Add(PinName);
			UEdGraphPin* Pin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, PinName);
		}
	}
}

FName UK2Node_SwitchObject::GetUniquePinName()
{
	FName NewPinName;
	int32 Index = 0;
	while (true)
	{
		NewPinName =  *FString::Printf(TEXT("Case_%d"), Index++);
		if (!FindPin(NewPinName))
		{
			break;
		}
	}
	return NewPinName;
}

void UK2Node_SwitchObject::AddPinToSwitchNode()
{
	PinClasses.Add(nullptr);
	ReconstructNode();
}

void UK2Node_SwitchObject::RemovePin(UEdGraphPin* TargetPin)
{
	checkSlow(TargetPin);

	// Clean-up pin name array
	if(PinsClassMap.Contains(TargetPin->PinName))
	{
		PinClasses.Remove(PinsClassMap[TargetPin->PinName]);
	}
	else
	{
		PinClasses.Remove(nullptr);
	}
	PinsClassMap.Remove(TargetPin->PinName);
	PinNames.Remove(TargetPin->PinName);
}

Does anyone have any idea what the problem is?
Thanks in advance

Hello.

Looks like you’re having a null pointer exception.
In two different places, you are calling Class->IsValidLowLevel(), but that code itself can crash with a null pointer exception if the Class object itself is null.

You can either change those checks to use IsValid(Class) (which internally handles the case of Class being nullptr), or change your condition to if(Class && Class->IsValidLowLevel()).

Any of those two options should avoid a null pointer crash.

Hi, thanks for answer. I changed Class->IsValidLowLevel() to IsValid(Class) but unfortunately the fatal error still occurs :confused:

You can’t have the function that gets called through FunctionName and FunctionClass be within your uncooked module. Since that module’s not in cooked, that function won’t exist in the cooked build when you run the game.

You’ll need to put it in a function library or some other class in a Runtime module.

■■■■, I’m so stupid ^^’ Thanks you!