// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "K2Node_FunctionEntry.h" #include "Engine/Blueprint.h" #include "Animation/AnimBlueprint.h" #include "UObject/UnrealType.h" #include "UObject/BlueprintsObjectVersion.h" #include "UObject/FrameworkObjectVersion.h" #include "UObject/StructOnScope.h" #include "Engine/UserDefinedStruct.h" #include "EdGraph/EdGraphSchema.h" #include "EdGraphSchema_K2.h" #include "K2Node_CallFunction.h" #include "K2Node_FunctionResult.h" #include "K2Node_MakeArray.h" #include "K2Node_MakeVariable.h" #include "K2Node_VariableSet.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/KismetEditorUtilities.h" #include "EdGraphUtilities.h" #include "BPTerminal.h" #include "UObject/PropertyPortFlags.h" #include "KismetCompilerMisc.h" #include "KismetCompiler.h" #include "Misc/OutputDeviceNull.h" #define LOCTEXT_NAMESPACE "K2Node_FunctionEntry" ////////////////////////////////////////////////////////////////////////// // FKCHandler_FunctionEntry class FKCHandler_FunctionEntry : public FNodeHandlingFunctor { public: FKCHandler_FunctionEntry(FKismetCompilerContext& InCompilerContext) : FNodeHandlingFunctor(InCompilerContext) { } void RegisterFunctionInput(FKismetFunctionContext& Context, UEdGraphPin* Net, UFunction* Function) { // This net is a parameter into the function FBPTerminal* Term = new FBPTerminal(); Context.Parameters.Add(Term); Term->CopyFromPin(Net, Net->PinName); // Flag pass by reference parameters specially //@TODO: Still doesn't handle/allow users to declare new pass by reference, this only helps inherited functions if( Function ) { if (UProperty* ParentProperty = FindField(Function, Net->PinName)) { if (ParentProperty->HasAnyPropertyFlags(CPF_ReferenceParm)) { Term->bPassedByReference = true; } } } Context.NetMap.Add(Net, Term); } virtual void RegisterNets(FKismetFunctionContext& Context, UEdGraphNode* Node) override { UK2Node_FunctionEntry* EntryNode = CastChecked(Node); UFunction* Function = EntryNode->FunctionReference.ResolveMember(EntryNode->GetBlueprintClassFromNode()); // if this function has a predefined signature (like for inherited/overridden // functions), then we want to make sure to account for the output // parameters - this is normally handled by the FunctionResult node, but // we're not guaranteed that one is connected to the entry node if (Function && Function->HasAnyFunctionFlags(FUNC_HasOutParms)) { const UEdGraphSchema_K2* K2Schema = GetDefault(); for (TFieldIterator ParamIt(Function, EFieldIteratorFlags::ExcludeSuper); ParamIt; ++ParamIt) { UProperty* ParamProperty = *ParamIt; // mirrored from UK2Node_FunctionResult::CreatePinsForFunctionEntryExit() const bool bIsFunctionInput = !ParamProperty->HasAnyPropertyFlags(CPF_OutParm) || ParamProperty->HasAnyPropertyFlags(CPF_ReferenceParm); if (bIsFunctionInput) { // continue; } FEdGraphPinType ParamType; if (K2Schema->ConvertPropertyToPinType(ParamProperty, ParamType)) { FString ParamName = ParamProperty->GetName(); bool bTermExists = false; // check to see if this terminal already exists (most // likely added by a FunctionResult node) - if so, then // we don't need to add it ourselves for (const FBPTerminal& ResultTerm : Context.Results) { if (ResultTerm.Name == ParamName && ResultTerm.Type == ParamType) { bTermExists = true; break; } } if (!bTermExists) { // create a terminal that represents a output param // for this function; if there is a FunctionResult // node wired into our function graph, know that it // will first check to see if this already exists // for it to use (rather than creating one of its own) FBPTerminal* ResultTerm = new FBPTerminal(); Context.Results.Add(ResultTerm); ResultTerm->Name = ParamName; ResultTerm->Type = ParamType; ResultTerm->bPassedByReference = ParamType.bIsReference; ResultTerm->SetContextTypeStruct(ParamType.PinCategory == UEdGraphSchema_K2::PC_Struct && Cast(ParamType.PinSubCategoryObject.Get())); } } } } for (UEdGraphPin* Pin : Node->Pins) { if (Pin->ParentPin == nullptr && !CompilerContext.GetSchema()->IsMetaPin(*Pin)) { UEdGraphPin* Net = FEdGraphUtilities::GetNetFromPin(Pin); if (Context.NetMap.Find(Net) == NULL) { // New net, resolve the term that will be used to construct it FBPTerminal* Term = NULL; check(Net->Direction == EGPD_Output); RegisterFunctionInput(Context, Pin, Function); } } } } virtual void Compile(FKismetFunctionContext& Context, UEdGraphNode* Node) override { UK2Node_FunctionEntry* EntryNode = CastChecked(Node); //check(EntryNode->SignatureName != NAME_None); if (EntryNode->FunctionReference.GetMemberName() == UEdGraphSchema_K2::FN_ExecuteUbergraphBase) { UEdGraphPin* EntryPointPin = Node->FindPin(UEdGraphSchema_K2::PN_EntryPoint); FBPTerminal** pTerm = Context.NetMap.Find(EntryPointPin); if ((EntryPointPin != NULL) && (pTerm != NULL)) { FBlueprintCompiledStatement& ComputedGotoStatement = Context.AppendStatementForNode(Node); ComputedGotoStatement.Type = KCST_ComputedGoto; ComputedGotoStatement.LHS = *pTerm; } else { CompilerContext.MessageLog.Error(*LOCTEXT("NoEntryPointPin_Error", "Expected a pin named EntryPoint on @@").ToString(), Node); } } else { // Generate the output impulse from this node GenerateSimpleThenGoto(Context, *Node); } } virtual bool RequiresRegisterNetsBeforeScheduling() const override { return true; } }; struct FFunctionEntryHelper { static const FName& GetWorldContextPinName() { static const FName WorldContextPinName(TEXT("__WorldContext")); return WorldContextPinName; } static bool RequireWorldContextParameter(const UK2Node_FunctionEntry* Node) { const UEdGraphSchema_K2* K2Schema = GetDefault(); return K2Schema->IsStaticFunctionGraph(Node->GetGraph()); } }; UK2Node_FunctionEntry::UK2Node_FunctionEntry(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { // Enforce const-correctness by default bEnforceConstCorrectness = true; } void UK2Node_FunctionEntry::PreSave(const class ITargetPlatform* TargetPlatform) { Super::PreSave(TargetPlatform); const UBlueprint* Blueprint = HasValidBlueprint() ? GetBlueprint() : nullptr; if (LocalVariables.Num() > 0 && Blueprint) { // This code is here as it's unsafe to call when GIsSavingPackage is true UFunction* Function = FindField(Blueprint->SkeletonGeneratedClass, *GetOuter()->GetName()); if (Function) { if (Function->GetStructureSize() > 0 || !ensure(Function->PropertyLink == nullptr)) { TSharedPtr LocalVarData = MakeShareable(new FStructOnScope(Function)); for (TFieldIterator It(Function); It; ++It) { if (const UProperty* Property = *It) { const UStructProperty* PotentialUDSProperty = Cast(Property); // UDS requires default data even when the LocalVariable value is empty for (FBPVariableDescription& LocalVar : LocalVariables) { if (LocalVar.VarName == Property->GetFName() && !LocalVar.DefaultValue.IsEmpty()) { // Go to property and back, this handles redirector fixup and will sanitize the output // The asset registry only knows about these references because when the node is expanded it turns into a hard reference FBlueprintEditorUtils::PropertyValueFromString(Property, LocalVar.DefaultValue, LocalVarData->GetStructMemory()); FBlueprintEditorUtils::PropertyValueToString(Property, LocalVarData->GetStructMemory(), LocalVar.DefaultValue); } } } } } } } } void UK2Node_FunctionEntry::Serialize(FArchive& Ar) { Super::Serialize(Ar); Ar.UsingCustomVersion(FBlueprintsObjectVersion::GUID); if (Ar.IsSaving()) { for (FBPVariableDescription& LocalVariable : LocalVariables) { if (!LocalVariable.DefaultValue.IsEmpty()) { // If looking for references during save, expand any default values on the local variables // This is only reliable when saving in the editor, the cook case is handled below if (Ar.IsObjectReferenceCollector() && LocalVariable.VarType.PinCategory == UEdGraphSchema_K2::PC_Struct && LocalVariable.VarType.PinSubCategoryObject.IsValid()) { UScriptStruct* Struct = Cast(LocalVariable.VarType.PinSubCategoryObject.Get()); if (Struct) { TSharedPtr StructData = MakeShareable(new FStructOnScope(Struct)); // Import the literal text to a dummy struct and then serialize that. Hard object references will not properly import, this is only useful for soft references! FOutputDeviceNull NullOutput; Struct->ImportText(*LocalVariable.DefaultValue, StructData->GetStructMemory(), nullptr, PPF_SerializedAsImportText, &NullOutput, LocalVariable.VarName.ToString()); Struct->SerializeItem(Ar, StructData->GetStructMemory(), nullptr); } } if (LocalVariable.VarType.PinCategory == UEdGraphSchema_K2::PC_SoftObject || LocalVariable.VarType.PinCategory == UEdGraphSchema_K2::PC_SoftClass) { FSoftObjectPath TempRef(LocalVariable.DefaultValue); // Serialize the asset reference, this will do the save fixup. It won't actually serialize the string if this is a real archive like linkersave FSoftObjectPathSerializationScope DisableSerialize(NAME_None, NAME_None, ESoftObjectPathCollectType::AlwaysCollect, ESoftObjectPathSerializeType::SkipSerializeIfArchiveHasSize); Ar << TempRef; LocalVariable.DefaultValue = TempRef.ToString(); } } } } else if (Ar.IsLoading()) { if (Ar.CustomVer(FFrameworkObjectVersion::GUID) < FFrameworkObjectVersion::LocalVariablesBlueprintVisible) { for (FBPVariableDescription& LocalVariable : LocalVariables) { LocalVariable.PropertyFlags |= CPF_BlueprintVisible; } } if (Ar.UE4Ver() < VER_UE4_BLUEPRINT_ENFORCE_CONST_IN_FUNCTION_OVERRIDES || ((Ar.CustomVer(FFrameworkObjectVersion::GUID) < FFrameworkObjectVersion::EnforceConstInAnimBlueprintFunctionGraphs) && GetBlueprint()->IsA())) { // Allow legacy implementations to violate const-correctness bEnforceConstCorrectness = false; } if (Ar.CustomVer(FBlueprintsObjectVersion::GUID) < FBlueprintsObjectVersion::CleanBlueprintFunctionFlags) { // Flags we explicitly use ExtraFlags for (at the time this fix was made): // FUNC_Public, FUNC_Protected, FUNC_Private, // FUNC_Static, FUNC_Const, // FUNC_BlueprintPure, FUNC_BlueprintCallable, FUNC_BlueprintEvent, FUNC_BlueprintAuthorityOnly, // FUNC_Net, FUNC_NetMulticast, FUNC_NetServer, FUNC_NetClient, FUNC_NetReliable // // FUNC_Exec, FUNC_Event, & FUNC_BlueprintCosmetic are all inherited // in FKismetCompilerContext::PrecompileFunction() static const uint32 InvalidExtraFlagsMask = FUNC_Final | FUNC_RequiredAPI | FUNC_BlueprintCosmetic | FUNC_NetRequest | FUNC_Exec | FUNC_Native | FUNC_Event | FUNC_NetResponse | FUNC_MulticastDelegate | FUNC_Delegate | FUNC_HasOutParms | FUNC_HasDefaults | FUNC_DLLImport | FUNC_NetValidate; ExtraFlags &= ~InvalidExtraFlagsMask; } if (Ar.CustomVer(FFrameworkObjectVersion::GUID) < FFrameworkObjectVersion::ChangeAssetPinsToString) { const UEdGraphSchema_K2* K2Schema = GetDefault(); // Prior to this version, changing the type of a local variable would lead to corrupt default value strings for (FBPVariableDescription& LocalVar : LocalVariables) { FString UseDefaultValue; UObject* UseDefaultObject = nullptr; FText UseDefaultText; if (!LocalVar.DefaultValue.IsEmpty()) { K2Schema->GetPinDefaultValuesFromString(LocalVar.VarType, this, LocalVar.DefaultValue, UseDefaultValue, UseDefaultObject, UseDefaultText); FString ErrorMessage; if (!K2Schema->DefaultValueSimpleValidation(LocalVar.VarType, LocalVar.VarName, UseDefaultValue, UseDefaultObject, UseDefaultText, &ErrorMessage)) { const UBlueprint* Blueprint = GetBlueprint(); UE_LOG(LogBlueprint, Log, TEXT("Clearing invalid default value for local variable %s on blueprint %s: %s"), *LocalVar.VarName.ToString(), Blueprint ? *Blueprint->GetName() : TEXT("Unknown"), *ErrorMessage); LocalVar.DefaultValue.Reset(); } } } } // In editor, fixup soft object ptrs on load on to handle redirects and finding refs for cooking // We're not handling soft object ptrs inside FStructs because it's a rare edge case and would be a performance hit on load if (GIsEditor) { FSoftObjectPathSerializationScope SetPackage(GetOutermost()->GetFName(), NAME_None, ESoftObjectPathCollectType::AlwaysCollect, ESoftObjectPathSerializeType::SkipSerializeIfArchiveHasSize); for (FBPVariableDescription& LocalVariable : LocalVariables) { if (!LocalVariable.DefaultValue.IsEmpty() && (LocalVariable.VarType.PinCategory == UEdGraphSchema_K2::PC_SoftObject || LocalVariable.VarType.PinCategory == UEdGraphSchema_K2::PC_SoftClass)) { FSoftObjectPath TempRef(LocalVariable.DefaultValue); TempRef.PostLoadPath(&Ar); TempRef.PreSavePath(); LocalVariable.DefaultValue = TempRef.ToString(); } } } } } FText UK2Node_FunctionEntry::GetNodeTitle(ENodeTitleType::Type TitleType) const { UEdGraph* Graph = GetGraph(); FGraphDisplayInfo DisplayInfo; Graph->GetSchema()->GetGraphDisplayInformation(*Graph, DisplayInfo); return DisplayInfo.DisplayName; } void UK2Node_FunctionEntry::AllocateDefaultPins() { CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then); if (UFunction* Function = FunctionReference.ResolveMember(GetBlueprintClassFromNode())) { CreatePinsForFunctionEntryExit(Function, /*bIsFunctionEntry=*/ true); } Super::AllocateDefaultPins(); if (FFunctionEntryHelper::RequireWorldContextParameter(this) && ensure(!FindPin(FFunctionEntryHelper::GetWorldContextPinName()))) { UEdGraphPin* WorldContextPin = CreatePin( EGPD_Output, UEdGraphSchema_K2::PC_Object, UObject::StaticClass(), FFunctionEntryHelper::GetWorldContextPinName()); WorldContextPin->bHidden = true; } } UEdGraphPin* UK2Node_FunctionEntry::GetAutoWorldContextPin() const { return FindPin(FFunctionEntryHelper::GetWorldContextPinName()); } void UK2Node_FunctionEntry::RemoveOutputPin(UEdGraphPin* PinToRemove) { UK2Node_FunctionEntry* OwningSeq = Cast( PinToRemove->GetOwningNode() ); if (OwningSeq) { PinToRemove->MarkPendingKill(); OwningSeq->Pins.Remove(PinToRemove); } } bool UK2Node_FunctionEntry::CanCreateUserDefinedPin(const FEdGraphPinType& InPinType, EEdGraphPinDirection InDesiredDirection, FText& OutErrorMessage) { bool bResult = Super::CanCreateUserDefinedPin(InPinType, InDesiredDirection, OutErrorMessage); if (bResult) { if(InDesiredDirection == EGPD_Input) { OutErrorMessage = LOCTEXT("AddInputPinError", "Cannot add input pins to function entry node!"); bResult = false; } } return bResult; } UEdGraphPin* UK2Node_FunctionEntry::CreatePinFromUserDefinition(const TSharedPtr NewPinInfo) { // Make sure that if this is an exec node we are allowed one. const UEdGraphSchema_K2* Schema = GetDefault(); if (NewPinInfo->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec && !CanModifyExecutionWires()) { return nullptr; } UEdGraphPin* NewPin = CreatePin(EGPD_Output, NewPinInfo->PinType, NewPinInfo->PinName); Schema->SetPinAutogeneratedDefaultValue(NewPin, NewPinInfo->PinDefaultValue); return NewPin; } FNodeHandlingFunctor* UK2Node_FunctionEntry::CreateNodeHandler(FKismetCompilerContext& CompilerContext) const { return new FKCHandler_FunctionEntry(CompilerContext); } void UK2Node_FunctionEntry::GetRedirectPinNames(const UEdGraphPin& Pin, TArray& RedirectPinNames) const { Super::GetRedirectPinNames(Pin, RedirectPinNames); if(RedirectPinNames.Num() > 0) { const FString OldPinName = RedirectPinNames[0]; // first add functionname.param const FName SignatureName = FunctionReference.GetMemberName(); RedirectPinNames.Add(FString::Printf(TEXT("%s.%s"), *SignatureName.ToString(), *OldPinName)); // if there is class, also add an option for class.functionname.param if(UClass const* SignatureClass = FunctionReference.GetMemberParentClass()) { RedirectPinNames.Add(FString::Printf(TEXT("%s.%s.%s"), *SignatureClass->GetName(), *SignatureName.ToString(), *OldPinName)); } } } bool UK2Node_FunctionEntry::IsDeprecated() const { if (UFunction* const Function = FunctionReference.ResolveMember(GetBlueprintClassFromNode())) { return Function->HasMetaData(FBlueprintMetadata::MD_DeprecatedFunction); } return false; } FString UK2Node_FunctionEntry::GetDeprecationMessage() const { if (UFunction* const Function = FunctionReference.ResolveMember(GetBlueprintClassFromNode())) { if (Function->HasMetaData(FBlueprintMetadata::MD_DeprecationMessage)) { return FString::Printf(TEXT("%s %s"), *LOCTEXT("FunctionDeprecated_Warning", "@@ is deprecated;").ToString(), *Function->GetMetaData(FBlueprintMetadata::MD_DeprecationMessage)); } } return Super::GetDeprecationMessage(); } FText UK2Node_FunctionEntry::GetTooltipText() const { if (UFunction* const Function = FunctionReference.ResolveMember(GetBlueprintClassFromNode())) { return FText::FromString(UK2Node_CallFunction::GetDefaultTooltipForFunction(Function)); } return Super::GetTooltipText(); } int32 UK2Node_FunctionEntry::GetFunctionFlags() const { int32 ReturnFlags = 0; if (UFunction* const Function = FunctionReference.ResolveMember(GetBlueprintClassFromNode())) { ReturnFlags = Function->FunctionFlags; } return ReturnFlags | ExtraFlags; } void UK2Node_FunctionEntry::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) { Super::ExpandNode(CompilerContext, SourceGraph); const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema(); UEdGraphPin* OldStartExecPin = nullptr; if(Pins[0]->LinkedTo.Num()) { OldStartExecPin = Pins[0]->LinkedTo[0]; } UEdGraphPin* LastActiveOutputPin = Pins[0]; // Only look for FunctionEntry nodes who were duplicated and have a source object if ( UK2Node_FunctionEntry* OriginalNode = Cast(CompilerContext.MessageLog.FindSourceObject(this)) ) { check(OriginalNode->GetOuter()); // Find the associated UFunction UFunction* Function = FindField(CompilerContext.Blueprint->SkeletonGeneratedClass, *OriginalNode->GetOuter()->GetName()); // When regenerating on load, we may need to import text on certain properties to force load the assets TSharedPtr LocalVarData; if (Function && CompilerContext.Blueprint->bIsRegeneratingOnLoad) { if (Function->GetStructureSize() > 0 || !ensure(Function->PropertyLink == nullptr)) { LocalVarData = MakeShareable(new FStructOnScope(Function)); } } for (TFieldIterator It(Function); It; ++It) { if (const UProperty* Property = *It) { const UStructProperty* PotentialUDSProperty = Cast(Property); for (const FBPVariableDescription& LocalVar : LocalVariables) { if (LocalVar.VarName == Property->GetFName() && !LocalVar.DefaultValue.IsEmpty()) { // Add a variable set node for the local variable and hook it up immediately following the entry node or the last added local variable UK2Node_VariableSet* VariableSetNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); VariableSetNode->SetFromProperty(Property, false); Schema->ConfigureVarNode(VariableSetNode, LocalVar.VarName, Function, CompilerContext.Blueprint); VariableSetNode->AllocateDefaultPins(); if(UEdGraphPin* SetPin = VariableSetNode->FindPin(Property->GetFName())) { if(LocalVar.VarType.IsArray()) { TSharedPtr StructData = MakeShareable(new FStructOnScope(Function)); FBlueprintEditorUtils::PropertyValueFromString(Property, LocalVar.DefaultValue, StructData->GetStructMemory()); // Create a Make Array node to setup the array's defaults UK2Node_MakeArray* MakeArray = CompilerContext.SpawnIntermediateNode(this, SourceGraph); MakeArray->AllocateDefaultPins(); MakeArray->GetOutputPin()->MakeLinkTo(SetPin); MakeArray->PostReconstructNode(); const UArrayProperty* ArrayProperty = Cast(Property); check(ArrayProperty); FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, StructData->GetStructMemory()); FScriptArrayHelper_InContainer DefaultArrayHelper(ArrayProperty, StructData->GetStructMemory()); // Go through each element in the array to set the default value for( int32 ArrayIndex = 0 ; ArrayIndex < ArrayHelper.Num() ; ArrayIndex++ ) { uint8* PropData = ArrayHelper.GetRawPtr(ArrayIndex); // Retrieve the element's default value FString DefaultValue; FBlueprintEditorUtils::PropertyValueToString(ArrayProperty->Inner, PropData, DefaultValue); if(ArrayIndex > 0) { MakeArray->AddInputPin(); } // Add one to the index for the pin to set the default on to skip the output pin Schema->TrySetDefaultValue(*MakeArray->Pins[ArrayIndex + 1], DefaultValue); } } else if(LocalVar.VarType.IsSet() || LocalVar.VarType.IsMap()) { UK2Node_MakeVariable* MakeVariableNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); MakeVariableNode->SetupVariable(LocalVar, SetPin, CompilerContext, Function, Property); } else { if (CompilerContext.Blueprint->bIsRegeneratingOnLoad) { // When regenerating on load, we want to force load assets referenced by local variables. // This functionality is already handled when generating Terms in the Kismet Compiler for Arrays and Structs, so we do not have to worry about them. if (LocalVar.VarType.PinCategory == UEdGraphSchema_K2::PC_Object || LocalVar.VarType.PinCategory == UEdGraphSchema_K2::PC_Class || LocalVar.VarType.PinCategory == UEdGraphSchema_K2::PC_Interface) { FBlueprintEditorUtils::PropertyValueFromString(Property, LocalVar.DefaultValue, LocalVarData->GetStructMemory()); } } // Set the default value Schema->TrySetDefaultValue(*SetPin, LocalVar.DefaultValue); } } LastActiveOutputPin->BreakAllPinLinks(); LastActiveOutputPin->MakeLinkTo(VariableSetNode->Pins[0]); LastActiveOutputPin = VariableSetNode->Pins[1]; } } } } // Finally, hook up the last node to the old node the function entry node was connected to if(OldStartExecPin) { LastActiveOutputPin->MakeLinkTo(OldStartExecPin); } } } void UK2Node_FunctionEntry::PostReconstructNode() { Super::PostReconstructNode(); } bool UK2Node_FunctionEntry::ModifyUserDefinedPinDefaultValue(TSharedPtr PinInfo, const FString& NewDefaultValue) { if (Super::ModifyUserDefinedPinDefaultValue(PinInfo, NewDefaultValue)) { const UEdGraphSchema_K2* K2Schema = GetDefault(); K2Schema->HandleParameterDefaultValueChanged(this); return true; } return false; } bool UK2Node_FunctionEntry::ShouldUseConstRefParams() const { // Interface functions with no outputs will be implemented as events. As with native interface functions with no outputs, the entry // node is expected to use 'const Type&' for input parameters that are passed by reference. See UEditablePinBase::PostLoad() for details. if (const UEdGraph* OwningGraph = GetGraph()) { const UBlueprint* OwningBlueprint = FBlueprintEditorUtils::FindBlueprintForGraph(OwningGraph); if (OwningBlueprint && OwningBlueprint->BlueprintType == BPTYPE_Interface) { // Find paired result node and check for outputs. for (UEdGraphNode* Node : OwningGraph->Nodes) { if (UK2Node_FunctionResult* ResultNode = Cast(Node)) { // This might be called from the super's Serialize() method for older assets, so make sure the result node's pins have been loaded. if (ResultNode->HasAnyFlags(RF_NeedLoad)) { GetLinker()->Preload(ResultNode); } return ResultNode->UserDefinedPins.Num() == 0; } } // No result node, so there are no outputs. return true; } } return false; } #undef LOCTEXT_NAMESPACE