// Copyright Epic Games, Inc. All Rights Reserved. #include "MVVMBlueprintViewConversionFunction.h" #include "MVVMDeveloperProjectSettings.h" #include "Bindings/MVVMBindingHelper.h" #include "Bindings/MVVMConversionFunctionHelper.h" #include "Bindings/MVVMBindingHelper.h" #include "Engine/Blueprint.h" #include "Kismet2/BlueprintEditorUtils.h" #include "EdGraphSchema_K2.h" #include "EdGraph/EdGraphPin.h" #include "GraphEditAction.h" #include "K2Node_CallFunction.h" #include "K2Node_FunctionEntry.h" #include "K2Node_FunctionResult.h" #include "KismetCompiler.h" #include "Kismet/BlueprintFunctionLibrary.h" #include "Templates/ValueOrError.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(MVVMBlueprintViewConversionFunction) namespace UE::MVVM::Private { static FLazyName DefaultConversionFunctionName = TEXT("__ConversionFunction"); } bool UMVVMBlueprintViewConversionFunction::IsValidConversionFunction(const UBlueprint* WidgetBlueprint, const UFunction* Function) { // functions in the widget blueprint can do anything they want, other functions have to be static functions in a BlueprintFunctionLibrary const UClass* FunctionClass = Function->GetOuterUClass(); bool bIsFromWidgetBlueprint = WidgetBlueprint->GeneratedClass && WidgetBlueprint->GeneratedClass->IsChildOf(FunctionClass) && Function->HasAllFunctionFlags(FUNC_BlueprintPure | FUNC_Const); bool bIsFromSkeletonWidgetBlueprint = WidgetBlueprint->SkeletonGeneratedClass && WidgetBlueprint->SkeletonGeneratedClass->IsChildOf(FunctionClass) && Function->HasAllFunctionFlags(FUNC_BlueprintPure | FUNC_Const); bool bFromBlueprintFunctionLibrary = FunctionClass->IsChildOf() && Function->HasAllFunctionFlags(FUNC_Static | FUNC_BlueprintPure); if (!bIsFromWidgetBlueprint && !bIsFromSkeletonWidgetBlueprint && !bFromBlueprintFunctionLibrary) { return false; } TValueOrError ReturnResult = UE::MVVM::BindingHelper::TryGetReturnTypeForConversionFunction(Function); if (ReturnResult.HasError()) { return false; } const FProperty* ReturnProperty = ReturnResult.GetValue(); if (ReturnProperty == nullptr) { return false; } TValueOrError, FText> ArgumentsResult = UE::MVVM::BindingHelper::TryGetArgumentsForConversionFunction(Function); if (ArgumentsResult.HasError()) { return false; } return GetDefault()->IsConversionFunctionAllowed(WidgetBlueprint, Function); } bool UMVVMBlueprintViewConversionFunction::IsValid(const UBlueprint* SelfContext) const { if (FunctionNode.Get()) { return false; // not valid for now } UClass* SelfClass = SelfContext->SkeletonGeneratedClass ? SelfContext->SkeletonGeneratedClass : SelfContext->GeneratedClass; const UFunction* ConversionFunction = FunctionReference.ResolveMember(SelfClass); if (ConversionFunction) { if (!GetDefault()->IsConversionFunctionAllowed(SelfContext, ConversionFunction)) { return false; } return IsValidConversionFunction(SelfContext, ConversionFunction); } return false; } bool UMVVMBlueprintViewConversionFunction::NeedsWrapperGraph(const UBlueprint* SelfContext) const { UClass* SelfClass = SelfContext->SkeletonGeneratedClass ? SelfContext->SkeletonGeneratedClass : SelfContext->GeneratedClass; return NeedsWrapperGraphInternal(SelfClass); } bool UMVVMBlueprintViewConversionFunction::NeedsWrapperGraphInternal(const UClass* SkeletalSelfContext) const { if (FunctionNode.Get()) { return true; // not valid for now } const UFunction* ConversionFunction = FunctionReference.ResolveMember(const_cast(SkeletalSelfContext)); if (ensure(ConversionFunction)) { return SavedPins.Num() > 1 || !UE::MVVM::BindingHelper::IsValidForSimpleRuntimeConversion(ConversionFunction); } return false; } /** The wrapper Graph is generated on domains and is not saved. */ bool UMVVMBlueprintViewConversionFunction::IsWrapperGraphTransient() const { return bWrapperGraphTransient; } const UFunction* UMVVMBlueprintViewConversionFunction::GetCompiledFunction(const UClass* SelfContext) const { if (NeedsWrapperGraphInternal(SelfContext)) { FMemberReference CompiledFunction; CompiledFunction.SetSelfMember(GraphName); return CompiledFunction.ResolveMember(const_cast(SelfContext)); } return FunctionReference.ResolveMember(const_cast(SelfContext)); } FName UMVVMBlueprintViewConversionFunction::GetCompiledFunctionName(const UClass* SelfContext) const { if (NeedsWrapperGraphInternal(SelfContext)) { ensure(!GraphName.IsNone()); return GraphName; } return FunctionReference.GetMemberName(); } TVariant> UMVVMBlueprintViewConversionFunction::GetConversionFunction(const UBlueprint* SelfContext) const { if (FunctionNode.Get()) { return TVariant>( TInPlaceType>(), FunctionNode); } else { UClass* SelfClass = SelfContext->SkeletonGeneratedClass ? SelfContext->SkeletonGeneratedClass : SelfContext->GeneratedClass; const UFunction* ConversionFunction = FunctionReference.ResolveMember(SelfClass); return TVariant>( TInPlaceType(), ConversionFunction); } } void UMVVMBlueprintViewConversionFunction::Reset() { FunctionReference = FMemberReference(); FunctionNode = TSubclassOf(); GraphName = FName(); bWrapperGraphTransient = false; SavedPins.Reset(); SetCachedWrapperGraph(nullptr, nullptr, nullptr); } void UMVVMBlueprintViewConversionFunction::InitializeFromFunction(UBlueprint* InContext, FName InGraphName, const UFunction* InFunction) { Reset(); if (ensure(InFunction && !InGraphName.IsNone())) { UClass* OwnerClass = InFunction->GetOuterUClass(); FGuid MemberGuid; UBlueprint::GetGuidFromClassByFieldName(OwnerClass, InFunction->GetFName(), MemberGuid); bool bIsSelf = (InContext->GeneratedClass && InContext->GeneratedClass->IsChildOf(OwnerClass)) || (InContext->SkeletonGeneratedClass && InContext->SkeletonGeneratedClass->IsChildOf(OwnerClass)); if (bIsSelf) { FunctionReference.SetSelfMember(InFunction->GetFName(), MemberGuid); } else { if (UBlueprint* VariableOwnerBP = Cast(OwnerClass->ClassGeneratedBy)) { OwnerClass = VariableOwnerBP->SkeletonGeneratedClass ? VariableOwnerBP->SkeletonGeneratedClass : VariableOwnerBP->GeneratedClass; } FunctionReference.SetExternalMember(InFunction->GetFName(), OwnerClass, MemberGuid); } check(GraphName.IsNone()); // the name needs to be set before a GetOrCreateWrapperGraph GraphName = InGraphName; bWrapperGraphTransient = !GetDefault()->bAllowConversionFunctionGeneratedGraphInEditor; GetOrCreateWrapperGraphInternal(InContext, InFunction); SavePinValues(InContext); } } void UMVVMBlueprintViewConversionFunction::Deprecation_InitializeFromWrapperGraph(UBlueprint* SelfContext, UEdGraph* Graph) { Reset(); if (UK2Node* WrapperNode = UE::MVVM::ConversionFunctionHelper::GetWrapperNode(Graph)) { if (UK2Node_CallFunction* CallFunction = Cast(WrapperNode)) { FunctionReference = CallFunction->FunctionReference; } else { FunctionNode = WrapperNode->GetClass(); } check(WrapperNode->GetGraph()); SetCachedWrapperGraph(SelfContext, WrapperNode->GetGraph(), WrapperNode); check(GraphName.IsNone()); GraphName = CachedWrapperGraph->GetFName(); bWrapperGraphTransient = !GetDefault()->bAllowConversionFunctionGeneratedGraphInEditor; SavePinValues(SelfContext); if (bWrapperGraphTransient && CachedWrapperNode) { SelfContext->FunctionGraphs.RemoveSingle(CachedWrapperGraph); } } } void UMVVMBlueprintViewConversionFunction::Deprecation_InitializeFromMemberReference(UBlueprint* SelfContext, FName InGraphName, FMemberReference MemberReference, const FMVVMBlueprintPropertyPath& Source) { Reset(); FunctionReference = MemberReference; check(GraphName.IsNone()); // the name needs to be set before a GetOrCreateWrapperGraph GraphName = InGraphName; bWrapperGraphTransient = !GetDefault()->bAllowConversionFunctionGeneratedGraphInEditor; // since it is a new object, we can't create a the graph right away UClass* GeneratedClass = SelfContext->SkeletonGeneratedClass ? SelfContext->SkeletonGeneratedClass : SelfContext->GeneratedClass; const UFunction* ConversionFunction = FunctionReference.ResolveMember(GeneratedClass); const FProperty* PinProperty = ConversionFunction ? UE::MVVM::BindingHelper::GetFirstArgumentProperty(ConversionFunction) : nullptr; if (PinProperty) { FMVVMBlueprintPin NewPin = PinProperty->GetFName(); NewPin.SetPath(Source); SavedPins.Add(MoveTemp(NewPin)); } else { SavedPins.Reset(); } } void UMVVMBlueprintViewConversionFunction::Deprecation_SetWrapperGraphName(UBlueprint* SelfContext, FName InGraphName, const FMVVMBlueprintPropertyPath& Source) { if (ensure(SavedPins.Num() == 0) && ensure(GraphName.IsNone())) { GraphName = InGraphName; bWrapperGraphTransient = !GetDefault()->bAllowConversionFunctionGeneratedGraphInEditor; // since it is a new object, we can't create a the graph right away UClass* GeneratedClass = SelfContext->SkeletonGeneratedClass ? SelfContext->SkeletonGeneratedClass : SelfContext->GeneratedClass; const UFunction* ConversionFunction = FunctionReference.ResolveMember(GeneratedClass); const FProperty* PinProperty = ConversionFunction ? UE::MVVM::BindingHelper::GetFirstArgumentProperty(ConversionFunction) : nullptr; if (PinProperty) { FMVVMBlueprintPin NewPin = PinProperty->GetFName(); NewPin.SetPath(Source); SavedPins.Add(MoveTemp(NewPin)); } else { SavedPins.Reset(); } } } void UMVVMBlueprintViewConversionFunction::SetCachedWrapperGraph(UBlueprint* Context, UEdGraph* Graph, UK2Node* Node) { if (CachedWrapperNode && OnUserDefinedPinRenamedHandle.IsValid()) { CachedWrapperNode->OnUserDefinedPinRenamed().Remove(OnUserDefinedPinRenamedHandle); } if (CachedWrapperGraph && OnGraphChangedHandle.IsValid()) { CachedWrapperGraph->RemoveOnGraphChangedHandler(OnGraphChangedHandle); } CachedWrapperGraph = Graph; CachedWrapperNode = Node; OnGraphChangedHandle.Reset(); OnUserDefinedPinRenamedHandle.Reset(); if (CachedWrapperGraph && Context) { TWeakObjectPtr WeakContext = Context; OnGraphChangedHandle = CachedWrapperGraph->AddOnGraphChangedHandler(FOnGraphChanged::FDelegate::CreateUObject(this, &UMVVMBlueprintViewConversionFunction::HandleGraphChanged, WeakContext)); } if (CachedWrapperNode && Context) { TWeakObjectPtr WeakContext = Context; OnUserDefinedPinRenamedHandle = CachedWrapperNode->OnUserDefinedPinRenamed().AddUObject(this, &UMVVMBlueprintViewConversionFunction::HandleUserDefinedPinRenamed, WeakContext); } } void UMVVMBlueprintViewConversionFunction::CreateWrapperGraphName() { ensure(!GraphName.IsNone()); if (GraphName.IsNone()) { TStringBuilder<256> StringBuilder; StringBuilder << UE::MVVM::Private::DefaultConversionFunctionName.Resolve(); StringBuilder << FGuid::NewDeterministicGuid(GetFullName()).ToString(EGuidFormats::DigitsWithHyphensLower); GraphName = StringBuilder.ToString(); } } UEdGraph* UMVVMBlueprintViewConversionFunction::GetOrCreateIntermediateWrapperGraph(FKismetCompilerContext& Context) { check(Context.NewClass); if (CachedWrapperGraph) { return CachedWrapperGraph; } TObjectPtr* FoundGraph = !GraphName.IsNone() ? Context.Blueprint->FunctionGraphs.FindByPredicate([GraphName = GetWrapperGraphName()](const UEdGraph* Other) { return Other->GetFName() == GraphName; }) : nullptr; if (FoundGraph) { ensureMsgf(!IsWrapperGraphTransient(), TEXT("The graph is transient. It should not be saved in the editor.")); UBlueprint* NullContext = nullptr; // do not register the callback const_cast(this)->SetCachedWrapperGraph(NullContext, *FoundGraph, UE::MVVM::ConversionFunctionHelper::GetWrapperNode(*FoundGraph)); LoadPinValuesInternal(Context.Blueprint); return CachedWrapperGraph; } else if (IsValid(Context.Blueprint)) { TVariant> ConversionFunction = GetConversionFunction(Context.Blueprint); if (ConversionFunction.IsType>()) { ensure(false); // not supported for now return nullptr; } CreateWrapperGraphName(); return GetOrCreateWrapperGraphInternal(Context, ConversionFunction.Get()); } return nullptr; } UEdGraph* UMVVMBlueprintViewConversionFunction::GetOrCreateWrapperGraph(UBlueprint* Blueprint) { check(Blueprint); if (CachedWrapperGraph) { return CachedWrapperGraph; } TObjectPtr* FoundGraph = Blueprint->FunctionGraphs.FindByPredicate([GraphName = GetWrapperGraphName()](const UEdGraph* Other) { return Other->GetFName() == GraphName; }); if (FoundGraph) { ensureMsgf(!IsWrapperGraphTransient(), TEXT("The graph is transient. It should not be saved in the editor.")); const_cast(this)->SetCachedWrapperGraph(Blueprint, *FoundGraph, UE::MVVM::ConversionFunctionHelper::GetWrapperNode(*FoundGraph)); LoadPinValuesInternal(Blueprint); } else if (IsValid(Blueprint)) { TVariant> ConversionFunction = GetConversionFunction(Blueprint); if (ConversionFunction.IsType>()) { ensure(false); // not supported for now return nullptr; } CreateWrapperGraphName(); return GetOrCreateWrapperGraphInternal(Blueprint, ConversionFunction.Get()); } return nullptr; } UEdGraphPin* UMVVMBlueprintViewConversionFunction::GetOrCreateGraphPin(UBlueprint* Blueprint, FName PinName) { GetOrCreateWrapperGraph(Blueprint); if (CachedWrapperNode) { return CachedWrapperNode->FindPin(PinName); } return nullptr; } UEdGraph* UMVVMBlueprintViewConversionFunction::GetOrCreateWrapperGraphInternal(FKismetCompilerContext& Context, const UFunction* Function) { return GetOrCreateWrapperGraphInternal(Context.Blueprint, Function); } UEdGraph* UMVVMBlueprintViewConversionFunction::GetOrCreateWrapperGraphInternal(UBlueprint* Blueprint, const UFunction* Function) { check(Blueprint); check(Function); bool bConst = true; TPair Result = UE::MVVM::ConversionFunctionHelper::CreateGraph(Blueprint, GraphName, nullptr, Function, bConst, bWrapperGraphTransient); const_cast(this)->SetCachedWrapperGraph(Blueprint, Result.Get<0>(), Result.Get<1>()); LoadPinValuesInternal(Blueprint); return CachedWrapperGraph; } void UMVVMBlueprintViewConversionFunction::RemoveWrapperGraph(UBlueprint* Blueprint) { TObjectPtr* Result = Blueprint->FunctionGraphs.FindByPredicate([WrapperName = GraphName](const UEdGraph* GraphPtr) { return GraphPtr->GetFName() == WrapperName; }); if (Result) { FBlueprintEditorUtils::RemoveGraph(Blueprint, Result->Get()); } SetCachedWrapperGraph(Blueprint, nullptr, nullptr); } void UMVVMBlueprintViewConversionFunction::SetGraphPin(UBlueprint* Blueprint, FName PinName, const FMVVMBlueprintPropertyPath& Path) { UEdGraphPin* GraphPin = GetOrCreateGraphPin(Blueprint, PinName); // Set the value and make the blueprint as dirty before creating the pin. //A property may not be created yet and the skeletal needs to be recreated. FMVVMBlueprintPin* Pin = SavedPins.FindByPredicate([PinName](const FMVVMBlueprintPin& Other) { return PinName == Other.GetName(); }); if (!Pin) { Pin = &SavedPins.Add_GetRef(FMVVMBlueprintPin::CreateFromPin(Blueprint, GraphPin)); } Pin->SetPath(Path); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); UE::MVVM::ConversionFunctionHelper::SetPropertyPathForPin(Blueprint, Path, GraphPin); } void UMVVMBlueprintViewConversionFunction::SavePinValues(UBlueprint* Blueprint) { if (CachedWrapperNode) { SavedPins = FMVVMBlueprintPin::CreateFromNode(Blueprint, CachedWrapperNode); } } void UMVVMBlueprintViewConversionFunction::LoadPinValuesInternal(UBlueprint* Blueprint) { if (CachedWrapperNode) { TArray MissingPins = FMVVMBlueprintPin::CopyAndReturnMissingPins(Blueprint, CachedWrapperNode, SavedPins); SavedPins.Append(MissingPins); } } void UMVVMBlueprintViewConversionFunction::HandleGraphChanged(const FEdGraphEditAction& EditAction, TWeakObjectPtr WeakBlueprint) { UBlueprint* Blueprint = WeakBlueprint.Get(); if (Blueprint && EditAction.Graph == CachedWrapperGraph && CachedWrapperGraph) { if (CachedWrapperNode && EditAction.Nodes.Contains(CachedWrapperNode)) { if (EditAction.Action == EEdGraphActionType::GRAPHACTION_RemoveNode) { CachedWrapperNode = UE::MVVM::ConversionFunctionHelper::GetWrapperNode(CachedWrapperGraph); SavePinValues(Blueprint); OnWrapperGraphModified.Broadcast(); } else if (EditAction.Action == EEdGraphActionType::GRAPHACTION_EditNode) { SavePinValues(Blueprint); OnWrapperGraphModified.Broadcast(); } } else if (CachedWrapperNode == nullptr && EditAction.Action == EEdGraphActionType::GRAPHACTION_AddNode) { CachedWrapperNode = UE::MVVM::ConversionFunctionHelper::GetWrapperNode(CachedWrapperGraph); SavePinValues(Blueprint); OnWrapperGraphModified.Broadcast(); } } } void UMVVMBlueprintViewConversionFunction::HandleUserDefinedPinRenamed(UK2Node* InNode, FName OldPinName, FName NewPinName, TWeakObjectPtr WeakBlueprint) { UBlueprint* Blueprint = WeakBlueprint.Get(); if (Blueprint && InNode == CachedWrapperNode) { SavePinValues(Blueprint); OnWrapperGraphModified.Broadcast(); } }