Files
UnrealEngine/Engine/Source/Runtime/UMG/Private/WidgetBlueprintGeneratedClass.cpp
2025-05-18 13:04:45 +08:00

577 lines
19 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Blueprint/WidgetBlueprintGeneratedClass.h"
#include "Blueprint/WidgetNavigation.h"
#include "Blueprint/WidgetTree.h"
#include "MovieScene.h"
#include "Animation/WidgetAnimation.h"
#include "Serialization/TextReferenceCollector.h"
#include "Engine/UserInterfaceSettings.h"
#include "Extensions/WidgetBlueprintGeneratedClassExtension.h"
#include "UMGPrivate.h"
#include "Interfaces/ITargetPlatform.h"
#include "UObject/AssetRegistryTagsContext.h"
#include "UObject/EditorObjectVersion.h"
#include "UObject/ObjectResource.h"
#include "UObject/LinkerLoad.h"
#include "Engine/StreamableManager.h"
#include "UObject/Package.h"
#include "UObject/UObjectHash.h"
#include "UObject/UObjectIterator.h"
#include "UObject/FortniteMainBranchObjectVersion.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(WidgetBlueprintGeneratedClass)
#if WITH_EDITOR
#include "Engine/Blueprint.h"
#endif
#define LOCTEXT_NAMESPACE "UMG"
FAutoConsoleCommand GDumpTemplateSizesCommand(
TEXT("Widget.DumpTemplateSizes"),
TEXT("Dump the sizes of all widget class templates in memory"),
FConsoleCommandDelegate::CreateStatic([]()
{
struct FClassAndSize
{
FString ClassName;
int32 TemplateSize = 0;
};
TArray<FClassAndSize> TemplateSizes;
for (TObjectIterator<UWidgetBlueprintGeneratedClass> WidgetClassIt; WidgetClassIt; ++WidgetClassIt)
{
UWidgetBlueprintGeneratedClass* WidgetClass = *WidgetClassIt;
if (WidgetClass->HasAnyClassFlags(CLASS_Abstract | CLASS_Deprecated | CLASS_NewerVersionExists))
{
continue;
}
#if WITH_EDITOR
if (Cast<UBlueprint>(WidgetClass->ClassGeneratedBy)->SkeletonGeneratedClass == WidgetClass)
{
continue;
}
#endif
FClassAndSize Entry;
Entry.ClassName = WidgetClass->GetName();
if (UUserWidget* TemplateWidget = WidgetClass->GetDefaultObject<UUserWidget>())
{
int32 TemplateSize = WidgetClass->GetStructureSize();
if (const UWidgetTree* TemplateWidgetTree = WidgetClass->GetWidgetTreeArchetype())
{
TemplateWidgetTree->ForEachWidget([&TemplateSize](UWidget* Widget) {
TemplateSize += Widget->GetClass()->GetStructureSize();
});
}
Entry.TemplateSize = TemplateSize;
}
TemplateSizes.Add(Entry);
}
TemplateSizes.StableSort([](const FClassAndSize& A, const FClassAndSize& B) {
return A.TemplateSize > B.TemplateSize;
});
uint32 TotalSizeBytes = 0;
UE_LOG(LogUMG, Display, TEXT("%-60s %-15s"), TEXT("Template Class"), TEXT("Size (bytes)"));
for (const FClassAndSize& Entry : TemplateSizes)
{
TotalSizeBytes += Entry.TemplateSize;
if (Entry.TemplateSize > 0)
{
UE_LOG(LogUMG, Display, TEXT("%-60s %-15d"), *Entry.ClassName, Entry.TemplateSize);
}
else
{
UE_LOG(LogUMG, Display, TEXT("%-60s %-15s"), *Entry.ClassName, TEXT("0 - (No Template)"));
}
}
UE_LOG(LogUMG, Display, TEXT("Total size of templates %.3f MB"), TotalSizeBytes/(1024.f*1024.f));
}), ECVF_Cheat);
#if WITH_EDITOR
int32 TemplatePreviewInEditor = 0;
static FAutoConsoleVariableRef CVarTemplatePreviewInEditor(TEXT("Widget.TemplatePreviewInEditor"), TemplatePreviewInEditor, TEXT("Should a dynamic template be generated at runtime for the editor for widgets? Useful for debugging templates."), ECVF_Default);
PRAGMA_DISABLE_DEPRECATION_WARNINGS;
FWidgetBlueprintGeneratedClassDelegates::FGetAssetTags FWidgetBlueprintGeneratedClassDelegates::GetAssetTags;
PRAGMA_ENABLE_DEPRECATION_WARNINGS;
FWidgetBlueprintGeneratedClassDelegates::FGetAssetTagsWithContext FWidgetBlueprintGeneratedClassDelegates::GetAssetTagsWithContext;
FWidgetBlueprintGeneratedClassDelegates::FCollectSaveOverrides FWidgetBlueprintGeneratedClassDelegates::CollectSaveOverrides;
#endif
#if WITH_EDITORONLY_DATA
namespace
{
void CollectWidgetBlueprintGeneratedClassTextReferences(UObject* Object, FArchive& Ar)
{
// In an editor build, both UWidgetBlueprint and UWidgetBlueprintGeneratedClass reference an identical WidgetTree.
// So we ignore the UWidgetBlueprintGeneratedClass when looking for persistent text references since it will be overwritten by the UWidgetBlueprint version.
}
}
#endif
/////////////////////////////////////////////////////
// UWidgetBlueprintGeneratedClass
UWidgetBlueprintGeneratedClass::UWidgetBlueprintGeneratedClass()
{
bCanCallInitializedWithoutPlayerContext = false;
#if WITH_EDITORONLY_DATA
{
static const FAutoRegisterTextReferenceCollectorCallback AutomaticRegistrationOfTextReferenceCollector(UWidgetBlueprintGeneratedClass::StaticClass(), &CollectWidgetBlueprintGeneratedClassTextReferences);
}
bCanCallPreConstruct = true;
#endif
}
void UWidgetBlueprintGeneratedClass::InitializeBindingsStatic(UUserWidget* UserWidget, const TArrayView<const FDelegateRuntimeBinding> InBindings, const TMap<FName, FObjectPropertyBase*>& InPropertyMap)
{
check(!UserWidget->IsTemplate());
// Note: It's not safe to assume here that the UserWidget class type is a UWidgetBlueprintGeneratedClass!
// - @see InitializeWidgetStatic()
// For each property binding that we're given, find the corresponding field, and setup the delegate binding on the widget.
for (const FDelegateRuntimeBinding& Binding : InBindings)
{
if (FObjectPropertyBase*const* PropPtr = InPropertyMap.Find(*Binding.ObjectName))
{
const FObjectPropertyBase* WidgetProperty = *PropPtr;
check(WidgetProperty);
if (UWidget* Widget = Cast<UWidget>(WidgetProperty->GetObjectPropertyValue_InContainer(UserWidget)))
{
FDelegateProperty* DelegateProperty = FindFProperty<FDelegateProperty>(Widget->GetClass(), FName(*(Binding.PropertyName.ToString() + TEXT("Delegate"))));
if (!DelegateProperty)
{
DelegateProperty = FindFProperty<FDelegateProperty>(Widget->GetClass(), Binding.PropertyName);
}
if (DelegateProperty)
{
bool bSourcePathBound = false;
if (Binding.SourcePath.IsValid())
{
bSourcePathBound = Widget->AddBinding(DelegateProperty, UserWidget, Binding.SourcePath);
}
// If no native binder is found then the only possibility is that the binding is for
// a delegate that doesn't match the known native binders available and so we
// fallback to just attempting to bind to the function directly.
if (bSourcePathBound == false)
{
FScriptDelegate* ScriptDelegate = DelegateProperty->GetPropertyValuePtr_InContainer(Widget);
if (ScriptDelegate)
{
ScriptDelegate->BindUFunction(UserWidget, Binding.FunctionName);
}
}
}
}
}
}
}
void UWidgetBlueprintGeneratedClass::InitializeWidgetStatic(UUserWidget* UserWidget
, const UClass* InClass
, UWidgetTree* InWidgetTree
, const UClass* InWidgetTreeWidgetClass
, const TArrayView<UWidgetAnimation*> InAnimations
, const TArrayView<const FDelegateRuntimeBinding> InBindings)
{
check(InClass);
if ( UserWidget->IsTemplate() )
{
return;
}
#if UE_HAS_WIDGET_GENERATED_BY_CLASS
TWeakObjectPtr<UClass> WidgetGeneratedByClass = MakeWeakObjectPtr(const_cast<UClass*>(InClass));
UserWidget->WidgetGeneratedByClass = WidgetGeneratedByClass;
#endif
UWidgetTree* CreatedWidgetTree = UserWidget->WidgetTree;
// Normally the ClonedTree should be null - we do in the case of design time with the widget, actually
// clone the widget tree directly from the WidgetBlueprint so that the rebuilt preview matches the newest
// widget tree, without a full blueprint compile being required. In that case, the WidgetTree on the UserWidget
// will have already been initialized to some value. When that's the case, we'll avoid duplicating it from the class
// similar to how we use to use the DesignerWidgetTree.
if ( CreatedWidgetTree == nullptr )
{
TMap<FName, UWidget*> NamedSlotContentToMerge;
if (const UWidgetBlueprintGeneratedClass* WidgetsActualClass = Cast<UWidgetBlueprintGeneratedClass>(UserWidget->GetClass()))
{
WidgetsActualClass->GetNamedSlotArchetypeContent([&NamedSlotContentToMerge](FName SlotName, UWidget* Content)
{
NamedSlotContentToMerge.Add(SlotName, Content);
});
}
UserWidget->DuplicateAndInitializeFromWidgetTree(InWidgetTree, NamedSlotContentToMerge);
CreatedWidgetTree = UserWidget->WidgetTree;
}
#if WITH_EDITOR
UserWidget->WidgetGeneratedBy = InClass->ClassGeneratedBy;
#endif
if (CreatedWidgetTree)
{
UClass* WidgetBlueprintClass = UserWidget->GetClass();
TMap<FName, FObjectPropertyBase*> ObjectPropertiesMap;
for (TFieldIterator<FObjectPropertyBase>It(WidgetBlueprintClass, EFieldIterationFlags::Default); It; ++It)
{
check(*It);
ensureMsgf(!ObjectPropertiesMap.Contains(It->GetFName()), TEXT("There are properties with the same names: '%s'"), *It->GetName());
ObjectPropertiesMap.Add(It->GetFName(), *It);
}
BindAnimationsStatic(UserWidget, InAnimations, ObjectPropertiesMap);
// Support navigation rules set on dynamically created user widgets by resolving the navigation rules against itself
// if this widget is pre-placed in another widget, the navigation rules will be resolved afterward against the outer widget below in CreatedWidgetTree->ForEachWidget
if (UserWidget->Navigation)
{
UserWidget->Navigation->ResolveRules(UserWidget, CreatedWidgetTree);
}
CreatedWidgetTree->ForEachWidget([&](UWidget* Widget) {
// Not fatal if NULL, but shouldn't happen
if (!ensure(Widget != nullptr))
{
return;
}
#if UE_HAS_WIDGET_GENERATED_BY_CLASS
Widget->WidgetGeneratedByClass = WidgetGeneratedByClass;
#endif
#if WITH_EDITOR
Widget->WidgetGeneratedBy = InClass->ClassGeneratedBy;
#endif
// Find property with the same name as the template and assign the new widget to it.
if (FObjectPropertyBase** PropPtr = ObjectPropertiesMap.Find(Widget->GetFName()))
{
FObjectPropertyBase* Prop = *PropPtr;
check(Prop);
Prop->SetObjectPropertyValue_InContainer(UserWidget, Widget);
UObject* Value = Prop->GetObjectPropertyValue_InContainer(UserWidget);
check(Value == Widget);
}
// Initialize Navigation Data
if (Widget->Navigation)
{
Widget->Navigation->ResolveRules(UserWidget, CreatedWidgetTree);
}
#if WITH_EDITOR
Widget->ConnectEditorData();
#endif
});
InitializeBindingsStatic(UserWidget, InBindings, ObjectPropertiesMap);
// Bind any delegates on widgets
if (!UserWidget->IsDesignTime())
{
UBlueprintGeneratedClass::BindDynamicDelegates(InClass, UserWidget);
}
}
}
void UWidgetBlueprintGeneratedClass::BindAnimationsStatic(UUserWidget* Instance, const TArrayView<UWidgetAnimation*> InAnimations, const TMap<FName, FObjectPropertyBase*>& InPropertyMap)
{
// Note: It's not safe to assume here that the UserWidget class type is a UWidgetBlueprintGeneratedClass!
// - @see InitializeWidgetStatic()
for (UWidgetAnimation* Animation : InAnimations)
{
if (Animation->GetMovieScene())
{
// Find property with the same name as the animation and assign the animation to it.
if (FObjectPropertyBase*const* PropPtr = InPropertyMap.Find(Animation->GetMovieScene()->GetFName()))
{
check(*PropPtr);
(*PropPtr)->SetObjectPropertyValue_InContainer(Instance, Animation);
}
}
}
}
#if WITH_EDITOR
void UWidgetBlueprintGeneratedClass::SetClassRequiresNativeTick(bool InClassRequiresNativeTick)
{
bClassRequiresNativeTick = InClassRequiresNativeTick;
}
#endif
void UWidgetBlueprintGeneratedClass::InitializeWidget(UUserWidget* UserWidget) const
{
TArray<UWidgetAnimation*, FConcurrentLinearArrayAllocator> AllAnims;
TArray<FDelegateRuntimeBinding, FConcurrentLinearArrayAllocator> AllBindings;
// Iterate all generated classes in the widget's parent class hierarchy and include animations and bindings
// found on each one.
UClass* SuperClass = UserWidget->GetClass();
while (UWidgetBlueprintGeneratedClass* WBPGC = Cast<UWidgetBlueprintGeneratedClass>(SuperClass))
{
AllAnims.Append(WBPGC->Animations);
AllBindings.Append(WBPGC->Bindings);
SuperClass = SuperClass->GetSuperClass();
}
UWidgetTree* PrimaryWidgetTree = WidgetTree;
UWidgetBlueprintGeneratedClass* PrimaryWidgetTreeClass = FindWidgetTreeOwningClass();
if (PrimaryWidgetTreeClass)
{
PrimaryWidgetTree = PrimaryWidgetTreeClass->WidgetTree;
}
InitializeWidgetStatic(UserWidget, this, PrimaryWidgetTree, PrimaryWidgetTreeClass, AllAnims, AllBindings);
}
void UWidgetBlueprintGeneratedClass::PostLoad()
{
Super::PostLoad();
if (WidgetTree)
{
// We don't want any of these flags to carry over from the WidgetBlueprint
WidgetTree->ClearFlags(RF_Public | RF_ArchetypeObject | RF_DefaultSubObject);
#if !WITH_EDITOR
WidgetTree->AddToCluster(this, true);
#endif
}
#if WITH_EDITOR
if ( GetLinkerUEVersion() < VER_UE4_RENAME_WIDGET_VISIBILITY )
{
static const FName Visiblity(TEXT("Visiblity"));
static const FName Visibility(TEXT("Visibility"));
for ( FDelegateRuntimeBinding& Binding : Bindings )
{
if ( Binding.PropertyName == Visiblity )
{
Binding.PropertyName = Visibility;
}
}
}
#endif
}
void UWidgetBlueprintGeneratedClass::PurgeClass(bool bRecompilingOnLoad)
{
Super::PurgeClass(bRecompilingOnLoad);
const ERenameFlags RenFlags = REN_DontCreateRedirectors | REN_NonTransactional | REN_DoNotDirty;
// Remove the old widdget tree.
if ( WidgetTree )
{
// Rename will remove the renamed object's linker when moving to a new package so invalidate the export beforehand
FLinkerLoad::InvalidateExport(WidgetTree);
WidgetTree->Rename(nullptr, GetTransientPackage(), RenFlags);
WidgetTree = nullptr;
}
// Remove all animations.
for ( UWidgetAnimation* Animation : Animations )
{
// Rename will remove the renamed object's linker when moving to a new package so invalidate the export beforehand
FLinkerLoad::InvalidateExport(Animation);
Animation->Rename(nullptr, GetTransientPackage(), RenFlags);
}
Animations.Empty();
Bindings.Empty();
}
bool UWidgetBlueprintGeneratedClass::NeedsLoadForServer() const
{
const UUserInterfaceSettings* UISettings = GetDefault<UUserInterfaceSettings>();
return UISettings->bLoadWidgetsOnDedicatedServer;
}
void UWidgetBlueprintGeneratedClass::SetWidgetTreeArchetype(UWidgetTree* InWidgetTree)
{
WidgetTree = InWidgetTree;
if (WidgetTree)
{
// We don't want any of these flags to carry over from the WidgetBlueprint
WidgetTree->ClearFlags(RF_Public | RF_ArchetypeObject | RF_DefaultSubObject | RF_Transient);
}
}
void UWidgetBlueprintGeneratedClass::GetNamedSlotArchetypeContent(TFunctionRef<void(FName /*SlotName*/, UWidget* /*Content*/)> Predicate) const
{
for (const FName& SlotName : NamedSlots)
{
const UWidgetBlueprintGeneratedClass* BPGClass = this;
while (BPGClass)
{
UWidgetTree* Tree = BPGClass->GetWidgetTreeArchetype();
if (UWidget* ContentInSlot = Tree->GetContentForSlot(SlotName))
{
// Report the content in the slot, and break so we can test the next slot.
Predicate(SlotName, ContentInSlot);
break;
}
BPGClass = Cast<UWidgetBlueprintGeneratedClass>(BPGClass->GetSuperClass());
}
}
}
void UWidgetBlueprintGeneratedClass::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
Ar.UsingCustomVersion(FEditorObjectVersion::GUID);
Ar.UsingCustomVersion(FFortniteMainBranchObjectVersion::GUID);
if (Ar.IsLoading())
{
// We've made it so the actual set of named slots we expose is AvailableNamedSlots, we need to copy
// the initial set we load though to ensure we don't get compile time load errors.
if (Ar.CustomVer(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::WidgetInheritedNamedSlots)
{
AvailableNamedSlots = NamedSlots;
InstanceNamedSlots = NamedSlots;
}
}
}
UWidgetBlueprintGeneratedClass* UWidgetBlueprintGeneratedClass::FindWidgetTreeOwningClass() const
{
UWidgetBlueprintGeneratedClass* RootBGClass = const_cast<UWidgetBlueprintGeneratedClass*>(this);
UWidgetBlueprintGeneratedClass* BGClass = RootBGClass;
while (BGClass)
{
//TODO NickD: This conditional post load shouldn't be needed any more once the Fast Widget creation path is the only path!
// Force post load on the generated class so all subobjects are done (specifically the widget tree).
BGClass->ConditionalPostLoad();
const bool bNoRootWidget = (nullptr == BGClass->WidgetTree) || (nullptr == BGClass->WidgetTree->RootWidget);
if (bNoRootWidget)
{
UWidgetBlueprintGeneratedClass* SuperBGClass = Cast<UWidgetBlueprintGeneratedClass>(BGClass->GetSuperClass());
if (SuperBGClass)
{
BGClass = SuperBGClass;
continue;
}
else
{
// If we reach a super class that isn't a UWidgetBlueprintGeneratedClass, return the root class.
return RootBGClass;
}
}
return BGClass;
}
return nullptr;
}
UWidgetBlueprintGeneratedClassExtension* UWidgetBlueprintGeneratedClass::GetExtension(TSubclassOf<UWidgetBlueprintGeneratedClassExtension> InExtensionType, bool bIncludeSuper)
{
for (UWidgetBlueprintGeneratedClassExtension* Extension : Extensions)
{
if (Extension->IsA(InExtensionType))
{
return Extension;
}
}
if (bIncludeSuper)
{
if (UWidgetBlueprintGeneratedClass* ParentClass = Cast<UWidgetBlueprintGeneratedClass>(GetSuperClass()))
{
return ParentClass->GetExtension(InExtensionType);
}
}
return nullptr;
}
TArray<UWidgetBlueprintGeneratedClassExtension*> UWidgetBlueprintGeneratedClass::GetExtensions(TSubclassOf<UWidgetBlueprintGeneratedClassExtension> InExtensionType, bool bIncludeSuper)
{
TArray<UWidgetBlueprintGeneratedClassExtension*> Result;
GetExtensions(Result, InExtensionType, bIncludeSuper);
return Result;
}
void UWidgetBlueprintGeneratedClass::GetExtensions(TArray<UWidgetBlueprintGeneratedClassExtension*>& OutExtensions, TSubclassOf<UWidgetBlueprintGeneratedClassExtension> InExtensionType, bool bIncludeSuper)
{
for (UWidgetBlueprintGeneratedClassExtension* Extension : Extensions)
{
if (Extension->IsA(InExtensionType))
{
OutExtensions.Add(Extension);
}
}
if (bIncludeSuper)
{
if (UWidgetBlueprintGeneratedClass* ParentClass = Cast<UWidgetBlueprintGeneratedClass>(GetSuperClass()))
{
ParentClass->GetExtensions(OutExtensions, InExtensionType, bIncludeSuper);
}
}
}
#if WITH_EDITOR
void UWidgetBlueprintGeneratedClass::GetAssetRegistryTags(TArray<FAssetRegistryTag>& OutTags) const
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS;
Super::GetAssetRegistryTags(OutTags);
PRAGMA_ENABLE_DEPRECATION_WARNINGS;
}
void UWidgetBlueprintGeneratedClass::GetAssetRegistryTags(FAssetRegistryTagsContext Context) const
{
Super::GetAssetRegistryTags(Context);
PRAGMA_DISABLE_DEPRECATION_WARNINGS;
TArray<UObject::FAssetRegistryTag> DeprecatedFunctionTags;
FWidgetBlueprintGeneratedClassDelegates::GetAssetTags.Broadcast(this, DeprecatedFunctionTags);
for (UObject::FAssetRegistryTag& Tag : DeprecatedFunctionTags)
{
Context.AddTag(MoveTemp(Tag));
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS;
FWidgetBlueprintGeneratedClassDelegates::GetAssetTagsWithContext.Broadcast(this, Context);
}
void UWidgetBlueprintGeneratedClass::CollectSaveOverrides(FObjectCollectSaveOverridesContext SaveContext)
{
Super::CollectSaveOverrides(SaveContext);
FWidgetBlueprintGeneratedClassDelegates::CollectSaveOverrides.Broadcast(this, SaveContext);
}
#endif
#undef LOCTEXT_NAMESPACE