// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. #include "WidgetBlueprintCompiler.h" #include "Components/SlateWrapperTypes.h" #include "Blueprint/UserWidget.h" #include "K2Node_FunctionEntry.h" #include "K2Node_FunctionResult.h" #include "K2Node_VariableGet.h" #include "Blueprint/WidgetTree.h" #include "Animation/WidgetAnimation.h" #include "Kismet2/Kismet2NameValidators.h" #include "Kismet2/KismetReinstanceUtilities.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Components/NamedSlot.h" #include "WidgetBlueprintEditorUtils.h" #include "WidgetGraphSchema.h" #include "IUMGModule.h" #define LOCTEXT_NAMESPACE "UMG" #define CPF_Instanced (CPF_PersistentInstance | CPF_ExportObject | CPF_InstancedReference) extern COREUOBJECT_API bool GMinimalCompileOnLoad; FWidgetBlueprintCompiler::FWidgetBlueprintCompiler(UWidgetBlueprint* SourceSketch, FCompilerResultsLog& InMessageLog, const FKismetCompilerOptions& InCompilerOptions, TArray* InObjLoaded) : Super(SourceSketch, InMessageLog, InCompilerOptions, InObjLoaded) , NewWidgetBlueprintClass(nullptr) , WidgetSchema(nullptr) { } FWidgetBlueprintCompiler::~FWidgetBlueprintCompiler() { } UEdGraphSchema_K2* FWidgetBlueprintCompiler::CreateSchema() { WidgetSchema = NewObject(); return WidgetSchema; } void FWidgetBlueprintCompiler::CreateFunctionList() { Super::CreateFunctionList(); for ( FDelegateEditorBinding& EditorBinding : WidgetBlueprint()->Bindings ) { if ( EditorBinding.SourcePath.IsEmpty() ) { const FName PropertyName = EditorBinding.SourceProperty; UProperty* Property = FindField(Blueprint->SkeletonGeneratedClass, PropertyName); if ( Property ) { // Create the function graph. FString FunctionName = FString(TEXT("__Get")) + PropertyName.ToString(); UEdGraph* FunctionGraph = FBlueprintEditorUtils::CreateNewGraph(Blueprint, FBlueprintEditorUtils::FindUniqueKismetName(Blueprint, FunctionName), UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass()); // Update the function binding to match the generated graph name EditorBinding.FunctionName = FunctionGraph->GetFName(); const UEdGraphSchema_K2* K2Schema = Cast(FunctionGraph->GetSchema()); Schema->CreateDefaultNodesForGraph(*FunctionGraph); K2Schema->MarkFunctionEntryAsEditable(FunctionGraph, true); // Create a function entry node FGraphNodeCreator FunctionEntryCreator(*FunctionGraph); UK2Node_FunctionEntry* EntryNode = FunctionEntryCreator.CreateNode(); EntryNode->SignatureClass = NULL; EntryNode->SignatureName = FunctionGraph->GetFName(); FunctionEntryCreator.Finalize(); FGraphNodeCreator FunctionReturnCreator(*FunctionGraph); UK2Node_FunctionResult* ReturnNode = FunctionReturnCreator.CreateNode(); ReturnNode->SignatureClass = NULL; ReturnNode->SignatureName = FunctionGraph->GetFName(); ReturnNode->NodePosX = EntryNode->NodePosX + EntryNode->NodeWidth + 256; ReturnNode->NodePosY = EntryNode->NodePosY; FunctionReturnCreator.Finalize(); FEdGraphPinType PinType; K2Schema->ConvertPropertyToPinType(Property, /*out*/ PinType); UEdGraphPin* ReturnPin = ReturnNode->CreateUserDefinedPin(TEXT("ReturnValue"), PinType, EGPD_Input); // Auto-connect the pins for entry and exit, so that by default the signature is properly generated UEdGraphPin* EntryNodeExec = K2Schema->FindExecutionPin(*EntryNode, EGPD_Output); UEdGraphPin* ResultNodeExec = K2Schema->FindExecutionPin(*ReturnNode, EGPD_Input); EntryNodeExec->MakeLinkTo(ResultNodeExec); FGraphNodeCreator MemberGetCreator(*FunctionGraph); UK2Node_VariableGet* VarNode = MemberGetCreator.CreateNode(); VarNode->VariableReference.SetSelfMember(PropertyName); MemberGetCreator.Finalize(); ReturnPin->MakeLinkTo(VarNode->GetValuePin()); // We need to flag the entry node to make sure that the compiled function is callable from Kismet2 int32 ExtraFunctionFlags = ( FUNC_Private | FUNC_Const ); K2Schema->AddExtraFunctionFlags(FunctionGraph, ExtraFunctionFlags); //Blueprint->FunctionGraphs.Add(FunctionGraph); ProcessOneFunctionGraph(FunctionGraph, true); //FEdGraphUtilities::MergeChildrenGraphsIn(Ubergraph, FunctionGraph, /*bRequireSchemaMatch=*/ true); } } } } void FWidgetBlueprintCompiler::ValidateWidgetNames() { UWidgetBlueprint* WidgetBP = WidgetBlueprint(); TSharedPtr ParentBPNameValidator; if ( WidgetBP->ParentClass != nullptr ) { UBlueprint* ParentBP = Cast(WidgetBP->ParentClass->ClassGeneratedBy); if ( ParentBP != nullptr ) { ParentBPNameValidator = MakeShareable(new FKismetNameValidator(ParentBP)); } } } template struct FCullTemplateObjectsHelper { const TArray& Templates; FCullTemplateObjectsHelper(const TArray& InComponentTemplates) : Templates(InComponentTemplates) {} bool operator()(const UObject* const RemovalCandidate) const { return ( NULL != Templates.FindByKey(RemovalCandidate) ); } }; void FWidgetBlueprintCompiler::CleanAndSanitizeClass(UBlueprintGeneratedClass* ClassToClean, UObject*& InOutOldCDO) { UWidgetBlueprint* WidgetBP = WidgetBlueprint(); if ( !Blueprint->bIsRegeneratingOnLoad && bIsFullCompile ) { UPackage* WidgetTemplatePackage = WidgetBP->GetWidgetTemplatePackage(); UUserWidget* OldArchetype = LoadObject(WidgetTemplatePackage, TEXT("WidgetArchetype"), nullptr, LOAD_NoWarn); if ( OldArchetype ) { const bool bRecompilingOnLoad = Blueprint->bIsRegeneratingOnLoad; const ERenameFlags RenFlags = REN_DontCreateRedirectors | ( bRecompilingOnLoad ? REN_ForceNoResetLoaders : 0 ) | REN_NonTransactional | REN_DoNotDirty; FString TransientArchetypeString = FString::Printf(TEXT("OLD_TEMPLATE_%s"), *OldArchetype->GetName()); FName TransientArchetypeName = MakeUniqueObjectName(GetTransientPackage(), OldArchetype->GetClass(), FName(*TransientArchetypeString)); OldArchetype->Rename(*TransientArchetypeName.ToString(), GetTransientPackage(), RenFlags); OldArchetype->SetFlags(RF_Transient); OldArchetype->ClearFlags(RF_Public | RF_Standalone | RF_ArchetypeObject); FLinkerLoad::InvalidateExport(OldArchetype); TArray Children; ForEachObjectWithOuter(OldArchetype, [&Children] (UObject* Child) { Children.Add(Child); }, false); for ( UObject* Child : Children ) { Child->Rename(nullptr, GetTransientPackage(), RenFlags); Child->SetFlags(RF_Transient); FLinkerLoad::InvalidateExport(Child); } } } Super::CleanAndSanitizeClass(ClassToClean, InOutOldCDO); // Make sure our typed pointer is set check(ClassToClean == NewClass); NewWidgetBlueprintClass = CastChecked((UObject*)NewClass); for ( UWidgetAnimation* Animation : NewWidgetBlueprintClass->Animations ) { Animation->Rename(nullptr, GetTransientPackage(), REN_ForceNoResetLoaders | REN_DontCreateRedirectors); } NewWidgetBlueprintClass->Animations.Empty(); NewWidgetBlueprintClass->Bindings.Empty(); } void FWidgetBlueprintCompiler::SaveSubObjectsFromCleanAndSanitizeClass(FSubobjectCollection& SubObjectsToSave, UBlueprintGeneratedClass* ClassToClean) { // Make sure our typed pointer is set check(ClassToClean == NewClass); NewWidgetBlueprintClass = CastChecked((UObject*)NewClass); UWidgetBlueprint* WidgetBP = WidgetBlueprint(); // We need to save the widget tree to survive the initial sub-object clean blitz, // otherwise they all get renamed, and it causes early loading errors. SubObjectsToSave.AddObject(WidgetBP->WidgetTree); // We need to save all the animations to survive the initial sub-object clean blitz, // otherwise they all get renamed, and it causes early loading errors. SubObjectsToSave.AddObject(NewWidgetBlueprintClass->WidgetTree); for ( UWidgetAnimation* Animation : NewWidgetBlueprintClass->Animations ) { SubObjectsToSave.AddObject(Animation); } } void FWidgetBlueprintCompiler::CreateClassVariablesFromBlueprint() { Super::CreateClassVariablesFromBlueprint(); UWidgetBlueprint* WidgetBP = WidgetBlueprint(); UClass* ParentClass = WidgetBP->ParentClass; ValidateWidgetNames(); // Build the set of variables based on the variable widgets in the widget tree. TArray Widgets; WidgetBP->WidgetTree->GetAllWidgets(Widgets); // Sort the widgets alphabetically Widgets.Sort( []( const UWidget& Lhs, const UWidget& Rhs ) { return Rhs.GetFName() < Lhs.GetFName(); } ); // Add widget variables for ( UWidget* Widget : Widgets ) { bool bIsVariable = Widget->bIsVariable; // In the event there are bindings for a widget, but it's not marked as a variable, make it one, but hide it from the UI. // we do this so we can use FindField to locate it at runtime. bIsVariable |= WidgetBP->Bindings.ContainsByPredicate([&Widget] (const FDelegateEditorBinding& Binding) { return Binding.ObjectName == Widget->GetName(); }); // All UNamedSlot widgets are automatically variables, so that we can properly look them up quickly with FindField // in UserWidgets. bIsVariable |= Widget->IsA(); // This code was added to fix the problem of recompiling dependent widgets, not using the newest // class thus resulting in REINST failures in dependent blueprints. UClass* WidgetClass = Widget->GetClass(); if ( UBlueprintGeneratedClass* BPWidgetClass = Cast(WidgetClass) ) { WidgetClass = BPWidgetClass->GetAuthoritativeClass(); } UObjectPropertyBase* ExistingProperty = Cast(ParentClass->FindPropertyByName(Widget->GetFName())); if (ExistingProperty && FWidgetBlueprintEditorUtils::IsBindWidgetProperty(ExistingProperty) && Widget->IsA(ExistingProperty->PropertyClass)) { WidgetToMemberVariableMap.Add(Widget, ExistingProperty); continue; } // Skip non-variable widgets if ( !bIsVariable ) { continue; } FEdGraphPinType WidgetPinType(Schema->PC_Object, TEXT(""), WidgetClass, false, false, false, false, FEdGraphTerminalType()); // Always name the variable according to the underlying FName of the widget object UProperty* WidgetProperty = CreateVariable(Widget->GetFName(), WidgetPinType); if ( WidgetProperty != nullptr ) { const FString VariableName = Widget->IsGeneratedName() ? Widget->GetName() : Widget->GetLabelText().ToString(); WidgetProperty->SetMetaData(TEXT("DisplayName"), *VariableName); WidgetProperty->SetMetaData(TEXT("Category"), *WidgetBP->GetName()); // Only show variables if they're explicitly marked as variables. if ( Widget->bIsVariable ) { WidgetProperty->SetPropertyFlags(CPF_BlueprintVisible); } WidgetProperty->SetPropertyFlags(CPF_Instanced); WidgetProperty->SetPropertyFlags(CPF_RepSkip); WidgetToMemberVariableMap.Add(Widget, WidgetProperty); } } // Add movie scenes variables here for(UWidgetAnimation* Animation : WidgetBP->Animations) { FEdGraphPinType WidgetPinType(Schema->PC_Object, TEXT(""), Animation->GetClass(), false, true, false, false, FEdGraphTerminalType()); UProperty* AnimationProperty = CreateVariable(Animation->GetFName(), WidgetPinType); if ( AnimationProperty != nullptr ) { AnimationProperty->SetMetaData(TEXT("Category"), TEXT("Animations")); AnimationProperty->SetPropertyFlags(CPF_Instanced); AnimationProperty->SetPropertyFlags(CPF_BlueprintVisible); AnimationProperty->SetPropertyFlags(CPF_BlueprintReadOnly); AnimationProperty->SetPropertyFlags(CPF_RepSkip); } } } void FWidgetBlueprintCompiler::CopyTermDefaultsToDefaultObject(UObject* DefaultObject) { FKismetCompilerContext::CopyTermDefaultsToDefaultObject(DefaultObject); UWidgetBlueprint* WidgetBP = WidgetBlueprint(); UUserWidget* DefaultWidget = CastChecked(DefaultObject); UWidgetBlueprintGeneratedClass* WidgetClass = CastChecked(DefaultObject->GetClass()); if ( DefaultWidget ) { //TODO Once we handle multiple derived blueprint classes, we need to check parent versions of the class. if ( const UFunction* ReceiveTickEvent = FKismetCompilerUtilities::FindOverriddenImplementableEvent(GET_FUNCTION_NAME_CHECKED(UUserWidget, Tick), NewWidgetBlueprintClass) ) { DefaultWidget->bCanEverTick = true; } else { DefaultWidget->bCanEverTick = false; } //TODO Once we handle multiple derived blueprint classes, we need to check parent versions of the class. if ( const UFunction* ReceivePaintEvent = FKismetCompilerUtilities::FindOverriddenImplementableEvent(GET_FUNCTION_NAME_CHECKED(UUserWidget, OnPaint), NewWidgetBlueprintClass) ) { DefaultWidget->bCanEverPaint = true; } else { DefaultWidget->bCanEverPaint = false; } } } bool FWidgetBlueprintCompiler::CanAllowTemplate(FCompilerResultsLog& MessageLog, UWidgetBlueprintGeneratedClass* InClass) { if ( InClass == nullptr ) { MessageLog.Error(*LOCTEXT("NoWidgetClass", "No Widget Class Found.").ToString()); return false; } UWidgetBlueprint* WidgetBP = Cast(InClass->ClassGeneratedBy); if ( WidgetBP == nullptr ) { MessageLog.Error(*LOCTEXT("NoWidgetBlueprint", "No Widget Blueprint Found.").ToString()); return false; } // If this widget forces the slow construction path, we can't template it. if ( WidgetBP->bForceSlowConstructionPath ) { MessageLog.Note(*LOCTEXT("ForceSlowConstruction", "Fast Templating Disabled By User.").ToString()); return false; } // For now we don't support nativization, it's going to require some extra work moving the template support // during the nativization process. if ( WidgetBP->NativizationFlag != EBlueprintNativizationFlag::Disabled ) { MessageLog.Warning(*LOCTEXT("TemplatingAndNativization", "Nativization and Fast Widget Creation is not supported at this time.").ToString()); return false; } return true; } bool FWidgetBlueprintCompiler::CanTemplateWidget(FCompilerResultsLog& MessageLog, UUserWidget* ThisWidget, TArray& OutErrors) { UWidgetBlueprintGeneratedClass* WidgetClass = Cast(ThisWidget->GetClass()); if ( WidgetClass == nullptr ) { MessageLog.Error(*LOCTEXT("NoWidgetClass", "No Widget Class Found.").ToString()); return false; } if ( WidgetClass->bAllowTemplate == false ) { MessageLog.Warning(*LOCTEXT("ClassDoesNotAllowTemplating", "This widget class is not allowed to be templated.").ToString()); return false; } return ThisWidget->VerifyTemplateIntegrity(OutErrors); } void FWidgetBlueprintCompiler::FinishCompilingClass(UClass* Class) { UWidgetBlueprint* WidgetBP = WidgetBlueprint(); // Don't do a bunch of extra work on the skeleton generated class if ( WidgetBP->SkeletonGeneratedClass != Class ) { UWidgetBlueprintGeneratedClass* BPGClass = CastChecked(Class); if( !WidgetBP->bHasBeenRegenerated ) { UBlueprint::ForceLoadMembers(WidgetBP->WidgetTree); } BPGClass->WidgetTree = Cast(StaticDuplicateObject(WidgetBP->WidgetTree, BPGClass, NAME_None, RF_AllFlags & ~RF_DefaultSubObject)); for ( const UWidgetAnimation* Animation : WidgetBP->Animations ) { UWidgetAnimation* ClonedAnimation = DuplicateObject(Animation, BPGClass, *( Animation->GetName() + TEXT("_INST") )); BPGClass->Animations.Add(ClonedAnimation); } // Only check bindings on a full compile. Also don't check them if we're regenerating on load, // that has a nasty tendency to fail because the other dependent classes that may also be blueprints // might not be loaded yet. const bool bIsLoading = WidgetBP->bIsRegeneratingOnLoad; if ( bIsFullCompile ) { // Convert all editor time property bindings into a list of bindings // that will be applied at runtime. Ensure all bindings are still valid. for ( const FDelegateEditorBinding& EditorBinding : WidgetBP->Bindings ) { if ( bIsLoading || EditorBinding.IsBindingValid(Class, WidgetBP, MessageLog) ) { BPGClass->Bindings.Add(EditorBinding.ToRuntimeBinding(WidgetBP)); } } } // Add all the names of the named slot widgets to the slot names structure. BPGClass->NamedSlots.Reset(); BPGClass->WidgetTree->ForEachWidget([&] (UWidget* Widget) { if ( Widget && Widget->IsA() ) { BPGClass->NamedSlots.Add(Widget->GetFName()); } }); } UClass* ParentClass = WidgetBP->ParentClass; for ( TUObjectPropertyBase* WidgetProperty : TFieldRange>( ParentClass ) ) { bool bIsOptional; bool bIsBindWidget = FWidgetBlueprintEditorUtils::IsBindWidgetProperty(WidgetProperty, bIsOptional); if ( bIsBindWidget && !bIsOptional ) { UWidget* const* Widget = WidgetToMemberVariableMap.FindKey( WidgetProperty ); if (!Widget) { if (Blueprint->bIsNewlyCreated) { MessageLog.Warning(*LOCTEXT("RequiredWidget_NotBound", "Non-optional widget binding @@ not found.").ToString(), WidgetProperty); } else { MessageLog.Error(*LOCTEXT("RequiredWidget_NotBound", "Non-optional widget binding @@ not found.").ToString(), WidgetProperty); } } else if (!(*Widget)->IsA(WidgetProperty->PropertyClass)) { if (Blueprint->bIsNewlyCreated) { MessageLog.Warning(*LOCTEXT("IncorrectWidgetTypes", "@@ is of type @@ property is of type @@.").ToString(), *Widget, (*Widget)->GetClass(), WidgetProperty->PropertyClass); } else { MessageLog.Error(*LOCTEXT("IncorrectWidgetTypes", "@@ is of type @@ property is of type @@.").ToString(), *Widget, (*Widget)->GetClass(), WidgetProperty->PropertyClass); } } } } Super::FinishCompilingClass(Class); } void FWidgetBlueprintCompiler::PostCompile() { Super::PostCompile(); WidgetToMemberVariableMap.Empty(); UWidgetBlueprintGeneratedClass* WidgetClass = NewWidgetBlueprintClass; UWidgetBlueprint* WidgetBP = WidgetBlueprint(); WidgetClass->bAllowTemplate = CanAllowTemplate(MessageLog, NewWidgetBlueprintClass); if ( WidgetClass->bAllowTemplate ) { if ( !Blueprint->bIsRegeneratingOnLoad && bIsFullCompile ) { UUserWidget* WidgetTemplate = NewObject(GetTransientPackage(), WidgetClass); WidgetTemplate->TemplateInit(); // Determine if we can generate a template for this widget to speed up CreateWidget time. TArray OutErrors; const bool bCanTemplate = CanTemplateWidget(MessageLog, WidgetTemplate, OutErrors); if ( bCanTemplate ) { MessageLog.Note(*LOCTEXT("TemplateSuccess", "Fast Template Successfully Created.").ToString()); } else { MessageLog.Error(*LOCTEXT("NoTemplate", "Unable To Create Template For Widget.").ToString()); for ( FText& Error : OutErrors ) { MessageLog.Error(*Error.ToString()); } } } } } void FWidgetBlueprintCompiler::EnsureProperGeneratedClass(UClass*& TargetUClass) { if ( TargetUClass && !( (UObject*)TargetUClass )->IsA(UWidgetBlueprintGeneratedClass::StaticClass()) ) { FKismetCompilerUtilities::ConsignToOblivion(TargetUClass, Blueprint->bIsRegeneratingOnLoad); TargetUClass = nullptr; } } void FWidgetBlueprintCompiler::SpawnNewClass(const FString& NewClassName) { NewWidgetBlueprintClass = FindObject(Blueprint->GetOutermost(), *NewClassName); if ( NewWidgetBlueprintClass == nullptr ) { NewWidgetBlueprintClass = NewObject(Blueprint->GetOutermost(), FName(*NewClassName), RF_Public | RF_Transactional); } else { // Already existed, but wasn't linked in the Blueprint yet due to load ordering issues FBlueprintCompileReinstancer::Create(NewWidgetBlueprintClass); } NewClass = NewWidgetBlueprintClass; } void FWidgetBlueprintCompiler::PrecompileFunction(FKismetFunctionContext& Context, EInternalCompilerFlags InternalFlags) { Super::PrecompileFunction(Context, InternalFlags); VerifyEventReplysAreNotEmpty(Context); } void FWidgetBlueprintCompiler::VerifyEventReplysAreNotEmpty(FKismetFunctionContext& Context) { TArray FunctionResults; Context.SourceGraph->GetNodesOfClass(FunctionResults); UScriptStruct* EventReplyStruct = FEventReply::StaticStruct(); FEdGraphPinType EventReplyPinType(Schema->PC_Struct, TEXT(""), EventReplyStruct, /*bIsArray =*/false, /*bIsReference =*/false, /*bIsSet =*/false, /*bIsMap =*/ false, /*InValueTerminalType =*/FEdGraphTerminalType()); for ( UK2Node_FunctionResult* FunctionResult : FunctionResults ) { for ( UEdGraphPin* ReturnPin : FunctionResult->Pins ) { if ( ReturnPin->PinType == EventReplyPinType ) { const bool IsUnconnectedEventReply = ReturnPin->Direction == EEdGraphPinDirection::EGPD_Input && ReturnPin->LinkedTo.Num() == 0; if ( IsUnconnectedEventReply ) { MessageLog.Warning(*LOCTEXT("MissingEventReply_Warning", "Event Reply @@ should not be empty. Return a reply such as Handled or Unhandled.").ToString(), ReturnPin); } } } } } bool FWidgetBlueprintCompiler::ValidateGeneratedClass(UBlueprintGeneratedClass* Class) { bool SuperResult = Super::ValidateGeneratedClass(Class); bool Result = UWidgetBlueprint::ValidateGeneratedClass(Class); return SuperResult && Result; } #undef LOCTEXT_NAMESPACE