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