Files
UnrealEngineUWP/Engine/Source/Editor/BlueprintGraph/Private/K2Node_AssignmentStatement.cpp
dave jones2 bcadd23489 UE-151338 - Implement refactored implicit conversion for math types.
Removed container and struct conversions from the script VM. This introduced complexity that the VM doesn't need, nor did it scale for the various struct types that we want to implicitly convert in Blueprints. Instead, the script VM is *only* aware of float<->double conversion. Container and struct conversions have now been moved to BlueprintTypeConversions. Currently, only the existing FVector3f<->FVector3d conversion has been added, but the remaining LWC types will be added in a subsequent change.

During Blueprint compilation of container and struct conversions, we now inject a function call into the bytecode that performs the conversion, which is better suited to the task instead of burdening the VM with the work. One drawback to this technique is that containers are slightly more inefficient when it comes to conversions. They won't know their type(s) ahead of time, which requires dynamically looking up a conversion function at runtime. We can possibly optimize this further, but the generaly recommendation is to avoid implicit conversions of container types when possible.

Additionally, a couple of convenience functions were added to the KismetCastingUtils to help remove a fair amount of boilerplate code that was used for implicit casting in various node types. ScriptCastingUtils.h was also removed since the VM no longer needs to concern itself with complex conversions.

#jira UE-151338
#preflight 629a507fb42820769428c133
#rb phillip.kavan

[CL 20560449 by dave jones2 in ue5-main branch]
2022-06-08 13:50:57 -04:00

