// Copyright 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 "MovieScene.h" #include "FieldNotification/CustomizationHelper.h" #include "FieldNotificationHelpers.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" #include "WidgetEditingProjectSettings.h" #include "WidgetCompilerRule.h" #include "WidgetBlueprintExtension.h" #include "Editor/WidgetCompilerLog.h" #include "Editor.h" #include "Algo/RemoveIf.h" #define LOCTEXT_NAMESPACE "UMG" #define CPF_Instanced (CPF_PersistentInstance | CPF_ExportObject | CPF_InstancedReference) extern COREUOBJECT_API bool GMinimalCompileOnLoad; namespace UE::WidgetBlueprintCompiler::Private { FProperty* FindChildProperty(const UStruct* Struct, const FName& PropertyName) { for (FField* Field = Struct->ChildProperties; Field != nullptr; Field = Field->Next) { if (Field->GetFName() == PropertyName) { return CastField(Field); } } return nullptr; } } ////////////////////////////////////////////////////////////////////////// // FWidgetBlueprintCompiler::FPopulateGeneratedVariablesContext FWidgetBlueprintCompilerContext::FPopulateGeneratedVariablesContext::FPopulateGeneratedVariablesContext(FWidgetBlueprintCompilerContext& InContext) : Context(InContext) {} void FWidgetBlueprintCompilerContext::FPopulateGeneratedVariablesContext::AddGeneratedVariable(FBPVariableDescription&& VariableDescription) const { Context.AddGeneratedVariable(MoveTemp(VariableDescription)); } UWidgetBlueprint* FWidgetBlueprintCompilerContext::FPopulateGeneratedVariablesContext::GetWidgetBlueprint() const { return Context.WidgetBlueprint(); } ////////////////////////////////////////////////////////////////////////// // FWidgetBlueprintCompiler::FCreateVariableContext FWidgetBlueprintCompilerContext::FCreateVariableContext::FCreateVariableContext(FWidgetBlueprintCompilerContext& InContext) : Context(InContext) {} FProperty* FWidgetBlueprintCompilerContext::FCreateVariableContext::CreateVariable(const FName Name, const FEdGraphPinType& Type) const { return Context.CreateVariable(Name, Type); } FMulticastDelegateProperty* FWidgetBlueprintCompilerContext::FCreateVariableContext::CreateMulticastDelegateVariable(const FName Name, const FEdGraphPinType& Type) const { return Context.CreateMulticastDelegateVariable(Name, Type); } FMulticastDelegateProperty* FWidgetBlueprintCompilerContext::FCreateVariableContext::CreateMulticastDelegateVariable(const FName Name) const { return Context.CreateMulticastDelegateVariable(Name); } void FWidgetBlueprintCompilerContext::FCreateVariableContext::AddGeneratedFunctionGraph(UEdGraph* Graph) const { Context.GeneratedFunctionGraphs.Add(Graph); } UWidgetBlueprint* FWidgetBlueprintCompilerContext::FCreateVariableContext::GetWidgetBlueprint() const { return Context.WidgetBlueprint(); } UWidgetBlueprintGeneratedClass* FWidgetBlueprintCompilerContext::FCreateVariableContext::GetSkeletonGeneratedClass() const { return Context.NewWidgetBlueprintClass; } UWidgetBlueprintGeneratedClass* FWidgetBlueprintCompilerContext::FCreateVariableContext::GetGeneratedClass() const { return Context.NewWidgetBlueprintClass; } EKismetCompileType::Type FWidgetBlueprintCompilerContext::FCreateVariableContext::GetCompileType() const { return Context.CompileOptions.CompileType; } ////////////////////////////////////////////////////////////////////////// // FWidgetBlueprintCompiler::FCreateFunctionContext FWidgetBlueprintCompilerContext::FCreateFunctionContext::FCreateFunctionContext(FWidgetBlueprintCompilerContext& InContext) : Context(InContext) {} void FWidgetBlueprintCompilerContext::FCreateFunctionContext::AddGeneratedFunctionGraph(UEdGraph* Graph) const { Context.GeneratedFunctionGraphs.Add(Graph); } void FWidgetBlueprintCompilerContext::FCreateFunctionContext::AddGeneratedUbergraphPage(UEdGraph* Graph) const { Context.GeneratedUbergraphPages.Add(Graph); } UWidgetBlueprintGeneratedClass* FWidgetBlueprintCompilerContext::FCreateFunctionContext::GetGeneratedClass() const { return Context.NewWidgetBlueprintClass; } ////////////////////////////////////////////////////////////////////////// // FWidgetBlueprintCompiler FWidgetBlueprintCompiler::FWidgetBlueprintCompiler() : ReRegister(nullptr) , CompileCount(0) { } bool FWidgetBlueprintCompiler::CanCompile(const UBlueprint* Blueprint) { return Cast(Blueprint) != nullptr; } void FWidgetBlueprintCompiler::PreCompile(UBlueprint* Blueprint, const FKismetCompilerOptions& CompileOptions) { if (ReRegister == nullptr && CanCompile(Blueprint) && CompileOptions.CompileType == EKismetCompileType::Full) { ReRegister = new TComponentReregisterContext(); } CompileCount++; } void FWidgetBlueprintCompiler::Compile(UBlueprint * Blueprint, const FKismetCompilerOptions & CompileOptions, FCompilerResultsLog & Results) { if (UWidgetBlueprint* WidgetBlueprint = CastChecked(Blueprint)) { FWidgetBlueprintCompilerContext Compiler(WidgetBlueprint, Results, CompileOptions); Compiler.Compile(); check(Compiler.NewClass); } } void FWidgetBlueprintCompiler::PostCompile(UBlueprint* Blueprint, const FKismetCompilerOptions& CompileOptions) { CompileCount--; if (CompileCount == 0 && ReRegister) { delete ReRegister; ReRegister = nullptr; if (GIsEditor && GEditor) { GEditor->RedrawAllViewports(true); } } } bool FWidgetBlueprintCompiler::GetBlueprintTypesForClass(UClass* ParentClass, UClass*& OutBlueprintClass, UClass*& OutBlueprintGeneratedClass) const { if (ParentClass == UUserWidget::StaticClass() || ParentClass->IsChildOf(UUserWidget::StaticClass())) { OutBlueprintClass = UWidgetBlueprint::StaticClass(); OutBlueprintGeneratedClass = UWidgetBlueprintGeneratedClass::StaticClass(); return true; } return false; } FWidgetBlueprintCompilerContext::FWidgetBlueprintCompilerContext(UWidgetBlueprint* SourceSketch, FCompilerResultsLog& InMessageLog, const FKismetCompilerOptions& InCompilerOptions) : Super(SourceSketch, InMessageLog, InCompilerOptions) , NewWidgetBlueprintClass(nullptr) , OldWidgetTree(nullptr) , WidgetSchema(nullptr) { UWidgetBlueprintExtension::ForEachExtension(WidgetBlueprint(), [this](UWidgetBlueprintExtension* InExtension) { InExtension->BeginCompilation(*this); }); } FWidgetBlueprintCompilerContext::~FWidgetBlueprintCompilerContext() { UWidgetBlueprintExtension::ForEachExtension(WidgetBlueprint(), [](UWidgetBlueprintExtension* InExtension) { InExtension->EndCompilation(); }); } UEdGraphSchema_K2* FWidgetBlueprintCompilerContext::CreateSchema() { WidgetSchema = NewObject(); return WidgetSchema; } void FWidgetBlueprintCompilerContext::CreateFunctionList() { UWidgetBlueprintExtension::ForEachExtension(WidgetBlueprint(), [Self = this](UWidgetBlueprintExtension* InExtension) { InExtension->CreateFunctionList(FCreateFunctionContext(*Self)); }); Super::CreateFunctionList(); for ( FDelegateEditorBinding& EditorBinding : WidgetBlueprint()->Bindings ) { if ( EditorBinding.SourcePath.IsEmpty() ) { const FName PropertyName = EditorBinding.SourceProperty; FProperty* Property = FindFProperty(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->FunctionReference.SetSelfMember(FunctionGraph->GetFName()); FunctionEntryCreator.Finalize(); FGraphNodeCreator FunctionReturnCreator(*FunctionGraph); UK2Node_FunctionResult* ReturnNode = FunctionReturnCreator.CreateNode(); ReturnNode->FunctionReference.SetSelfMember(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); } } } } 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 FWidgetBlueprintCompilerContext::CleanAndSanitizeClass(UBlueprintGeneratedClass* ClassToClean, UObject*& InOutOldCDO) { UWidgetBlueprint* WidgetBP = WidgetBlueprint(); const bool bRecompilingOnLoad = Blueprint->bIsRegeneratingOnLoad; auto RenameObjectToTransientPackage = [bRecompilingOnLoad](UObject* ObjectToRename, const FName BaseName, bool bClearFlags) { ObjectToRename->SetFlags(RF_Transient); if (bClearFlags) { ObjectToRename->ClearFlags(RF_Public | RF_Standalone | RF_ArchetypeObject); } // Rename will remove the renamed object's linker when moving to a new package so invalidate the export beforehand FLinkerLoad::InvalidateExport(ObjectToRename); const ERenameFlags RenFlags = REN_DontCreateRedirectors | REN_NonTransactional | REN_DoNotDirty; if (BaseName.IsNone()) { ObjectToRename->Rename(nullptr, GetTransientPackage(), RenFlags); } else { FName TransientArchetypeName = MakeUniqueObjectName(GetTransientPackage(), ObjectToRename->GetClass(), BaseName); ObjectToRename->Rename(*TransientArchetypeName.ToString(), GetTransientPackage(), RenFlags); } }; if ( !Blueprint->bIsRegeneratingOnLoad && bIsFullCompile ) { if (UWidgetBlueprintGeneratedClass* WBC_ToClean = Cast(ClassToClean)) { if (UWidgetTree* OldArchetype = WBC_ToClean->GetWidgetTreeArchetype()) { FString TransientArchetypeString = FString::Printf(TEXT("OLD_TEMPLATE_TREE%s"), *OldArchetype->GetName()); RenameObjectToTransientPackage(OldArchetype, *TransientArchetypeString, true); TArray Children; ForEachObjectWithOuter(OldArchetype, [&Children] (UObject* Child) { Children.Add(Child); }, false); for ( UObject* Child : Children ) { RenameObjectToTransientPackage(Child, FName(), false); } WBC_ToClean->SetWidgetTreeArchetype(nullptr); } } } // Remove widgets that are created but not referenced by the widget tree. This could happen when another referenced UserWidget is modified. { TArray OuterWidgets = WidgetBP->GetAllSourceWidgets(); TArray TreeWidgets; if (WidgetBP->WidgetTree) { WidgetBP->WidgetTree->GetAllWidgets(TreeWidgets); } FMemMark Mark(FMemStack::Get()); TArray> WidgetsToRemove; WidgetsToRemove.Reserve(OuterWidgets.Num()); struct FNameSlotInfo { TScriptInterface NamedSlotHost; FName SlotName; }; TMap WidgetToNamedSlotInfo; WidgetToNamedSlotInfo.Reserve(OuterWidgets.Num()); for (UWidget* OuterWidget : OuterWidgets) { if (TScriptInterface NamedSlotHost = TScriptInterface(OuterWidget)) { TArray SlotNames; NamedSlotHost->GetSlotNames(SlotNames); for (FName SlotName : SlotNames) { if (UWidget* SlotContent = NamedSlotHost->GetContentForSlot(SlotName)) { FNameSlotInfo Info = { NamedSlotHost, SlotName }; WidgetToNamedSlotInfo.Add(SlotContent, Info); } } } if (!TreeWidgets.Contains(OuterWidget)) { WidgetsToRemove.Push(OuterWidget); } } if (WidgetsToRemove.Num() != 0) { if (WidgetBP->WidgetTree->RootWidget == nullptr) { MessageLog.Note(*LOCTEXT("RootWidgetEmpty", "There is no valid Widgets in this Widget Hierarchy.").ToString()); } else { MessageLog.Note(*FText::Format(LOCTEXT("RootWidgetNamedMessage", "Some Widgets will be removed since they are not part of the Widget Hierarchy. Root Widget is '{0}'."), FText::FromName(WidgetBP->WidgetTree->RootWidget->GetFName())).ToString()); } // Log first to have all the parents and named slot intact for logging for (const UWidget* WidgetToClean : WidgetsToRemove) { if (UPanelWidget* Parent = WidgetToClean->GetParent()) { MessageLog.Note(*FText::Format(LOCTEXT("UnusedWidgetFoundAndRemovedWithParent", "Removing unused widget '{0}' (Parent: '{1}')."), FText::FromName(WidgetToClean->GetFName()), FText::FromName(Parent->GetFName())).ToString()); } else if (const FNameSlotInfo* Info = WidgetToNamedSlotInfo.Find(WidgetToClean)) { UObject* NamedSlotWidget = Info->NamedSlotHost.GetObject(); if (ensure(NamedSlotWidget)) { MessageLog.Note(*FText::Format(LOCTEXT("UnusedWidgetFoundAndRemovedWithNamedSlot", "Removing unused widget '{0}' (Named Slot '{1} in '{2}')."), FText::FromName(WidgetToClean->GetFName()), FText::FromName(Info->SlotName), FText::FromName(NamedSlotWidget->GetFName())).ToString()); } } else { MessageLog.Note(*FText::Format(LOCTEXT("UnusedWidgetFoundAndRemoved", "Removing unused widget '{0}'."), FText::FromName(WidgetToClean->GetFName())).ToString()); } } // Remove Widget for (UWidget* WidgetToClean : WidgetsToRemove) { FString TransientCDOString = FString::Printf(TEXT("TRASH_%s"), *WidgetToClean->GetName()); RenameObjectToTransientPackage(WidgetToClean, *TransientCDOString, true); } } } Super::CleanAndSanitizeClass(ClassToClean, InOutOldCDO); // Make sure our typed pointer is set check(ClassToClean == NewClass && NewWidgetBlueprintClass == NewClass); for (UWidgetAnimation* Animation : NewWidgetBlueprintClass->Animations) { RenameObjectToTransientPackage(Animation, FName(), false); } NewWidgetBlueprintClass->Animations.Empty(); NewWidgetBlueprintClass->Bindings.Empty(); NewWidgetBlueprintClass->Extensions.Empty(); if (UWidgetBlueprintGeneratedClass* WidgetClassToClean = Cast(ClassToClean)) { UWidgetBlueprintExtension::ForEachExtension(WidgetBlueprint(), [WidgetClassToClean, InOutOldCDO](UWidgetBlueprintExtension* InExtension) { InExtension->CleanAndSanitizeClass(WidgetClassToClean, InOutOldCDO); }); } } void FWidgetBlueprintCompilerContext::SaveSubObjectsFromCleanAndSanitizeClass(FSubobjectCollection& SubObjectsToSave, UBlueprintGeneratedClass* ClassToClean) { Super::SaveSubObjectsFromCleanAndSanitizeClass(SubObjectsToSave, ClassToClean); // Make sure our typed pointer is set check(ClassToClean == NewClass); NewWidgetBlueprintClass = CastChecked((UObject*)NewClass); OldWidgetTree = nullptr; OldWidgetAnimations.Empty(); if (NewWidgetBlueprintClass) { OldWidgetTree = NewWidgetBlueprintClass->GetWidgetTreeArchetype(); OldWidgetAnimations.Append(NewWidgetBlueprintClass->Animations); } 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); if (UUserWidget* ClassDefaultWidgetToClean = Cast(ClassToClean->GetDefaultObject(false))) { // We need preserve any named slots that have been slotted into the CDO. This can happen when someone subclasses // from a widget with named slots. Those named slots are exposed to the child classes widget tree as // containers they can slot stuff into. Those widgets need to survive recompile. for (FNamedSlotBinding& CDONamedSlotBinding : ClassDefaultWidgetToClean->NamedSlotBindings) { SubObjectsToSave.AddObject(CDONamedSlotBinding.Content); } } UWidgetBlueprintExtension::ForEachExtension(WidgetBlueprint(), [&SubObjectsToSave, LocalClass = NewWidgetBlueprintClass](UWidgetBlueprintExtension* InExtension) { SubObjectsToSave.AddObjects(InExtension->SaveSubObjectsFromCleanAndSanitizeClass(LocalClass)); }); } void FWidgetBlueprintCompilerContext::CreateClassVariablesFromBlueprint() { Super::CreateClassVariablesFromBlueprint(); UWidgetBlueprint* WidgetBP = WidgetBlueprint(); if (WidgetBP == nullptr) { return; } UClass* ParentClass = WidgetBP->ParentClass; // Build the set of variables based on the variable widgets in the first Widget Tree we find: // in the current blueprint, the parent blueprint, and so on, until we find one. TArray Widgets; UWidgetBlueprint* WidgetBPToScan = WidgetBP; while (WidgetBPToScan != nullptr) { Widgets = WidgetBPToScan->GetAllSourceWidgets(); if (Widgets.Num() != 0) { // We found widgets. Stop search, but still check if we have a parent for bind widget validation UWidgetBlueprint* ParentWidgetBP = WidgetBPToScan->ParentClass && WidgetBPToScan->ParentClass->ClassGeneratedBy ? Cast(WidgetBPToScan->ParentClass->ClassGeneratedBy) : nullptr; if (ParentWidgetBP) { TArray ParentOwnedWidgets = ParentWidgetBP->GetAllSourceWidgets(); ParentOwnedWidgets.Sort([](const UWidget& Lhs, const UWidget& Rhs) { return Rhs.GetFName().LexicalLess(Lhs.GetFName()); }); for (UWidget* ParentOwnedWidget : ParentOwnedWidgets) { // Look in the Parent class properties to find a property with the BindWidget meta tag of the same name and Type. FObjectPropertyBase* ExistingProperty = CastField(ParentClass->FindPropertyByName(ParentOwnedWidget->GetFName())); if (ExistingProperty && FWidgetBlueprintEditorUtils::IsBindWidgetProperty(ExistingProperty) && ParentOwnedWidget->IsA(ExistingProperty->PropertyClass)) { ParentWidgetToBindWidgetMap.Add(ParentOwnedWidget, ExistingProperty); } } } break; } // Get the parent WidgetBlueprint WidgetBPToScan = WidgetBPToScan->ParentClass && WidgetBPToScan->ParentClass->ClassGeneratedBy ? Cast(WidgetBPToScan->ParentClass->ClassGeneratedBy) : nullptr; } // Add widget variables for ( UWidget* Widget : Widgets ) { // Look in the Parent class properties to find a property with the BindWidget meta tag of the same name and Type. FObjectPropertyBase* ExistingProperty = CastField(ParentClass->FindPropertyByName(Widget->GetFName())); if (ExistingProperty && FWidgetBlueprintEditorUtils::IsBindWidgetProperty(ExistingProperty) && Widget->IsA(ExistingProperty->PropertyClass)) { WidgetToMemberVariableMap.Add(Widget, ExistingProperty); continue; } // Check if the widget has a generated variable for (const FBPVariableDescription& VarDesc : WidgetBP->GeneratedVariables) { if (VarDesc.VarName == Widget->GetFName()) { FProperty* WidgetProperty = UE::WidgetBlueprintCompiler::Private::FindChildProperty(NewClass, VarDesc.VarName); if (ensureMsgf(WidgetProperty, TEXT("The Widget Blueprint [%s] has a generated variable for the widget [%s] but we failed to find a property for it."), *WidgetBP->GetName(), *Widget->GetName())) { WidgetToMemberVariableMap.Add(Widget, WidgetProperty); } break; } } } WidgetBPToScan = WidgetBP; while (WidgetBPToScan != nullptr) { // Look for BindWidgetAnim properties in parent widgetblueprints for (UWidgetAnimation* Animation : WidgetBPToScan->Animations) { FObjectPropertyBase* ExistingProperty = CastField(ParentClass->FindPropertyByName(Animation->GetFName())); if (ExistingProperty && FWidgetBlueprintEditorUtils::IsBindWidgetAnimProperty(ExistingProperty) && ExistingProperty->PropertyClass->IsChildOf(UWidgetAnimation::StaticClass())) { WidgetAnimToMemberVariableMap.Add(Animation, ExistingProperty); continue; } // Create variables for widget animation if (WidgetBPToScan == WidgetBP) { // Check if the animation has a generated variable for (const FBPVariableDescription& VarDesc : WidgetBP->GeneratedVariables) { if (VarDesc.VarName == Animation->GetFName()) { FProperty* AnimationProperty = UE::WidgetBlueprintCompiler::Private::FindChildProperty(NewClass, VarDesc.VarName); if (ensureMsgf(AnimationProperty, TEXT("The Widget Blueprint [%s] has a generated variable for the animation [%s] but we failed to find a property for it."), *WidgetBP->GetName(), *Animation->GetName())) { WidgetAnimToMemberVariableMap.Add(Animation, AnimationProperty); } break; } } } } // Get the parent WidgetBlueprint WidgetBPToScan = WidgetBPToScan->ParentClass && WidgetBPToScan->ParentClass->ClassGeneratedBy ? Cast(WidgetBPToScan->ParentClass->ClassGeneratedBy) : nullptr; } FWidgetBlueprintCompilerContext* Self = this; UWidgetBlueprintExtension::ForEachExtension(WidgetBlueprint(), [Self](UWidgetBlueprintExtension* InExtension) { InExtension->CreateClassVariablesFromBlueprint(FCreateVariableContext(*Self)); }); } void FWidgetBlueprintCompilerContext::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. const UFunction* ReceiveTickEvent = FKismetCompilerUtilities::FindOverriddenImplementableEvent(GET_FUNCTION_NAME_CHECKED(UUserWidget, Tick), NewWidgetBlueprintClass); if (ReceiveTickEvent) { DefaultWidget->bHasScriptImplementedTick = true; } else { DefaultWidget->bHasScriptImplementedTick = 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->bHasScriptImplementedPaint = true; } else { DefaultWidget->bHasScriptImplementedPaint = false; } // Reset the value of this flag, which is set on PostCDOCompiled if there are any input nodes // in the widget graphs. DefaultWidget->bAutomaticallyRegisterInputOnConstruction = false; } bool bClassOrParentsHaveLatentActions = false; bool bClassOrParentsHaveAnimations = false; bool bClassRequiresNativeTick = false; WidgetBP->UpdateTickabilityStats(bClassOrParentsHaveLatentActions, bClassOrParentsHaveAnimations, bClassRequiresNativeTick); WidgetClass->SetClassRequiresNativeTick(bClassRequiresNativeTick); // If the widget is not tickable, warn the user that widgets with animations or implemented ticks will most likely not work if (DefaultWidget->GetDesiredTickFrequency() == EWidgetTickFrequency::Never) { if (bClassOrParentsHaveAnimations) { MessageLog.Warning(*LOCTEXT("NonTickableButAnimationsFound", "This widget has animations but the widget is set to never tick. These animations will not function correctly.").ToString()); } if (bClassOrParentsHaveLatentActions) { MessageLog.Warning(*LOCTEXT("NonTickableButLatentActionsFound", "This widget has latent actions but the widget is set to never tick. These latent actions will not function correctly.").ToString()); } if (bClassRequiresNativeTick) { MessageLog.Warning(*LOCTEXT("NonTickableButNativeTickFound", "This widget may require a native tick but the widget is set to never tick. Native tick will not be called.").ToString()); } if (DefaultWidget->bHasScriptImplementedTick) { MessageLog.Warning(*LOCTEXT("NonTickableButTickFound", "This widget has a blueprint implemented Tick event but the widget is set to never tick. This tick event will never be called.").ToString()); } } UWidgetBlueprintExtension::ForEachExtension(WidgetBlueprint(), [DefaultObject](UWidgetBlueprintExtension* InExtension) { InExtension->CopyTermDefaultsToDefaultObject(DefaultObject); }); } void FWidgetBlueprintCompilerContext::SanitizeBindings(UBlueprintGeneratedClass* Class) { UWidgetBlueprint* WidgetBP = WidgetBlueprint(); // Fast recompilation leaves bindings pointing to the skeleton and not the generated class. Rebase. for (FDelegateEditorBinding& Binding : WidgetBP->Bindings) { Binding.SourcePath.Rebase(WidgetBP); } // TArray StaleBindings; for (const FDelegateEditorBinding& Binding : WidgetBP->Bindings) { if (!Binding.DoesBindingTargetExist(WidgetBP)) { StaleBindings.Add(Binding); } } // for (const FDelegateEditorBinding& Binding : StaleBindings) { WidgetBP->Bindings.Remove(Binding); } // int32 AttributeBindings = 0; for (const FDelegateEditorBinding& Binding : WidgetBP->Bindings) { if (Binding.IsAttributePropertyBinding(WidgetBP)) { AttributeBindings++; } } WidgetBP->PropertyBindings = AttributeBindings; } void FWidgetBlueprintCompilerContext::ValidateAndFixUpVariableGuids() { UWidgetBlueprint* WidgetBP = WidgetBlueprint(); // If we don't yet have any tracked variable guids, populate them deterministically // The determinism is required so that stable guids are serialized when creating external references to this widget's variables before this widget is resaved if (WidgetBP->WidgetVariableNameToGuidMap.IsEmpty()) { WidgetBP->ForEachSourceWidget([WidgetBP](UWidget* Widget) { ensureAlways(!WidgetBP->WidgetVariableNameToGuidMap.Contains(Widget->GetFName())); WidgetBP->WidgetVariableNameToGuidMap.Emplace(Widget->GetFName(), FGuid::NewDeterministicGuid(Widget->GetPathName())); }); for (UWidgetAnimation* Animation : WidgetBP->Animations) { if (Animation) { ensureAlways(!WidgetBP->WidgetVariableNameToGuidMap.Contains(Animation->GetFName())); WidgetBP->WidgetVariableNameToGuidMap.Emplace(Animation->GetFName(), FGuid::NewDeterministicGuid(Animation->GetPathName())); } } } else { // Validate that our variable guids are properly tracked and fixup issues that may have been caused by missed cases // Verify all variables have a stored GUID TSet SeenVariableNames; WidgetBP->ForEachSourceWidget([WidgetBP, &SeenVariableNames](UWidget* Widget) { if (!ensureAlwaysMsgf(WidgetBP->WidgetVariableNameToGuidMap.Contains(Widget->GetFName()), TEXT("Widget [%s] was added but did not get a GUID"), *Widget->GetName())) { WidgetBP->WidgetVariableNameToGuidMap.Add(Widget->GetFName(), FGuid::NewGuid()); } SeenVariableNames.Add(Widget->GetFName()); }); for (UWidgetAnimation* Animation : WidgetBP->Animations) { if (Animation) { if (!ensureAlwaysMsgf(WidgetBP->WidgetVariableNameToGuidMap.Contains(Animation->GetFName()), TEXT("Animation [%s] was added but did not get a GUID"), *Animation->GetName())) { WidgetBP->WidgetVariableNameToGuidMap.Add(Animation->GetFName(), FGuid::NewGuid()); } SeenVariableNames.Add(Animation->GetFName()); } } TMap GuidToVariableNameMap; // Verify we're only storing GUIDs for variables we still have and that none collide for (auto It = WidgetBP->WidgetVariableNameToGuidMap.CreateIterator(); It; ++It) { if (!ensureAlwaysMsgf(It.Value().IsValid(), TEXT("Variable [%s] has an invalid GUID"), *It.Key().ToString())) { It.Value() = FGuid::NewGuid(); } if (ensureAlwaysMsgf(!GuidToVariableNameMap.Contains(It.Value()), TEXT("The variables [%s] and [%s] have the same GUID, delete and recreate one of them to fix this error"), *It.Key().ToString(), *GuidToVariableNameMap[It.Value()].ToString())) { GuidToVariableNameMap.Add(It.Value(), It.Key()); } if (!ensureAlwaysMsgf(SeenVariableNames.Contains(It.Key()), TEXT("Variable [%s] was deleted but still has a GUID referenced by WidgetBlueprint [%s]"), *It.Key().ToString(), *WidgetBP->GetName())) { It.RemoveCurrent(); } } } } void FWidgetBlueprintCompilerContext::FixAbandonedWidgetTree(UWidgetBlueprint* WidgetBP) { UWidgetTree* WidgetTree = WidgetBP->WidgetTree; if (ensure(WidgetTree)) { if (WidgetTree->GetName() != TEXT("WidgetTree")) { if (UWidgetTree* AbandonedWidgetTree = static_cast(FindObjectWithOuter(WidgetBP, UWidgetTree::StaticClass(), TEXT("WidgetTree")))) { AbandonedWidgetTree->ClearFlags(RF_DefaultSubObject); AbandonedWidgetTree->SetFlags(RF_Transient); AbandonedWidgetTree->Rename(nullptr, GetTransientPackage(), REN_DontCreateRedirectors | REN_NonTransactional | REN_DoNotDirty); } WidgetTree->Rename(TEXT("WidgetTree"), nullptr, REN_DontCreateRedirectors | REN_NonTransactional | REN_DoNotDirty); WidgetTree->SetFlags(RF_DefaultSubObject); } } } void FWidgetBlueprintCompilerContext::FinishCompilingClass(UClass* Class) { if (Class == nullptr) return; UWidgetBlueprint* WidgetBP = WidgetBlueprint(); if (WidgetBP == nullptr) return; UClass* ParentClass = WidgetBP->ParentClass; if (ParentClass == nullptr) return; const bool bIsSkeletonOnly = CompileOptions.CompileType == EKismetCompileType::SkeletonOnly; UWidgetBlueprintGeneratedClass* BPGClass = CastChecked(Class); if (BPGClass == nullptr) return; // Don't do a bunch of extra work on the skeleton generated class. if ( !bIsSkeletonOnly ) { if( !WidgetBP->bHasBeenRegenerated ) { UBlueprint::ForceLoadMembers(WidgetBP->WidgetTree, WidgetBP); } FixAbandonedWidgetTree(WidgetBP); { TGuardValue DisableInitializeFromWidgetTree(UUserWidget::GetInitializingFromWidgetTree(), 0); // Need to clear archetype flag before duplication as we check during dup to see if we should postload EObjectFlags PreviousFlags = WidgetBP->WidgetTree->GetFlags(); WidgetBP->WidgetTree->ClearFlags(RF_ArchetypeObject); TMap DupObjectsMap; FObjectDuplicationParameters DupParams(WidgetBP->WidgetTree, BPGClass); DupParams.DestName = DupParams.SourceObject->GetFName(); DupParams.FlagMask = RF_AllFlags & ~RF_DefaultSubObject; DupParams.PortFlags |= PPF_DuplicateVerbatim; // Skip resetting text IDs // if we are recompiling the BP on load, skip post load and defer it to the loading process FUObjectSerializeContext* LinkerLoadingContext = nullptr; if (WidgetBP->bIsRegeneratingOnLoad) { FLinkerLoad* Linker = WidgetBP->GetLinker(); LinkerLoadingContext = Linker ? FUObjectThreadContext::Get().GetSerializeContext() : nullptr; DupParams.bSkipPostLoad = true; DupParams.CreatedObjects = &DupObjectsMap; } UWidgetTree* NewWidgetTree = Cast(StaticDuplicateObjectEx(DupParams)); // if we have anything in here after duplicate, then hook them in the loading process so they get post loaded if (LinkerLoadingContext) { TArray DupObjects; DupObjectsMap.GenerateValueArray(DupObjects); LinkerLoadingContext->AddUniqueLoadedObjects(DupObjects); } //WidgetBP->IsWidgetFreeFromCircularReferences(); BPGClass->SetWidgetTreeArchetype(NewWidgetTree); if (OldWidgetTree) { FLinkerLoad::PRIVATE_PatchNewObjectIntoExport(OldWidgetTree, NewWidgetTree); } OldWidgetTree = nullptr; WidgetBP->WidgetTree->SetFlags(PreviousFlags); } { TValueOrError HasReference = WidgetBP->HasCircularReferences(); if (HasReference.HasError()) { if (UWidget* FoundCircularWidget = BPGClass->GetWidgetTreeArchetype()->FindWidget(HasReference.GetError()->GetFName())) { BPGClass->GetWidgetTreeArchetype()->RemoveWidget(FoundCircularWidget); } MessageLog.Error(*FText::Format(LOCTEXT("WidgetTreeCircularReference", "The WidgetTree '{0}' Contains circular references. See widget '{1}'"), FText::FromString(WidgetBP->WidgetTree->GetPathName()), FText::FromString(HasReference.GetError()->GetName()) ).ToString()); } } { #if WITH_EDITOR BPGClass->NameClashingInHierarchy.Reset(); #endif TValueOrError> HasConflictingWidgetNames = WidgetBP->HasConflictingWidgetNamesFromInheritance(); if (HasConflictingWidgetNames.HasError()) { TSet& ConflictingNames = HasConflictingWidgetNames.GetError(); for (UWidget* ConflictingWidget : ConflictingNames) { #if WITH_EDITOR FName ConflictingWidgetName = ConflictingWidget->GetFName(); BPGClass->NameClashingInHierarchy.Add(ConflictingWidgetName); #endif if (UWidget* FoundConflictingWidget = BPGClass->GetWidgetTreeArchetype()->FindWidget(ConflictingWidget->GetFName())) { BPGClass->GetWidgetTreeArchetype()->RemoveWidget(FoundConflictingWidget); } MessageLog.Error(*FText::Format(LOCTEXT("WidgetTreeDuplicateNames", "The WidgetTree '{0}' already contains a widget named '{1}'."), FText::FromString(WidgetBP->WidgetTree->GetPathName()), FText::FromString(ConflictingWidget->GetName()) ).ToString()); } } } int32 AnimIndex = 0; for ( const UWidgetAnimation* Animation : WidgetBP->Animations ) { UWidgetAnimation* ClonedAnimation = DuplicateObject(Animation, BPGClass, *( Animation->GetName() + TEXT("_INST") )); //ClonedAnimation->SetFlags(RF_Public); // Needs to be marked public so that it can be referenced from widget instances. if (OldWidgetAnimations.IsValidIndex(AnimIndex) && OldWidgetAnimations[AnimIndex]) if ((AnimIndex < OldWidgetAnimations.Num()) && OldWidgetAnimations[AnimIndex]) { FLinkerLoad::PRIVATE_PatchNewObjectIntoExport(OldWidgetAnimations[AnimIndex], ClonedAnimation); } BPGClass->Animations.Add(ClonedAnimation); AnimIndex++; } OldWidgetAnimations.Empty(); // 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 ) { SanitizeBindings(BPGClass); // 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)); } } const EPropertyBindingPermissionLevel PropertyBindingRule = WidgetBP->GetRelevantSettings()->CompilerOption_PropertyBindingRule(WidgetBP); if (PropertyBindingRule != EPropertyBindingPermissionLevel::Allow) { if (WidgetBP->Bindings.Num() > 0) { for (const FDelegateEditorBinding& EditorBinding : WidgetBP->Bindings) { if (EditorBinding.IsAttributePropertyBinding(WidgetBP)) { FText NoPropertyBindingsAllowedError = FText::Format(LOCTEXT("NoPropertyBindingsAllowed", "Property Bindings have been disabled for this widget. You should remove the binding from {0}.{1}"), FText::FromString(EditorBinding.ObjectName), FText::FromName(EditorBinding.PropertyName)); switch (PropertyBindingRule) { case EPropertyBindingPermissionLevel::PreventAndWarn: MessageLog.Warning(*NoPropertyBindingsAllowedError.ToString()); break; case EPropertyBindingPermissionLevel::PreventAndError: MessageLog.Error(*NoPropertyBindingsAllowedError.ToString()); break; } } } } } if (!WidgetBP->GetRelevantSettings()->CompilerOption_AllowBlueprintTick(WidgetBP)) { const UFunction* ReceiveTickEvent = FKismetCompilerUtilities::FindOverriddenImplementableEvent(GET_FUNCTION_NAME_CHECKED(UUserWidget, Tick), NewWidgetBlueprintClass); if (ReceiveTickEvent) { MessageLog.Error(*LOCTEXT("TickNotAllowedForWidget", "Blueprint implementable ticking has been disabled for this widget in the Widget Designer (Team) - Project Settings").ToString()); } } if (!WidgetBP->GetRelevantSettings()->CompilerOption_AllowBlueprintPaint(WidgetBP)) { if (const UFunction* ReceivePaintEvent = FKismetCompilerUtilities::FindOverriddenImplementableEvent(GET_FUNCTION_NAME_CHECKED(UUserWidget, OnPaint), NewWidgetBlueprintClass)) { MessageLog.Error(*LOCTEXT("PaintNotAllowedForWidget", "Blueprint implementable painting has been disabled for this widget in the Widget Designer (Team) - Project Settings.").ToString()); } } // It's possible we may encounter some rules that haven't had a chance to load yet during early loading phases // They're automatically removed from the returned set. TArray CustomRules = WidgetBP->GetRelevantSettings()->CompilerOption_Rules(WidgetBP); for (UWidgetCompilerRule* CustomRule : CustomRules) { CustomRule->ExecuteRule(WidgetBP, MessageLog); } } // Add all the names of the named slot widgets to the slot names structure. { #if WITH_EDITOR BPGClass->NamedSlotsWithID.Reset(); BPGClass->NamedSlotsWithContentInSameTree.Reset(); #endif BPGClass->NamedSlots.Reset(); BPGClass->InstanceNamedSlots.Reset(); TArray NamedSlotsPerWidgetBlueprint; UWidgetBlueprint* WidgetBPIt = WidgetBP; while (WidgetBPIt) { NamedSlotsPerWidgetBlueprint.Reset(); WidgetBPIt->ForEachSourceWidget([&] (const UWidget* Widget) { if (const UNamedSlot* NamedSlot = Cast(Widget)) { NamedSlotsPerWidgetBlueprint.Add(Widget->GetFName()); #if WITH_EDITOR BPGClass->NamedSlotsWithID.Add(TPair(Widget->GetFName(), NamedSlot->GetSlotGUID())); // A namedslot whose content is in the same blueprint class is treated as a regular panel widget. // We need to keep track of these to later remove them from the hierarchy. if (NamedSlot->GetChildrenCount() > 0) { BPGClass->NamedSlotsWithContentInSameTree.Add(NamedSlot->GetFName()); } #endif if (NamedSlot->bExposeOnInstanceOnly) { BPGClass->InstanceNamedSlots.Add(Widget->GetFName()); } } }); // Here we reverse this array to maintain the order of sibling namedslots once the final array BPGClass->NamedSlots is reversed. Algo::Reverse(NamedSlotsPerWidgetBlueprint); BPGClass->NamedSlots.Append(NamedSlotsPerWidgetBlueprint); WidgetBPIt = Cast(WidgetBPIt->ParentClass->ClassGeneratedBy); } // We iterate widget blueprints from child to parent, but we need the final namedslot array to be sorted from parent to child, so we reverse it. Algo::Reverse(BPGClass->NamedSlots); BPGClass->AvailableNamedSlots = BPGClass->NamedSlots; // Remove any named slots from the available slots that has content for it. BPGClass->GetNamedSlotArchetypeContent([BPGClass](FName SlotName, UWidget* Content) { // If we find content for this slot, remove it from the available set. BPGClass->AvailableNamedSlots.Remove(SlotName); }); // Remove any available subclass named slots that are marked as instance named slot. for (const FName& InstanceNamedSlot : BPGClass->InstanceNamedSlots) { BPGClass->AvailableNamedSlots.Remove(InstanceNamedSlot); } // Now add any available named slot that doesn't have anything in it also. for (const FName& AvailableNamedSlot : BPGClass->AvailableNamedSlots) { BPGClass->InstanceNamedSlots.AddUnique(AvailableNamedSlot); } } // Make sure that we don't have dueling widget hierarchies if (UWidgetBlueprintGeneratedClass* SuperBPGClass = Cast(BPGClass->GetSuperClass())) { if (SuperBPGClass->ClassGeneratedBy) // ClassGeneratedBy can be null for cooked widget blueprints { UWidgetBlueprint* SuperBlueprint = Cast(SuperBPGClass->ClassGeneratedBy); if (ensure(SuperBlueprint) && SuperBlueprint->WidgetTree != nullptr) { if ((SuperBlueprint->WidgetTree->RootWidget != nullptr) && (WidgetBlueprint()->WidgetTree->RootWidget != nullptr)) { // We both have a widget tree, terrible things will ensue // @todo: nickd - we need to switch this back to a warning in engine, but note for games MessageLog.Note(*LOCTEXT("ParentAndChildBothHaveWidgetTrees", "This widget @@ and parent class widget @@ both have a widget hierarchy, which is not supported. Only one of them should have a widget tree.").ToString(), WidgetBP, SuperBPGClass->ClassGeneratedBy); } } } } // Do validation that as we subclass trees, we never stomp the slotted content of a parent widget. // doing that is not valid, as it would invalidate variables that were set? This check could be // made more complex to only worry about cases with variables being generated, but that's a whole lot // extra, so for now lets just limit it to be safe. { TMap NamedSlotContentMap; // Make sure that we don't have dueling widget hierarchies UWidgetBlueprintGeneratedClass* NamedSlotClass = BPGClass; while (NamedSlotClass) { UWidgetTree* Tree = NamedSlotClass->GetWidgetTreeArchetype(); TArray SlotNames; Tree->GetSlotNames(SlotNames); for (FName SlotName : SlotNames) { if (UWidget* ContentInSlot = Tree->GetContentForSlot(SlotName)) { if (NamedSlotClass->NamedSlotsWithContentInSameTree.Contains(SlotName)) { UClass* SubClassWithSlotFilled = ContentInSlot->GetTypedOuter(); MessageLog.Error( *FText::Format( LOCTEXT("NamedSlotAlreadyFilledInOriginalTree", "The Named Slot '{0}' already has content in the widget blueprint it was created in but the subclass @@ tried to slot @@ into it. Please remove at least one of the contents."), FText::FromName(SlotName) ).ToString(), SubClassWithSlotFilled, ContentInSlot); } if (!NamedSlotContentMap.Contains(SlotName)) { NamedSlotContentMap.Add(SlotName, ContentInSlot); } else { UClass* SubClassWithSlotFilled = ContentInSlot->GetTypedOuter(); UClass* ParentClassWithSlotFilled = NamedSlotClass; MessageLog.Error( *FText::Format( LOCTEXT("NamedSlotAlreadyFilled", "The Named Slot '{0}' already contains @@ from the class @@ but the subclass @@ tried to slot @@ into it. Please remove the content @@ from the class @@ to fix this error."), FText::FromName(SlotName) ).ToString(), ContentInSlot, ParentClassWithSlotFilled, SubClassWithSlotFilled, NamedSlotContentMap.FindRef(SlotName), ContentInSlot, ParentClassWithSlotFilled); } } } NamedSlotClass = Cast(NamedSlotClass->GetSuperClass()); } } } if (bIsSkeletonOnly || WidgetBP->SkeletonGeneratedClass != Class) { bool bCanCallPreConstruct = true; // Check that all BindWidget properties are present and of the appropriate type for (TFObjectPropertyBase* WidgetProperty : TFieldRange>(ParentClass)) { bool bIsOptional = false; if (FWidgetBlueprintEditorUtils::IsBindWidgetProperty(WidgetProperty, bIsOptional)) { const FText OptionalBindingAvailableNote = LOCTEXT("OptionalWidgetNotBound", "An optional widget binding \"{0}\" of type @@ is available."); const FText RequiredWidgetNotBoundError = LOCTEXT("RequiredWidgetNotBound", "A required widget binding \"{0}\" of type @@ was not found."); const FText IncorrectWidgetTypeError = LOCTEXT("IncorrectWidgetTypes", "The widget @@ is of type @@, but the bind widget property is of type @@."); UWidget* const* Widget = WidgetToMemberVariableMap.FindKey(WidgetProperty); // If at first we don't find the binding, search the parent binding map if (!Widget) { Widget = ParentWidgetToBindWidgetMap.FindKey(WidgetProperty); } if (!Widget) { if (bIsOptional) { MessageLog.Note(*FText::Format(OptionalBindingAvailableNote, FText::FromName(WidgetProperty->GetFName())).ToString(), WidgetProperty->PropertyClass); } else if (Blueprint->bIsNewlyCreated) { MessageLog.Warning(*FText::Format(RequiredWidgetNotBoundError, FText::FromName(WidgetProperty->GetFName())).ToString(), WidgetProperty->PropertyClass); bCanCallPreConstruct = false; } else { MessageLog.Error(*FText::Format(RequiredWidgetNotBoundError, FText::FromName(WidgetProperty->GetFName())).ToString(), WidgetProperty->PropertyClass); bCanCallPreConstruct = false; } } else if (!(*Widget)->IsA(WidgetProperty->PropertyClass)) { if (Blueprint->bIsNewlyCreated) { MessageLog.Warning(*IncorrectWidgetTypeError.ToString(), *Widget, (*Widget)->GetClass(), WidgetProperty->PropertyClass); bCanCallPreConstruct = false; } else { MessageLog.Error(*IncorrectWidgetTypeError.ToString(), *Widget, (*Widget)->GetClass(), WidgetProperty->PropertyClass); bCanCallPreConstruct = false; } } } } if (UWidgetBlueprintGeneratedClass* BPGC = Cast(WidgetBP->GeneratedClass)) { BPGC->bCanCallPreConstruct = bCanCallPreConstruct; } // Check that all BindWidgetAnim properties are present for (TFObjectPropertyBase* WidgetAnimProperty : TFieldRange>(ParentClass)) { bool bIsOptional = false; if (FWidgetBlueprintEditorUtils::IsBindWidgetAnimProperty(WidgetAnimProperty, bIsOptional)) { const FText OptionalBindingAvailableNote = LOCTEXT("OptionalWidgetAnimNotBound", "An optional widget animation binding @@ is available."); const FText RequiredWidgetAnimNotBoundError = LOCTEXT("RequiredWidgetAnimNotBound", "A required widget animation binding @@ was not found."); UWidgetAnimation* const* WidgetAnim = WidgetAnimToMemberVariableMap.FindKey(WidgetAnimProperty); if (!WidgetAnim) { if (bIsOptional) { MessageLog.Note(*OptionalBindingAvailableNote.ToString(), WidgetAnimProperty); } else if (Blueprint->bIsNewlyCreated) { MessageLog.Warning(*RequiredWidgetAnimNotBoundError.ToString(), WidgetAnimProperty); } else { MessageLog.Error(*RequiredWidgetAnimNotBoundError.ToString(), WidgetAnimProperty); } } if (!WidgetAnimProperty->HasAnyPropertyFlags(CPF_Transient)) { const FText BindWidgetAnimTransientError = LOCTEXT("BindWidgetAnimTransient", "The property @@ uses BindWidgetAnim, but isn't Transient!"); MessageLog.Error(*BindWidgetAnimTransientError.ToString(), WidgetAnimProperty); } } } } if (BPGClass) { BPGClass->bCanCallInitializedWithoutPlayerContext = WidgetBP->bCanCallInitializedWithoutPlayerContext; } Super::FinishCompilingClass(Class); CA_ASSUME(BPGClass); UWidgetBlueprintExtension::ForEachExtension(WidgetBlueprint(), [BPGClass](UWidgetBlueprintExtension* InExtension) { InExtension->FinishCompilingClass(BPGClass); }); } class FBlueprintCompilerLog : public IWidgetCompilerLog { public: FBlueprintCompilerLog(FCompilerResultsLog& InMessageLog, TSubclassOf InClassContext) : MessageLog(InMessageLog) , ClassContext(InClassContext) { } virtual TSubclassOf GetContextClass() const override { return ClassContext; } virtual void InternalLogMessage(TSharedRef& InMessage) override { MessageLog.AddTokenizedMessage(InMessage); } private: // Compiler message log (errors, warnings, notes) FCompilerResultsLog& MessageLog; TSubclassOf ClassContext; }; void FWidgetBlueprintCompilerContext::ValidateWidgetAnimations() { UWidgetBlueprintGeneratedClass* WidgetClass = NewWidgetBlueprintClass; UWidgetBlueprint* WidgetBP = WidgetBlueprint(); UUserWidget* UserWidget = WidgetClass->GetDefaultObject(); FBlueprintCompilerLog BlueprintLog(MessageLog, WidgetClass); UWidgetTree* LatestWidgetTree = FWidgetBlueprintEditorUtils::FindLatestWidgetTree(WidgetBP, UserWidget); for (const UWidgetAnimation* InAnimation : WidgetBP->Animations) { for (const FWidgetAnimationBinding& Binding : InAnimation->AnimationBindings) { // Look for the object bindings within the widget UObject* FoundObject = Binding.FindRuntimeObject(*LatestWidgetTree, *UserWidget, InAnimation, nullptr); // If any of the FoundObjects is null, we do not play the animation. if (FoundObject == nullptr) { FoundObject = Binding.FindRuntimeObject(*WidgetBP->WidgetTree, *UserWidget, InAnimation, nullptr); if (FoundObject == nullptr) { // Notify the user of the null track in the editor const FText AnimationNullTrackMessage = LOCTEXT("AnimationNullTrack", "UMG Animation '{0}' from '{1}' is trying to animate a non-existent widget through binding '{2}'. Please re-bind or delete this object from the animation."); BlueprintLog.Warning(FText::Format(AnimationNullTrackMessage, InAnimation->GetDisplayName(), FText::FromString(UserWidget->GetClass()->GetName()), FText::FromName(Binding.WidgetName))); } } } } } void FWidgetBlueprintCompilerContext::OnPostCDOCompiled(const UObject::FPostCDOCompiledContext& Context) { Super::OnPostCDOCompiled(Context); if (Context.bIsSkeletonOnly) { return; } WidgetToMemberVariableMap.Empty(); WidgetAnimToMemberVariableMap.Empty(); ParentWidgetToBindWidgetMap.Empty(); UWidgetBlueprintGeneratedClass* WidgetClass = NewWidgetBlueprintClass; UWidgetBlueprint* WidgetBP = WidgetBlueprint(); if (!Blueprint->bIsRegeneratingOnLoad && bIsFullCompile) { FBlueprintCompilerLog BlueprintLog(MessageLog, WidgetClass); WidgetClass->GetDefaultObject()->ValidateBlueprint(*WidgetBP->WidgetTree, BlueprintLog); ValidateWidgetAnimations(); } ValidateDesiredFocusWidgetName(); } void FWidgetBlueprintCompilerContext::ValidateDesiredFocusWidgetName() { if (UWidgetBlueprintGeneratedClass* WidgetClass = NewWidgetBlueprintClass) { UWidgetBlueprint* WidgetBP = WidgetBlueprint(); UUserWidget* UserWidgetCDO = WidgetClass->GetDefaultObject(); if (WidgetBP && UserWidgetCDO) { UWidgetTree* LatestWidgetTree = FWidgetBlueprintEditorUtils::FindLatestWidgetTree(WidgetBP, UserWidgetCDO); FName DesiredFocusWidgetName = UserWidgetCDO->GetDesiredFocusWidgetName(); if (!DesiredFocusWidgetName.IsNone() && !LatestWidgetTree->FindWidget(DesiredFocusWidgetName)) { FBlueprintCompilerLog BlueprintLog(MessageLog, WidgetClass); // Notify that the desired focus widget is not found in the Widget tree, so it's invalid. const FText InvalidDesiredFocusWidgetNameMessage = LOCTEXT("InvalidDesiredFocusWidgetName", "User Widget '{0}' Desired Focus is set to a non-existent widget '{1}'. Select a valid desired focus for this User Widget."); BlueprintLog.Warning(FText::Format(InvalidDesiredFocusWidgetNameMessage, FText::FromString(UserWidgetCDO->GetClass()->GetName()), FText::FromName(DesiredFocusWidgetName))); } } } } void FWidgetBlueprintCompilerContext::EnsureProperGeneratedClass(UClass*& TargetUClass) { if ( TargetUClass && !( (UObject*)TargetUClass )->IsA(UWidgetBlueprintGeneratedClass::StaticClass()) ) { FKismetCompilerUtilities::ConsignToOblivion(TargetUClass, Blueprint->bIsRegeneratingOnLoad); TargetUClass = nullptr; } } void FWidgetBlueprintCompilerContext::PopulateBlueprintGeneratedVariables() { Super::PopulateBlueprintGeneratedVariables(); ValidateAndFixUpVariableGuids(); UWidgetBlueprint* WidgetBP = WidgetBlueprint(); // Widget Variables { TArray SortedWidgets = WidgetBP->GetAllSourceWidgets(); SortedWidgets.Sort( []( const UWidget& Lhs, const UWidget& Rhs ) { return Rhs.GetFName().LexicalLess(Lhs.GetFName()); } ); for (UWidget* Widget : SortedWidgets) { // All UNamedSlot widgets are automatically variables, so that we can properly look them up quickly with FindField // in UserWidgets. // 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. const bool bShouldGenerateVariable = Widget->bIsVariable || Widget->IsA() || WidgetBP->Bindings.ContainsByPredicate([&Widget] (const FDelegateEditorBinding& Binding) { return Binding.ObjectName == Widget->GetName(); }); if (!bShouldGenerateVariable) { continue; } // Look in the Parent class properties to find a property with the BindWidget meta tag of the same name and Type. FObjectPropertyBase* ExistingProperty = WidgetBP->ParentClass ? CastField(WidgetBP->ParentClass->FindPropertyByName(Widget->GetFName())) : nullptr; if (ExistingProperty && FWidgetBlueprintEditorUtils::IsBindWidgetProperty(ExistingProperty) && Widget->IsA(ExistingProperty->PropertyClass)) { continue; } // 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(); } ensure(WidgetBP->WidgetVariableNameToGuidMap.Contains(Widget->GetFName())); FBPVariableDescription WidgetVariableDesc; WidgetVariableDesc.VarName = Widget->GetFName(); WidgetVariableDesc.VarGuid = WidgetBP->WidgetVariableNameToGuidMap.FindRef(Widget->GetFName()); WidgetVariableDesc.VarType = FEdGraphPinType(UEdGraphSchema_K2::PC_Object, NAME_None, WidgetClass, EPinContainerType::None, false, FEdGraphTerminalType()); WidgetVariableDesc.FriendlyName = Widget->IsGeneratedName() ? Widget->GetName() : Widget->GetLabelText().ToString(); WidgetVariableDesc.PropertyFlags = (CPF_PersistentInstance | CPF_ExportObject | CPF_InstancedReference | CPF_RepSkip); // Only show variables if they're explicitly marked as variables. if (Widget->bIsVariable) { WidgetVariableDesc.PropertyFlags |= (CPF_BlueprintVisible | CPF_BlueprintReadOnly | CPF_DisableEditOnInstance); // Only include Category metadata for variables (i.e. a visible/editable property); otherwise, UHT will raise a warning if this Blueprint is nativized. const FString& CategoryName = Widget->GetCategoryName(); WidgetVariableDesc.SetMetaData(TEXT("Category"), *(CategoryName.IsEmpty() ? WidgetBP->GetName() : CategoryName)); } AddGeneratedVariable(MoveTemp(WidgetVariableDesc)); } } // Animation Variables { for (UWidgetAnimation* Animation : WidgetBP->Animations) { // BindWidgetAnims already have properties FObjectPropertyBase* ExistingProperty = WidgetBP->ParentClass ? CastField(WidgetBP->ParentClass->FindPropertyByName(Animation->GetFName())) : nullptr; if (ExistingProperty && FWidgetBlueprintEditorUtils::IsBindWidgetAnimProperty(ExistingProperty) && ExistingProperty->PropertyClass->IsChildOf(UWidgetAnimation::StaticClass())) { continue; } ensure(WidgetBP->WidgetVariableNameToGuidMap.Contains(Animation->GetFName())); FBPVariableDescription AnimVariableDesc; AnimVariableDesc.VarName = Animation->GetFName(); AnimVariableDesc.VarGuid = WidgetBP->WidgetVariableNameToGuidMap.FindRef(Animation->GetFName()); AnimVariableDesc.VarType = FEdGraphPinType(UEdGraphSchema_K2::PC_Object, NAME_None, Animation->GetClass(), EPinContainerType::None, true, FEdGraphTerminalType()); AnimVariableDesc.FriendlyName = Animation->GetDisplayName().ToString(); AnimVariableDesc.PropertyFlags = (CPF_Transient | CPF_BlueprintVisible | CPF_BlueprintReadOnly | CPF_RepSkip); AnimVariableDesc.SetMetaData(TEXT("Category"), TEXT("Animations")); ensure(!WidgetBP->GeneratedVariables.ContainsByPredicate([&AnimVariableDesc](const FBPVariableDescription& VariableDescription) { return VariableDescription.VarName == AnimVariableDesc.VarName; })); ensure(!AnimVariableDesc.VarGuid.IsValid() || !WidgetBP->GeneratedVariables.ContainsByPredicate([&AnimVariableDesc](const FBPVariableDescription& VariableDescription) { return VariableDescription.VarGuid.IsValid() && VariableDescription.VarGuid == AnimVariableDesc.VarGuid; })); AddGeneratedVariable(MoveTemp(AnimVariableDesc)); } } FWidgetBlueprintCompilerContext* Self = this; UWidgetBlueprintExtension::ForEachExtension(WidgetBlueprint(), [Self](UWidgetBlueprintExtension* InExtension) { InExtension->PopulateGeneratedVariables(FPopulateGeneratedVariablesContext(*Self)); }); } void FWidgetBlueprintCompilerContext::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 FWidgetBlueprintCompilerContext::OnNewClassSet(UBlueprintGeneratedClass* ClassToUse) { NewWidgetBlueprintClass = CastChecked(ClassToUse); } void FWidgetBlueprintCompilerContext::PrecompileFunction(FKismetFunctionContext& Context, EInternalCompilerFlags InternalFlags) { Super::PrecompileFunction(Context, InternalFlags); VerifyEventReplysAreNotEmpty(Context); } void FWidgetBlueprintCompilerContext::VerifyEventReplysAreNotEmpty(FKismetFunctionContext& Context) { TArray FunctionResults; Context.SourceGraph->GetNodesOfClass(FunctionResults); UScriptStruct* EventReplyStruct = FEventReply::StaticStruct(); FEdGraphPinType EventReplyPinType(UEdGraphSchema_K2::PC_Struct, NAME_None, EventReplyStruct, EPinContainerType::None, /*bIsReference =*/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 FWidgetBlueprintCompilerContext::ValidateGeneratedClass(UBlueprintGeneratedClass* Class) { const bool bSuperResult = Super::ValidateGeneratedClass(Class); const bool bResult = UWidgetBlueprint::ValidateGeneratedClass(Class); UWidgetBlueprintGeneratedClass* WidgetClass = Cast(Class); bool bExtension = WidgetClass != nullptr; if (bExtension) { UWidgetBlueprintExtension::ForEachExtension(WidgetBlueprint(), [&bExtension, WidgetClass](UWidgetBlueprintExtension* InExtension) { bExtension = InExtension->ValidateGeneratedClass(WidgetClass) && bExtension; }); } return bSuperResult && bResult && bExtension; } void FWidgetBlueprintCompilerContext::AddExtension(UWidgetBlueprintGeneratedClass* Class, UWidgetBlueprintGeneratedClassExtension* Extension) { check(Class); check(Extension); Class->Extensions.Add(Extension); } void FWidgetBlueprintCompilerContext::AddGeneratedVariable(FBPVariableDescription&& VariableDescription) const { UWidgetBlueprint* WidgetBP = WidgetBlueprint(); ensureAlwaysMsgf(!WidgetBP->GeneratedVariables.ContainsByPredicate([&VariableDescription](const FBPVariableDescription& TestVar) { return TestVar.VarName == VariableDescription.VarName; }), TEXT("Widget Blueprint [%s] already contains generated variable with name [%s]"), *GetNameSafe(WidgetBP), *VariableDescription.VarName.ToString()); ensureAlwaysMsgf(!VariableDescription.VarGuid.IsValid() || !WidgetBP->GeneratedVariables.ContainsByPredicate([&VariableDescription](const FBPVariableDescription& TestVar) { return TestVar.VarGuid.IsValid() && TestVar.VarGuid == VariableDescription.VarGuid; }), TEXT("Attempting to add generated variable [%s] to Widget Blueprint [%s] that has the same GUID as another variable"), *VariableDescription.VarName.ToString(), *GetNameSafe(WidgetBP)); WidgetBP->GeneratedVariables.Emplace(MoveTemp(VariableDescription)); } #undef LOCTEXT_NAMESPACE