// Copyright Epic Games, Inc. All Rights Reserved. #include "Bindings/MVVMConversionFunctionHelper.h" #include "Bindings/MVVMBindingHelper.h" #include "Bindings/MVVMFieldPathHelper.h" #include "Blueprint/WidgetBlueprintGeneratedClass.h" #include "Components/Widget.h" #include "Containers/Deque.h" #include "MVVMBlueprintView.h" #include "MVVMBlueprintViewConversionFunction.h" #include "MVVMFunctionGraphHelper.h" #include "MVVMWidgetBlueprintExtension_View.h" #include "UObject/MetaData.h" #include "K2Node_BaseAsyncTask.h" #include "K2Node_BreakStruct.h" #include "K2Node_CallFunction.h" #include "K2Node_Event.h" #include "K2Node_FunctionEntry.h" #include "K2Node_FunctionResult.h" #include "K2Node_GeneratedBoundEvent.h" #include "K2Node_Self.h" #include "K2Node_VariableGet.h" #include "K2Node_VariableSet.h" #include "Kismet2/BlueprintEditorUtils.h" #include "MVVMConversionFunctionGraphSchema.h" #include "Node/MVVMK2Node_AreSourcesValidForBinding.h" #define LOCTEXT_NAMESPACE "MVVMConversionFunctionHelper" namespace UE::MVVM::ConversionFunctionHelper { namespace Private { static const FLazyName ConversionFunctionMetadataKey = "ConversionFunction"; static const FStringView AutoPromoteFunctionMetadataKey = TEXT("AutoPromoteFunction"); static const FLazyName ConversionFunctionCategory = "Conversion Functions"; namespace NamedNodes { static const FLazyName GeneratedSetter = "MVVM_Named_Node_Generated_Setter"; static const FLazyName GeneratedCallFunction = "MVVM_Named_Node_Generated_CallFunction"; } UMVVMBlueprintView* GetView(const UBlueprint* Blueprint) { const TObjectPtr* ExtensionViewPtr = Blueprint->GetExtensions().FindByPredicate([](const UBlueprintExtension* Other) { return Other && Other->GetClass() == UMVVMWidgetBlueprintExtension_View::StaticClass(); }); if (ExtensionViewPtr) { return CastChecked(*ExtensionViewPtr)->GetBlueprintView(); } return nullptr; } bool IsSystemInputPin(const UEdGraphPin* Pin) { return Pin && Pin->PinName != UEdGraphSchema_K2::PN_Execute && Pin->Direction == EGPD_Input && (!Pin->bOrphanedPin || Pin->ShouldSavePinIfOrphaned()) && !Pin->bHidden; } void MarkAsConversionFunction(const UK2Node* FunctionNode, const UEdGraph* Graph) { check(FunctionNode != nullptr); FunctionNode->GetPackage()->GetMetaData().SetValue(FunctionNode, ConversionFunctionMetadataKey.Resolve(), TEXT("")); } UK2Node_FunctionEntry* FindFunctionEntry(const UEdGraph* Graph) { for (UEdGraphNode* Node : Graph->Nodes) { if (UK2Node_FunctionEntry* FunctionEntry = Cast(Node)) { return FunctionEntry; } } return nullptr; } UK2Node_Event* FindEventEntry(const UEdGraph* Graph) { for (UEdGraphNode* Node : Graph->Nodes) { if (UK2Node_Event* EventEntry = Cast(Node)) { return EventEntry; } } return nullptr; } UK2Node_FunctionResult* FindFunctionResult(const UEdGraph* Graph) { for (UEdGraphNode* Node : Graph->Nodes) { if (UK2Node_FunctionResult* FunctionResult = Cast(Node)) { return FunctionResult; } } return nullptr; } struct FCreateGraphResult { UEdGraph* FunctionGraph = nullptr; UK2Node_Event* EventEntry = nullptr; UK2Node_FunctionEntry* FunctionEntry = nullptr; UK2Node_FunctionResult* FunctionResult = nullptr; }; struct FCreateGraphParams { bool bIsConst = false; bool bIsEditable = false; bool bAddToBlueprint = false; /** If true implies this graph will create events*/ bool bCreateUbergraphPage = false; }; FCreateGraphResult CreateGraph(UBlueprint* Blueprint, FName GraphName, const UFunction* FunctionEntryDefinition, FCreateGraphParams InParams) { if (UObject* ExistingObject = StaticFindObject(nullptr, Blueprint, *GraphName.ToString(), true)) { FunctionGraphHelper::RenameObjectToTransientPackage(ExistingObject); } FName UniqueFunctionName = FBlueprintEditorUtils::FindUniqueKismetName(Blueprint, GraphName.ToString()); // Ubergraph pages create multicast delegate variables of the same name during BP Skeleton generation prior to graph generation // These will cause name conflicts, so don't require unique names for ubergraphs if (InParams.bCreateUbergraphPage) { UniqueFunctionName = GraphName; } ensure(GraphName == UniqueFunctionName); // Create function graph UEdGraph* FunctionGraph = FBlueprintEditorUtils::CreateNewGraph(Blueprint, UniqueFunctionName, UEdGraph::StaticClass(), UMVVMConversionFunctionGraphSchema::StaticClass()); ensure(FunctionGraph->GetFName() == GraphName); FunctionGraph->bEditable = InParams.bIsEditable; if (InParams.bAddToBlueprint) { Blueprint->FunctionGraphs.Add(FunctionGraph); } else { FunctionGraph->SetFlags(RF_Transient); } const UEdGraphSchema_K2* Schema = GetDefault(); Schema->MarkFunctionEntryAsEditable(FunctionGraph, InParams.bIsEditable); Schema->CreateDefaultNodesForGraph(*FunctionGraph); FCreateGraphResult Result; // Function entry node if (!InParams.bCreateUbergraphPage) { FGraphNodeCreator FunctionEntryCreator(*FunctionGraph); UK2Node_FunctionEntry* FunctionEntry = FunctionEntryCreator.CreateNode(); if (FunctionEntryDefinition) { UClass* OwnerClass = FunctionEntryDefinition->GetOwnerClass(); FunctionEntry->FunctionReference.SetExternalMember(FunctionEntryDefinition->GetFName(), OwnerClass); FunctionEntry->CustomGeneratedFunctionName = GraphName; } else { FunctionEntry->FunctionReference.SetSelfMember(FunctionGraph->GetFName()); } if (InParams.bIsConst) { FunctionEntry->AddExtraFlags(FUNC_BlueprintCallable | FUNC_BlueprintPure | FUNC_Const | FUNC_Protected | FUNC_Final); } else { FunctionEntry->AddExtraFlags(FUNC_BlueprintCallable | FUNC_Protected | FUNC_Final); } FunctionEntry->bIsEditable = InParams.bIsEditable; FunctionEntry->MetaData.Category = FText::FromName(ConversionFunctionCategory.Resolve()); FunctionEntry->NodePosX = -500; FunctionEntry->NodePosY = 0; FunctionEntryCreator.Finalize(); FGraphNodeCreator FunctionResultCreator(*FunctionGraph); UK2Node_FunctionResult* FunctionResult = FunctionResultCreator.CreateNode(); FunctionResult->FunctionReference.SetSelfMember(FunctionGraph->GetFName()); FunctionResult->bIsEditable = InParams.bIsEditable; FunctionResult->NodePosX = 500; FunctionResult->NodePosY = 0; FunctionResultCreator.Finalize(); Result.FunctionEntry = FunctionEntry; Result.FunctionResult = FunctionResult; } else { FGraphNodeCreator EventEntryCreator(*FunctionGraph); UK2Node_GeneratedBoundEvent* EventEntry = EventEntryCreator.CreateNode(); EventEntry->EventReference.SetSelfMember(FunctionGraph->GetFName()); EventEntry->bIsEditable = InParams.bIsEditable; EventEntry->CustomFunctionName = UE::MVVM::BindingHelper::GetDelegateSignatureName(FunctionGraph->GetFName()); EventEntry->NodePosX = -750; EventEntry->NodePosY = 0; EventEntryCreator.Finalize(); Result.EventEntry = EventEntry; } Result.FunctionGraph = FunctionGraph; return Result; } TArray> GetPropertyPathGraphNode(const UEdGraphPin* StartPin) { TArray> NodesInPath; auto AddNode = [&NodesInPath](const UEdGraphPin* Pin) { UEdGraphNode* Result = nullptr; if (Private::IsSystemInputPin(Pin) && Pin->LinkedTo.Num() == 1) { Result = Pin->LinkedTo[0]->GetOwningNode(); if (Result && !IsAutoPromoteNode(Result)) { NodesInPath.Emplace(Result, Pin->LinkedTo[0]); } } return Result; }; UEdGraphNode* CurrentNode = AddNode(StartPin); while (CurrentNode) { TArray& Pins = CurrentNode->Pins; CurrentNode = nullptr; for (UEdGraphPin* Pin : Pins) { if (UEdGraphNode* NewNode = AddNode(Pin)) { CurrentNode = NewNode; break; } } } Algo::Reverse(NodesInPath); return NodesInPath; } FMVVMBlueprintPropertyPath GetPropertyPathForPin(const UBlueprint* Blueprint, const UEdGraphPin* StartPin, bool bSkipResolve) { if (!IsInputPin(StartPin)) { return FMVVMBlueprintPropertyPath(); } UMVVMBlueprintView* BlueprintView = GetView(Blueprint); if (BlueprintView == nullptr) { return FMVVMBlueprintPropertyPath(); } FMVVMBlueprintPropertyPath ResultPath; auto AddRoot = [&ResultPath, BlueprintView, Blueprint, bSkipResolve](FMemberReference& Member) { if (bSkipResolve) { // if the generated class hasn't yet been generated we can blindly forge ahead and try to figure out if it's a widget or a viewmodel if (const FMVVMBlueprintViewModelContext* ViewModel = BlueprintView->FindViewModel(Member.GetMemberName())) { ResultPath.SetViewModelId(ViewModel->GetViewModelId()); } else { ResultPath.SetWidgetName(Member.GetMemberName()); ensure(Member.GetMemberName() != Blueprint->GetFName()); } } else { if (const FObjectProperty* Property = CastField(Member.ResolveMember(Blueprint->SkeletonGeneratedClass))) { if (Property->PropertyClass->IsChildOf() || Property->PropertyClass->IsChildOf()) { ResultPath.SetWidgetName(Property->GetFName()); ensure(Property->GetFName() != Blueprint->GetFName()); } else if (Property->PropertyClass->ImplementsInterface(UNotifyFieldValueChanged::StaticClass())) { if (const FMVVMBlueprintViewModelContext* ViewModel = BlueprintView->FindViewModel(Property->GetFName())) { ResultPath.SetViewModelId(ViewModel->GetViewModelId()); } } } } }; auto AddPropertyPath = [&ResultPath, Blueprint](FMemberReference& MemberReference) { if (UFunction* Function = MemberReference.ResolveMember(Blueprint->SkeletonGeneratedClass)) { ResultPath.AppendPropertyPath(Blueprint, UE::MVVM::FMVVMConstFieldVariant(Function)); } else if (const FProperty* Property = MemberReference.ResolveMember(Blueprint->SkeletonGeneratedClass)) { ResultPath.AppendPropertyPath(Blueprint, UE::MVVM::FMVVMConstFieldVariant(Property)); } }; auto AddBreakNode = [&ResultPath, Blueprint](UScriptStruct* Struct, FName PropertyName) { FProperty* FoundProperty = Struct->FindPropertyByName(PropertyName); if (ensure(FoundProperty)) { ResultPath.AppendPropertyPath(Blueprint, UE::MVVM::FMVVMConstFieldVariant(FoundProperty)); } }; bool bFirst = true; TArray> NodesToSearch = GetPropertyPathGraphNode(StartPin); for (const TTuple& NodePair : NodesToSearch) { UEdGraphNode* Node = NodePair.Get(); if (UK2Node_VariableGet* GetNode = Cast(Node)) { if (bFirst) { AddRoot(GetNode->VariableReference); } else { AddPropertyPath(GetNode->VariableReference); } } else if (UK2Node_CallFunction* FunctionNode = Cast(Node)) { // UK2Node_CallFunction can be native break function if (UFunction* Function = FunctionNode->FunctionReference.ResolveMember(Blueprint->SkeletonGeneratedClass)) { bool bAddPropertyPath = true; const FProperty* ArgumentProperty = UE::MVVM::BindingHelper::GetFirstArgumentProperty(Function); const FStructProperty* ArgumentStructProperty = CastField(ArgumentProperty); if (ArgumentStructProperty && ArgumentStructProperty->Struct) { const FString& MetaData = ArgumentStructProperty->Struct->GetMetaData(FBlueprintMetadata::MD_NativeBreakFunction); if (MetaData.Len() > 0) { if (const UFunction* NativeBreakFunction = FindObject(nullptr, *MetaData, true)) { AddBreakNode(ArgumentStructProperty->Struct, NodePair.Get()->GetFName()); bAddPropertyPath = false; } } } if (bAddPropertyPath) { ResultPath.AppendPropertyPath(Blueprint, UE::MVVM::FMVVMConstFieldVariant(Function)); } } } else if (UK2Node_BreakStruct* StructNode = Cast(Node)) { if (ensure(StructNode->StructType)) { AddBreakNode(StructNode->StructType, NodePair.Get()->GetFName()); } } else if (UK2Node_Self* Self = Cast(Node)) { if (bFirst) { ResultPath.SetSelfContext(); } else { ensure(false); } } bFirst = false; } return ResultPath; } UEdGraphPin* FindNewOutputPin(const UEdGraphNode* NewNode) { if (!NewNode) { return nullptr; } // then update our previous pin pointers for (UEdGraphPin* Pin : NewNode->Pins) { if (Pin->Direction == EGPD_Output) { if (Pin->PinName != UEdGraphSchema_K2::PN_Then && Pin->PinName != UEdGraphSchema_K2::PN_Completed) { return Pin; } } } return nullptr; }; TValueOrError, void> BuildPropertyPath(const UBlueprint* Blueprint, UEdGraph* FunctionGraph, const FMVVMBlueprintPropertyPath& PropertyPath, int32 NumberOfFieldExcludingThePropertyPathSource, FVector2f EndLocation) { // Add new nodes if (!PropertyPath.IsValid()) { return MakeError(); } auto CanNewConnections = [](UEdGraphPin* Pin, UEdGraphPin* PreviousDataPin, const UClass* Context) { return Pin->Direction == EGPD_Input && Pin->PinName != UEdGraphSchema_K2::PN_Execute && GetDefault()->ArePinsCompatible(PreviousDataPin, Pin, Context); }; NumberOfFieldExcludingThePropertyPathSource = FMath::Clamp(NumberOfFieldExcludingThePropertyPathSource, 0, PropertyPath.GetFieldPaths().Num()); const FVector2f LocationDelta = FVector2f(300.0f, 0.f); FVector2f Location = EndLocation; Location.X -= LocationDelta.X * (NumberOfFieldExcludingThePropertyPathSource + 1); UClass* BlueprintClass = Blueprint->SkeletonGeneratedClass ? Blueprint->SkeletonGeneratedClass : Blueprint->GeneratedClass; UEdGraphPin* PreviousDataPin = nullptr; UClass* PreviousClass = nullptr; const FProperty* PreviousProperty = nullptr; // create the root property getter node, ie. the Widget/ViewModel { const FProperty* RootProperty = nullptr; bool bCreateSelfNodeForRootProperty = false; { switch (PropertyPath.GetSource(Blueprint)) { case EMVVMBlueprintFieldPathSource::SelfContext: bCreateSelfNodeForRootProperty = true; break; case EMVVMBlueprintFieldPathSource::ViewModel: { UMVVMBlueprintView* View = Private::GetView(Blueprint); const FMVVMBlueprintViewModelContext* Context = View ? View->FindViewModel(PropertyPath.GetViewModelId()) : nullptr; RootProperty = Context ? Blueprint->SkeletonGeneratedClass->FindPropertyByName(Context->GetViewModelName()) : nullptr; break; } case EMVVMBlueprintFieldPathSource::Widget: if (PropertyPath.IsComponent()) { const UWidgetBlueprintGeneratedClass* ComponentSource = FieldPathHelper::GetComponentPropertyPathSource(PropertyPath.GetFields(BlueprintClass), Cast(BlueprintClass)); if (ComponentSource == BlueprintClass) { bCreateSelfNodeForRootProperty = true; break; } } RootProperty = Blueprint->SkeletonGeneratedClass->FindPropertyByName(PropertyPath.GetWidgetName()); break; default: check(false); return MakeError(); } } if (bCreateSelfNodeForRootProperty) { FGraphNodeCreator RootGetterCreator(*FunctionGraph); UK2Node_Self* RootSelfNode = RootGetterCreator.CreateNode(); RootSelfNode->NodePosX = Location.X; RootSelfNode->NodePosY = Location.Y; RootGetterCreator.Finalize(); PreviousDataPin = RootSelfNode->FindPinChecked(UEdGraphSchema_K2::PSC_Self); PreviousDataPin->PinType.PinSubCategoryObject = Blueprint->SkeletonGeneratedClass; PreviousClass = Blueprint->SkeletonGeneratedClass; PreviousProperty = RootProperty; } else { if (RootProperty == nullptr) { ensureMsgf(false, TEXT("Could not resolve root property!")); return MakeError(); } FGraphNodeCreator RootGetterCreator(*FunctionGraph); UK2Node_VariableGet* RootGetterNode = RootGetterCreator.CreateNode(); RootGetterNode->NodePosX = Location.X; RootGetterNode->NodePosY = Location.Y; RootGetterNode->VariableReference.SetFromField(RootProperty, true, BlueprintClass); RootGetterCreator.Finalize(); PreviousDataPin = RootGetterNode->Pins[0]; PreviousClass = CastField(RootProperty)->PropertyClass; PreviousProperty = RootProperty; } Location += LocationDelta; } TArray Result; Result.Reserve(NumberOfFieldExcludingThePropertyPathSource); Result.Add(PreviousDataPin); TArray Fields = PropertyPath.GetFields(BlueprintClass); // create all the subsequent nodes in the path for (int32 Index = 0; Index < NumberOfFieldExcludingThePropertyPathSource; ++Index) { const UE::MVVM::FMVVMConstFieldVariant& Field = Fields[Index]; UEdGraphNode* NewNode = nullptr; UEdGraphPin* NewPreviousDataPin = nullptr; UClass* NewPreviousClass = nullptr; const FProperty* NewPreviousProperty = nullptr; if (Field.IsProperty()) { const FProperty* Property = Field.GetProperty(); // for struct in the middle of a path, we need to use a break node if (const FStructProperty* PreviousStructProperty = CastField(PreviousProperty)) { const FString& MetaData = PreviousStructProperty->Struct->GetMetaData(FBlueprintMetadata::MD_NativeBreakFunction); if (MetaData.Len() > 0) { if (const UFunction* Function = FindObject(nullptr, *MetaData, true)) { FGraphNodeCreator MakeStructCreator(*FunctionGraph); UK2Node_CallFunction* FunctionNode = MakeStructCreator.CreateNode(false); FunctionNode->SetFromFunction(Function); MakeStructCreator.Finalize(); NewNode = FunctionNode; NewPreviousProperty = UE::MVVM::BindingHelper::GetReturnProperty(Function); } else { ensureMsgf(false, TEXT("A function in path couldn't be resolved. Blueprint: %s Function: %s"), *Blueprint->GetName(), *MetaData); return MakeError(); } } else { FGraphNodeCreator BreakCreator(*FunctionGraph); UK2Node_BreakStruct* BreakNode = BreakCreator.CreateNode(); BreakNode->StructType = PreviousStructProperty->Struct; BreakNode->AllocateDefaultPins(); BreakCreator.Finalize(); NewNode = BreakNode; NewPreviousProperty = Property; } NewPreviousDataPin = nullptr; for (UEdGraphPin* Pin : NewNode->Pins) { if (Pin->Direction == EGPD_Output) { if (Pin->PinName == Property->GetFName()) { NewPreviousDataPin = Pin; break; } } } } else if (PreviousClass != nullptr) { FGraphNodeCreator GetterCreator(*FunctionGraph); UK2Node_VariableGet* GetterNode = GetterCreator.CreateNode(); GetterNode->SetFromProperty(Property, false, PreviousClass); GetterNode->AllocateDefaultPins(); GetterCreator.Finalize(); NewNode = GetterNode; NewPreviousProperty = Property; NewPreviousDataPin = FindNewOutputPin(NewNode); } else { ensure(false); return MakeError(); } } else if (Field.IsFunction()) { if (CastField(PreviousProperty)) { ensure(false); return MakeError(); } if (const UFunction* Function = Field.GetFunction()) { FGraphNodeCreator CallFunctionCreator(*FunctionGraph); UK2Node_CallFunction* FunctionNode = CallFunctionCreator.CreateNode(); FunctionNode->SetFromFunction(Function); FunctionNode->AllocateDefaultPins(); CallFunctionCreator.Finalize(); NewNode = FunctionNode; NewPreviousProperty = UE::MVVM::BindingHelper::GetReturnProperty(Function); NewPreviousDataPin = FindNewOutputPin(NewNode); } else { const TArrayView FieldPaths = PropertyPath.GetFieldPaths(); FName FunctionName; if (FieldPaths.IsValidIndex(Index)) { FunctionName = FieldPaths[Index].GetRawFieldName(); } ensureMsgf(false, TEXT("A function in path couldn't be resolved. Blueprint: %s Function: %s"), *Blueprint->GetName(), *FunctionName.ToString()); return MakeError(); } } else { ensureMsgf(false, TEXT("Invalid path, empty field in path.")); return MakeError(); } check(NewNode); NewNode->NodePosX = Location.X; NewNode->NodePosY = Location.Y; Location += LocationDelta; if (!NewPreviousDataPin) { ensureMsgf(false, TEXT("A node in path doesn't have a return value. Node:%s"), *GetPathNameSafe(NewNode)); return MakeError(); } // create new data connections for (UEdGraphPin* Pin : NewNode->Pins) { if (CanNewConnections(Pin, PreviousDataPin, PreviousClass)) { GetDefault()->TryCreateConnection(Pin, PreviousDataPin); } } if (const FObjectProperty* ObjectProperty = CastField(NewPreviousProperty)) { NewPreviousClass = ObjectProperty->PropertyClass; } else { NewPreviousClass = nullptr; } // then update our previous pin pointers PreviousDataPin = NewPreviousDataPin; PreviousClass = NewPreviousClass; PreviousProperty = NewPreviousProperty; Result.Add(PreviousDataPin); } return MakeValue(MoveTemp(Result)); } struct FCanSetterGraphResult { /** * true when the path is ObjectA.StructB.StructC.PropertyD * false when the path is ObjectA.PropertyD */ bool bSplitPin = false; /** * 1 when the path is ObjectA.StructB.StructC.PropertyD * 2 when the path is ObjectA.StructB.ObjectC.PropertyD * Can be INDEX_NONE if self is the container. */ int32 LocalContainerPathIndex = INDEX_NONE; }; TValueOrError CanCreateSetterGraph(UBlueprint* WidgetBlueprint, const TArrayView Path, const bool bIsForEvent) { /** * Different types: * (1). The container is a object and it set a property or it call a setter function. * propertyA/functionA * objectA.objectB.propertyC/functionC * objectA.structB.objectC.propertyD/functionD (here we will need to split the pin to get objectC) * (2). The container is a struct and we need to create a local variable. We can't have setter function here. * objectA.structB.propertyC * objectA.structB.structC.propertyD * We cannot create graph for * ObjectA.GetterB, because GetterB is not a valid destination * ObjectA.GetterB.PropertyC, if GetterB returns a structure, because we cannot set the local container. */ if (Path.Num() == 0) { return MakeError(LOCTEXT("SetterGraph_NoPath", "The path is empty.")); } // Decide the type. FCanSetterGraphResult Result; for (int32 Index = 0; Index < Path.Num() - 1; ++Index) { const FMVVMConstFieldVariant& Field = Path[Index]; TValueOrError ContainerAsResult = FieldPathHelper::GetFieldAsContainer(Field); if (ContainerAsResult.HasError()) { return MakeError(FText::Format(LOCTEXT("SetterGraph_NoContainer", "Can't find the container for field {0}."), FText::FromName(Field.GetName()))); } // Getter needs to be BlueprintPure. No param. if (!BindingHelper::IsValidForSourceBinding(Field)) { return MakeError(FText::Format(LOCTEXT("SetterGraph_InvalidGetter", "The getter for field {0} can't be used."), FText::FromName(Field.GetName()))); } // Find the container if (const UClass* Class = Cast(ContainerAsResult.GetValue())) { // was a struct, now a class Result = FCanSetterGraphResult(); Result.LocalContainerPathIndex = Index; } else if (const UScriptStruct* ScriptStruct = Cast(ContainerAsResult.GetValue())) { // was a class, now a struct if (!Result.bSplitPin) { Result.LocalContainerPathIndex = Index; } Result.bSplitPin = true; } } const FMVVMConstFieldVariant& LastField = Path.Last(); if (bIsForEvent) { if (!BindingHelper::IsValidForEventBinding(LastField)) { return MakeError(FText::Format(LOCTEXT("SetterGraph_InvalidSetter", "The setter for field {0} can't be used."), FText::FromName(LastField.GetName()))); } } else if (!BindingHelper::IsValidForDestinationBinding(LastField)) { return MakeError(FText::Format(LOCTEXT("SetterGraph_InvalidSetter", "The setter for field {0} can't be used."), FText::FromName(LastField.GetName()))); } if (Result.bSplitPin) { // Can the local container be set. check(Path.IsValidIndex(Result.LocalContainerPathIndex)); const FMVVMConstFieldVariant& Field = Path[Result.LocalContainerPathIndex]; if (bIsForEvent) { if (!BindingHelper::IsValidForEventBinding(LastField)) { return MakeError(FText::Format(LOCTEXT("SetterGraph_ContainerSetter", "The path contains a getter of a struct that can't be set. See field {0}."), FText::FromName(Field.GetName()))); } } else if (!BindingHelper::IsValidForDestinationBinding(Field)) { return MakeError(FText::Format(LOCTEXT("SetterGraph_ContainerSetter", "The path contains a getter of a struct that can't be set. See field {0}."), FText::FromName(Field.GetName()))); } } return MakeValue(Result); } void RemoveGraph(UBlueprint* Blueprint, bool bAddToBlueprint, Private::FCreateGraphResult& CreateGraphInternalResult) { if (CreateGraphInternalResult.FunctionGraph && bAddToBlueprint) { Blueprint->FunctionGraphs.RemoveSingleSwap(CreateGraphInternalResult.FunctionGraph); } }; UE::MVVM::ConversionFunctionHelper::FCreateGraphResult CreateCallFunctionAndLinkNodes(const TSubclassOf& Node , UE::MVVM::ConversionFunctionHelper::FCreateGraphParams& InParams , UE::MVVM::ConversionFunctionHelper::Private::FCreateGraphResult& CreateGraphInternalResult , TFunctionRef InitNodeCallback) { UE::MVVM::ConversionFunctionHelper::FCreateGraphResult Result; Result.NewGraph = CreateGraphInternalResult.FunctionGraph; Result.WrappedNode = nullptr; Result.bIsUbergraphPage = InParams.bCreateUbergraphPage; const UMVVMConversionFunctionGraphSchema* GraphSchema = GetDefault(); UK2Node* CallFunctionNode = nullptr; { FGraphNodeCreator CallFunctionCreator(*CreateGraphInternalResult.FunctionGraph); CallFunctionNode = CallFunctionCreator.CreateNode(true, Node); InitNodeCallback(CallFunctionNode); CallFunctionNode->NodePosX = 0; CallFunctionCreator.Finalize(); Private::MarkAsConversionFunction(CallFunctionNode, CreateGraphInternalResult.FunctionGraph); } Result.WrappedNode = CallFunctionNode; Result.NamedNodes.Add(Private::NamedNodes::GeneratedCallFunction, CallFunctionNode); // Create return value pin UEdGraphPin* CallFunctionOutputPin = Private::FindNewOutputPin(CallFunctionNode); if (!Result.bIsUbergraphPage && CallFunctionOutputPin) { TSharedPtr PinInfo = MakeShared(); PinInfo->PinType = CallFunctionOutputPin->PinType; PinInfo->PinName = CallFunctionOutputPin->GetFName(); PinInfo->DesiredPinDirection = EGPD_Input; CreateGraphInternalResult.FunctionResult->UserDefinedPins.Add(PinInfo); CreateGraphInternalResult.FunctionResult->ReconstructNode(); } // Make link Entry -> CallFunction || Entry -> Return if (!Result.bIsUbergraphPage) { UEdGraphPin* FunctionEntryThenPin = CreateGraphInternalResult.FunctionEntry->GetThenPin(); UEdGraphPin* FunctionResultExecPin = CreateGraphInternalResult.FunctionResult->GetExecPin(); if (!CallFunctionNode->IsNodePure()) { UEdGraphPin* CallFunctionExecPin = CallFunctionNode->GetExecPin(); UEdGraphPin* CallFunctionThenPin = CallFunctionNode->GetThenPin(); GraphSchema->TryCreateConnection(FunctionEntryThenPin, CallFunctionExecPin); GraphSchema->TryCreateConnection(CallFunctionThenPin, FunctionResultExecPin); CallFunctionNode->NodePosY = 0; } else { GraphSchema->TryCreateConnection(FunctionEntryThenPin, FunctionResultExecPin); CallFunctionNode->NodePosY = 100; } } else { UEdGraphPin* EventEntryThenPin = CreateGraphInternalResult.EventEntry->GetThenPin(); if (!CallFunctionNode->IsNodePure()) { UEdGraphPin* CallFunctionExecPin = CallFunctionNode->GetExecPin(); UEdGraphPin* CallFunctionThenPin = CallFunctionNode->GetThenPin(); GraphSchema->TryCreateConnection(EventEntryThenPin, CallFunctionExecPin); CallFunctionNode->NodePosY = 0; } } if (!Result.bIsUbergraphPage && CallFunctionOutputPin) { UEdGraphPin* FunctionResultPin = CreateGraphInternalResult.FunctionResult->FindPin(CallFunctionOutputPin->GetFName(), EGPD_Input); check(FunctionResultPin); GraphSchema->TryCreateConnection(CallFunctionOutputPin, FunctionResultPin); } return Result; } const TValueOrError CreateSetterAndLinkNodes(const FMVVMBlueprintPropertyPath& PropertyPath , UBlueprint* Blueprint , UE::MVVM::ConversionFunctionHelper::FCreateGraphParams& InParams , UE::MVVM::ConversionFunctionHelper::FCreateGraphResult& Result , UE::MVVM::ConversionFunctionHelper::Private::FCanSetterGraphResult& CanCreateSetterGraphResult , UE::MVVM::ConversionFunctionHelper::Private::FCreateGraphResult& CreateGraphInternalResult) { TArray Fields = PropertyPath.GetCompleteFields(Blueprint); const bool bIsEditable = false; const bool bAddToBlueprint = !InParams.bTransient; Result.bIsUbergraphPage = InParams.bCreateUbergraphPage; const UMVVMConversionFunctionGraphSchema* GraphSchema = GetDefault(); // Create the wrapper setter node UK2Node* SetterNode = nullptr; { int32 WrapperFieldIndex = CanCreateSetterGraphResult.bSplitPin ? CanCreateSetterGraphResult.LocalContainerPathIndex : (CanCreateSetterGraphResult.LocalContainerPathIndex + 1); check(Fields.IsValidIndex(WrapperFieldIndex)); const FMVVMConstFieldVariant& WrapperField = Fields[WrapperFieldIndex]; if (WrapperField.IsProperty()) { FGraphNodeCreator CallFunctionCreator(*CreateGraphInternalResult.FunctionGraph); UK2Node_VariableSet* VariableNode = CallFunctionCreator.CreateNode(false); UEdGraphSchema_K2::ConfigureVarNode(VariableNode, WrapperField.GetName(), WrapperField.GetOwner(), Blueprint); VariableNode->NodePosX = Result.bIsUbergraphPage ? 500 : 0; CallFunctionCreator.Finalize(); SetterNode = VariableNode; } else { check(!CanCreateSetterGraphResult.bSplitPin); check(WrapperField.IsFunction()); FGraphNodeCreator CallFunctionCreator(*CreateGraphInternalResult.FunctionGraph); UK2Node_CallFunction* CallFunctionNode = CallFunctionCreator.CreateNode(); CallFunctionNode->SetFromFunction(WrapperField.GetFunction()); CallFunctionNode->NodePosX = Result.bIsUbergraphPage ? 500 : 0; CallFunctionCreator.Finalize(); SetterNode = CallFunctionNode; } // If we are not creating a setter for an ubergraph page the relevant node is the setter node if (!Result.bIsUbergraphPage) { Result.WrappedNode = SetterNode; } Result.NamedNodes.Add(Private::NamedNodes::GeneratedSetter, SetterNode); Private::MarkAsConversionFunction(Result.WrappedNode, Result.NewGraph); } // Make link Entry -> CallFunction || Entry -> Return if (CreateGraphInternalResult.FunctionResult) { UEdGraphPin* FunctionEntryThenPin = InParams.bCreateUbergraphPage ? CreateGraphInternalResult.EventEntry->GetThenPin() : CreateGraphInternalResult.FunctionEntry->GetThenPin(); UEdGraphPin* FunctionResultExecPin = CreateGraphInternalResult.FunctionResult->GetExecPin(); if (SetterNode->IsNodePure()) { GraphSchema->TryCreateConnection(FunctionEntryThenPin, FunctionResultExecPin); } else { GraphSchema->TryCreateConnection(FunctionEntryThenPin, SetterNode->GetExecPin()); GraphSchema->TryCreateConnection(SetterNode->GetThenPin(), FunctionResultExecPin); SetterNode->NodePosY = 0; } } // Build the path to get the container to get to the wrapper. UEdGraphPin* SplitPin = nullptr; if (ensure(Fields.Num() > 0)) { UEdGraphPin* WrapperSelfPin = SetterNode->FindPinChecked(UEdGraphSchema_K2::PSC_Self); int32 ArgumentIndex = SetterNode->Pins.IndexOfByKey(WrapperSelfPin); const float PosX = SetterNode->NodePosX; const float PosY = SetterNode->NodePosY + ArgumentIndex * 100.0f; EMVVMBlueprintFieldPathSource PropertyPathSource = PropertyPath.GetSource(Blueprint); int32 NumberOfFieldExcludingThePropertyPathSource = PropertyPath.IsComponent() || PropertyPathSource == EMVVMBlueprintFieldPathSource::SelfContext ? CanCreateSetterGraphResult.LocalContainerPathIndex + 1 : CanCreateSetterGraphResult.LocalContainerPathIndex; TValueOrError, void> PropertyPathPinsResult = Private::BuildPropertyPath(Blueprint, CreateGraphInternalResult.FunctionGraph, PropertyPath, NumberOfFieldExcludingThePropertyPathSource, FVector2f(PosX, PosY)); if (PropertyPathPinsResult.HasError()) { Private::RemoveGraph(Blueprint, bAddToBlueprint, CreateGraphInternalResult); return MakeError(LOCTEXT("SetterGraph_BuildPropertyPathFail", "Can't build the path to the setter.")); } // Link the last data pin to the Conversation Function Pin int32 DeltaIndex = CanCreateSetterGraphResult.bSplitPin ? -1 : 0; int32 WrapperPinIndex = PropertyPathPinsResult.GetValue().Num() - 1 + DeltaIndex; UEdGraphPin* ToLinkPin = PropertyPathPinsResult.GetValue()[WrapperPinIndex]; GraphSchema->TryCreateConnection(ToLinkPin, WrapperSelfPin); SplitPin = CanCreateSetterGraphResult.bSplitPin ? PropertyPathPinsResult.GetValue().Last() : nullptr; } // If the path continue, split the pins and make the link if (CanCreateSetterGraphResult.bSplitPin && SplitPin) { // need to break the pin UK2Node* CurrentNode = SetterNode; check(CurrentNode); TArrayView InPins = CurrentNode->Pins; TArrayView OutPins = TArrayView(&SplitPin, 1); FName PinName; for (int32 Index = CanCreateSetterGraphResult.LocalContainerPathIndex; Index < Fields.Num(); ++Index) { const FMVVMConstFieldVariant& NewWrapperField = Fields[Index]; if (PinName.IsNone()) { PinName = NewWrapperField.GetName(); } else { PinName = FName(*FString::Printf(TEXT("%s_%s"), *PinName.ToString(), *NewWrapperField.GetName().ToString())); for (int32 SubPinIndex = 0; SubPinIndex < InPins.Num(); ++SubPinIndex) { if (OutPins[SubPinIndex]->GetFName() != PinName) { GraphSchema->TryCreateConnection(OutPins[SubPinIndex], InPins[SubPinIndex]); InPins[SubPinIndex]->bHidden = true; } } } bool bLastItem = Index == Fields.Num() - 1; if (!bLastItem) { UEdGraphPin** FoundInPinPtr = InPins.FindByPredicate([PinName](const UEdGraphPin* Other) { return Other->GetFName() == PinName && Other->Direction == EGPD_Input && !Other->bHidden; }); UEdGraphPin** FoundOutPinPtr = OutPins.FindByPredicate([PinName](const UEdGraphPin* Other) { return Other->GetFName() == PinName && Other->Direction == EGPD_Output && !Other->bHidden; }); if (FoundInPinPtr == nullptr || FoundOutPinPtr == nullptr || !GraphSchema->CanSplitStructPin(**FoundInPinPtr) || !GraphSchema->CanSplitStructPin(**FoundOutPinPtr)) { Private::RemoveGraph(Blueprint, bAddToBlueprint, CreateGraphInternalResult); return MakeError(FText::Format(LOCTEXT("SetterGraph_CantSplitPin", "The pin {0} can't be split."), FText::FromName(NewWrapperField.GetName()))); } GraphSchema->SplitPin(*FoundInPinPtr, false); GraphSchema->SplitPin(*FoundOutPinPtr, false); InPins = (*FoundInPinPtr)->SubPins; OutPins = (*FoundOutPinPtr)->SubPins; } } } return MakeValue(MoveTemp(Result)); } void RemoveNodesFromPin(UEdGraph* FunctionGraph, UEdGraphPin* PathPin) { TArray> AllNodesForPath = GetPropertyPathGraphNode(PathPin); for (const TTuple& Pair : AllNodesForPath) { UEdGraphNode* Node = Pair.Get(); FunctionGraph->RemoveNode(Node, true); } } void LinkAllNodes(UEdGraph* FunctionGraph, UEdGraphPin* EntryPin, UEdGraphNode* Wrapper, UK2Node_FunctionResult* FunctionResult) { check(FunctionGraph && EntryPin && Wrapper && FunctionResult); const UEdGraphSchema* GraphSchema = GetDefault(); UEdGraphPin* ThenPin = EntryPin; check(ThenPin); ThenPin->BreakAllPinLinks(); // Break Pins for every pins for (UEdGraphPin* Pin : Wrapper->Pins) { TArray> AllNodesForPath = Private::GetPropertyPathGraphNode(Pin); for (const TTuple& Pair : AllNodesForPath) { UEdGraphNode* PathNode = Pair.Get(); if (UK2Node_CallFunction* CallFunction = Cast(PathNode)) { // if it not a pure node if (UEdGraphPin* ExecPin = CallFunction->FindPin(UEdGraphSchema_K2::PN_Execute)) { ThenPin->BreakAllPinLinks(); GraphSchema->TryCreateConnection(ThenPin, ExecPin); ThenPin = CallFunction->FindPinChecked(UEdGraphSchema_K2::PN_Then); } } } } // Make pin to the conversion node or to the return node if (UEdGraphPin* CallFunctionExecPin = Wrapper->FindPin(UEdGraphSchema_K2::PN_Execute)) { GraphSchema->TryCreateConnection(ThenPin, CallFunctionExecPin); } else { UEdGraphPin* FunctionResultExecPin = FunctionResult->GetExecPin(); GraphSchema->TryCreateConnection(ThenPin, FunctionResultExecPin); } } void LinkAllNodesForEvent(UEdGraph* FunctionGraph, UK2Node_Event* EventEntry, UEdGraphNode* Wrapper) { check(FunctionGraph && EventEntry && Wrapper); const UEdGraphSchema* GraphSchema = GetDefault(); UEdGraphPin* ThenPin = EventEntry->FindPinChecked(UEdGraphSchema_K2::PN_Then); check(ThenPin); ThenPin->BreakAllPinLinks(); // Break Pins for every pins for (UEdGraphPin* Pin : Wrapper->Pins) { TArray> AllNodesForPath = Private::GetPropertyPathGraphNode(Pin); for (const TTuple& Pair : AllNodesForPath) { UEdGraphNode* PathNode = Pair.Get(); if (UK2Node_CallFunction* CallFunction = Cast(PathNode)) { // if it not a pure node if (UEdGraphPin* ExecPin = CallFunction->FindPin(UEdGraphSchema_K2::PN_Execute)) { ThenPin->BreakAllPinLinks(); GraphSchema->TryCreateConnection(ThenPin, ExecPin); ThenPin = CallFunction->FindPinChecked(UEdGraphSchema_K2::PN_Then); } } } } // Make pin to the conversion node or to the return node if (UEdGraphPin* CallFunctionExecPin = Wrapper->FindPin(UEdGraphSchema_K2::PN_Execute)) { GraphSchema->TryCreateConnection(ThenPin, CallFunctionExecPin); } } } //namespace bool RequiresWrapper(const UFunction* ConversionFunction) { if (ConversionFunction == nullptr) { return false; } TValueOrError, FText> ArgumentsResult = UE::MVVM::BindingHelper::TryGetArgumentsForConversionFunction(ConversionFunction); if (ArgumentsResult.HasValue()) { return (ArgumentsResult.GetValue().Num() > 1); } return false; } bool IsInputPin(const UEdGraphPin* Pin) { return Private::IsSystemInputPin(Pin) && Pin->PinName != UEdGraphSchema_K2::PN_Self; } FName CreateWrapperName(const FMVVMBlueprintViewBinding& Binding, bool bSourceToDestination) { TStringBuilder<256> StringBuilder; StringBuilder << TEXT("__"); StringBuilder << Binding.GetFName(); StringBuilder << (bSourceToDestination ? TEXT("_SourceToDest") : TEXT("_DestToSource")); return FName(StringBuilder.ToString()); } TValueOrError CanCreateSetterGraph(UBlueprint* Blueprint, const FMVVMBlueprintPropertyPath& PropertyPath) { TArray Fields = PropertyPath.GetCompleteFields(Blueprint); TValueOrError Result = Private::CanCreateSetterGraph(Blueprint, Fields, false); if (Result.HasError()) { return MakeError(Result.StealError()); } return MakeValue(); } TValueOrError CreateSetterGraph(UBlueprint* Blueprint, FName GraphName, const UFunction* Signature, const FMVVMBlueprintPropertyPath& PropertyPath, FCreateGraphParams InParams) { TArray Fields = PropertyPath.GetCompleteFields(Blueprint); TValueOrError CanCreateSetterGraphResult = Private::CanCreateSetterGraph(Blueprint, Fields, InParams.bIsForEvent); if (CanCreateSetterGraphResult.HasError()) { return MakeError(CanCreateSetterGraphResult.StealError()); } const bool bIsEditable = false; const bool bAddToBlueprint = !InParams.bTransient; Private::FCreateGraphParams CreateGraphParams; CreateGraphParams.bIsConst = InParams.bIsConst; CreateGraphParams.bIsEditable = bIsEditable; CreateGraphParams.bAddToBlueprint = bAddToBlueprint; Private::FCreateGraphResult CreateGraphInternalResult = Private::CreateGraph(Blueprint, GraphName, Signature, CreateGraphParams); if (CreateGraphInternalResult.FunctionGraph == nullptr || CreateGraphInternalResult.FunctionEntry == nullptr) { Private::RemoveGraph(Blueprint, bAddToBlueprint, CreateGraphInternalResult); return MakeError(LOCTEXT("SetterGraph_CreateGraphFail", "Can create the graph object.")); } FCreateGraphResult Result; Result.NewGraph = CreateGraphInternalResult.FunctionGraph; Result.WrappedNode = nullptr; return Private::CreateSetterAndLinkNodes(PropertyPath, Blueprint, InParams, Result, CanCreateSetterGraphResult.GetValue(), CreateGraphInternalResult); } TValueOrError CreateSetterGraph(UBlueprint* Blueprint, FName GraphName, const TSubclassOf Node, const FMVVMBlueprintPropertyPath& PropertyPath, FCreateGraphParams InParams) { TArray Fields = PropertyPath.GetCompleteFields(Blueprint); TValueOrError CanCreateSetterGraphResult = Private::CanCreateSetterGraph(Blueprint, Fields, InParams.bIsForEvent); if (CanCreateSetterGraphResult.HasError()) { return MakeError(CanCreateSetterGraphResult.StealError()); } const bool bIsEditable = false; const bool bAddToBlueprint = !InParams.bTransient; Private::FCreateGraphParams CreateGraphParams; CreateGraphParams.bIsConst = InParams.bIsConst; CreateGraphParams.bIsEditable = bIsEditable; CreateGraphParams.bAddToBlueprint = bAddToBlueprint; CreateGraphParams.bCreateUbergraphPage = InParams.bCreateUbergraphPage; Private::FCreateGraphResult CreateGraphInternalResult = Private::CreateGraph(Blueprint, GraphName, nullptr, CreateGraphParams); if (CreateGraphInternalResult.FunctionGraph == nullptr || (!CreateGraphInternalResult.FunctionEntry && !CreateGraphInternalResult.EventEntry)) { Private::RemoveGraph(Blueprint, bAddToBlueprint, CreateGraphInternalResult); return MakeError(LOCTEXT("SetterGraph_CreateGraphFail", "Can create the graph object.")); } FCreateGraphResult Result = CreateCallFunctionAndLinkNodes(Node, InParams, CreateGraphInternalResult, [](UK2Node*) {}); // Create the wrapper setter node TValueOrError LinkedSetterGraph = Private::CreateSetterAndLinkNodes(PropertyPath, Blueprint, InParams, Result, CanCreateSetterGraphResult.GetValue(), CreateGraphInternalResult); const UMVVMAsyncConversionFunctionGraphSchema* GraphSchema = GetDefault(); if (!LinkedSetterGraph.HasError()) { UK2Node** SetterNodeItr = LinkedSetterGraph.GetValue().NamedNodes.Find(Private::NamedNodes::GeneratedSetter); UK2Node** CallFunctionNodeItr = LinkedSetterGraph.GetValue().NamedNodes.Find(Private::NamedNodes::GeneratedCallFunction); if (SetterNodeItr && CallFunctionNodeItr) { UK2Node* SetterNode = *SetterNodeItr; UK2Node* CallFunctionNode = *CallFunctionNodeItr; UEdGraphPin* AsyncCompletedPin = CallFunctionNode->FindPinChecked(UEdGraphSchema_K2::PN_Completed); // Link the Async result Exec with the setter, adding a valid binding check should sources expire after the async if (AsyncCompletedPin) { UMVVMK2Node_AreSourcesValidForBinding* BranchNode = nullptr; { FGraphNodeCreator BranchNodeCreator(*LinkedSetterGraph.GetValue().NewGraph); BranchNode = BranchNodeCreator.CreateNode(false, UMVVMK2Node_AreSourcesValidForBinding::StaticClass()); BranchNode->NodePosX = SetterNode->NodePosX; BranchNode->NodePosY = SetterNode->NodePosY - 300; BranchNodeCreator.Finalize(); } if (ensure(BranchNode)) { GraphSchema->TryCreateConnection(AsyncCompletedPin, BranchNode->GetExecPin()); GraphSchema->TryCreateConnection(BranchNode->GetThenPin(), SetterNode->GetExecPin()); } } // Link the Async result value with the setter args if (AsyncCompletedPin) { UClass* BlueprintClass = Blueprint->SkeletonGeneratedClass ? Blueprint->SkeletonGeneratedClass : Blueprint->GeneratedClass; if (UEdGraphPin* SetterParamPin = SetterNode->FindPin(PropertyPath.GetFieldNames(BlueprintClass).Last())) { GraphSchema->TryCreateConnection(CallFunctionNode->GetAllPins().Last(), SetterParamPin); } else { // @TODO: UE-221370 - Find a more robust way to link to multiple function args. Maybe enforce functions to have one arg for now if async. GraphSchema->TryCreateConnection(CallFunctionNode->GetAllPins().Last(), SetterNode->GetAllPins().Last()); } } } } return LinkedSetterGraph; } FCreateGraphResult CreateGraph(UBlueprint* Blueprint, FName GraphName, const UFunction* Signature, const UFunction* FunctionToWrap, FCreateGraphParams InParams) { bool bIsEditable = false; bool bAddToBlueprint = !InParams.bTransient; bool bIsUbergraphPage = false; Private::FCreateGraphParams CreateGraphParams; CreateGraphParams.bIsConst = InParams.bIsConst; CreateGraphParams.bIsEditable = bIsEditable; CreateGraphParams.bAddToBlueprint = bAddToBlueprint; CreateGraphParams.bCreateUbergraphPage = bIsUbergraphPage; Private::FCreateGraphResult NewGraph = Private::CreateGraph(Blueprint, GraphName, Signature, CreateGraphParams); const UMVVMConversionFunctionGraphSchema* GraphSchema = GetDefault(); // create return value pin const FProperty* ReturnProperty = UE::MVVM::BindingHelper::GetReturnProperty(FunctionToWrap); if (ReturnProperty) { TSharedPtr PinInfo = MakeShared(); GraphSchema->ConvertPropertyToPinType(ReturnProperty, PinInfo->PinType); PinInfo->PinName = ReturnProperty->GetFName(); PinInfo->DesiredPinDirection = EGPD_Input; NewGraph.FunctionResult->UserDefinedPins.Add(PinInfo); NewGraph.FunctionResult->ReconstructNode(); } UK2Node_CallFunction* CallFunctionNode = nullptr; { FGraphNodeCreator CallFunctionCreator(*NewGraph.FunctionGraph); CallFunctionNode = CallFunctionCreator.CreateNode(); CallFunctionNode->SetFromFunction(FunctionToWrap); CallFunctionNode->NodePosX = 0; CallFunctionCreator.Finalize(); Private::MarkAsConversionFunction(CallFunctionNode, NewGraph.FunctionGraph); } // Make link Entry -> CallFunction || Entry -> Return { UEdGraphPin* FunctionEntryThenPin = NewGraph.FunctionEntry->GetThenPin(); UEdGraphPin* FunctionResultExecPin = NewGraph.FunctionResult->GetExecPin(); if (!CallFunctionNode->IsNodePure()) { UEdGraphPin* CallFunctionExecPin = CallFunctionNode->GetExecPin(); UEdGraphPin* CallFunctionThenPin = CallFunctionNode->GetThenPin(); GraphSchema->TryCreateConnection(FunctionEntryThenPin, CallFunctionExecPin); GraphSchema->TryCreateConnection(CallFunctionThenPin, FunctionResultExecPin); CallFunctionNode->NodePosY = 0; } else { GraphSchema->TryCreateConnection(FunctionEntryThenPin, FunctionResultExecPin); CallFunctionNode->NodePosY = 100; } } if (ReturnProperty) { UEdGraphPin* FunctionReturnPin = CallFunctionNode->FindPin(ReturnProperty->GetName(), EGPD_Output); UEdGraphPin* FunctionResultPin = NewGraph.FunctionResult->FindPin(ReturnProperty->GetFName(), EGPD_Input); check(FunctionResultPin && FunctionReturnPin); GraphSchema->TryCreateConnection(FunctionReturnPin, FunctionResultPin); } return FCreateGraphResult{ NewGraph.FunctionGraph , CallFunctionNode, TMap{}, bIsUbergraphPage }; } FCreateGraphResult CreateGraph(UBlueprint* Blueprint, FName GraphName, const UFunction* Signature, TSubclassOf NodeType, FCreateGraphParams InParams, TFunctionRef InitNodeCallback) { bool bIsEditable = false; bool bAddToBlueprint = !InParams.bTransient; Private::FCreateGraphParams CreateGraphParams; CreateGraphParams.bIsConst = InParams.bIsConst; CreateGraphParams.bIsEditable = bIsEditable; CreateGraphParams.bAddToBlueprint = bAddToBlueprint; CreateGraphParams.bCreateUbergraphPage = InParams.bCreateUbergraphPage; Private::FCreateGraphResult CreateGraphInternalResult = Private::CreateGraph(Blueprint, GraphName, Signature, CreateGraphParams); FCreateGraphResult Result = CreateCallFunctionAndLinkNodes(NodeType, InParams, CreateGraphInternalResult, InitNodeCallback); return Result; } PRAGMA_DISABLE_DEPRECATION_WARNINGS TValueOrError CreateSetterGraph(UBlueprint* WidgetBlueprint, FName GraphName, const UFunction* Signature, const FMVVMBlueprintPropertyPath& PropertyPath, bool bIsConst, bool bTransient, const bool bIsForEvent) { FCreateGraphParams Params; Params.bIsConst = bIsConst; Params.bTransient = bTransient; Params.bIsForEvent = bIsForEvent; return CreateSetterGraph(WidgetBlueprint, GraphName, Signature, PropertyPath, Params); } FCreateGraphResult CreateGraph(UBlueprint* WidgetBlueprint, FName GraphName, const UFunction* Signature, const UFunction* FunctionToWrap, bool bIsConst, bool bTransient) { FCreateGraphParams Params; Params.bIsConst = bIsConst; Params.bTransient = bTransient; return CreateGraph(WidgetBlueprint, GraphName, Signature, FunctionToWrap, Params); } FCreateGraphResult CreateGraph(UBlueprint* WidgetBlueprint, FName GraphName, const UFunction* Signature, const TSubclassOf Node, bool bIsConst, bool bTransient, TFunctionRef InitNodeCallback) { FCreateGraphParams Params; Params.bIsConst = bIsConst; Params.bTransient = bTransient; return CreateGraph(WidgetBlueprint, GraphName, Signature, Node, Params, InitNodeCallback); } PRAGMA_ENABLE_DEPRECATION_WARNINGS UK2Node* InsertEarlyExitBranchNode(UEdGraph* Graph, TSubclassOf BranchNodeType) { if (Graph == nullptr || BranchNodeType.Get() == nullptr) { return nullptr; } UK2Node_FunctionEntry* FunctionEntry = Private::FindFunctionEntry(Graph); if (FunctionEntry == nullptr) { return nullptr; } UK2Node* BranchNode = nullptr; { FGraphNodeCreator BranchNodeCreator(*Graph); BranchNode = BranchNodeCreator.CreateNode(true, BranchNodeType.Get()); BranchNode->NodePosX = FunctionEntry->NodePosX; BranchNode->NodePosY = FunctionEntry->NodePosY+100; BranchNodeCreator.Finalize(); } const UEdGraphSchema* GraphSchema = GetDefault(); { UEdGraphPin* EntryThenPin = FunctionEntry->FindPinChecked(UEdGraphSchema_K2::PN_Then); UEdGraphPin* BranchThenPin = BranchNode->FindPinChecked(UEdGraphSchema_K2::PN_Then, EEdGraphPinDirection::EGPD_Output); GraphSchema->MovePinLinks(*EntryThenPin, *BranchThenPin, true, false); UEdGraphPin* BranchInputPin = BranchNode->FindPinChecked(UEdGraphSchema_K2::PN_Execute, EEdGraphPinDirection::EGPD_Input); GraphSchema->TryCreateConnection(EntryThenPin, BranchInputPin); } return BranchNode; } UK2Node* GetWrapperNode(const UEdGraph* Graph) { if (Graph == nullptr) { return nullptr; } FName ConversionFunctionMetadataKey = Private::ConversionFunctionMetadataKey.Resolve(); for (UEdGraphNode* Node : Graph->Nodes) { // check if we've set any metadata on the nodes to figure out which one it is if (Cast(Node) && Node->GetPackage()->GetMetaData().HasValue(Node, ConversionFunctionMetadataKey)) { return CastChecked(Node); } } if (UK2Node_FunctionResult* FunctionResult = Private::FindFunctionResult(Graph)) { for (UEdGraphPin* GraphPin : FunctionResult->Pins) { if (GraphPin->GetFName() != UEdGraphSchema_K2::PN_Execute && GraphPin->LinkedTo.Num() == 1) { if (UK2Node* Node = Cast(GraphPin->LinkedTo[0]->GetOwningNode())) { Private::MarkAsConversionFunction(Node, Graph); return Node; } } } } return nullptr; } FMVVMBlueprintPropertyPath GetPropertyPathForPin(const UBlueprint* Blueprint, const UEdGraphPin* Pin, bool bSkipResolve) { if (Pin == nullptr || Pin->LinkedTo.Num() == 0) { return FMVVMBlueprintPropertyPath(); } return Private::GetPropertyPathForPin(Blueprint, Pin, bSkipResolve); } void SetPropertyPathForPin(const UBlueprint* Blueprint, const FMVVMBlueprintPropertyPath& PropertyPath, UEdGraphPin* PathPin) { if (PathPin == nullptr) { return; } UEdGraphNode* ConversionNode = PathPin->GetOwningNode(); UEdGraph* FunctionGraph = ConversionNode ? ConversionNode->GetGraph() : nullptr; const UEdGraphSchema* Schema = GetDefault(); UK2Node* K2ConversionNode = Cast(ConversionNode); if (K2ConversionNode && FunctionGraph) { if (UE::MVVM::ConversionFunctionHelper::IsAsyncNode(K2ConversionNode->GetClass())) { UK2Node_Event* ConversionEventEntry = Private::FindEventEntry(FunctionGraph); // Remove previous nodes Private::RemoveNodesFromPin(FunctionGraph, PathPin); // Add new nodes if (PropertyPath.IsValid()) { const int32 ArgumentIndex = K2ConversionNode->Pins.IndexOfByPredicate([PathPin](const UEdGraphPin* Other){ return Other == PathPin; }); if (!ensure(K2ConversionNode->Pins.IsValidIndex(ArgumentIndex))) { return; } const int32 NumberOfFields = PropertyPath.IsComponent() ? PropertyPath.GetCompleteFields(Blueprint).Num() : PropertyPath.GetFieldPaths().Num(); const float PosX = K2ConversionNode->NodePosX; const float PosY = K2ConversionNode->NodePosY + ArgumentIndex * 100; TValueOrError, void> BuildPropertyPathResult = Private::BuildPropertyPath(Blueprint, FunctionGraph, PropertyPath, NumberOfFields, FVector2f(PosX, PosY)); if (BuildPropertyPathResult.HasError()) { return; } // Link the last data pin to the Conversation Function Pin Schema->TryCreateConnection(BuildPropertyPathResult.GetValue().Last(), PathPin); } // Link Then / Exec pin Private::LinkAllNodesForEvent(FunctionGraph, ConversionEventEntry, K2ConversionNode); } } UK2Node_FunctionEntry* ConversionFunctionEntry = ConversionNode ? Private::FindFunctionEntry(FunctionGraph) : nullptr; UK2Node_FunctionResult* ConversionFunctionResult = ConversionNode ? Private::FindFunctionResult(FunctionGraph) : nullptr; if (!FunctionGraph || !ConversionNode || !ConversionFunctionEntry || !ConversionFunctionResult) { return; } // Remove previous nodes Private::RemoveNodesFromPin(FunctionGraph, PathPin); UEdGraphPin* EntryPin = ConversionFunctionEntry->FindPin(UEdGraphSchema_K2::PN_Then); if (UEdGraphPin* ExecutePin = ConversionNode->FindPin(UEdGraphSchema_K2::PN_Execute)) { if (!ExecutePin->LinkedTo.IsEmpty()) { EntryPin = ExecutePin->LinkedTo[0]; } } // Add new nodes if (PropertyPath.IsValid()) { const int32 ArgumentIndex = ConversionNode->Pins.IndexOfByPredicate([PathPin](const UEdGraphPin* Other){ return Other == PathPin; }); if (!ensure(ConversionNode->Pins.IsValidIndex(ArgumentIndex))) { return; } const int32 NumberOfFields = PropertyPath.IsComponent() ? PropertyPath.GetCompleteFields(Blueprint).Num() : PropertyPath.GetFieldPaths().Num(); const float PosX = ConversionNode->NodePosX; const float PosY = ConversionNode->NodePosY + ArgumentIndex * 100; TValueOrError, void> BuildPropertyPathResult = Private::BuildPropertyPath(Blueprint, FunctionGraph, PropertyPath, NumberOfFields, FVector2f(PosX, PosY)); if (BuildPropertyPathResult.HasError()) { return; } // Link the last data pin to the Conversation Function Pin Schema->TryCreateConnection(BuildPropertyPathResult.GetValue().Last(), PathPin); } // Link Then / Exec pin Private::LinkAllNodes(FunctionGraph, EntryPin, ConversionNode, ConversionFunctionResult); } FMVVMBlueprintPropertyPath GetPropertyPathForArgument(const UBlueprint* WidgetBlueprint, const UK2Node_CallFunction* FunctionNode, FName ArgumentName, bool bSkipResolve) { const UEdGraphPin* ArgumentPin = FunctionNode->FindPin(ArgumentName, EGPD_Input); return GetPropertyPathForPin(WidgetBlueprint, ArgumentPin, bSkipResolve); } UEdGraphPin* FindPin(const UEdGraph* Graph, const TArrayView PinNames) { if (PinNames.Num() == 0 || Graph == nullptr) { return nullptr; } const UEdGraphNode* CurrentGraphNode = GetWrapperNode(Graph); return FindPin(CurrentGraphNode, PinNames); } UEdGraphPin* FindPin(const UEdGraphNode* Node, const TArrayView PinNames) { if (PinNames.Num() == 0 || Node == nullptr) { return nullptr; } for (int32 Index = 0; Index < PinNames.Num() - 1; ++Index) { FName PinName = PinNames[Index]; const UEdGraphPin* Pin = Node->FindPin(PinName); if (Pin == nullptr || Pin->LinkedTo.Num() != 1) { return nullptr; } Node = Pin->LinkedTo[0]->GetOwningNode(); if (Node == nullptr) { return nullptr; } } return Node ? Node->FindPin(PinNames.Last()) : nullptr; } TArray FindPinId(const UEdGraphPin* GraphPin) { if (GraphPin == nullptr) { return TArray(); } const UEdGraphNode* ConversionFunctionNode = GetWrapperNode(GraphPin->GetOwningNode()->GetGraph()); if (ConversionFunctionNode == nullptr) { return TArray(); } TArray Result; while (GraphPin) { Result.Insert(GraphPin->GetFName(), 0); const UEdGraphNode* CurrentGraphNode = GraphPin->GetOwningNode(); if (ConversionFunctionNode == CurrentGraphNode) { break; } const UEdGraphPin* OutputPin = Private::FindNewOutputPin(CurrentGraphNode); if (OutputPin->LinkedTo.Num() != 1) { break; } GraphPin = OutputPin->LinkedTo[0]; } return Result; } TArray FindInputPins(const UK2Node* Node) { TArray Result; if (Node == nullptr) { return Result; } Result.Reserve(Node->Pins.Num()); for (UEdGraphPin* GraphPin : Node->Pins) { if (IsInputPin(GraphPin)) { Result.Add(GraphPin); } } return Result; } UEdGraphPin* FindOutputPin(const UK2Node* Node) { return Node? Private::FindNewOutputPin(Node) : nullptr; } void SetMetaData(UEdGraph* NewGraph, FName MetaData, FStringView Value) { if (NewGraph == nullptr || MetaData.IsNone()) { return; } if (UK2Node_FunctionEntry* FunctionEntry = Private::FindFunctionEntry(NewGraph)) { FunctionEntry->MetaData.SetMetaData(MetaData, Value); } } void MarkNodeAsAutoPromote(UEdGraphNode* Node) { if (Node && !Node->NodeComment.Contains(Private::AutoPromoteFunctionMetadataKey)) { Node->NodeComment.Append(Private::AutoPromoteFunctionMetadataKey); } } bool IsAutoPromoteNode(const UEdGraphNode* Node) { return Node && Node->NodeComment.Contains(Private::AutoPromoteFunctionMetadataKey); } bool IsAsyncNode(const TSubclassOf Node) { // Note: Currently exists in MVVM as we would like for the kismet-level solution to not use CDO's. // CDO's may give false readings if they are conditionally async (Ex: CallFunction). if (!Node) { return false; } return Node->GetDefaultObject()->IsCompatibleWithGraph(UMVVMFakeTestUbergraph::StaticClass()->GetDefaultObject()) && !Node->GetDefaultObject()->IsCompatibleWithGraph(UMVVMFakeTestFunctiongraph::StaticClass()->GetDefaultObject()); } void MarkNodeToKeepConnections(const UK2Node* Node) { } bool IsNodeMarkedToKeepConnections(const UK2Node* Node) { return false; } } // UE::MVVM::ConversionFunctionHelper #undef LOCTEXT_NAMESPACE