Files
alain lafleur 1b905ebe01 Merging
//Fortnite/Main/...
to //Fortnite/Dev-FN-32/...

Data driven condition bindings

Authoring and compilation of the new type is hidden behind a project setting.
Fix crash when changing hooked event and trying to edit conversion function parameters.
Fix issue with events valid source check node being purged from the graph upon regenerating connections in the wrapper graph.

Fixes FORT-768257

#tests Tested in editor and in game. Tried with widget property bindings, tried with vm bindings. Verified with multiple bindings to ensure we only execute when it's the condition property that is modified. Ran with cooked version. Tested in standalone and pie.
#rnx
#rb Vincent.Gauthier

[CL 36754418 by alain lafleur in 5.5 branch]
2024-10-01 18:59:25 -04:00

416 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MVVMWidgetBlueprintExtension_View.h"
#include "Blueprint/WidgetTree.h"
#include "Extensions/MVVMBlueprintViewExtension.h"
#include "FindInBlueprintManager.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "MVVMBlueprintInstancedViewModel.h"
#include "MVVMBlueprintView.h"
#include "MVVMBlueprintViewModel.h"
#include "MVVMBlueprintViewConversionFunction.h"
#include "MVVMViewBlueprintCompiler.h"
#include "ScopedTransaction.h"
#include "View/MVVMViewClass.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(MVVMWidgetBlueprintExtension_View)
#define LOCTEXT_NAMESPACE "MVVMBlueprintExtensionView"
namespace UE::MVVM::Private
{
bool GAllowViewClass = true;
static FAutoConsoleVariableRef CVarAllowViewClass(
TEXT("MVVM.AllowViewClass"),
GAllowViewClass,
TEXT("Is the model view viewmodel view is allowed to be added to the generated Widget GeneratedClass."),
ECVF_ReadOnly
);
bool GAutogeneratedFunctionsAreTransient = false;
static FAutoConsoleVariableRef CVarAutogeneratedFunctionsAreTransient(
TEXT("MVVM.AutogeneratedFunctionsAreTransient"),
GAutogeneratedFunctionsAreTransient,
TEXT("Is the autogenerated function should be mark as transient."),
ECVF_ReadOnly
);
}
void UMVVMWidgetBlueprintExtension_View::CreateBlueprintViewInstance()
{
BlueprintView = NewObject<UMVVMBlueprintView>(this, FName(), RF_Transactional);
BlueprintViewChangedDelegate.Broadcast();
FBlueprintEditorUtils::MarkBlueprintAsModified(GetWidgetBlueprint());
}
void UMVVMWidgetBlueprintExtension_View::DestroyBlueprintViewInstance()
{
BlueprintView = nullptr;
BlueprintViewChangedDelegate.Broadcast();
}
void UMVVMWidgetBlueprintExtension_View::PostLoad()
{
Super::PostLoad();
if (!HasAnyFlags(RF_Transactional))
{
SetFlags(RF_Transactional);
}
}
UMVVMBlueprintViewExtension* UMVVMWidgetBlueprintExtension_View::CreateBlueprintWidgetExtension(TSubclassOf<UMVVMBlueprintViewExtension> ExtensionClass, FName WidgetName)
{
if (ensure(ExtensionClass.Get()))
{
UObject* ExtensionObj = NewObject<UObject>(this, ExtensionClass.Get(), NAME_None, RF_Transactional);
UMVVMBlueprintViewExtension* NewExtension = CastChecked<UMVVMBlueprintViewExtension>(ExtensionObj);
const FScopedTransaction Transaction(LOCTEXT("AddViewModelExtension", "Add viewmodel extension"));
NewExtension->Modify();
Modify();
FMVVMExtensionItem ExtensionToAdd;
ExtensionToAdd.WidgetName = WidgetName;
ExtensionToAdd.ExtensionObj = NewExtension;
BlueprintExtensions.Add(ExtensionToAdd);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetWidgetBlueprint());
return NewExtension;
}
return nullptr;
}
void UMVVMWidgetBlueprintExtension_View::RemoveBlueprintWidgetExtension(UMVVMBlueprintViewExtension* ExtensionToRemove, FName WidgetName)
{
FMVVMExtensionItem Extension;
Extension.WidgetName = WidgetName;
Extension.ExtensionObj = ExtensionToRemove;
const FScopedTransaction Transaction(LOCTEXT("RemoveViewModelExtension", "Remove viewmodel extension"));
Modify();
BlueprintExtensions.RemoveSingle(Extension);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetWidgetBlueprint());
}
TArray<UMVVMBlueprintViewExtension*> UMVVMWidgetBlueprintExtension_View::GetBlueprintExtensionsForWidget(FName WidgetName) const
{
TArray<UMVVMBlueprintViewExtension*> ThisWidgetExtensions;
for (const FMVVMExtensionItem& Extension : BlueprintExtensions)
{
if (Extension.WidgetName == WidgetName && Extension.ExtensionObj)
{
ThisWidgetExtensions.Add(Extension.ExtensionObj);
}
}
return ThisWidgetExtensions;
}
TArray<UMVVMBlueprintViewExtension*> UMVVMWidgetBlueprintExtension_View::GetAllBlueprintExtensions() const
{
TArray<UMVVMBlueprintViewExtension*> AllExtensions;
AllExtensions.Reset(BlueprintExtensions.Num());
for (const FMVVMExtensionItem& Extension : BlueprintExtensions)
{
if (Extension.ExtensionObj)
{
AllExtensions.Add(Extension.ExtensionObj);
}
}
AllExtensions.Shrink();
return AllExtensions;
}
void UMVVMWidgetBlueprintExtension_View::VerifyWidgetExtensions()
{
if (const UWidgetBlueprint* WidgetBlueprint = GetWidgetBlueprint())
{
if (const UWidgetTree* WidgetTree = WidgetBlueprint->WidgetTree)
{
bool bModified = false;
auto UpdateModify = [&bModified, Self=this]()
{
if (!bModified)
{
bModified = true;
Self->Modify();
}
};
TArray <FName, TInlineAllocator<4>> WidgetNamesToRemove;
for (int32 Index = BlueprintExtensions.Num() - 1; Index >= 0; --Index)
{
if (BlueprintExtensions[Index].ExtensionObj == nullptr)
{
UpdateModify();
BlueprintExtensions.RemoveAtSwap(Index);
}
else if (!BlueprintExtensions[Index].WidgetName.IsNone())
{
WidgetNamesToRemove.Add(BlueprintExtensions[Index].WidgetName);
}
}
// Find widgets that are no longer in the tree and delete their extensions.
if (WidgetNamesToRemove.Num() > 0)
{
WidgetTree->ForEachWidget([&WidgetNamesToRemove, this](TObjectPtr<UWidget> Widget) {
if (Widget)
{
for (int32 Index = WidgetNamesToRemove.Num() - 1; Index >= 0; Index--)
{
const FName WidgetName = WidgetNamesToRemove[Index];
if (WidgetName == Widget->GetFName())
{
WidgetNamesToRemove.RemoveSingleSwap(WidgetName);
}
}
}
});
}
if (WidgetNamesToRemove.Num() > 0)
{
UpdateModify();
for (int32 Index = BlueprintExtensions.Num() - 1; Index >= 0; --Index)
{
if (WidgetNamesToRemove.Contains(BlueprintExtensions[Index].WidgetName))
{
BlueprintExtensions.RemoveAtSwap(Index);
}
}
}
}
}
}
void UMVVMWidgetBlueprintExtension_View::RenameWidgetExtensions(FName OldName, FName NewName)
{
for (FMVVMExtensionItem& Extension : BlueprintExtensions)
{
if (Extension.WidgetName == OldName)
{
Modify();
Extension.WidgetName = NewName;
if (Extension.ExtensionObj)
{
Extension.ExtensionObj->WidgetRenamed(OldName, NewName);
}
}
}
}
void UMVVMWidgetBlueprintExtension_View::HandlePreloadObjectsForCompilation(UBlueprint* OwningBlueprint)
{
if (IsInGameThread() && BlueprintView)
{
BlueprintView->ConditionalPostLoad();
for (const FMVVMBlueprintViewModelContext& AvailableViewModel : BlueprintView->GetViewModels())
{
if (AvailableViewModel.InstancedViewModel)
{
UBlueprint::ForceLoad(AvailableViewModel.InstancedViewModel);
AvailableViewModel.InstancedViewModel->GenerateClass(true);
}
if (AvailableViewModel.GetViewModelClass())
{
UBlueprint::ForceLoad(AvailableViewModel.GetViewModelClass());
}
}
for (FMVVMBlueprintViewBinding& Binding : BlueprintView->GetBindings())
{
if (Binding.Conversion.DestinationToSourceConversion)
{
UBlueprint::ForceLoad(Binding.Conversion.DestinationToSourceConversion);
}
if (Binding.Conversion.SourceToDestinationConversion)
{
UBlueprint::ForceLoad(Binding.Conversion.SourceToDestinationConversion);
}
}
}
}
void UMVVMWidgetBlueprintExtension_View::HandleBeginCompilation(FWidgetBlueprintCompilerContext& InCreationContext)
{
VerifyWidgetExtensions();
for (const FMVVMBlueprintViewModelContext& AvailableViewModel : BlueprintView->GetViewModels())
{
if (AvailableViewModel.InstancedViewModel)
{
AvailableViewModel.InstancedViewModel->GenerateClass(false);
}
}
CurrentCompilerContext.Reset();
GeneratedFunctions.Reset();
if (BlueprintView)
{
BlueprintView->ResetBindingMessages();
for (UMVVMBlueprintViewEvent* ViewEvent : BlueprintView->GetEvents())
{
ViewEvent->ResetCompilationMessages();
}
CurrentCompilerContext = MakePimpl<UE::MVVM::Private::FMVVMViewBlueprintCompiler>(InCreationContext, GetBlueprintView());
}
}
void UMVVMWidgetBlueprintExtension_View::HandleEndCompilation()
{
CurrentCompilerContext.Reset();
}
void UMVVMWidgetBlueprintExtension_View::HandleCleanAndSanitizeClass(UWidgetBlueprintGeneratedClass* ClassToClean, UObject* OldCDO)
{
Super::HandleCleanAndSanitizeClass(ClassToClean, OldCDO);
if (CurrentCompilerContext)
{
CurrentCompilerContext->CleanOldData(ClassToClean, OldCDO);
}
}
void UMVVMWidgetBlueprintExtension_View::HandleCreateClassVariablesFromBlueprint(const FWidgetBlueprintCompilerContext::FCreateVariableContext& Context)
{
Super::HandleCreateClassVariablesFromBlueprint(Context);
CurrentCompilerContext->CreateVariables(Context);
}
void UMVVMWidgetBlueprintExtension_View::HandleCreateFunctionList(const FWidgetBlueprintCompilerContext::FCreateFunctionContext& Context)
{
Super::HandleCreateFunctionList(Context);
if (CurrentCompilerContext)
{
CurrentCompilerContext->CreateFunctions(Context);
}
}
void UMVVMWidgetBlueprintExtension_View::HandleFinishCompilingClass(UWidgetBlueprintGeneratedClass* Class)
{
Super::HandleFinishCompilingClass(Class);
check(CurrentCompilerContext);
if (CurrentCompilerContext->GetCompilerContext().bIsFullCompile)
{
UMVVMViewClass* ViewExtension = nullptr;
bool bCompiled = false;
if (CurrentCompilerContext->PreCompile(Class))
{
FName ClassName = "ViewClass";
if (UObject* PreviousObj = StaticFindObjectFastInternal(nullptr, Class, ClassName, true))
{
// Remove previous object.
ERenameFlags RenameFlags = REN_NonTransactional | REN_DoNotDirty | REN_DontCreateRedirectors;
FName TrashName = MakeUniqueObjectName(GetTransientPackage(), PreviousObj->GetClass(), *FString::Printf(TEXT("TRASH_%s"), *PreviousObj->GetName()));
PreviousObj->Rename(*TrashName.ToString(), GetTransientPackage(), RenameFlags);
}
ViewExtension = NewObject<UMVVMViewClass>(Class);
bCompiled = CurrentCompilerContext->Compile(Class, ViewExtension);
}
if (bCompiled)
{
check(ViewExtension);
// Does it have any bindings
if (ViewExtension->GetBindings().Num() > 0 || ViewExtension->GetEvents().Num() > 0 || ViewExtension->GetConditions().Num() > 0)
{
// Test if parent also has a view
if (Class->GetExtension<UMVVMViewClass>(true))
{
CurrentCompilerContext->GetCompilerContext().MessageLog.Warning(*LOCTEXT("MoreThanOneViewWarning", "There is more than one view.").ToString());
}
// If we are not allowed to add the view class
if (UE::MVVM::Private::GAllowViewClass)
{
CurrentCompilerContext->AddExtension(Class, ViewExtension);
}
}
}
}
GeneratedFunctions = CurrentCompilerContext->GetGeneratedFunctions();
// If we can't auto-generate function add the transient flags
if (UE::MVVM::Private::GAutogeneratedFunctionsAreTransient)
{
// If we are not allowed to add the view class, add the transient flags on added conversion graphs and event graphs.
for (TFieldIterator<UFunction> FunctionIter(Class, EFieldIteratorFlags::ExcludeSuper); FunctionIter; ++FunctionIter)
{
UFunction* Function = *FunctionIter;
Function->SetFlags(RF_Transient);
}
}
}
UMVVMWidgetBlueprintExtension_View::FSearchData UMVVMWidgetBlueprintExtension_View::HandleGatherSearchData(const UBlueprint* OwningBlueprint) const
{
UMVVMWidgetBlueprintExtension_View::FSearchData SearchData;
if (GetBlueprintView())
{
{
TUniquePtr<FSearchArrayData> ViewModelContextSearchData = MakeUnique<FSearchArrayData>();
ViewModelContextSearchData->Identifier = LOCTEXT("ViewmodelSearchTag", "Viewmodels");
for (const FMVVMBlueprintViewModelContext& ViewModelContext : GetBlueprintView()->GetViewModels())
{
FSearchData& ViewModelSearchData = ViewModelContextSearchData->SearchSubList.AddDefaulted_GetRef();
ViewModelSearchData.Datas.Emplace(LOCTEXT("ViewmodelGuidSearchTag", "Guid"), FText::FromString(ViewModelContext.GetViewModelId().ToString(EGuidFormats::Digits)));
ViewModelSearchData.Datas.Emplace(FFindInBlueprintSearchTags::FiB_Name, ViewModelContext.GetDisplayName());
ViewModelSearchData.Datas.Emplace(FFindInBlueprintSearchTags::FiB_ClassName, ViewModelContext.GetViewModelClass() ? ViewModelContext.GetViewModelClass()->GetDisplayNameText() : FText::GetEmpty());
ViewModelSearchData.Datas.Emplace(LOCTEXT("ViewmodelCreationTypeSearchTag", "CreationType"), StaticEnum<EMVVMBlueprintViewModelContextCreationType>()->GetDisplayNameTextByValue((int64)ViewModelContext.CreationType));
}
SearchData.SearchArrayDatas.Add(MoveTemp(ViewModelContextSearchData));
}
{
TUniquePtr<FSearchArrayData> BindingContextSearchData = MakeUnique<FSearchArrayData>();
BindingContextSearchData->Identifier = LOCTEXT("BindingSearchTag", "Bindings");
for (const FMVVMBlueprintViewBinding& Binding : GetBlueprintView()->GetBindings())
{
FSearchData& BindingSearchData = BindingContextSearchData->SearchSubList.AddDefaulted_GetRef();
BindingSearchData.Datas.Emplace(LOCTEXT("ViewBindingSearchTag", "Binding"), FText::FromString(Binding.GetDisplayNameString(GetWidgetBlueprint())));
}
SearchData.SearchArrayDatas.Add(MoveTemp(BindingContextSearchData));
}
}
return SearchData;
}
void UMVVMWidgetBlueprintExtension_View::SetFilterSettings(FMVVMViewBindingFilterSettings InFilterSettings)
{
FilterSettings = InFilterSettings;
}
#if WITH_EDITORONLY_DATA
void UMVVMWidgetBlueprintExtension_View::PostInitProperties()
{
Super::PostInitProperties();
if (!IsTemplate())
{
SetFilterSettings(GetDefault<UMVVMDeveloperProjectSettings>()->FilterSettings);
}
}
#endif
#undef LOCTEXT_NAMESPACE