Files
UnrealEngine/Engine/Source/Editor/BlueprintGraph/Private/CallFunctionHandler.cpp
2025-05-18 13:04:45 +08:00

1057 lines
40 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CallFunctionHandler.h"
#include "BlueprintCompilationManager.h"
#include "UObject/MetaData.h"
#include "EdGraphSchema_K2.h"
#include "K2Node_Event.h"
#include "K2Node_CallParentFunction.h"
#include "K2Node_ExecutionSequence.h"
#include "K2Node_Self.h"
#include "K2Node_VariableGet.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "K2Node_CallFunction.h"
#include "EdGraphUtilities.h"
#include "Engine/BlueprintGeneratedClass.h"
#include "KismetCastingUtils.h"
#include "KismetCompiler.h"
#include "Net/Core/PushModel/PushModelMacros.h"
#include "PushModelHelpers.h"
#if WITH_PUSH_MODEL
#include "Net/NetPushModelHelpers.h"
#endif
#define LOCTEXT_NAMESPACE "CallFunctionHandler"
namespace UE::K2NodeCallFunction::Private
{
static bool IsCalledFunctionPure(const UEdGraphNode* Node)
{
if (const UK2Node_CallFunction* CallFunctionNode = Cast<UK2Node_CallFunction>(Node))
{
return CallFunctionNode->IsNodePure();
}
return false;
}
static bool FindMatchingReferencedNetOrFieldNotifyPropertyAndPin(TArray<UEdGraphPin*>& RemainingPins, FProperty* FunctionProperty, FProperty*& NetProperty, FProperty*& FieldNotifyProperty, UEdGraphPin*& PropertyObjectPin)
{
NetProperty = nullptr;
FieldNotifyProperty = nullptr;
PropertyObjectPin = nullptr;
if (UNLIKELY(FunctionProperty->HasAllPropertyFlags(CPF_OutParm | CPF_ReferenceParm) && !FunctionProperty->HasAnyPropertyFlags(CPF_ReturnParm | CPF_ConstParm)))
{
for (int32 i = 0; i < RemainingPins.Num(); ++i)
{
if (FunctionProperty->GetFName() == RemainingPins[i]->PinName)
{
bool bResult = false;
UEdGraphPin* ParamPin = RemainingPins[i];
RemainingPins.RemoveAtSwap(i);
if (UEdGraphPin* PinToTry = FEdGraphUtilities::GetNetFromPin(ParamPin))
{
// TODO: Should we traverse pin links to find references somehow?
// E.G., How are select statements that pass through references
// to net properties handled?
if (UK2Node_VariableGet* GetPropertyNode = Cast<UK2Node_VariableGet>(PinToTry->GetOwningNode()))
{
if (FProperty* ToCheck = GetPropertyNode->GetPropertyForVariable())
{
if (UNLIKELY(FKismetCompilerUtilities::IsPropertyUsesFieldNotificationSetValueAndBroadcast(ToCheck)))
{
FieldNotifyProperty = ToCheck;
PropertyObjectPin = GetPropertyNode->FindPinChecked(UEdGraphSchema_K2::PN_Self);
bResult = true;
}
if (UNLIKELY(ToCheck->HasAnyPropertyFlags(CPF_Net)))
{
NetProperty = ToCheck;
PropertyObjectPin = GetPropertyNode->FindPinChecked(UEdGraphSchema_K2::PN_Self);
bResult = true;
}
}
}
}
return bResult;
}
}
}
return false;
}
}
//////////////////////////////////////////////////////////////////////////
// FImportTextErrorContext
// Support class to pipe logs from FProperty->ImportText (for struct literals) to the message log as warnings
class FImportTextErrorContext : public FOutputDevice
{
protected:
FCompilerResultsLog& MessageLog;
UObject* TargetObject;
public:
int32 NumErrors;
FImportTextErrorContext(FCompilerResultsLog& InMessageLog, UObject* InTargetObject)
: FOutputDevice()
, MessageLog(InMessageLog)
, TargetObject(InTargetObject)
, NumErrors(0)
{
}
virtual void Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName& Category) override
{
if (TargetObject == NULL)
{
MessageLog.Error(V);
}
else
{
const FString ErrorString = FString::Printf(TEXT("Invalid default on node @@: %s"), V);
MessageLog.Error(*ErrorString, TargetObject);
}
NumErrors++;
}
};
//////////////////////////////////////////////////////////////////////////
// FKCHandler_CallFunction
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4750)
#endif
/**
* Searches for the function referenced by a graph node in the CallingContext class's list of functions,
* validates that the wiring matches up correctly, and creates an execution statement.
*/
void FKCHandler_CallFunction::CreateFunctionCallStatement(FKismetFunctionContext& Context, UEdGraphNode* Node, UEdGraphPin* SelfPin)
{
using namespace UE::KismetCompiler;
int32 NumErrorsAtStart = CompilerContext.MessageLog.NumErrors;
// Find the function, starting at the parent class
if (UFunction* Function = FindFunction(Context, Node))
{
CheckIfFunctionIsCallable(Function, Context, Node);
// Make sure the pin mapping is sound (all pins wire up to a matching function parameter, and all function parameters match a pin)
// Remaining unmatched pins
// Note: Should maintain a stable order for variadic arguments
TArray<UEdGraphPin*> RemainingPins;
RemainingPins.Append(Node->Pins);
const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema();
// Remove expected exec and self pins
RemainingPins.RemoveAll([Schema](UEdGraphPin* Pin) { return (Pin->bOrphanedPin || Schema->IsMetaPin(*Pin)); });
// Check for magic pins
const bool bIsLatent = Function->HasMetaData(FBlueprintMetadata::MD_Latent);
if (bIsLatent && (CompilerContext.UbergraphContext != &Context))
{
CompilerContext.MessageLog.Error(*LOCTEXT("ContainsLatentCall_Error", "@@ contains a latent call, which cannot exist outside of the event graph").ToString(), Node);
}
UEdGraphPin* LatentInfoPin = nullptr;
if (TMap<FName, FString>* MetaData = FMetaData::GetMapForObject(Function))
{
for (TMap<FName, FString>::TConstIterator It(*MetaData); It; ++It)
{
const FName& Key = It.Key();
if (Key == TEXT("LatentInfo"))
{
UEdGraphPin* Pin = Node->FindPin(It.Value());
if (Pin && (Pin->Direction == EGPD_Input) && (Pin->LinkedTo.Num() == 0))
{
LatentInfoPin = Pin;
UEdGraphPin* PinToTry = FEdGraphUtilities::GetNetFromPin(Pin);
if (FBPTerminal** Term = Context.NetMap.Find(PinToTry))
{
check((*Term)->bIsLiteral);
int32 LatentUUID = CompilerContext.MessageLog.CalculateStableIdentifierForLatentActionManager(LatentInfoPin->GetOwningNode());
const FString ExecutionFunctionName = UEdGraphSchema_K2::FN_ExecuteUbergraphBase.ToString() + TEXT("_") + Context.Blueprint->GetName();
(*Term)->Name = FString::Printf(TEXT("(Linkage=%s,UUID=%s,ExecutionFunction=%s,CallbackTarget=None)"), *FString::FromInt(INDEX_NONE), *FString::FromInt(LatentUUID), *ExecutionFunctionName);
// Record the UUID in the debugging information
UEdGraphNode* TrueSourceNode = Cast<UEdGraphNode>(Context.MessageLog.FindSourceObject(Node));
Context.NewClass->GetDebugData().RegisterUUIDAssociation(TrueSourceNode, LatentUUID);
}
}
else
{
CompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("FindPinFromLinkage_ErrorFmt", "Function {0} (called from @@) was specified with LatentInfo metadata but does not have a pin named {1}"),
FText::FromString(Function->GetName()), FText::FromString(It.Value())).ToString(), Node);
}
}
}
}
// Parameter info to be stored, and assigned to all function call statements generated below
FBPTerminal* LHSTerm = nullptr;
TArray<FBPTerminal*> RHSTerms;
UEdGraphPin* ThenExecPin = nullptr;
UEdGraphNode* LatentTargetNode = nullptr;
int32 LatentTargetParamIndex = INDEX_NONE;
// Grab the special case structs that use their own literal path
UScriptStruct* VectorStruct = TBaseStructure<FVector>::Get();
UScriptStruct* Vector3fStruct = TVariantStructure<FVector3f>::Get();
UScriptStruct* RotatorStruct = TBaseStructure<FRotator>::Get();
UScriptStruct* TransformStruct = TBaseStructure<FTransform>::Get();
// If a function parameter needs an implicit double<->float cast *and* it's a non-const reference,
// then we need to copy the value of the casted temporary *back* to its source.
//
// Just to illustrate the scenario, take the following example in pseudocode:
//
// double Input = 2.0
// float CastedInput = (float)Input ; Narrowing conversion needed for function input
// NativeFunctionWithReferenceParam(CastedInput) ; CastedInput has possibly changed since this function takes a float&
// Input = (double)CastedInput ; Now we need to propagate that change back to Input
//
using CastEntryT = TPair<FBPTerminal*, CastingUtils::FImplicitCastParams>;
TArray<CastEntryT> ModifiedCastInputs;
// Check each property
bool bMatchedAllParams = true;
for (TFieldIterator<FProperty> It(Function); It && (It->PropertyFlags & CPF_Parm); ++It)
{
FProperty* Property = *It;
bool bFoundParam = false;
for (int32 i = 0; !bFoundParam && (i < RemainingPins.Num()); ++i)
{
UEdGraphPin* PinMatch = RemainingPins[i];
if (Property->GetFName() == PinMatch->PinName)
{
// Found a corresponding pin, does it match in type and direction?
if (UK2Node_CallFunction::IsStructureWildcardProperty(Function, Property->GetFName()) ||
FKismetCompilerUtilities::IsTypeCompatibleWithProperty(PinMatch, Property, CompilerContext.MessageLog, CompilerContext.GetSchema(), Context.NewClass))
{
UEdGraphPin* PinToTry = FEdGraphUtilities::GetNetFromPin(PinMatch);
if (FBPTerminal** Term = Context.NetMap.Find(PinToTry))
{
// For literal structs, we have to verify the default here to make sure that it has valid formatting
if( (*Term)->bIsLiteral && (PinMatch != LatentInfoPin))
{
FStructProperty* StructProperty = CastField<FStructProperty>(Property);
if( StructProperty )
{
UScriptStruct* Struct = StructProperty->Struct;
if( Struct != VectorStruct
&& Struct != Vector3fStruct
&& Struct != RotatorStruct
&& Struct != TransformStruct )
{
// Ensure all literal struct terms can be imported if its empty
if ( (*Term)->Name.IsEmpty() )
{
(*Term)->Name = TEXT("()");
}
int32 StructSize = Struct->GetStructureSize();
[this, StructSize, StructProperty, Node, Term, &bMatchedAllParams]()
{
uint8* StructData = (uint8*)FMemory_Alloca(StructSize);
StructProperty->InitializeValue(StructData);
// Import the literal text to a dummy struct to verify it's well-formed
FImportTextErrorContext ErrorPipe(CompilerContext.MessageLog, Node);
StructProperty->ImportText_Direct(*((*Term)->Name), StructData, nullptr, 0, &ErrorPipe);
if(ErrorPipe.NumErrors > 0)
{
bMatchedAllParams = false;
}
}();
}
}
}
if (Property->HasAnyPropertyFlags(CPF_ReturnParm))
{
LHSTerm = *Term;
}
else
{
FBPTerminal* RHSTerm = *Term;
// if this term is an object that needs to be cast to an interface
if (FBPTerminal** InterfaceTerm = InterfaceTermMap.Find(PinMatch))
{
UClass* InterfaceClass = CastChecked<UClass>(PinMatch->PinType.PinSubCategoryObject.Get());
FBPTerminal* ClassTerm = Context.CreateLocalTerminal(ETerminalSpecification::TS_Literal);
ClassTerm->Name = InterfaceClass->GetName();
ClassTerm->bIsLiteral = true;
ClassTerm->Source = Node;
ClassTerm->ObjectLiteral = InterfaceClass;
ClassTerm->Type.PinCategory = UEdGraphSchema_K2::PC_Class;
// insert a cast op before a call to the function (and replace
// the param with the result from the cast)
FBlueprintCompiledStatement& CastStatement = Context.AppendStatementForNode(Node);
CastStatement.Type = InterfaceClass->HasAnyClassFlags(CLASS_Interface) ? KCST_CastObjToInterface : KCST_CastInterfaceToObj;
CastStatement.LHS = *InterfaceTerm;
CastStatement.RHS.Add(ClassTerm);
CastStatement.RHS.Add(*Term);
RHSTerm = *InterfaceTerm;
}
{
const CastingUtils::FImplicitCastParams* CastParams =
Context.ImplicitCastMap.Find(PinMatch);
if (CastParams)
{
FBPTerminal* ImplicitCastTerm =
CastingUtils::InsertImplicitCastStatement(Context, PinMatch, RHSTerm);
check(ImplicitCastTerm);
bool bIsNonConstReference =
Property->HasAllPropertyFlags(CPF_OutParm | CPF_ReferenceParm) &&
!Property->HasAllPropertyFlags(CPF_ConstParm);
if (bIsNonConstReference)
{
CastingUtils::FImplicitCastParams InverseCastParams = *CastParams;
if (CastParams->Conversion.Type == CastingUtils::FloatingPointCastType::FloatToDouble)
{
InverseCastParams.Conversion.Type = CastingUtils::FloatingPointCastType::DoubleToFloat;
}
else if (CastParams->Conversion.Type == CastingUtils::FloatingPointCastType::DoubleToFloat)
{
InverseCastParams.Conversion.Type = CastingUtils::FloatingPointCastType::FloatToDouble;
}
InverseCastParams.TargetTerminal = RHSTerm;
ModifiedCastInputs.Add(CastEntryT{ImplicitCastTerm, InverseCastParams});
}
RHSTerm = ImplicitCastTerm;
}
}
int32 ParameterIndex = RHSTerms.Add(RHSTerm);
if (PinMatch == LatentInfoPin)
{
// Record the (latent) output impulse from this node
ThenExecPin = CompilerContext.GetSchema()->FindExecutionPin(*Node, EGPD_Output);
if( ThenExecPin && (ThenExecPin->LinkedTo.Num() > 0) )
{
LatentTargetNode = ThenExecPin->LinkedTo[0]->GetOwningNode();
}
if (LatentTargetNode)
{
LatentTargetParamIndex = ParameterIndex;
}
}
}
// Make sure it isn't trying to modify a const term
if (Property->HasAnyPropertyFlags(CPF_OutParm) && !((*Term)->IsTermWritable()))
{
if (Property->HasAnyPropertyFlags(CPF_ReferenceParm))
{
if (!Property->HasAnyPropertyFlags(CPF_ConstParm))
{
CompilerContext.MessageLog.Error(*LOCTEXT("PassReadOnlyReferenceParam_Error", "Cannot pass a read-only variable to a reference parameter @@").ToString(), PinMatch);
}
}
else
{
CompilerContext.MessageLog.Error(*LOCTEXT("PassReadOnlyOutputParam_Error", "Cannot pass a read-only variable to a output parameter @@").ToString(), PinMatch);
}
}
}
else
{
CompilerContext.MessageLog.Error(*LOCTEXT("ResolveTermPassed_Error", "Failed to resolve term passed into @@").ToString(), PinMatch);
bMatchedAllParams = false;
}
}
else
{
bMatchedAllParams = false;
}
bFoundParam = true;
RemainingPins.RemoveAt(i);
}
}
if (!bFoundParam)
{
CompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("FindPinParameter_ErrorFmt", "Could not find a pin for the parameter {0} of {1} on @@"), FText::FromString(Property->GetName()), FText::FromString(Function->GetName())).ToString(), Node);
bMatchedAllParams = false;
}
}
// If we have pins remaining then it's either an error, or extra variadic terms that need to be emitted
if (RemainingPins.Num() > 0)
{
const bool bIsVariadic = Function->HasMetaData(FBlueprintMetadata::MD_Variadic);
if (bIsVariadic)
{
// Add a RHS term for every remaining pin
for (UEdGraphPin* RemainingPin : RemainingPins)
{
// Variadic pins are assumed to be wildcard pins that have been connected to something else
if (RemainingPin->LinkedTo.Num() == 0)
{
CompilerContext.MessageLog.Error(*LOCTEXT("UnlinkedVariadicPin_Error", "The variadic pin @@ must be connected. Connect something to @@.").ToString(), RemainingPin, RemainingPin->GetOwningNodeUnchecked());
continue;
}
UEdGraphPin* PinToTry = FEdGraphUtilities::GetNetFromPin(RemainingPin);
if (FBPTerminal** Term = Context.NetMap.Find(PinToTry))
{
FBPTerminal* RHSTerm = *Term;
RHSTerms.Add(RHSTerm);
}
else
{
CompilerContext.MessageLog.Error(*LOCTEXT("ResolveTermVariadic_Error", "Failed to resolve variadic term passed into @@").ToString(), RemainingPin);
bMatchedAllParams = false;
}
}
}
else
{
// At this point, we should have consumed all pins. If not, there are extras that need to be removed.
for (const UEdGraphPin* RemainingPin : RemainingPins)
{
CompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("PinMismatchParameter_ErrorFmt", "Pin @@ named {0} doesn't match any parameters of function {1}"), FText::FromName(RemainingPin->PinName), FText::FromString(Function->GetName())).ToString(), RemainingPin);
}
}
}
if (NumErrorsAtStart == CompilerContext.MessageLog.NumErrors)
{
// Build up a list of contexts that this function will be called on
TArray<FBPTerminal*> ContextTerms;
if (SelfPin)
{
const bool bIsConstSelfContext = Context.IsConstFunction();
const bool bIsNonConstFunction = !Function->HasAnyFunctionFlags(FUNC_Const|FUNC_Static);
const bool bEnforceConstCorrectness = Context.EnforceConstCorrectness();
auto CheckAndAddSelfTermLambda = [this, &Node, &ContextTerms, bIsConstSelfContext, bIsNonConstFunction, bEnforceConstCorrectness](FBPTerminal* Target)
{
bool bIsSelfTerm = true;
if(Target != nullptr)
{
const UEdGraphPin* SourcePin = Target->SourcePin;
bIsSelfTerm = (SourcePin == nullptr || CompilerContext.GetSchema()->IsSelfPin(*SourcePin));
}
// Ensure const correctness within the context of the function call:
// a) Attempting to call a non-const, non-static function within a const function graph (i.e. 'const self' as context)
// b) Attempting to call a non-const, non-static function with a 'const' term linked to the target pin as the function context
if(bIsSelfTerm && bIsConstSelfContext && bIsNonConstFunction)
{
// If we're not enforcing const correctness in this context, emit a warning here rather than an error, and allow compilation of this statement to proceed
if(Target != nullptr)
{
if(bEnforceConstCorrectness)
{
CompilerContext.MessageLog.Error(*LOCTEXT("NonConstFunctionCallOnReadOnlyTarget_Error", "Function @@ can modify state and cannot be called on @@ because it is a read-only Target in this context").ToString(), Node, Target->Source);
}
else
{
CompilerContext.MessageLog.Warning(*LOCTEXT("NonConstFunctionCallOnReadOnlyTarget_Warning", "Function @@ can modify state and should not be called on @@ because it is considered to be a read-only Target in this context").ToString(), Node, Target->Source);
}
}
else
{
if(bEnforceConstCorrectness)
{
CompilerContext.MessageLog.Error(*LOCTEXT("NonConstFunctionCallOnReadOnlySelfScope_Error", "Function @@ can modify state and cannot be called on 'self' because it is a read-only Target in this context").ToString(), Node);
}
else
{
CompilerContext.MessageLog.Warning(*LOCTEXT("NonConstFunctionCallOnReadOnlySelfScope_Warning", "Function @@ can modify state and should not be called on 'self' because it is considered to be a read-only Target in this context").ToString(), Node);
}
}
}
ContextTerms.Add(Target);
};
if( SelfPin->LinkedTo.Num() > 0 )
{
for(int32 i = 0; i < SelfPin->LinkedTo.Num(); i++)
{
FBPTerminal** pContextTerm = Context.NetMap.Find(SelfPin->LinkedTo[i]);
if(ensureMsgf(pContextTerm != nullptr, TEXT("'%s' is missing a target input - if this is a server build, the input may be a cosmetic only property which was discarded (if this is the case, and this is expecting component variable try resaving.)"), *Node->GetPathName()))
{
CheckAndAddSelfTermLambda(*pContextTerm);
}
}
}
else
{
FBPTerminal** pContextTerm = Context.NetMap.Find(SelfPin);
CheckAndAddSelfTermLambda((pContextTerm != nullptr) ? *pContextTerm : nullptr);
}
}
// Check for a call into the ubergraph, which will require a patchup later on for the exact state entry point
UEdGraphNode** pSrcEventNode = NULL;
if (!bIsLatent)
{
pSrcEventNode = CompilerContext.CallsIntoUbergraph.Find(Node);
}
// Iterate over all the contexts this functions needs to be called on, and emit a call function statement for each
FBlueprintCompiledStatement* LatentStatement = nullptr;
for (FBPTerminal* Target : ContextTerms)
{
// Currently, call site nodes will (incorrectly) expose the target pin as an interface type for calls to
// interface functions that are implemented by the owning class, so in that case we need to flag that the
// calling context is an interface if the target pin is also linked to an interface pin type (e.g. result
// of a cast node). Otherwise, we'll infer the wrong context type at runtime and corrupt the stack by
// reading an interface ptr (16 bytes) into an object ptr (8 bytes) when we process the context opcode.
const bool bIsInterfaceContextTerm = Target && Target->AssociatedVarProperty && Target->AssociatedVarProperty->IsA<FInterfaceProperty>();
UClass* FunctionOwnerClass = Function->GetOwnerClass();
const bool bIsInterfaceFunc = FunctionOwnerClass ? FunctionOwnerClass->HasAnyClassFlags(CLASS_Interface) : false;
FBlueprintCompiledStatement& Statement = Context.AppendStatementForNode(Node);
Statement.FunctionToCall = Function;
Statement.FunctionContext = Target;
Statement.Type = KCST_CallFunction;
Statement.bIsInterfaceContext = bIsInterfaceFunc || bIsInterfaceContextTerm;
Statement.bIsParentContext = Node->IsA<UK2Node_CallParentFunction>();
Statement.LHS = LHSTerm;
Statement.RHS = RHSTerms;
if (!bIsLatent)
{
// Fixup ubergraph calls
if (pSrcEventNode)
{
UEdGraphPin* ExecOut = CompilerContext.GetSchema()->FindExecutionPin(**pSrcEventNode, EGPD_Output);
check(CompilerContext.UbergraphContext);
CompilerContext.UbergraphContext->GotoFixupRequestMap.Add(&Statement, ExecOut);
Statement.UbergraphCallIndex = 0;
}
}
else
{
// Fixup latent functions
if (LatentTargetNode && (Target == ContextTerms.Last()))
{
check(LatentTargetParamIndex != INDEX_NONE);
Statement.UbergraphCallIndex = LatentTargetParamIndex;
Context.GotoFixupRequestMap.Add(&Statement, ThenExecPin);
LatentStatement = &Statement;
}
}
AdditionalCompiledStatementHandling(Context, Node, Statement);
if(Statement.Type == KCST_CallFunction && Function->HasAnyFunctionFlags(FUNC_Delegate))
{
CompilerContext.MessageLog.Error(*LOCTEXT("CallingDelegate_Error", "@@ is trying to call a delegate function - delegates cannot be called directly").ToString(), Node);
// Sanitize the statement, this would have ideally been detected earlier but we need
// to run AdditionalCompiledStatementHandling to satisify the DelegateNodeHandler
// implementation:
Statement.Type = KCST_CallDelegate;
}
}
{
for (const auto& It : ModifiedCastInputs)
{
FBPTerminal* LocalRHSTerm = It.Get<0>();
CastingUtils::FImplicitCastParams LocalInverseCastParams = It.Get<1>();
CastingUtils::InsertImplicitCastStatement(Context, LocalInverseCastParams, LocalRHSTerm);
}
}
// Create the exit from this node if there is one
if (bIsLatent)
{
// End this thread of execution; the latent function will resume it at some point in the future
FBlueprintCompiledStatement& PopStatement = Context.AppendStatementForNode(Node);
PopStatement.Type = KCST_EndOfThread;
}
else
{
// Generate the output impulse from this node
if (!UE::K2NodeCallFunction::Private::IsCalledFunctionPure(Node))
{
GenerateSimpleThenGoto(Context, *Node);
}
}
}
}
else
{
FString WarningMessage = FText::Format(LOCTEXT("FindFunction_ErrorFmt", "Could not find the function '{0}' called from @@"), FText::FromString(GetFunctionNameFromNode(Node).ToString())).ToString();
CompilerContext.MessageLog.Warning(*WarningMessage, Node);
}
}
UClass* FKCHandler_CallFunction::GetCallingContext(FKismetFunctionContext& Context, UEdGraphNode* Node)
{
// Find the calling scope
UClass* SearchScope = Context.NewClass;
if (UK2Node_CallParentFunction* ParentCall = Cast<UK2Node_CallParentFunction>(Node))
{
// Special Case: super call functions should search up their class hierarchy, and find the first legitimate implementation of the function
const FName FuncName = ParentCall->FunctionReference.GetMemberName();
UClass* SearchContext = Context.NewClass->GetSuperClass();
UFunction* ParentFunc = nullptr;
if (SearchContext)
{
ParentFunc = SearchContext->FindFunctionByName(FuncName);
}
return ParentFunc ? ParentFunc->GetOuterUClass() : nullptr;
}
else
{
if (UEdGraphPin* SelfPin = CompilerContext.GetSchema()->FindSelfPin(*Node, EGPD_Input))
{
SearchScope = Cast<UClass>(Context.GetScopeFromPinType(SelfPin->PinType, Context.NewClass));
}
}
return SearchScope;
}
UClass* FKCHandler_CallFunction::GetTrueCallingClass(FKismetFunctionContext& Context, UEdGraphPin* SelfPin)
{
if (SelfPin)
{
// TODO: here FBlueprintCompiledStatement::GetScopeFromPinType should be called, but since FEdGraphPinType::PinSubCategory is not always initialized properly that function works wrong
// return Cast<UClass>(Context.GetScopeFromPinType(SelfPin->PinType, Context.NewClass));
FEdGraphPinType& Type = SelfPin->PinType;
if ((Type.PinCategory == UEdGraphSchema_K2::PC_Object) || (Type.PinCategory == UEdGraphSchema_K2::PC_Class) || (Type.PinCategory == UEdGraphSchema_K2::PC_Interface))
{
if (!Type.PinSubCategory.IsNone() && (Type.PinSubCategory != UEdGraphSchema_K2::PSC_Self))
{
return Cast<UClass>(Type.PinSubCategoryObject.Get());
}
}
}
return Context.NewClass;
}
void FKCHandler_CallFunction::RegisterNets(FKismetFunctionContext& Context, UEdGraphNode* Node)
{
check(Node);
if (UFunction* Function = FindFunction(Context, Node))
{
TArray<FName> DefaultToSelfParamNames;
TArray<FName> RequiresSetValue;
if (Function->HasMetaData(FBlueprintMetadata::MD_DefaultToSelf))
{
const FName DefaltToSelfPinName = *Function->GetMetaData(FBlueprintMetadata::MD_DefaultToSelf);
DefaultToSelfParamNames.Add(DefaltToSelfPinName);
}
if (Function->HasMetaData(FBlueprintMetadata::MD_WorldContext))
{
UEdGraphSchema_K2 const* K2Schema = CompilerContext.GetSchema();
const bool bHasIntrinsicWorldContext = !K2Schema->IsStaticFunctionGraph(Context.SourceGraph) && FBlueprintEditorUtils::ImplementsGetWorld(Context.Blueprint);
const FName WorldContextPinName = *Function->GetMetaData(FBlueprintMetadata::MD_WorldContext);
if (bHasIntrinsicWorldContext)
{
DefaultToSelfParamNames.Add(WorldContextPinName);
}
else if (!Function->HasMetaData(FBlueprintMetadata::MD_CallableWithoutWorldContext))
{
RequiresSetValue.Add(WorldContextPinName);
}
}
for (UEdGraphPin* Pin : Node->Pins)
{
const bool bIsConnected = (Pin->LinkedTo.Num() != 0);
// if this pin could use a default (it doesn't have a connection or default of its own)
if (!bIsConnected && (Pin->DefaultObject == nullptr))
{
if (DefaultToSelfParamNames.Contains(Pin->PinName) && FKismetCompilerUtilities::ValidateSelfCompatibility(Pin, Context))
{
ensure(Pin->PinType.PinSubCategoryObject != nullptr);
ensure((Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Object) || (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Interface));
FBPTerminal* Term = Context.RegisterLiteral(Pin);
Term->Type.PinSubCategory = UEdGraphSchema_K2::PN_Self;
Context.NetMap.Add(Pin, Term);
}
else if (RequiresSetValue.Contains(Pin->PinName))
{
CompilerContext.MessageLog.Error(*NSLOCTEXT("KismetCompiler", "PinMustHaveConnection_Error", "Pin @@ must have a connection").ToString(), Pin);
}
}
}
}
for (UEdGraphPin* Pin : Node->Pins)
{
check(Pin);
if ((Pin->Direction != EGPD_Input) || (Pin->LinkedTo.Num() == 0))
{
continue;
}
// if we have an object plugged into an interface pin, let's create a
// term that'll be used as an intermediate, holding the result of a cast
// from object to interface
if (((Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Interface) && (Pin->LinkedTo[0]->PinType.PinCategory == UEdGraphSchema_K2::PC_Object)) ||
((Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Object) && (Pin->LinkedTo[0]->PinType.PinCategory == UEdGraphSchema_K2::PC_Interface)))
{
FBPTerminal* InterfaceTerm = Context.CreateLocalTerminal();
InterfaceTerm->CopyFromPin(Pin, Context.NetNameMap->MakeValidName(Pin, TEXT("CastInput")));
InterfaceTerm->Source = Node;
InterfaceTermMap.Add(Pin, InterfaceTerm);
}
}
FNodeHandlingFunctor::RegisterNets(Context, Node);
}
void FKCHandler_CallFunction::RegisterNet(FKismetFunctionContext& Context, UEdGraphPin* Net)
{
// This net is an output from a function call
FBPTerminal* Term = Context.CreateLocalTerminalFromPinAutoChooseScope(Net, Context.NetNameMap->MakeValidName(Net));
Context.NetMap.Add(Net, Term);
}
UFunction* FKCHandler_CallFunction::FindFunction(FKismetFunctionContext& Context, UEdGraphNode* Node)
{
UClass* CallingContext = GetCallingContext(Context, Node);
if (CallingContext)
{
const UBlueprint* BlueprintContext = UBlueprint::GetBlueprintFromClass(CallingContext);
// Redirect the calling context to the most up-to-date class (when not up-to-date,
// this will redirect to the Blueprint's skeleton class)
// It may be advisable to always do this branch in GetMostUpToDateClass, but
// being conservative:
if (!BlueprintContext || (BlueprintContext->bBeingCompiled && !FBlueprintCompilationManager::IsGeneratedClassLayoutReady()) || (!BlueprintContext->bBeingCompiled && !BlueprintContext->IsUpToDate()))
{
CallingContext = FBlueprintEditorUtils::GetMostUpToDateClass(CallingContext);
}
const FName FunctionName = GetFunctionNameFromNode(Node);
return CallingContext->FindFunctionByName(FunctionName);
}
return nullptr;
}
void FKCHandler_CallFunction::Transform(FKismetFunctionContext& Context, UEdGraphNode* Node)
{
// Add an object reference pin for this call
UK2Node_CallFunction* CallFuncNode = Cast<UK2Node_CallFunction>(Node);
if (!CallFuncNode)
{
return;
}
const bool bIsPure = CallFuncNode->IsNodePure();
bool bIsPureAndNoUsedOutputs = false;
if (bIsPure)
{
// Flag for removal if pure and there are no consumers of the outputs
//@TODO: This isn't recursive (and shouldn't be here), it'll just catch the last node in a line of pure junk
bool bAnyOutputsUsed = false;
for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex)
{
UEdGraphPin* Pin = Node->Pins[PinIndex];
if ((Pin->Direction == EGPD_Output) && (Pin->LinkedTo.Num() > 0))
{
bAnyOutputsUsed = true;
break;
}
}
if (!bAnyOutputsUsed)
{
//@TODO: Remove this node, not just warn about it
bIsPureAndNoUsedOutputs = true;
}
}
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
// Find the function, starting at the parent class
if (UFunction* Function = FindFunction(Context, Node))
{
const bool bIsLatent = Function->HasMetaData(FBlueprintMetadata::MD_Latent);
if (bIsLatent)
{
UEdGraphPin* OldOutPin = K2Schema->FindExecutionPin(*CallFuncNode, EGPD_Output);
if ((OldOutPin != NULL) && (OldOutPin->LinkedTo.Num() > 0))
{
// Create a dummy execution sequence that will be the target of the return call from the latent action
UK2Node_ExecutionSequence* DummyNode = CompilerContext.SpawnIntermediateNode<UK2Node_ExecutionSequence>(CallFuncNode);
DummyNode->AllocateDefaultPins();
// Wire in the dummy node
UEdGraphPin* NewInPin = K2Schema->FindExecutionPin(*DummyNode, EGPD_Input);
UEdGraphPin* NewOutPin = K2Schema->FindExecutionPin(*DummyNode, EGPD_Output);
if ((NewInPin != NULL) && (NewOutPin != NULL))
{
CompilerContext.MessageLog.NotifyIntermediatePinCreation(NewOutPin, OldOutPin);
while (OldOutPin->LinkedTo.Num() > 0)
{
UEdGraphPin* LinkedPin = OldOutPin->LinkedTo[0];
LinkedPin->BreakLinkTo(OldOutPin);
LinkedPin->MakeLinkTo(NewOutPin);
}
OldOutPin->MakeLinkTo(NewInPin);
}
}
}
/**
* This code is for property dirty tracking.
* It works by injecting in extra nodes while compiling that will call UNetPushModelHelpers::MarkPropertyDirtyFromRepIndex.
* See FKCPushModelHelpers::ConstructMarkDirtyNodeForProperty for node generation.
*
* If we're passing net properties as reference variables, there's no guarantee what the function will do,
* so we won't actually know whether or not the value has changed. In that case we'll go ahead
* and mark the property as dirty.
*/
// If the function is pure but won't actually be evaluated, if there are no out params,
// or there are no input pins, then we don't need to worry about any extra generation
// because there will either be no way to reference a NetProperty, or the node won't
// have any effect.
if (!bIsPureAndNoUsedOutputs && Function->NumParms > 0 && Function->HasAllFunctionFlags(FUNC_HasOutParms))
{
// We don't care about returns (non-ref out params, or const-ref out params), because those would
// require a Set call to change any net property, and that will already have the appropriate nodes
// generated.
//
// Additionally, we only need to care about pins that are actually connected to something.
TArray<UEdGraphPin*> RemainingPins;
RemainingPins.Reserve(Node->Pins.Num());
for (UEdGraphPin* Pin : Node->Pins)
{
if (Pin->Direction == EGPD_Input &&
!Pin->PinType.bIsConst &&
Pin->PinType.bIsReference &&
Pin->LinkedTo.Num() > 0)
{
RemainingPins.Add(Pin);
}
}
if (RemainingPins.Num() > 0)
{
FProperty* NetProperty = nullptr;
FProperty* FieldNotifyProperty = nullptr;
UEdGraphPin* PropertyObjectPin = nullptr;
UEdGraphPin* OldThenPin = CallFuncNode->GetThenPin();
// Note: This feels like it's going to be a very hot path during compilation as it will be hit for every
// CallFunction node. Any optimizations that can be made here are probably worth.
// Iterate the properties looking for Out Params that are tied to Net Properties.
// This is similar to the loop in CreateCallFunction
for (TFieldIterator<FProperty> It(Function); It; ++It)
{
if (UE::K2NodeCallFunction::Private::FindMatchingReferencedNetOrFieldNotifyPropertyAndPin(RemainingPins, *It, NetProperty, FieldNotifyProperty, PropertyObjectPin))
{
if (bIsPure)
{
// TODO: JDN - Reenable this when we can discern Const from Non Const Pure Refs.
// Don't need to cause BP Spam since no one is using this right now.
// CompilerContext.MessageLog.Warning(*NSLOCTEXT("KismetCompiler", "PurePushModel_Warning", "@@ is a pure node with references to a net property. The property may not be marked dirty in the correct frame. Consider making the function impure to solve the problem.").ToString(), Node);
break;
}
if (FieldNotifyProperty)
{
CompilerContext.MessageLog.Warning(*NSLOCTEXT("KismetCompiler", "RefFieldNotifyProperty_Warning", "@@ references a Field Notify property. The property may not be Broadcast correctly. Consider using a temporary variable.").ToString(), Node);
}
if (NetProperty)
{
if (UEdGraphNode* MarkPropertyDirtyNode = FKCPushModelHelpers::ConstructMarkDirtyNodeForProperty(Context, NetProperty, PropertyObjectPin))
{
// bool bWereNodesAdded = false;
UEdGraphPin* NewThenPin = MarkPropertyDirtyNode->FindPinChecked(UEdGraphSchema_K2::PN_Then);
UEdGraphPin* NewInPin = MarkPropertyDirtyNode->FindPinChecked(UEdGraphSchema_K2::PN_Execute);
if (ensure(NewThenPin) && ensure(NewInPin))
{
if (OldThenPin)
{
NewThenPin->CopyPersistentDataFromOldPin(*OldThenPin);
OldThenPin->BreakAllPinLinks();
OldThenPin->MakeLinkTo(NewInPin);
OldThenPin = NewThenPin;
// bWereNodesAdded = true;
}
else
{
// If there's no then pin, we'll instead insert the dirty nodes before the execution
// of function with the reference.
// This may do weird things with Latent Nodes, so warn about that.
if (bIsLatent)
{
CompilerContext.MessageLog.Warning(*NSLOCTEXT("KismetCompiler", "LatentPushModel_Warning", "@@ is a latent node with references to a net property. The property may not be marked dirty in the correct frame.").ToString(), Node);
}
UEdGraphPin* OldInPin = CallFuncNode->FindPin(UEdGraphSchema_K2::PN_Execute);
if (OldInPin)
{
NewInPin->CopyPersistentDataFromOldPin(*OldInPin);
OldInPin->BreakAllPinLinks();
NewThenPin->MakeLinkTo(OldInPin);
OldThenPin = NewThenPin;
// bWereNodesAdded = true;
}
}
}
/*
if (!bWereNodesAdded)
{
// TODO: JDN - Reenable this once we have other edge cases worked out.
// This warning is confusing / not necessarily actionable, and could contribute to spam,
// but no one currently relies on these features.
// CompilerContext.MessageLog.Warning(*NSLOCTEXT("KismetCompiler", "PushModelNoDirty_Warning", "@@ has reference to net properties, but we were unable to generate dirty nodes.").ToString(), Node);
}
*/
}
}
}
if (RemainingPins.Num() == 0)
{
break;
}
}
}
}
}
}
void FKCHandler_CallFunction::Compile(FKismetFunctionContext& Context, UEdGraphNode* Node)
{
check(NULL != Node);
//@TODO: Can probably move this earlier during graph verification instead of compilation, but after island pruning
if (!UE::K2NodeCallFunction::Private::IsCalledFunctionPure(Node))
{
// For imperative nodes, make sure the exec function was actually triggered and not just included due to an output data dependency
UEdGraphPin* ExecTriggeringPin = CompilerContext.GetSchema()->FindExecutionPin(*Node, EGPD_Input);
if (ExecTriggeringPin == NULL)
{
CompilerContext.MessageLog.Error(*NSLOCTEXT("KismetCompiler", "NoValidExecutionPinForCallFunc_Error", "@@ must have a valid execution pin").ToString(), Node);
return;
}
else if (ExecTriggeringPin->LinkedTo.Num() == 0)
{
CompilerContext.MessageLog.Warning(*NSLOCTEXT("KismetCompiler", "NodeNeverExecuted_Warning", "@@ will never be executed").ToString(), Node);
return;
}
}
// Validate the self pin again if it is disconnected, because pruning isolated nodes could have caused an invalid target
UEdGraphPin* SelfPin = CompilerContext.GetSchema()->FindSelfPin(*Node, EGPD_Input);
if (SelfPin && (SelfPin->LinkedTo.Num() == 0))
{
FEdGraphPinType SelfType;
SelfType.PinCategory = UEdGraphSchema_K2::PC_Object;
SelfType.PinSubCategory = UEdGraphSchema_K2::PSC_Self;
if (!CompilerContext.GetSchema()->ArePinTypesCompatible(SelfType, SelfPin->PinType, Context.NewClass) && (SelfPin->DefaultObject == NULL))
{
CompilerContext.MessageLog.Error(*NSLOCTEXT("KismetCompiler", "PinMustHaveConnectionPruned_Error", "Pin @@ must have a connection. Self pins cannot be connected to nodes that are culled.").ToString(), SelfPin);
}
}
// Make sure the function node is valid to call
CreateFunctionCallStatement(Context, Node, SelfPin);
}
void FKCHandler_CallFunction::CheckIfFunctionIsCallable(UFunction* Function, FKismetFunctionContext& Context, UEdGraphNode* Node)
{
// Verify that the function is a Blueprint callable function (in case a BlueprintCallable specifier got removed)
if (!Function->HasAnyFunctionFlags(FUNC_BlueprintCallable) && (Function->GetOuter() != Context.NewClass))
{
const bool bIsParentFunction = Node && Node->IsA<UK2Node_CallParentFunction>();
if (!bIsParentFunction && Function->GetName().Find(UEdGraphSchema_K2::FN_ExecuteUbergraphBase.ToString()))
{
CompilerContext.MessageLog.Error(*FText::Format(NSLOCTEXT("KismetCompiler", "ShouldNotCallFromBlueprint_ErrorFmt", "Function '{0}' called from @@ should not be called from a Blueprint"), FText::FromString(Function->GetName())).ToString(), Node);
}
}
}
// Get the name of the function to call from the node
FName FKCHandler_CallFunction::GetFunctionNameFromNode(UEdGraphNode* Node) const
{
UK2Node_CallFunction* CallFuncNode = Cast<UK2Node_CallFunction>(Node);
if (CallFuncNode)
{
return CallFuncNode->FunctionReference.GetMemberName();
}
else
{
CompilerContext.MessageLog.Error(*NSLOCTEXT("KismetCompiler", "UnableResolveFunctionName_Error", "Unable to resolve function name for @@").ToString(), Node);
return NAME_None;
}
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif
//////////////////////////////////////////////////////////////////////////
#undef LOCTEXT_NAMESPACE