Files
UnrealEngineUWP/Engine/Source/Editor/BlueprintGraph/Private/K2Node_PromotableOperator.cpp
ben hoffman 8488e2d403 Fix a crash when you connect a wildcard pin that has no owning node on it to a promotable operator Equals node. The function would hit a "Check" inside of the "GetOwningNode" function when dragging off of an "Enum Select" node.
Also added an ensure to make sure that the function doesn't crash when evaluating pin changes. This wasn't happening,  but it is preventative just in case.


#jira UE-146547
#rb phillip.kavan
#preflight 6239e3e0ec68595f3b85d4f5
#rnx

[CL 19467076 by ben hoffman in ue5-main branch]
2022-03-22 11:31:01 -04:00

1389 lines
46 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "K2Node_PromotableOperator.h"
#include "BlueprintTypePromotion.h"
#include "EdGraphSchema_K2.h"
#include "EdGraphUtilities.h"
#include "K2Node_CommutativeAssociativeBinaryOperator.h"
#include "KismetCompiler.h"
#include "ScopedTransaction.h"
#include "ToolMenus.h"
#include "Framework/Commands/UIAction.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/WildcardNodeUtils.h"
#include "Kismet2/CompilerResultsLog.h"
#define LOCTEXT_NAMESPACE "PromotableOperatorNode"
///////////////////////////////////////////////////////////
// Pin names for default construction
static const FName InputPinA_Name = FName(TEXT("A"));
static const FName InputPinB_Name = FName(TEXT("B"));
static const FName TolerancePin_Name = FName(TEXT("ErrorTolerance"));
static const int32 NumFunctionInputs = 2;
///////////////////////////////////////////////////////////
// UK2Node_PromotableOperator
UK2Node_PromotableOperator::UK2Node_PromotableOperator(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
UpdateOpName();
OrphanedPinSaveMode = ESaveOrphanPinMode::SaveAllButExec;
NumAdditionalInputs = 0;
}
///////////////////////////////////////////////////////////
// UEdGraphNode interface
namespace PromotableOpUtils
{
bool FindTolerancePinType(const UFunction* Func, FEdGraphPinType& OutPinType)
{
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
// Check if there is a tolerance field that we need to add
// If UFunction has a third input param
int32 InputArgsFound = 0;
for (TFieldIterator<FProperty> PropIt(Func); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt)
{
const FProperty* Param = *PropIt;
// We don't care about the return property, and we are looking for
// any additional input param that isn't in the normal function input range
if (Param->HasAnyPropertyFlags(CPF_ReturnParm) || (InputArgsFound++ < NumFunctionInputs))
{
continue;
}
// Found the tolerance type!
FEdGraphPinType ParamType;
if (Schema->ConvertPropertyToPinType(Param, /* out */ ParamType))
{
OutPinType = ParamType;
return true;
}
}
return false;
}
}
void UK2Node_PromotableOperator::AllocateDefaultPins()
{
FWildcardNodeUtils::CreateWildcardPin(this, InputPinA_Name, EGPD_Input);
FWildcardNodeUtils::CreateWildcardPin(this, InputPinB_Name, EGPD_Input);
UEdGraphPin* OutPin = FWildcardNodeUtils::CreateWildcardPin(this, UEdGraphSchema_K2::PN_ReturnValue, EGPD_Output);
const UFunction* Func = GetTargetFunction();
// For comparison functions we always want a bool output, so make it visually so
if (Func && FTypePromotion::IsComparisonFunc(Func))
{
OutPin->PinType.PinCategory = UEdGraphSchema_K2::PC_Boolean;
// If we need a tolerance pin then we need to add it here, but not if we are in a wildcard state
FEdGraphPinType ToleranceType;
if (PromotableOpUtils::FindTolerancePinType(Func, ToleranceType))
{
UEdGraphPin* TolerancePin = CreatePin(EGPD_Input, ToleranceType, TolerancePin_Name);
}
}
// Update the op name so that if there is a blank wildcard node left on the graph we
// can ensure that it is correct
UpdateOpName();
ensureMsgf((OperationName != TEXT("NO_OP")), TEXT("Invalid operation name on Promotable Operator node!"));
// Create any additional input pin. Their appropriate type is determined in ReallocatePinsDuringReconstruction
// because we cannot get a promoted type with no links to the pin.
for (int32 i = NumFunctionInputs; i < (NumAdditionalInputs + NumFunctionInputs); ++i)
{
AddInputPinImpl(i);
}
}
void UK2Node_PromotableOperator::GetNodeContextMenuActions(UToolMenu* Menu, UGraphNodeContextMenuContext* Context) const
{
Super::GetNodeContextMenuActions(Menu, Context);
static const FName PromotableOperatorNodeName = FName("PromotableOperator");
static const FText PromotableOperatorStr = LOCTEXT("PromotableOperatorNode", "Operator Node");
// Add the option to remove a pin via the context menu
if (CanRemovePin(Context->Pin))
{
FToolMenuSection& Section = Menu->AddSection(PromotableOperatorNodeName, PromotableOperatorStr);
Section.AddMenuEntry(
"RemovePin",
LOCTEXT("RemovePin", "Remove pin"),
LOCTEXT("RemovePinTooltip", "Remove this input pin"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateUObject(const_cast<UK2Node_PromotableOperator*>(this), &UK2Node_PromotableOperator::RemoveInputPin, const_cast<UEdGraphPin*>(Context->Pin))
)
);
}
else if (CanAddPin())
{
FToolMenuSection& Section = Menu->AddSection(PromotableOperatorNodeName, PromotableOperatorStr);
Section.AddMenuEntry(
"AddPin",
LOCTEXT("AddPin", "Add pin"),
LOCTEXT("AddPinTooltip", "Add another input pin"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateUObject(const_cast<UK2Node_PromotableOperator*>(this), &UK2Node_PromotableOperator::AddInputPin)
)
);
}
// Add the pin conversion sub menu
if (CanConvertPinType(Context->Pin))
{
// Give the user an option to reset this node to wildcard
if (!FWildcardNodeUtils::IsWildcardPin(Context->Pin))
{
static const FName ResetWildcardName = FName("PromotableOperatorResetWildcardPinConvs");
static const FText ResetWildcardStr = LOCTEXT("PromotableOperatorResetWildcardPinConvs", "Reset To Wildcard");
FToolMenuSection& Section = Menu->AddSection(ResetWildcardName, ResetWildcardStr);
const FText ResetName = LOCTEXT("ResetFunction_Tooltip", "Reset this node to wildcard");
Section.AddMenuEntry(
FName(ResetName.ToString()),
ResetName,
LOCTEXT("ResetToWildcardTooltip", "Reset this node to a wildcard state."),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateUObject(const_cast<UK2Node_PromotableOperator*>(this), &UK2Node_PromotableOperator::ConvertPinType, const_cast<UEdGraphPin*>(Context->Pin), FWildcardNodeUtils::GetDefaultWildcardPinType())
)
);
}
FToolMenuSection& Section = Menu->AddSection("K2NodePromoOpConversionGraphNode");
Section.AddSubMenu(
"ConvertPin",
LOCTEXT("ConvertPin", "Convert Pin..."),
LOCTEXT("ConvertPinTooltip", "Convert the selected pin to another type"),
FNewToolMenuDelegate::CreateUObject(this, &UK2Node_PromotableOperator::CreateConversionSubMenu, (UEdGraphPin*)Context->Pin)
);
}
}
void UK2Node_PromotableOperator::CreateConversionSubMenu(UToolMenu* Menu, UEdGraphPin* ContextPin) const
{
check(ContextPin);
static const FName ConvNodeName = FName("PromotableOperatorPinConvs");
static const FText ConvNodeStr = LOCTEXT("PromotableOperatorPinConvs", "Pin Conversions");
// Gather what pin types could possibly be used for a conversion with this operator
TArray<UFunction*> AvailableFunctions;
FTypePromotion::GetAllFuncsForOp(OperationName, AvailableFunctions);
TArray<FEdGraphPinType> PossiblePromos;
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
// If this is a split pin, then we need to convert the parent pin, not the child.
if (ContextPin->ParentPin != nullptr)
{
ContextPin = ContextPin->ParentPin;
}
// If we have a pin that matches our current type, then we can use it to see if we can still get a valid function
FEdGraphPinType OriginalContextType = ContextPin->PinType;
for (const UFunction* Func : AvailableFunctions)
{
for (TFieldIterator<FProperty> PropIt(Func); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt)
{
const FProperty* Param = *PropIt;
FEdGraphPinType ParamType;
if (Schema->ConvertPropertyToPinType(Param, /* out */ ParamType))
{
if (FWildcardNodeUtils::IsWildcardPin(ContextPin) || FTypePromotion::IsValidPromotion(ParamType, ContextPin->PinType) || FTypePromotion::IsValidPromotion(ContextPin->PinType, ParamType))
{
PossiblePromos.AddUnique(ParamType);
}
}
}
}
// Don't display the conversion menu if there are no valid conversions
if (AvailableFunctions.Num() == 0)
{
return;
}
// Add the options to the context menu
for (const FEdGraphPinType& PinType : PossiblePromos)
{
FToolMenuSection& Section = Menu->AddSection(ConvNodeName, ConvNodeStr);
FFormatNamedArguments Args;
Args.Add(TEXT("PinType"), Schema->TypeToText(PinType));
const FText PinConversionName = FText::Format(LOCTEXT("CallFunction_Tooltip", "{PinType}"), Args);
Section.AddMenuEntry(
FName(PinConversionName.ToString()),
PinConversionName,
LOCTEXT("ConvertPinTypeTooltip", "Convert this pin type to the selected type"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateUObject(const_cast<UK2Node_PromotableOperator*>(this), &UK2Node_PromotableOperator::ConvertPinType, const_cast<UEdGraphPin*>(ContextPin), PinType)
)
);
}
}
bool UK2Node_PromotableOperator::CanConvertPinType(const UEdGraphPin* Pin) const
{
// You can convert any pin except for output pins on comparison functions and the tolerance pin
return
Pin &&
!(Pin->Direction == EGPD_Output &&
FTypePromotion::IsComparisonFunc(GetTargetFunction())) &&
!IsTolerancePin(Pin);
}
FText UK2Node_PromotableOperator::GetTooltipText() const
{
// If there are no connections then just display the op name
if (!HasAnyConnectionsOrDefaults())
{
return FTypePromotion::GetUserFacingOperatorName(OperationName);
}
// Otherwise use the default one (a more specific function tooltip)
return Super::GetTooltipText();
}
void UK2Node_PromotableOperator::PinDefaultValueChanged(UEdGraphPin* Pin)
{
Super::PinDefaultValueChanged(Pin);
if (bDefaultValueReentranceGuard)
{
return;
}
// Re-entrance Guard just in case this function gets called from any notify triggers in the schema
// to prevent possible recursive calls from ResetPinToAutogeneratedDefaultValue when breaking
// all links to this node
TGuardValue<bool> ReentranceGuard(bDefaultValueReentranceGuard, true);
// If this default value resets to the default one on the pin, and there are no other
// connections or default values, then we should just reset the whole node to a wildcard
if (!HasAnyConnectionsOrDefaults())
{
ResetNodeToWildcard();
}
}
void UK2Node_PromotableOperator::NodeConnectionListChanged()
{
Super::NodeConnectionListChanged();
// This will handle the case of dragging off of this node, and connecting to a node via typing
// in the context menu. Without updating in this case, our pins would be left as wildcards!
if(HasAnyConnectionsOrDefaults())
{
UpdateOpName();
UpdateFromBestMatchingFunction();
// Get correct default value boxes
GetGraph()->NotifyGraphChanged();
}
}
void UK2Node_PromotableOperator::PostPasteNode()
{
Super::PostPasteNode();
// If we have copied a node with additional pins then we need to make sure
// they get reset to wildcard as well, otherwise their type will persist
if (!HasAnyConnectionsOrDefaults())
{
ResetNodeToWildcard();
}
}
///////////////////////////////////////////////////////////
// UK2Node interface
void UK2Node_PromotableOperator::ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
{
Super::ExpandNode(CompilerContext, SourceGraph);
const bool bValidOpName = UpdateOpName();
if (!bValidOpName)
{
UE_LOG(LogBlueprint, Error, TEXT("Could not find matching operation name for this function!"));
CompilerContext.MessageLog.Error(TEXT("Could not find matching operation on '@@'!"), this);
return;
}
UEdGraphPin* OriginalOutputPin = GetOutputPin();
TArray<UEdGraphPin*> OriginalInputPins = GetInputPins();
// Our operator function has been determined on pin connection change
UFunction* OpFunction = GetTargetFunction();
if (!OpFunction)
{
UE_LOG(LogBlueprint, Error, TEXT("Could not find matching op function during expansion!"));
CompilerContext.MessageLog.Error(TEXT("Could not find matching op function during expansion on '@@'!"), this);
return;
}
const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema();
/** Helper struct to gather the necessary pins we need to create redirections */
struct FIntermediateCastPinHelper
{
UEdGraphPin* InputA = nullptr;
UEdGraphPin* InputB = nullptr;
UEdGraphPin* OutputPin = nullptr;
UEdGraphPin* SelfPin = nullptr;
UEdGraphPin* TolerancePin = nullptr;
explicit FIntermediateCastPinHelper(UK2Node_CallFunction* NewOperator)
{
check(NewOperator);
SelfPin = NewOperator->FindPin(UEdGraphSchema_K2::PN_Self);
TolerancePin = NewOperator->FindPin(TolerancePin_Name, EGPD_Input);
// Find inputs and outputs
for (UEdGraphPin* Pin : NewOperator->Pins)
{
if (Pin == SelfPin || Pin == TolerancePin)
{
continue;
}
if (Pin->Direction == EEdGraphPinDirection::EGPD_Input)
{
if (!InputA)
{
InputA = Pin;
}
else if (!InputB)
{
InputB = Pin;
}
}
else if (Pin->Direction == EEdGraphPinDirection::EGPD_Output)
{
OutputPin = Pin;
}
}
}
~FIntermediateCastPinHelper() = default;
};
UK2Node_CallFunction* PrevIntermediateNode = nullptr;
UEdGraphPin* PrevOutputPin = nullptr;
UEdGraphPin* MyTolerancePin = FindTolerancePin();
// Create cast from original 2 inputs to the first intermediate node
{
UFunction* BestFunc = OpFunction;
// Look for a best matching function if we possibly don't have one
if(!BestFunc)
{
TArray<UEdGraphPin*> PinsToConsider =
{
OriginalInputPins[0],
OriginalInputPins[1],
OriginalOutputPin
};
if (UFunction* Func = FTypePromotion::FindBestMatchingFunc(OperationName, PinsToConsider))
{
BestFunc = Func;
}
}
PrevIntermediateNode = CreateIntermediateNode(this, BestFunc, CompilerContext, SourceGraph);
FIntermediateCastPinHelper NewOpHelper(PrevIntermediateNode);
PrevOutputPin = PrevIntermediateNode->FindPin(UEdGraphSchema_K2::PN_ReturnValue, EGPD_Output);
const bool bPinASuccess = UK2Node_PromotableOperator::CreateIntermediateCast(this, CompilerContext, SourceGraph, OriginalInputPins[0], NewOpHelper.InputA);
const bool bPinBSuccess = UK2Node_PromotableOperator::CreateIntermediateCast(this, CompilerContext, SourceGraph, OriginalInputPins[1], NewOpHelper.InputB);
// Attempt to connect the tolerance pin if both nodes have one
const bool bToleranceSuccess = MyTolerancePin && NewOpHelper.TolerancePin ? UK2Node_PromotableOperator::CreateIntermediateCast(this, CompilerContext, SourceGraph, MyTolerancePin, NewOpHelper.TolerancePin) : true;
if (!bPinASuccess || !bPinBSuccess || !bToleranceSuccess)
{
CompilerContext.MessageLog.Error(TEXT("'@@' could not successfuly expand pins!"), PrevIntermediateNode);
}
}
// Loop through all the additional inputs, create a new node of this function and connecting inputs as necessary
for (int32 i = NumFunctionInputs; i < NumAdditionalInputs + NumFunctionInputs; ++i)
{
check(i > 0 && i < OriginalInputPins.Num());
FIntermediateCastPinHelper PrevNodeHelper(PrevIntermediateNode);
// Find the best matching function that this intermediate node should use
// so that we can avoid unnecessary conversion nodes and casts
UFunction* BestMatchingFunc = OpFunction;
{
TArray<UEdGraphPin*> PinsToConsider =
{
PrevNodeHelper.OutputPin,
OriginalInputPins[i],
OriginalOutputPin
};
if (UFunction* Func = FTypePromotion::FindBestMatchingFunc(OperationName, PinsToConsider))
{
BestMatchingFunc = Func;
}
}
UK2Node_CallFunction* NewIntermediateNode = CreateIntermediateNode(PrevIntermediateNode, BestMatchingFunc, CompilerContext, SourceGraph);
FIntermediateCastPinHelper NewOpHelper(NewIntermediateNode);
// Connect the output pin of the previous intermediate node, to the input of the new one
const bool bPinASuccess = CreateIntermediateCast(PrevIntermediateNode, CompilerContext, SourceGraph, NewOpHelper.InputA, PrevOutputPin);
// Connect the original node's pin to the newly created intermediate node's B Pin
const bool bPinBSuccess = CreateIntermediateCast(this, CompilerContext, SourceGraph, OriginalInputPins[i], NewOpHelper.InputB);
// Make a connection to a tolerance pin if both nodes have one
const bool bToleranceSuccess = MyTolerancePin && NewOpHelper.TolerancePin ? UK2Node_PromotableOperator::CreateIntermediateCast(this, CompilerContext, SourceGraph, MyTolerancePin, NewOpHelper.TolerancePin) : true;
if (!bPinASuccess || !bPinBSuccess || !bToleranceSuccess)
{
CompilerContext.MessageLog.Error(TEXT("'@@' could not successfuly expand additional pins!"), PrevIntermediateNode);
}
// Track what the previous node is so that we can connect it's output appropriately
PrevOutputPin = NewOpHelper.OutputPin;
PrevIntermediateNode = NewIntermediateNode;
}
// Make the final output connection that we need
if (OriginalOutputPin && PrevOutputPin)
{
CompilerContext.MovePinLinksToIntermediate(*OriginalOutputPin, *PrevOutputPin);
// If there is no link to the output pin then the connection response called for a conversion node,
// but one was not available. This can occur when there is a connection to the output pin that
// is smaller than an input and cannot be casted. We throw a compiler error instead of auto-breaking
// pins because that can be confusing to the user.
if (PrevOutputPin->LinkedTo.Num() == 0)
{
CompilerContext.MessageLog.Error(
*FText::Format(LOCTEXT("FailedOutputConnection_ErrorFmt",
"Output pin type '{1}' is not compatible with input type of '{0}' on '@@'"),
Schema->TypeToText(PrevOutputPin->PinType),
Schema->TypeToText(OriginalOutputPin->PinType)).ToString(),
PrevIntermediateNode
);
}
}
}
void UK2Node_PromotableOperator::NotifyPinConnectionListChanged(UEdGraphPin* ChangedPin)
{
Super::NotifyPinConnectionListChanged(ChangedPin);
EvaluatePinsFromChange(ChangedPin);
}
void UK2Node_PromotableOperator::PostReconstructNode()
{
Super::PostReconstructNode();
// We only need to set the function if we have connections, otherwise we should stick in a wildcard state
if (HasAnyConnectionsOrDefaults())
{
if (UEdGraphPin* TolerancePin = FindTolerancePin())
{
FEdGraphPinType ToleranceType;
TolerancePin->bHidden = !PromotableOpUtils::FindTolerancePinType(GetTargetFunction(), ToleranceType);
}
// Allocate default pins will have been called before this, which means we are reset to wildcard state
// We need to Update the pins to be the proper function again
UpdatePinsFromFunction(GetTargetFunction());
for (UEdGraphPin* AddPin : Pins)
{
if (IsAdditionalPin(AddPin) && AddPin->LinkedTo.Num() > 0)
{
FEdGraphPinType TypeToSet = FTypePromotion::GetPromotedType(AddPin->LinkedTo);
AddPin->PinType = TypeToSet;
}
}
}
else if (UEdGraphPin* TolerancePin = FindTolerancePin())
{
TolerancePin->bHidden = true;
}
}
bool UK2Node_PromotableOperator::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const
{
check(MyPin && OtherPin);
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
// Container types cannot be promotable operators as they have their own special case wildcard propagation
if (OtherPin->PinType.IsContainer())
{
OutReason = LOCTEXT("NoExecPinsAllowed", "Promotable Operator nodes cannot have containers or references.").ToString();
return true;
}
// The output pin on comparison operators is always a boolean, and cannot have its type changed
// so we need to just treat it normally as a regular K2CallFunction node would
else if (MyPin == GetOutputPin() && FTypePromotion::IsComparisonFunc(GetTargetFunction()) && OtherPin->PinType.PinCategory != UEdGraphSchema_K2::PC_Boolean)
{
return Super::IsConnectionDisallowed(MyPin, OtherPin, OutReason);
}
// If the pins are the same type then there is no reason to check for a promotion
else if (MyPin->PinType == OtherPin->PinType)
{
return Super::IsConnectionDisallowed(MyPin, OtherPin, OutReason);
}
// Enums need to be casted to a byte manually before we can do math with them, like in C++
else if (!FWildcardNodeUtils::IsWildcardPin(MyPin) && MyPin->Direction == EGPD_Input && OtherPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Byte && OtherPin->PinType.PinSubCategoryObject != nullptr)
{
FFormatNamedArguments Args;
Args.Add(TEXT("OtherPinType"), K2Schema->TypeToText(OtherPin->PinType));
OutReason = FText::Format(LOCTEXT("NoCompatibleEnumConv", "'{OtherPinType}' must be converted to a numeric type before being connected"), Args).ToString();
return true;
}
else if (FWildcardNodeUtils::IsWildcardPin(MyPin) && !FWildcardNodeUtils::IsWildcardPin(OtherPin))
{
TArray<UEdGraphPin*> PinsToConsider;
PinsToConsider.Add(const_cast<UEdGraphPin*>(OtherPin));
if (!FTypePromotion::FindBestMatchingFunc(OperationName, PinsToConsider))
{
FFormatNamedArguments Args;
Args.Add(TEXT("OtherPinType"), K2Schema->TypeToText(OtherPin->PinType));
Args.Add(TEXT("OpName"), FText::FromName(OperationName));
const TSet<FName>& DenyList = GetDefault<UBlueprintEditorSettings>()->TypePromotionPinDenyList;
if (DenyList.Contains(OtherPin->PinType.PinCategory))
{
OutReason = FText::Format(LOCTEXT("NoCompatibleStructConv_Denied", "No matching '{OpName}' function for '{OtherPinType}'. This type is listed as denied by TypePromotionPinDenyList in the blueprint editor settings."), Args).ToString();
}
else
{
OutReason = FText::Format(LOCTEXT("NoCompatibleStructConv", "No matching '{OpName}' function for '{OtherPinType}'"), Args).ToString();
}
return true;
}
}
const bool bHasStructPin = MyPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Struct || OtherPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Struct;
// If the other pin can be promoted to my pin type, than allow the connection
if (FTypePromotion::IsValidPromotion(OtherPin->PinType, MyPin->PinType))
{
if (bHasStructPin)
{
// Compare the directions
const UEdGraphPin* InputPin = nullptr;
const UEdGraphPin* OutputPin = nullptr;
if (!K2Schema->CategorizePinsByDirection(MyPin, OtherPin, /*out*/ InputPin, /*out*/ OutputPin))
{
OutReason = LOCTEXT("DirectionsIncompatible", "Pin directions are not compatible!").ToString();
return true;
}
if (!FTypePromotion::HasStructConversion(InputPin, OutputPin))
{
FFormatNamedArguments Args;
Args.Add(TEXT("MyPinType"), K2Schema->TypeToText(MyPin->PinType));
Args.Add(TEXT("OtherPinType"), K2Schema->TypeToText(OtherPin->PinType));
OutReason = FText::Format(LOCTEXT("NoCompatibleOperatorConv", "No compatible operator functions between '{MyPinType}' and '{OtherPinType}'"), Args).ToString();
return true;
}
}
return false;
}
return Super::IsConnectionDisallowed(MyPin, OtherPin, OutReason);
}
void UK2Node_PromotableOperator::ReallocatePinsDuringReconstruction(TArray<UEdGraphPin*>& OldPins)
{
Super::ReallocatePinsDuringReconstruction(OldPins);
// We need to fix up any additional pins that may have been created as a wildcard pin
int32 AdditionalPinsFixed = 0;
// Additional Pin creation here? Check for orphan pins here and see if we can re-create them
for (UEdGraphPin* OldPin : OldPins)
{
if (IsAdditionalPin(OldPin))
{
if (UEdGraphPin* AddPin = GetAdditionalPin(AdditionalPinsFixed + NumFunctionInputs))
{
AddPin->PinType = OldPin->PinType;
AddPin->DefaultValue = OldPin->DefaultValue;
++AdditionalPinsFixed;
}
}
// Copy the default value and pin type for pins without any links to preseve it
UEdGraphPin* CurrentPin = FindPin(OldPin->GetFName(), OldPin->Direction);
if(CurrentPin && OldPin->LinkedTo.Num() == 0 && !OldPin->DoesDefaultValueMatchAutogenerated())
{
CurrentPin->PinType = OldPin->PinType;
CurrentPin->DefaultValue = OldPin->DefaultValue;
}
}
}
void UK2Node_PromotableOperator::AutowireNewNode(UEdGraphPin* ChangedPin)
{
Super::AutowireNewNode(ChangedPin);
EvaluatePinsFromChange(ChangedPin);
}
void UK2Node_PromotableOperator::GetPinHoverText(const UEdGraphPin& Pin, FString& HoverTextOut) const
{
Super::GetPinHoverText(Pin, HoverTextOut);
static const FText RightClickToConv = LOCTEXT("RightClickToConvTooltip", "\n\nRight click this pin to convert its type.");
HoverTextOut += RightClickToConv.ToString();
}
///////////////////////////////////////////////////////////
// IK2Node_AddPinInterface
void UK2Node_PromotableOperator::AddInputPin()
{
if (CanAddPin())
{
FScopedTransaction Transaction(LOCTEXT("AddPinPromotableOperator", "AddPin"));
Modify();
AddInputPinImpl(NumFunctionInputs + NumAdditionalInputs);
++NumAdditionalInputs;
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprint());
}
}
bool UK2Node_PromotableOperator::CanAddPin() const
{
return ((NumAdditionalInputs + NumFunctionInputs) < GetMaxInputPinsNum()) &&
!FTypePromotion::IsComparisonFunc(GetTargetFunction());
}
bool UK2Node_PromotableOperator::CanRemovePin(const UEdGraphPin* Pin) const
{
return (
Pin && Pin->ParentPin == nullptr &&
NumAdditionalInputs > 0 &&
INDEX_NONE != Pins.IndexOfByKey(Pin) &&
Pin->Direction == EEdGraphPinDirection::EGPD_Input
);
}
void UK2Node_PromotableOperator::RemoveInputPin(UEdGraphPin* Pin)
{
if (CanRemovePin(Pin))
{
FScopedTransaction Transaction(LOCTEXT("RemovePinPromotableOperator", "RemovePin"));
Modify();
if (RemovePin(Pin))
{
--NumAdditionalInputs;
int32 NameIndex = 0;
const UEdGraphPin* SelfPin = FindPin(UEdGraphSchema_K2::PN_Self);
for (int32 PinIndex = 0; PinIndex < Pins.Num(); ++PinIndex)
{
UEdGraphPin* LocalPin = Pins[PinIndex];
if (LocalPin && (LocalPin->Direction != EGPD_Output) && (LocalPin != SelfPin) && LocalPin->ParentPin == nullptr)
{
const FName PinName = GetNameForAdditionalPin(NameIndex);
if (PinName != LocalPin->PinName)
{
LocalPin->Modify();
LocalPin->PinName = PinName;
}
NameIndex++;
}
}
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprint());
}
}
// If the pin that was removed makes this node empty then we can just reset it
// and have no need to reevaluate any pins
if (!HasAnyConnectionsOrDefaults())
{
ResetNodeToWildcard();
}
}
UEdGraphPin* UK2Node_PromotableOperator::GetAdditionalPin(int32 PinIndex) const
{
const FName PinToFind = GetNameForAdditionalPin(PinIndex);
for (UEdGraphPin* Pin : Pins)
{
if (Pin && Pin->PinName == PinToFind)
{
return Pin;
}
}
return nullptr;
}
UEdGraphPin* UK2Node_PromotableOperator::FindTolerancePin() const
{
return FindPin(TolerancePin_Name, EGPD_Input);
}
///////////////////////////////////////////////////////////
// UK2Node_CallFunction interface
void UK2Node_PromotableOperator::SetFromFunction(const UFunction* Function)
{
if(Function)
{
OperationName = FTypePromotion::GetOpNameFromFunction(Function);
}
// During compilation of an Anim BP, if this node gets pruned then the outer will be the /Engine/Transient/
// package which will result in this node not having a valid SelfContext, so there is no need
// to set any further information based on this function as it will not be used.
if(GetBlueprintClassFromNode())
{
Super::SetFromFunction(Function);
}
}
///////////////////////////////////////////////////////////
// UK2Node_PromotableOperator
bool UK2Node_PromotableOperator::IsTolerancePin(const UEdGraphPin* Pin) const
{
return Pin && Pin->PinName == TolerancePin_Name && Pin->Direction == EGPD_Input;
}
UEdGraphPin* UK2Node_PromotableOperator::AddInputPinImpl(int32 PinIndex)
{
const FName NewPinName = GetNameForAdditionalPin(PinIndex);
UEdGraphPin* NewPin = FWildcardNodeUtils::CreateWildcardPin(this, NewPinName, EGPD_Input);
check(NewPin);
// Determine a default type for this pin if we have other input connections
const TArray<UEdGraphPin*> InputPins = GetInputPins(/* bIncludeLinks = */ true);
check(InputPins.Num());
FEdGraphPinType PromotedType = FTypePromotion::GetPromotedType(InputPins);
NewPin->PinType = PromotedType;
return NewPin;
}
bool UK2Node_PromotableOperator::IsAdditionalPin(const UEdGraphPin* Pin) const
{
// Quickly check if this input pin is one of the two default input pins
return Pin && Pin->Direction == EGPD_Input && Pin->PinName != InputPinA_Name && Pin->PinName != InputPinB_Name && !IsTolerancePin(Pin);
}
bool UK2Node_PromotableOperator::HasAnyConnectionsOrDefaults() const
{
if(FTypePromotion::IsComparisonOpName(OperationName))
{
return HasDeterminingComparisonTypes();
}
for (UEdGraphPin* Pin : Pins)
{
if (Pin->LinkedTo.Num() > 0 || !Pin->DoesDefaultValueMatchAutogenerated())
{
return true;
}
}
return false;
}
bool UK2Node_PromotableOperator::HasDeterminingComparisonTypes() const
{
if(!FTypePromotion::IsComparisonOpName(OperationName))
{
return false;
}
// For comparison ops, we only want to check the input pins as the output
// will always be a bool
for (const UEdGraphPin* Pin : Pins)
{
if (Pin && Pin->Direction == EGPD_Input && (Pin->LinkedTo.Num() > 0 || !Pin->DoesDefaultValueMatchAutogenerated()))
{
return true;
}
}
return false;
}
void UK2Node_PromotableOperator::EvaluatePinsFromChange(UEdGraphPin* ChangedPin, const bool bFromConversion /* = false*/)
{
UpdateOpName();
if (!ensureMsgf(ChangedPin, TEXT("UK2Node_PromotableOperator::EvaluatePinsFromChange failed to evaluate on a null pin!")))
{
return;
}
// True if the pin that has changed now has zero connections
const bool bWasAFullDisconnect = (ChangedPin->LinkedTo.Num() == 0) && !HasAnyConnectionsOrDefaults();
const bool bIsComparisonOp = FTypePromotion::IsComparisonOpName(OperationName);
// If we have been totally disconnected and don't have any non-default inputs,
// than we just reset the node to be a regular wildcard
if (!bFromConversion && (bWasAFullDisconnect || (bIsComparisonOp && !HasDeterminingComparisonTypes())))
{
ResetNodeToWildcard();
return;
}
// If the pin that was connected is linked to a wildcard pin, then we should make it a wildcard
// and do nothing else.
else if (ChangedPin->GetOwningNodeUnchecked() == this && FWildcardNodeUtils::IsLinkedToWildcard(ChangedPin))
{
return;
}
// Changing the tolerance pin doesn't effect the rest of the function signature, so don't attempt to update the func
else if (IsTolerancePin(ChangedPin))
{
return;
}
// If we have connected the output of our comparison operator (which is always a bool) then
// there is no need to update the other pins
else if (bIsComparisonOp && ChangedPin == GetOutputPin())
{
return;
}
// If the newest connection to this this pin is a different type,
// then we need to break all other type connections that are not the same
if(ChangedPin->LinkedTo.Num() > 1)
{
const FEdGraphPinType& MostRecentConnection = ChangedPin->LinkedTo.Last()->PinType;
for(int32 i = ChangedPin->LinkedTo.Num() - 1; i >= 0; --i)
{
UEdGraphPin* Link = ChangedPin->LinkedTo[i];
const bool bIsRealNumberConnection =
(Link->PinType.PinCategory == UEdGraphSchema_K2::PC_Real) &&
(MostRecentConnection.PinCategory == UEdGraphSchema_K2::PC_Real);
// Real numbers are an exception to the normal link breaking rules.
// Even if the subcategories don't match, they're still valid connections
// since we implicitly cast between float and double types.
if (bIsRealNumberConnection)
{
continue;
}
if( Link->PinType.PinCategory != MostRecentConnection.PinCategory ||
Link->PinType.PinSubCategory != MostRecentConnection.PinSubCategory ||
Link->PinType.PinSubCategoryObject != MostRecentConnection.PinSubCategoryObject
)
{
ChangedPin->BreakLinkTo(Link);
}
}
}
// Gather all pins and their links so we can determine the highest type
TArray<UEdGraphPin*> PinsToConsider;
GetPinsToConsider(PinsToConsider);
if (bFromConversion)
{
PinsToConsider.AddUnique(ChangedPin);
}
const UFunction* BestMatchingFunc = FTypePromotion::FindBestMatchingFunc(OperationName, PinsToConsider);
// Store these other function options for later so that the user can convert to them later
UpdatePinsFromFunction(BestMatchingFunc, ChangedPin, bFromConversion);
}
bool UK2Node_PromotableOperator::UpdateOpName()
{
// If the function is null then return false, because we did not successfully update it.
// This could be possible during node reconstruction/refresh, and we don't want to set the
// op name to "Empty" incorrectly.
if (const UFunction* Func = GetTargetFunction())
{
OperationName = FTypePromotion::GetOpNameFromFunction(Func);
return true;
}
return false;
}
UK2Node_CallFunction* UK2Node_PromotableOperator::CreateIntermediateNode(UK2Node_CallFunction* PreviousNode, const UFunction* const OpFunction, FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
{
// Spawn an intermediate UK2Node_CallFunction node of the function type we need
UK2Node_CallFunction* NewOperator = SourceGraph->CreateIntermediateNode<UK2Node_CallFunction>();
NewOperator->SetFromFunction(OpFunction);
NewOperator->AllocateDefaultPins();
// Move this node next to the thing it was linked to
NewOperator->NodePosY = PreviousNode->NodePosY + 50;
NewOperator->NodePosX = PreviousNode->NodePosX + 8;
CompilerContext.MessageLog.NotifyIntermediateObjectCreation(NewOperator, this);
return NewOperator;
}
bool UK2Node_PromotableOperator::CreateIntermediateCast(UK2Node_CallFunction* SourceNode, FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph, UEdGraphPin* InputPin, UEdGraphPin* OutputPin)
{
check(InputPin && OutputPin);
const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema();
// If the pin types are the same, than no casts are needed and we can just connect
if (InputPin->PinType == OutputPin->PinType)
{
// If SourceNode is 'this' then we need to move the pin links instead of just
// creating the connection because the output is not another new node, but
// just the intermediate expansion node.
if (SourceNode == this)
{
return !CompilerContext.MovePinLinksToIntermediate(*InputPin, *OutputPin).IsFatal();
}
else
{
return Schema->TryCreateConnection(InputPin, OutputPin);
}
}
UK2Node* TemplateConversionNode = nullptr;
FName TargetFunctionName;
UClass* ClassContainingConversionFunction = nullptr;
TSubclassOf<UK2Node> ConversionNodeClass;
if (Schema->SearchForAutocastFunction(InputPin->PinType, OutputPin->PinType, /*out*/ TargetFunctionName, /*out*/ClassContainingConversionFunction))
{
// Create a new call function node for the casting operator
UK2Node_CallFunction* TemplateNode = SourceGraph->CreateIntermediateNode<UK2Node_CallFunction>();
TemplateNode->FunctionReference.SetExternalMember(TargetFunctionName, ClassContainingConversionFunction);
TemplateConversionNode = TemplateNode;
TemplateNode->AllocateDefaultPins();
CompilerContext.MessageLog.NotifyIntermediateObjectCreation(TemplateNode, this);
}
else
{
Schema->FindSpecializedConversionNode(InputPin, OutputPin, true, /*out*/ TemplateConversionNode);
}
bool bInputSuccessful = false;
bool bOutputSuccessful = false;
if (TemplateConversionNode)
{
UEdGraphPin* ConversionInput = nullptr;
for (UEdGraphPin* ConvPin : TemplateConversionNode->Pins)
{
if (ConvPin && ConvPin->Direction == EGPD_Input && ConvPin->PinName != UEdGraphSchema_K2::PSC_Self)
{
ConversionInput = ConvPin;
break;
}
}
UEdGraphPin* ConversionOutput = TemplateConversionNode->FindPin(UEdGraphSchema_K2::PN_ReturnValue, EGPD_Output);
// Connect my input to the conversion node directly if we have links, otherwise we need to move the intermediate version of it
if (InputPin->LinkedTo.Num() > 0)
{
bInputSuccessful = Schema->TryCreateConnection(InputPin->LinkedTo[0], ConversionInput);
}
else if (InputPin && ConversionInput)
{
bInputSuccessful = !CompilerContext.MovePinLinksToIntermediate(*InputPin, *ConversionInput).IsFatal();
}
// Connect conversion node output to the input of the new operator
bOutputSuccessful = Schema->TryCreateConnection(ConversionOutput, OutputPin);
// Move this node next to the thing it was linked to
TemplateConversionNode->NodePosY = SourceNode->NodePosY;
TemplateConversionNode->NodePosX = SourceNode->NodePosX + 4;
}
else
{
CompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("NoValidPromotion", "Cannot find appropriate promotion from '{0}' to '{1}' on '@@'"),
Schema->TypeToText(InputPin->PinType),
Schema->TypeToText(OutputPin->PinType)).ToString(),
SourceNode
);
}
return bInputSuccessful && bOutputSuccessful;
}
void UK2Node_PromotableOperator::ResetNodeToWildcard()
{
RecombineAllSplitPins();
// Reset type to wildcard
const FEdGraphPinType& WildType = FWildcardNodeUtils::GetDefaultWildcardPinType();
const UEdGraphSchema* Schema = GetSchema();
for (UEdGraphPin* Pin : Pins)
{
// Ensure this pin is not a split pin
if (Pin && Pin->ParentPin == nullptr)
{
Pin->PinType = WildType;
Schema->ResetPinToAutogeneratedDefaultValue(Pin);
}
}
const UFunction* Func = GetTargetFunction();
if(Func && FTypePromotion::IsComparisonFunc(Func))
{
// Set output pins to have a bool output flag by default
if (UEdGraphPin* OutPin = GetOutputPin())
{
OutPin->PinType.PinCategory = UEdGraphSchema_K2::PC_Boolean;
}
// If we have a tolerance pin, and we reset to wildcard then we should hide it
if (UEdGraphPin* TolerancePin = FindTolerancePin())
{
TolerancePin->bHidden = true;
}
}
GetGraph()->NotifyGraphChanged();
}
void UK2Node_PromotableOperator::RecombineAllSplitPins()
{
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
// Gather what pins need to be recombined from a split pin
for (int32 Index = 0; Index < Pins.Num(); ++Index)
{
if (Pins[Index] && Pins[Index]->SubPins.Num() > 0)
{
K2Schema->RecombinePin(Pins[Index]);
}
}
}
void UK2Node_PromotableOperator::UpdateFromBestMatchingFunction()
{
// Gather all pins and their links so we can determine the highest type that the user could want
TArray<UEdGraphPin*> PinsToConsider;
GetPinsToConsider(PinsToConsider);
// We need to update the pins from our function if have a new connection
if (const UFunction* BestMatchingFunc = FTypePromotion::FindBestMatchingFunc(OperationName, PinsToConsider))
{
UpdatePinsFromFunction(BestMatchingFunc);
}
}
TArray<UEdGraphPin*> UK2Node_PromotableOperator::GetInputPins(bool bIncludeLinks /** = false */) const
{
TArray<UEdGraphPin*> InputPins;
for (UEdGraphPin* Pin : Pins)
{
// Exclude split pins from this
if (Pin && Pin->Direction == EEdGraphPinDirection::EGPD_Input && Pin->ParentPin == nullptr)
{
InputPins.Add(Pin);
if (bIncludeLinks)
{
for (UEdGraphPin* Link : Pin->LinkedTo)
{
InputPins.Emplace(Link);
}
}
}
}
return InputPins;
}
void UK2Node_PromotableOperator::GetPinsToConsider(TArray<UEdGraphPin*>& OutArray) const
{
const bool bIsComparisonOp = FTypePromotion::IsComparisonOpName(OperationName);
for (UEdGraphPin* Pin : Pins)
{
// Tolerance pins don't factor into what types we should be matching
// for the function, so we should not consider them
if (IsTolerancePin(Pin))
{
continue;
}
// If this is a comparison operator then we don't need to consider the boolean output
// pin because every comparison function will have a boolean output!
if (bIsComparisonOp && Pin->Direction == EGPD_Output)
{
continue;
}
// If this pin has links, then use those instead of the actual pin because we could be process of changing it
// which means that it would still have it's old pin type, and could be inaccurate
if (Pin->LinkedTo.Num() > 0)
{
// If this is from a split pin, then we care about the parent type
if (Pin->ParentPin != nullptr)
{
OutArray.Add(Pin->ParentPin);
}
// as well as all the links to it
for (UEdGraphPin* Link : Pin->LinkedTo)
{
OutArray.Emplace(Link);
}
}
else if (!FWildcardNodeUtils::IsWildcardPin(Pin))
{
// If this is from a split pin, then we care about the type of the parent, not this pin
if (Pin->ParentPin != nullptr)
{
OutArray.Add(Pin->ParentPin);
}
else
{
OutArray.Add(Pin);
}
}
}
}
void UK2Node_PromotableOperator::UpdatePinsFromFunction(const UFunction* Function, UEdGraphPin* ChangedPin /* = nullptr */, bool bIsFromConversion /*= false*/)
{
if (!Function)
{
return;
}
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
// Gather the pin types of the properties on the function we want to convert to
FEdGraphPinType FunctionReturnType;
FEdGraphPinType HighestFuncInputType;
FEdGraphPinType ToleranceType;
TArray<FEdGraphPinType> FunctionInputTypes;
{
for (TFieldIterator<FProperty> PropIt(Function); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt)
{
FProperty* Param = *PropIt;
FEdGraphPinType ParamType;
if (Schema->ConvertPropertyToPinType(Param, /* out */ ParamType))
{
if (Param->HasAnyPropertyFlags(CPF_ReturnParm))
{
FunctionReturnType = ParamType;
}
else
{
// Track the highest input pin type that we have
if (FTypePromotion::GetHigherType(HighestFuncInputType, ParamType) == FTypePromotion::ETypeComparisonResult::TypeBHigher)
{
HighestFuncInputType = ParamType;
}
FunctionInputTypes.Add(ParamType);
}
}
}
}
auto ConformPinLambda = [&ChangedPin, bIsFromConversion](const FEdGraphPinType& FunctionPinType, UEdGraphPin* NodePin)
{
// If the pin types are already equal, then we don't have to do any work
// If this is linked to wildcard pins, then we can just ignore it and handle it on expansion
if (!NodePin || FWildcardNodeUtils::IsLinkedToWildcard(NodePin))
{
return;
}
// Pins that are underdoing a conversion will have already had their types changed
if (NodePin == ChangedPin && bIsFromConversion)
{
return;
}
// By default, conform to the type of the function param
FEdGraphPinType ConformingType = FunctionPinType;
const FEdGraphPinType HighestLinkedType = NodePin->LinkedTo.Num() > 0 ? FTypePromotion::GetPromotedType(NodePin->LinkedTo) : NodePin->PinType;
const bool bDifferingStructs =
HighestLinkedType.PinCategory == UEdGraphSchema_K2::PC_Struct &&
FunctionPinType.PinCategory == UEdGraphSchema_K2::PC_Struct &&
HighestLinkedType.PinSubCategoryObject != FunctionPinType.PinSubCategoryObject;
const bool bHasDeterminingType = NodePin->LinkedTo.Num() > 0 || !NodePin->DoesDefaultValueMatchAutogenerated();
// If the highest type is the same as the function type, just continue on with life
if (bHasDeterminingType && (HighestLinkedType.PinCategory != FunctionPinType.PinCategory || bDifferingStructs))
{
NodePin->PinType = FunctionPinType;
const bool bValidPromo =
FTypePromotion::IsValidPromotion(HighestLinkedType, FunctionPinType) ||
(NodePin->LinkedTo.Num() > 0 && FTypePromotion::HasStructConversion(NodePin, NodePin->LinkedTo[0]));
// If the links cannot be promoted to the function type, then we need to break them
// We don't want to break the pin if it is the one that the user has dragged on to though,
// because that would result in the node breaking connection as soon as the user lets go
if ((NodePin != ChangedPin) && !FWildcardNodeUtils::IsWildcardPin(NodePin) && !bValidPromo)
{
NodePin->BreakAllPinLinks();
}
else
{
ConformingType = HighestLinkedType;
}
}
// Conform the pin type appropriately
NodePin->PinType = ConformingType;
};
// Check if we need to add a tolerance pin or not with this new function
if (FTypePromotion::IsComparisonFunc(Function))
{
UEdGraphPin* ExistingTolPin = FindTolerancePin();
const bool bHasTolerancePin = PromotableOpUtils::FindTolerancePinType(Function, ToleranceType);
if (!ExistingTolPin)
{
ExistingTolPin = CreatePin(EGPD_Input, ToleranceType, TolerancePin_Name);
}
// Set the tolerance pin to visible if it is currently on the node
ExistingTolPin->bHidden = !bHasTolerancePin;
}
int32 CurPinIndex = 0;
for (UEdGraphPin* CurPin : Pins)
{
// We don't want to try and conform split pin, because we will already have conformed the parent pin
if (CurPin->ParentPin != nullptr)
{
continue;
}
if (IsAdditionalPin(CurPin))
{
// Conform to the highest input pin on the function
ConformPinLambda(HighestFuncInputType, CurPin);
}
else if (CurPin->Direction == EGPD_Output)
{
// Match to the output pin
ConformPinLambda(FunctionReturnType, CurPin);
}
// Creation and conformation of the tolerance pin is handled before all others to
// ensure that connections are not broken accidentally
else if (IsTolerancePin(CurPin))
{
ConformPinLambda(ToleranceType, CurPin);
}
else
{
// Match to the appropriate function input type
ConformPinLambda(FunctionInputTypes[CurPinIndex], CurPin);
++CurPinIndex;
}
}
// Update the function reference and the FUNC_BlueprintPure/FUNC_Const appropriately
SetFromFunction(Function);
// Invalidate the tooltips
CachedTooltip.MarkDirty();
// We need to notify the graph that the node has changed to get
// the correct default value text boxes on the node
GetGraph()->NotifyGraphChanged();
}
UEdGraphPin* UK2Node_PromotableOperator::GetOutputPin() const
{
for (UEdGraphPin* Pin : Pins)
{
if (Pin && Pin->Direction == EGPD_Output)
{
return Pin;
}
}
return nullptr;
}
void UK2Node_PromotableOperator::ConvertPinType(UEdGraphPin* PinToChange, const FEdGraphPinType NewPinType)
{
// No work to be done here!
if (!PinToChange || PinToChange->PinType == NewPinType)
{
return;
}
FScopedTransaction Transaction(LOCTEXT("PromotableOperatorPinConvert", "Convert pin type"));
Modify();
if(NewPinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard)
{
const bool bIsComparison = FTypePromotion::IsComparisonFunc(GetTargetFunction());
// Break all input connections, but only break an output if it is not a comparison
// because the bool pin type will not change.
for(UEdGraphPin* Pin : Pins)
{
if(Pin->Direction == EGPD_Input || !bIsComparison)
{
Pin->BreakAllPinLinks();
}
}
ResetNodeToWildcard();
return;
}
// Break any pin links to this node because the type will be different
PinToChange->BreakAllPinLinks();
// Recombine any split pins that this type has before changing its type
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
if (PinToChange->SubPins.Num() > 0)
{
Schema->RecombinePin(PinToChange);
}
PinToChange->PinType = NewPinType;
EvaluatePinsFromChange(PinToChange, true);
InvalidatePinTooltips();
// Reset the default value on pins that have been converted
Schema->ResetPinToAutogeneratedDefaultValue(PinToChange, false);
}
#undef LOCTEXT_NAMESPACE // "PromotableOperatorNode"