248 lines
8.1 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "K2Node_AssignmentStatement.h"
#include "EdGraphSchema_K2.h"
#include "EdGraphUtilities.h"
#include "KismetCastingUtils.h"
#include "KismetCompiler.h"
#include "VariableSetHandler.h"
#include "BlueprintNodeSpawner.h"
#include "EditorCategoryUtils.h"
#include "BlueprintActionDatabaseRegistrar.h"
#define LOCTEXT_NAMESPACE "K2Node_AssignmentStatement"
//////////////////////////////////////////////////////////////////////////
// FKCHandler_AssignmentStatement
class FKCHandler_AssignmentStatement : public FKCHandler_VariableSet
{
public:
FKCHandler_AssignmentStatement(FKismetCompilerContext& InCompilerContext)
: FKCHandler_VariableSet(InCompilerContext)
{
}
virtual void RegisterNets(FKismetFunctionContext& Context, UEdGraphNode* Node) override
{
using namespace UE::KismetCompiler;
UEdGraphPin* VariablePin = Node->FindPin(TEXT("Variable"));
UEdGraphPin* ValuePin = Node->FindPin(TEXT("Value"));
if ((VariablePin == NULL) || (ValuePin == NULL))
{
CompilerContext.MessageLog.Error(*LOCTEXT("MissingPins_Error", "Missing pin(s) on @@; expected a pin named Variable and a pin named Value").ToString(), Node);
return;
}
if (VariablePin->LinkedTo.Num() == 0)
{
CompilerContext.MessageLog.Error(*LOCTEXT("NoVariableConnected_Error", "A variable needs to be connected to @@").ToString(), VariablePin);
return;
}
ValidateAndRegisterNetIfLiteral(Context, ValuePin);
UEdGraphPin* VariablePinNet = FEdGraphUtilities::GetNetFromPin(VariablePin);
UEdGraphPin* ValuePinNet = FEdGraphUtilities::GetNetFromPin(ValuePin);
if (VariablePinNet && ValuePinNet)
{
CastingUtils::FConversion Conversion =
CastingUtils::GetFloatingPointConversion(*ValuePinNet, *VariablePinNet);
if (Conversion.Type != CastingUtils::FloatingPointCastType::None)
{
FBPTerminal* NewTerm = CastingUtils::MakeImplicitCastTerminal(Context, VariablePinNet, Node);
Context.ImplicitCastMap.Add(VariablePin, CastingUtils::FImplicitCastParams{Conversion, NewTerm, Node});
}
}
else
{
CompilerContext.MessageLog.Error(*LOCTEXT("NoVariableOrValueNets_Error", "Expected Variable and Value pins to have valid connections in @@").ToString(), Node);
}
}
virtual void Compile(FKismetFunctionContext& Context, UEdGraphNode* Node) override
{
using namespace UE::KismetCompiler;
UEdGraphPin* VariablePin = Node->FindPin(TEXT("Variable"));
UEdGraphPin* ValuePin = Node->FindPin(TEXT("Value"));
check(VariablePin);
check(VariablePin->LinkedTo.Num() == 1);
check(ValuePin);
InnerAssignment(Context, Node, VariablePin, ValuePin);
// Generate the output impulse from this node
GenerateSimpleThenGoto(Context, *Node);
// The assignment node is very much a non-standard node: its pins don't directly reference any data, but their nets do.
// The only cast that could happen is from the value pin's net to the variable's pin net, which we handle in RegisterNets.
// Due to how RegisterImplicitCasts works, it can actually register an erroneous entry to the value pin,
// which we need to remove ourselves.
CastingUtils::RemoveRegisteredImplicitCast(Context, ValuePin);
}
protected:
virtual bool UsesVariablePinAsKey() const override { return true; }
};
FName UK2Node_AssignmentStatement::VariablePinName(TEXT("Variable"));
FName UK2Node_AssignmentStatement::ValuePinName(TEXT("Value"));
UK2Node_AssignmentStatement::UK2Node_AssignmentStatement(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
void UK2Node_AssignmentStatement::AllocateDefaultPins()
{
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then);
UEdGraphPin* VariablePin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, VariablePinName);
UEdGraphPin* ValuePin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, ValuePinName);
Super::AllocateDefaultPins();
}
FText UK2Node_AssignmentStatement::GetTooltipText() const
{
return LOCTEXT("AssignmentStatementTooltip", "Assigns Value to Variable");
}
FText UK2Node_AssignmentStatement::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
return LOCTEXT("Assign", "Assign");
}
bool UK2Node_AssignmentStatement::IsCompatibleWithGraph(const UEdGraph* TargetGraph) const
{
bool bIsCompatible = Super::IsCompatibleWithGraph(TargetGraph);
if (bIsCompatible)
{
const EGraphType GraphType = TargetGraph->GetSchema()->GetGraphType(TargetGraph);
bIsCompatible = (GraphType == GT_Macro);
}
return bIsCompatible;
}
bool UK2Node_AssignmentStatement::CanPasteHere(const UEdGraph* TargetGraph) const
{
// These nodes can be pasted anywhere that UK2Node's are compatible with the graph
// Avoiding the call to IsCompatibleWithGraph because these nodes should normally only
// be placed in Macros, but it's nice to be able to paste Macro functionality anywhere.
return Super::IsCompatibleWithGraph(TargetGraph);
}
void UK2Node_AssignmentStatement::NotifyPinConnectionListChanged(UEdGraphPin* Pin)
{
Super::NotifyPinConnectionListChanged(Pin);
UEdGraphPin* VariablePin = FindPin(TEXT("Variable"));
UEdGraphPin* ValuePin = FindPin(TEXT("Value"));
if ((VariablePin->LinkedTo.Num() == 0) && (ValuePin->LinkedTo.Num() == 0))
{
// Restore the wildcard status
VariablePin->PinType.PinCategory = UEdGraphSchema_K2::PC_Wildcard;
VariablePin->PinType.PinSubCategory = TEXT("");
VariablePin->PinType.PinSubCategoryObject = NULL;
ValuePin->PinType.PinCategory = UEdGraphSchema_K2::PC_Wildcard;
ValuePin->PinType.PinSubCategory = TEXT("");
ValuePin->PinType.PinSubCategoryObject = NULL;
}
else if (Pin->LinkedTo.Num() > 0 &&
( Pin->LinkedTo[0]->PinType.PinCategory != UEdGraphSchema_K2::PC_Wildcard ||
Pin->LinkedTo[0]->PinType.PinCategory == Pin->PinType.PinCategory) )
{
Pin->PinType = Pin->LinkedTo[0]->PinType;
// Enforce the type on the other pin
if (VariablePin == Pin)
{
ValuePin->PinType = VariablePin->PinType;
UEdGraphSchema_K2::ValidateExistingConnections(ValuePin);
}
else
{
VariablePin->PinType = ValuePin->PinType;
UEdGraphSchema_K2::ValidateExistingConnections(VariablePin);
}
}
}
void UK2Node_AssignmentStatement::PostReconstructNode()
{
UEdGraphPin* VariablePin = FindPin(TEXT("Variable"));
UEdGraphPin* ValuePin = FindPin(TEXT("Value"));
PinConnectionListChanged(VariablePin);
PinConnectionListChanged(ValuePin);
Super::PostReconstructNode();
}
UEdGraphPin* UK2Node_AssignmentStatement::GetThenPin() const
{
UEdGraphPin* Pin = FindPin(UEdGraphSchema_K2::PN_Then);
check(Pin != NULL);
return Pin;
}
UEdGraphPin* UK2Node_AssignmentStatement::GetVariablePin() const
{
UEdGraphPin* Pin = FindPin(VariablePinName);
check(Pin != NULL);
return Pin;
}
UEdGraphPin* UK2Node_AssignmentStatement::GetValuePin() const
{
UEdGraphPin* Pin = FindPin(ValuePinName);
check(Pin != NULL);
return Pin;
}
FNodeHandlingFunctor* UK2Node_AssignmentStatement::CreateNodeHandler(FKismetCompilerContext& CompilerContext) const
{
return new FKCHandler_AssignmentStatement(CompilerContext);
}
void UK2Node_AssignmentStatement::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);
}
}
FText UK2Node_AssignmentStatement::GetMenuCategory() const
{
return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::Macro);
}
#undef LOCTEXT_NAMESPACE