You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
[FYI] Patrick.Boutot Original CL Desc ----------------------------------------------------------------- MVVM: Big rework of the MVVM view compiler. It add supports for conversion function to go from complex to simple. Supports pin split on simple conversion function. Add ability to to support setter function with more than one argument. Build the list of all the sources. #jira UE-200602, UE-200602, UE-200602, UE-200602, UE-200602 #rb editor-ui-systems [CL 29801739 by bob tellez in ue5-main branch]
2780 lines
109 KiB
C++
2780 lines
109 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "MVVMViewBlueprintCompiler.h"
|
|
#include "Blueprint/WidgetTree.h"
|
|
#include "Bindings/MVVMBindingHelper.h"
|
|
#include "Bindings/MVVMConversionFunctionHelper.h"
|
|
#include "Bindings/MVVMFieldPathHelper.h"
|
|
#include "Components/Widget.h"
|
|
#include "HAL/IConsoleManager.h"
|
|
#include "MVVMBlueprintView.h"
|
|
#include "MVVMBlueprintViewConversionFunction.h"
|
|
#include "MVVMDeveloperProjectSettings.h"
|
|
#include "MVVMMessageLog.h"
|
|
#include "PropertyPermissionList.h"
|
|
#include "MVVMFunctionGraphHelper.h"
|
|
#include "MVVMWidgetBlueprintExtension_View.h"
|
|
#include "Templates/ValueOrError.h"
|
|
#include "Types/MVVMBindingName.h"
|
|
#include "UObject/LinkerLoad.h"
|
|
#include "UObject/UnrealType.h"
|
|
#include "View/MVVMViewClass.h"
|
|
#include "View/MVVMViewModelContextResolver.h"
|
|
#include "WidgetBlueprintEditorUtils.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "MVVMViewBlueprintCompiler"
|
|
|
|
namespace UE::MVVM::Private
|
|
{
|
|
FAutoConsoleVariable CVarLogViewCompliedResult(
|
|
TEXT("MVVM.LogViewCompliedResult"),
|
|
false,
|
|
TEXT("After the view is compiled log the compiled bindings and sources.")
|
|
);
|
|
|
|
#if UE_BUILD_DEVELOPMENT || UE_BUILD_DEBUG
|
|
FAutoConsoleCommand CVarTestGenerateSetter(
|
|
TEXT("MVVM.TestGenerateSetter"),
|
|
TEXT("Generate a setter function base on the input string."),
|
|
FConsoleCommandWithArgsDelegate::CreateLambda([](const TArray<FString>& Args)
|
|
{
|
|
if (Args.Num() < 3)
|
|
{
|
|
return;
|
|
}
|
|
FMVVMViewBlueprintCompiler::TestGenerateSetter(nullptr, Args[0], Args[1], Args[2]);
|
|
})
|
|
);
|
|
#endif
|
|
|
|
static const FText CouldNotCreateSourceFieldPathFormat = LOCTEXT("CouldNotCreateSourceFieldPath", "Couldn't create the source field path '{0}'. {1}");
|
|
static const FText PropertyPathIsInvalidFormat = LOCTEXT("PropertyPathIsInvalid", "The property path '{0}' is invalid.");
|
|
|
|
FString PropertyPathToString(const UClass* InSelfContext, const UMVVMBlueprintView* BlueprintView, const FMVVMBlueprintPropertyPath& PropertyPath)
|
|
{
|
|
if (!PropertyPath.IsValid())
|
|
{
|
|
return FString();
|
|
}
|
|
|
|
TStringBuilder<512> Result;
|
|
switch (PropertyPath.GetSource(BlueprintView->GetOuterUMVVMWidgetBlueprintExtension_View()->GetOuterUWidgetBlueprint()))
|
|
{
|
|
case EMVVMBlueprintFieldPathSource::SelfContext:
|
|
Result << InSelfContext->ClassGeneratedBy->GetName();
|
|
break;
|
|
case EMVVMBlueprintFieldPathSource::Widget:
|
|
Result << PropertyPath.GetWidgetName();
|
|
break;
|
|
case EMVVMBlueprintFieldPathSource::ViewModel:
|
|
if (const FMVVMBlueprintViewModelContext* SourceViewModelContext = BlueprintView->FindViewModel(PropertyPath.GetViewModelId()))
|
|
{
|
|
Result << SourceViewModelContext->GetViewModelName();
|
|
}
|
|
else
|
|
{
|
|
Result << TEXT("<none>");
|
|
}
|
|
break;
|
|
default:
|
|
Result << TEXT("<none>");
|
|
break;
|
|
}
|
|
|
|
FString BasePropertyPath = PropertyPath.GetPropertyPath(InSelfContext);
|
|
if (BasePropertyPath.Len())
|
|
{
|
|
Result << TEXT('.');
|
|
Result << MoveTemp(BasePropertyPath);
|
|
}
|
|
return Result.ToString();
|
|
}
|
|
|
|
FText PropertyPathToText(const UClass* InSelfContext, const UMVVMBlueprintView* BlueprintView, const FMVVMBlueprintPropertyPath& PropertyPath)
|
|
{
|
|
return FText::FromString(PropertyPathToString(InSelfContext, BlueprintView, PropertyPath));
|
|
}
|
|
|
|
FText GetViewModelIdText(const FMVVMBlueprintPropertyPath& PropertyPath)
|
|
{
|
|
return FText::FromString(PropertyPath.GetViewModelId().ToString(EGuidFormats::DigitsWithHyphensInBraces));
|
|
}
|
|
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FFieldPathHandle, FText> AddObjectFieldPath(FCompiledBindingLibraryCompiler& BindingLibraryCompiler, const UWidgetBlueprintGeneratedClass* Class, FStringView ObjectPath, UClass* ExpectedType)
|
|
{
|
|
// Generate a path to read the value at runtime
|
|
static const FText InvalidGetterFormat = LOCTEXT("ViewModelInvalidGetterWithReason", "Viewmodel has an invalid Getter. {0}");
|
|
|
|
TValueOrError<TArray<FMVVMConstFieldVariant>, FText> GeneratedField = FieldPathHelper::GenerateFieldPathList(Class, ObjectPath, true);
|
|
if (GeneratedField.HasError())
|
|
{
|
|
return MakeError(FText::Format(InvalidGetterFormat, GeneratedField.GetError()));
|
|
}
|
|
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FFieldPathHandle, FText> ReadFieldPathResult = BindingLibraryCompiler.AddObjectFieldPath(GeneratedField.GetValue(), ExpectedType, true);
|
|
if (ReadFieldPathResult.HasError())
|
|
{
|
|
return MakeError(FText::Format(InvalidGetterFormat, ReadFieldPathResult.GetError()));
|
|
}
|
|
|
|
return MakeValue(ReadFieldPathResult.StealValue());
|
|
}
|
|
|
|
FMVVMViewBlueprintCompiler::FMVVMViewBlueprintCompiler(FWidgetBlueprintCompilerContext& InCreationContext)
|
|
: WidgetBlueprintCompilerContext(InCreationContext)
|
|
, BindingLibraryCompiler(InCreationContext.WidgetBlueprint())
|
|
{}
|
|
|
|
void FMVVMViewBlueprintCompiler::AddMessageForBinding(FMVVMBlueprintViewBinding& Binding, UMVVMBlueprintView* BlueprintView, const FText& MessageText, EBindingMessageType MessageType, FName ArgumentName) const
|
|
{
|
|
const FText BindingName = FText::FromString(Binding.GetDisplayNameString(WidgetBlueprintCompilerContext.WidgetBlueprint()));
|
|
|
|
FText FormattedError;
|
|
if (!ArgumentName.IsNone())
|
|
{
|
|
FormattedError = FText::Format(LOCTEXT("BindingFormatWithArgument", "Binding '{0}': Argument '{1}' - {2}"), BindingName, FText::FromName(ArgumentName), MessageText);
|
|
}
|
|
else
|
|
{
|
|
FormattedError = FText::Format(LOCTEXT("BindingFormat", "Binding '{0}': {1}"), BindingName, MessageText);
|
|
}
|
|
|
|
switch (MessageType)
|
|
{
|
|
case EBindingMessageType::Info:
|
|
WidgetBlueprintCompilerContext.MessageLog.Note(*FormattedError.ToString());
|
|
break;
|
|
case EBindingMessageType::Warning:
|
|
WidgetBlueprintCompilerContext.MessageLog.Warning(*FormattedError.ToString());
|
|
break;
|
|
case EBindingMessageType::Error:
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FormattedError.ToString());
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
FBindingMessage NewMessage = { FormattedError, MessageType };
|
|
BlueprintView->AddMessageToBinding(Binding.BindingId, NewMessage);
|
|
}
|
|
|
|
void FMVVMViewBlueprintCompiler::AddMessageForEvent(UMVVMBlueprintViewEvent* Event, const FText& MessageText, UMVVMBlueprintViewEvent::EMessageType MessageType, FName ArgumentName) const
|
|
{
|
|
const FText EventName = Event->GetDisplayName(true);
|
|
|
|
FText FormattedError;
|
|
if (!ArgumentName.IsNone())
|
|
{
|
|
FormattedError = FText::Format(LOCTEXT("EventFormatWithArgument", "Event '{0}': Argument '{1}' - {2}"), EventName, FText::FromName(ArgumentName), MessageText);
|
|
}
|
|
else
|
|
{
|
|
FormattedError = FText::Format(LOCTEXT("EventFormat", "Event '{0}': {1}"), EventName, MessageText);
|
|
}
|
|
|
|
switch (MessageType)
|
|
{
|
|
case UMVVMBlueprintViewEvent::EMessageType::Info:
|
|
WidgetBlueprintCompilerContext.MessageLog.Note(*FormattedError.ToString());
|
|
break;
|
|
case UMVVMBlueprintViewEvent::EMessageType::Warning:
|
|
WidgetBlueprintCompilerContext.MessageLog.Warning(*FormattedError.ToString());
|
|
break;
|
|
case UMVVMBlueprintViewEvent::EMessageType::Error:
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FormattedError.ToString());
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
UMVVMBlueprintViewEvent::FMessage NewMessage = { FormattedError, MessageType };
|
|
Event->AddCompilationToBinding(NewMessage);
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::AddErrorForViewModel(const FMVVMBlueprintViewModelContext& ViewModel, const FText& Message) const
|
|
{
|
|
const FText FormattedError = FText::Format(LOCTEXT("ViewModelFormat", "Viewodel '{0}': {1}"), ViewModel.GetDisplayName(), Message);
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FormattedError.ToString());
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::AddExtension(UWidgetBlueprintGeneratedClass* Class, UMVVMViewClass* ViewExtension)
|
|
{
|
|
WidgetBlueprintCompilerContext.AddExtension(Class, ViewExtension);
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::CleanOldData(UWidgetBlueprintGeneratedClass* ClassToClean, UObject* OldCDO)
|
|
{
|
|
// Clean old View
|
|
if (!WidgetBlueprintCompilerContext.Blueprint->bIsRegeneratingOnLoad && WidgetBlueprintCompilerContext.bIsFullCompile)
|
|
{
|
|
auto RenameObjectToTransientPackage = [](UObject* ObjectToRename)
|
|
{
|
|
const ERenameFlags RenFlags = REN_DoNotDirty | REN_ForceNoResetLoaders | REN_DontCreateRedirectors;
|
|
|
|
ObjectToRename->Rename(nullptr, GetTransientPackage(), RenFlags);
|
|
ObjectToRename->SetFlags(RF_Transient);
|
|
ObjectToRename->ClearFlags(RF_Public | RF_Standalone | RF_ArchetypeObject);
|
|
FLinkerLoad::InvalidateExport(ObjectToRename);
|
|
};
|
|
|
|
TArray<UObject*> Children;
|
|
const bool bIncludeNestedObjects = false;
|
|
ForEachObjectWithOuter(ClassToClean, [&Children](UObject* Child)
|
|
{
|
|
if (Cast<UMVVMViewClass>(Child))
|
|
{
|
|
Children.Add(Child);
|
|
}
|
|
}, bIncludeNestedObjects);
|
|
|
|
for (UObject* Child : Children)
|
|
{
|
|
RenameObjectToTransientPackage(Child);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::CreateFunctions(UMVVMBlueprintView* BlueprintView)
|
|
{
|
|
if (!bAreSourcesCreatorValid || !bAreBindingsValid || !bAreEventsValid)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Build the list of BP destinations
|
|
CreateBindingDestinationContexts(BlueprintView);
|
|
|
|
if (!bAreSourcesCreatorValid || !bAreBindingsValid || !bAreEventsValid)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Generate the setter code
|
|
if (GetDefault<UMVVMDeveloperProjectSettings>()->bAllowGeneratedViewModelSetter)
|
|
{
|
|
for (const FCompilerSourceCreatorContext& SourceCreator : CompilerSourceCreatorContexts)
|
|
{
|
|
if (SourceCreator.SetterGraph)
|
|
{
|
|
if (!UE::MVVM::FunctionGraphHelper::GenerateViewModelSetter(WidgetBlueprintCompilerContext, SourceCreator.SetterGraph, SourceCreator.ViewModelContext.GetViewModelName()))
|
|
{
|
|
AddErrorForViewModel(SourceCreator.ViewModelContext, LOCTEXT("SetterFunctionCouldNotBeGenerated", "The setter function could not be generated."));
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::CreateVariables(const FWidgetBlueprintCompilerContext::FCreateVariableContext& Context, UMVVMBlueprintView* BlueprintView)
|
|
{
|
|
if (!BlueprintView)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!bAreSourcesCreatorValid || !bAreSourceContextsValid || !bAreBindingsValid || !bAreEventsValid)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (Context.GetCompileType() == EKismetCompileType::SkeletonOnly)
|
|
{
|
|
CreateWidgetMap(Context, BlueprintView);
|
|
CreateSourceLists(Context, BlueprintView);
|
|
CreateFunctionsDeclaration(Context, BlueprintView);
|
|
}
|
|
else
|
|
{
|
|
CreateIntermediateGraphFunctions(Context, BlueprintView);
|
|
}
|
|
|
|
auto CreateVariable = [&Context](const FCompilerUserWidgetPropertyContext& SourceContext) -> FProperty*
|
|
{
|
|
FEdGraphPinType NewPropertyPinType(UEdGraphSchema_K2::PC_Object, NAME_None, SourceContext.Class, EPinContainerType::None, false, FEdGraphTerminalType());
|
|
FProperty* NewProperty = Context.CreateVariable(SourceContext.PropertyName, NewPropertyPinType);
|
|
if (NewProperty != nullptr)
|
|
{
|
|
NewProperty->SetPropertyFlags(CPF_BlueprintVisible | CPF_RepSkip | CPF_Transient | CPF_DuplicateTransient);
|
|
if (SourceContext.BlueprintSetter.IsEmpty())
|
|
{
|
|
NewProperty->SetPropertyFlags(CPF_BlueprintReadOnly);
|
|
}
|
|
NewProperty->SetPropertyFlags(SourceContext.bExposeOnSpawn ? CPF_ExposeOnSpawn : CPF_DisableEditOnInstance);
|
|
|
|
#if WITH_EDITOR
|
|
if (!SourceContext.BlueprintSetter.IsEmpty())
|
|
{
|
|
NewProperty->SetMetaData(FBlueprintMetadata::MD_PropertySetFunction, *SourceContext.BlueprintSetter);
|
|
}
|
|
if (!SourceContext.DisplayName.IsEmpty())
|
|
{
|
|
NewProperty->SetMetaData(FBlueprintMetadata::MD_DisplayName, *SourceContext.DisplayName.ToString());
|
|
}
|
|
if (!SourceContext.CategoryName.IsEmpty())
|
|
{
|
|
NewProperty->SetMetaData(FBlueprintMetadata::MD_FunctionCategory, *SourceContext.CategoryName);
|
|
}
|
|
if (SourceContext.bExposeOnSpawn)
|
|
{
|
|
NewProperty->SetMetaData(FBlueprintMetadata::MD_ExposeOnSpawn, TEXT("true"));
|
|
}
|
|
if (SourceContext.bPrivate)
|
|
{
|
|
NewProperty->SetMetaData(FBlueprintMetadata::MD_Private, TEXT("true"));
|
|
}
|
|
#endif
|
|
}
|
|
return NewProperty;
|
|
};
|
|
|
|
for (FCompilerUserWidgetPropertyContext& SourceContext : CompilerUserWidgetPropertyContexts)
|
|
{
|
|
SourceContext.Field = BindingHelper::FindFieldByName(Context.GetGeneratedClass(), FMVVMBindingName(SourceContext.PropertyName));
|
|
|
|
// The class is not linked yet. It may not be available yet.
|
|
if (SourceContext.Field.IsEmpty())
|
|
{
|
|
for (FField* Field = Context.GetGeneratedClass()->ChildProperties; Field != nullptr; Field = Field->Next)
|
|
{
|
|
if (Field->GetFName() == SourceContext.PropertyName)
|
|
{
|
|
if (CastField<FProperty>(Field))
|
|
{
|
|
SourceContext.Field = FMVVMFieldVariant(CastField<FProperty>(Field));
|
|
}
|
|
else
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("FieldIsNotProperty", "The field for source '{0}' exists but is not a property."), SourceContext.DisplayName).ToString());
|
|
bAreSourcesCreatorValid = false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
for (UField* Field = Context.GetGeneratedClass()->Children; Field != nullptr; Field = Field->Next)
|
|
{
|
|
if (Field->GetFName() == SourceContext.PropertyName)
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("FieldIsNotProperty", "The field for source '{0}' exists but is not a property."), SourceContext.DisplayName).ToString());
|
|
bAreSourcesCreatorValid = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SourceContext.Field.IsEmpty())
|
|
{
|
|
UClass* ParentClass = Context.GetGeneratedClass()->GetSuperClass();
|
|
if (const FProperty* Property = ParentClass->FindPropertyByName(SourceContext.PropertyName))
|
|
{
|
|
SourceContext.Field = FMVVMFieldVariant(Property);
|
|
}
|
|
}
|
|
|
|
|
|
// Will always create viewmodel properties.
|
|
// Will never create properties for animation or other Self.Object
|
|
// Will create properties for widget when they are not already created.
|
|
|
|
const bool bIsWidgetInsideWidgetTree = WidgetNameToWidgetPointerMap.Find(SourceContext.PropertyName) != nullptr;
|
|
const bool bIsViewmodel = SourceContext.ViewModelId.IsValid();
|
|
|
|
if (bIsViewmodel && !SourceContext.Field.IsEmpty())
|
|
{
|
|
// Viewmodel property cannot already exist. It will creates issue with initialization and with View::SetViewModel.
|
|
const UClass* OwnerClass = Cast<UClass>(SourceContext.Field.GetOwner());
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("ViewmodelPropertyAlreadyExistInParent", "There is already a property named '{0}' in scope '{1}' for the viewmodel."), SourceContext.DisplayName, (OwnerClass ? OwnerClass->GetDisplayNameText() : FText::GetEmpty())).ToString());
|
|
bAreSourceContextsValid = false;
|
|
continue;
|
|
}
|
|
|
|
if (!SourceContext.Field.IsEmpty())
|
|
{
|
|
if (!BindingHelper::IsValidForSourceBinding(SourceContext.Field))
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("FieldNotAccessibleAtRuntime", "The field for source '{0}' exists but is not accessible at runtime."), SourceContext.DisplayName).ToString());
|
|
bAreSourcesCreatorValid = false;
|
|
continue;
|
|
}
|
|
|
|
const FProperty* Property = SourceContext.Field.IsProperty() ? SourceContext.Field.GetProperty() : BindingHelper::GetReturnProperty(SourceContext.Field.GetFunction());
|
|
const FObjectProperty* ObjectProperty = CastField<const FObjectProperty>(Property);
|
|
const bool bIsCompatible = ObjectProperty && SourceContext.Class->IsChildOf(ObjectProperty->PropertyClass);
|
|
if (!bIsCompatible)
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("PropertyExistsAndNotCompatible", "There is already a property named '{0}' that is not compatible with the source of the same name."), SourceContext.DisplayName).ToString());
|
|
bAreSourceContextsValid = false;
|
|
continue;
|
|
}
|
|
|
|
const bool bIsBindWidget = FWidgetBlueprintEditorUtils::IsBindWidgetProperty(ObjectProperty);
|
|
if (Context.GetGeneratedClass() != ObjectProperty->GetOwnerStruct() && !bIsBindWidget)
|
|
{
|
|
// Widget needs to be BindWidget to be reused as a property.
|
|
const UClass* OwnerClass = Cast<UClass>(ObjectProperty->GetOwnerStruct());
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("WidgetPropertyAlreadyExist", "There is already a property named '{0}' in scope '{1}' for the widget. Are you missing a BindWidget?."), SourceContext.DisplayName, (OwnerClass ? OwnerClass->GetDisplayNameText() : FText::GetEmpty())).ToString());
|
|
bAreSourceContextsValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Can we reused the property or we need to create a new one.
|
|
bool bCreateVariable = bIsViewmodel || (bIsWidgetInsideWidgetTree && SourceContext.Field.IsEmpty());
|
|
if (bCreateVariable)
|
|
{
|
|
SourceContext.Field = FMVVMConstFieldVariant(CreateVariable(SourceContext));
|
|
}
|
|
|
|
if (SourceContext.Field.IsEmpty())
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("VariableCouldNotBeCreated", "The variable for '{0}' could not be created."), SourceContext.DisplayName).ToString());
|
|
bAreSourceContextsValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::CreateWidgetMap(const FWidgetBlueprintCompilerContext::FCreateVariableContext& Context, UMVVMBlueprintView* BlueprintView)
|
|
{
|
|
// The widget tree is not created yet for SKEL class.
|
|
//Context.GetGeneratedClass()->GetWidgetTreeArchetype()
|
|
WidgetNameToWidgetPointerMap.Reset();
|
|
|
|
TArray<UWidget*> Widgets;
|
|
UWidgetBlueprint* WidgetBPToScan = Context.GetWidgetBlueprint();
|
|
while (WidgetBPToScan != nullptr)
|
|
{
|
|
Widgets = WidgetBPToScan->GetAllSourceWidgets();
|
|
if (Widgets.Num() != 0)
|
|
{
|
|
break;
|
|
}
|
|
WidgetBPToScan = WidgetBPToScan->ParentClass && WidgetBPToScan->ParentClass->ClassGeneratedBy ? Cast<UWidgetBlueprint>(WidgetBPToScan->ParentClass->ClassGeneratedBy) : nullptr;
|
|
}
|
|
|
|
for (UWidget* Widget : Widgets)
|
|
{
|
|
WidgetNameToWidgetPointerMap.Add(Widget->GetFName(), Widget);
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::CreateSourceLists(const FWidgetBlueprintCompilerContext::FCreateVariableContext& Context, UMVVMBlueprintView* BlueprintView)
|
|
{
|
|
CompilerUserWidgetPropertyContexts.Reset();
|
|
CompilerSourceCreatorContexts.Reset();
|
|
|
|
TSet<FGuid> ViewModelGuids;
|
|
TSet<FName> WidgetSources;
|
|
for (const FMVVMBlueprintViewModelContext& ViewModelContext : BlueprintView->GetViewModels())
|
|
{
|
|
if (!ViewModelContext.GetViewModelId().IsValid())
|
|
{
|
|
AddErrorForViewModel(ViewModelContext, LOCTEXT("ViewmodelInvalidGuid", "GUID is invalid."));
|
|
bAreSourcesCreatorValid = false;
|
|
continue;
|
|
}
|
|
|
|
if (ViewModelGuids.Contains(ViewModelContext.GetViewModelId()))
|
|
{
|
|
AddErrorForViewModel(ViewModelContext, LOCTEXT("ViewmodelAlreadyAdded", "Identical viewmodel has already been added."));
|
|
bAreSourcesCreatorValid = false;
|
|
continue;
|
|
}
|
|
|
|
ViewModelGuids.Add(ViewModelContext.GetViewModelId());
|
|
|
|
if (ViewModelContext.GetViewModelClass() == nullptr || !ViewModelContext.IsValid())
|
|
{
|
|
AddErrorForViewModel(ViewModelContext, LOCTEXT("ViewmodelInvalidClass", "Invalid class."));
|
|
bAreSourcesCreatorValid = false;
|
|
continue;
|
|
}
|
|
|
|
const bool bCreateSetterFunction = GetDefault<UMVVMDeveloperProjectSettings>()->bAllowGeneratedViewModelSetter
|
|
&& (ViewModelContext.bCreateSetterFunction || ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::Manual);
|
|
|
|
int32 FoundSourceCreatorContextIndex = INDEX_NONE;
|
|
if (Context.GetCompileType() == EKismetCompileType::SkeletonOnly)
|
|
{
|
|
FCompilerSourceCreatorContext SourceContext;
|
|
SourceContext.ViewModelContext = ViewModelContext;
|
|
SourceContext.Type = ECompilerSourceCreatorType::ViewModel;
|
|
if (bCreateSetterFunction)
|
|
{
|
|
SourceContext.SetterFunctionName = TEXT("Set") + ViewModelContext.GetViewModelName().ToString();
|
|
}
|
|
FoundSourceCreatorContextIndex = CompilerSourceCreatorContexts.Emplace(MoveTemp(SourceContext));
|
|
}
|
|
else
|
|
{
|
|
FGuid ViewModelId = ViewModelContext.GetViewModelId();
|
|
FoundSourceCreatorContextIndex = CompilerSourceCreatorContexts.IndexOfByPredicate([ViewModelId](const FCompilerSourceCreatorContext& Other)
|
|
{
|
|
return Other.ViewModelContext.GetViewModelId() == ViewModelId;
|
|
});
|
|
}
|
|
checkf(FoundSourceCreatorContextIndex != INDEX_NONE, TEXT("The viewmodel was added after the skeleton was created?"));
|
|
|
|
FCompilerUserWidgetPropertyContext SourceVariable;
|
|
SourceVariable.Class = ViewModelContext.GetViewModelClass();
|
|
SourceVariable.PropertyName = ViewModelContext.GetViewModelName();
|
|
SourceVariable.DisplayName = ViewModelContext.GetDisplayName();
|
|
SourceVariable.CategoryName = TEXT("Viewmodel");
|
|
SourceVariable.bExposeOnSpawn = bCreateSetterFunction;
|
|
SourceVariable.bPrivate = ViewModelContext.InstancedViewModel != nullptr;
|
|
SourceVariable.BlueprintSetter = CompilerSourceCreatorContexts[FoundSourceCreatorContextIndex].SetterFunctionName;
|
|
SourceVariable.ViewModelId = ViewModelContext.GetViewModelId();
|
|
CompilerUserWidgetPropertyContexts.Emplace(MoveTemp(SourceVariable));
|
|
}
|
|
|
|
bAreSourceContextsValid = bAreSourcesCreatorValid;
|
|
|
|
const FName DefaultWidgetCategory = Context.GetWidgetBlueprint()->GetFName();
|
|
|
|
auto GenerateCompilerSourceContext = [Self = this, BlueprintView, DefaultWidgetCategory, Class = Context.GetGeneratedClass(), &ViewModelGuids, &WidgetSources](const FMVVMBlueprintPropertyPath& PropertyPath) -> TValueOrError<void, FText>
|
|
{
|
|
switch (PropertyPath.GetSource(Self->WidgetBlueprintCompilerContext.WidgetBlueprint()))
|
|
{
|
|
case EMVVMBlueprintFieldPathSource::SelfContext:
|
|
return MakeValue();
|
|
case EMVVMBlueprintFieldPathSource::Widget:
|
|
{
|
|
// If the widget doesn't have a property, add one automatically.
|
|
if (!WidgetSources.Contains(PropertyPath.GetWidgetName()))
|
|
{
|
|
WidgetSources.Add(PropertyPath.GetWidgetName());
|
|
|
|
UWidget** WidgetPtr = Self->WidgetNameToWidgetPointerMap.Find(PropertyPath.GetWidgetName());
|
|
if (WidgetPtr == nullptr || *WidgetPtr == nullptr)
|
|
{
|
|
return MakeError(FText::Format(LOCTEXT("InvalidWidgetFormat", "Could not find the targeted widget: {0}"), FText::FromName(PropertyPath.GetWidgetName())));
|
|
}
|
|
|
|
UWidget* Widget = *WidgetPtr;
|
|
FCompilerUserWidgetPropertyContext SourceVariable;
|
|
SourceVariable.Class = Widget->GetClass();
|
|
SourceVariable.PropertyName = PropertyPath.GetWidgetName();
|
|
SourceVariable.DisplayName = FText::FromString(Widget->GetDisplayLabel());
|
|
SourceVariable.CategoryName = TEXT("Widget");
|
|
SourceVariable.ViewModelId = FGuid();
|
|
SourceVariable.bPrivate = false;
|
|
Self->CompilerUserWidgetPropertyContexts.Emplace(MoveTemp(SourceVariable));
|
|
}
|
|
break;
|
|
}
|
|
case EMVVMBlueprintFieldPathSource::ViewModel:
|
|
{
|
|
const FMVVMBlueprintViewModelContext* SourceViewModelContext = BlueprintView->FindViewModel(PropertyPath.GetViewModelId());
|
|
if (SourceViewModelContext == nullptr)
|
|
{
|
|
return MakeError(FText::Format(LOCTEXT("BindingViewModelNotFound", "Could not find viewmodel with GUID {0}."), GetViewModelIdText(PropertyPath)));
|
|
}
|
|
|
|
if (!ViewModelGuids.Contains(SourceViewModelContext->GetViewModelId()))
|
|
{
|
|
return MakeError(FText::Format(LOCTEXT("BindingViewModelInvalid", "Viewmodel {0} {1} was invalid."), SourceViewModelContext->GetDisplayName(), GetViewModelIdText(PropertyPath)));
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
return MakeError(LOCTEXT("SourcePathNotSet", "A source path is required, but not set."));
|
|
}
|
|
return MakeValue();
|
|
};
|
|
|
|
// Only find the source first property and destination first property.
|
|
//The full path will be tested later. We want to build the list of property needed.
|
|
for (int32 Index = 0; Index < BlueprintView->GetNumBindings(); ++Index)
|
|
{
|
|
FMVVMBlueprintViewBinding* BindingPtr = BlueprintView->GetBindingAt(Index);
|
|
if (BindingPtr == nullptr)
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("BindingInvalidIndex", "Internal error: Tried to fetch binding for invalid index {0}."), Index).ToString());
|
|
bAreSourceContextsValid = false;
|
|
continue;
|
|
}
|
|
|
|
FMVVMBlueprintViewBinding& Binding = *BindingPtr;
|
|
if (!Binding.bCompile)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const bool bIsForwardBinding = IsForwardBinding(Binding.BindingType);
|
|
const bool bIsBackwardBinding = IsBackwardBinding(Binding.BindingType);
|
|
|
|
|
|
auto RunGenerateCompilerSourceContext = [Self = this, &GenerateCompilerSourceContext, &Binding, BlueprintView](const FMVVMBlueprintPropertyPath& PropertyPath, FName ArgumentName)
|
|
{
|
|
TValueOrError<void, FText> SourceContextResult = GenerateCompilerSourceContext(PropertyPath);
|
|
if (SourceContextResult.HasError())
|
|
{
|
|
Self->AddMessageForBinding(Binding, BlueprintView, SourceContextResult.StealError(), EBindingMessageType::Error, ArgumentName);
|
|
Self->bAreSourceContextsValid = false;
|
|
}
|
|
};
|
|
|
|
if (bIsForwardBinding || bIsBackwardBinding)
|
|
{
|
|
UMVVMBlueprintViewConversionFunction* ConversionFunction = Binding.Conversion.GetConversionFunction(bIsForwardBinding);
|
|
if (ConversionFunction && ConversionFunction->NeedsWrapperGraph())
|
|
{
|
|
ConversionFunction->SavePinValues(Context.GetWidgetBlueprint());
|
|
if (ConversionFunction->GetPins().Num() > 0)
|
|
{
|
|
if (Binding.BindingType == EMVVMBindingMode::TwoWay)
|
|
{
|
|
AddMessageForBinding(Binding, BlueprintView, LOCTEXT("TwoWayBindingsWithConversion", "Two-way bindings are not allowed to use conversion functions."), EBindingMessageType::Error);
|
|
bAreSourceContextsValid = false;
|
|
continue;
|
|
}
|
|
|
|
// generate sources for conversion function arguments
|
|
for (const FMVVMBlueprintPin& Pin: ConversionFunction->GetPins())
|
|
{
|
|
if (Pin.UsedPathAsValue())
|
|
{
|
|
RunGenerateCompilerSourceContext(Pin.GetPath(), Pin.GetName());
|
|
}
|
|
}
|
|
|
|
// generate destination source
|
|
if (bIsForwardBinding)
|
|
{
|
|
RunGenerateCompilerSourceContext(Binding.DestinationPath, FName());
|
|
}
|
|
else
|
|
{
|
|
|
|
RunGenerateCompilerSourceContext(Binding.SourcePath, FName());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if we aren't using a conversion function, just validate the widget and viewmodel paths
|
|
RunGenerateCompilerSourceContext(Binding.DestinationPath, FName());
|
|
RunGenerateCompilerSourceContext(Binding.SourcePath, FName());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find the event property.
|
|
//All inputs must also exist (for the BP to compile).
|
|
for (UMVVMBlueprintViewEvent* EventPtr : BlueprintView->GetEvents())
|
|
{
|
|
if (EventPtr == nullptr)
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*LOCTEXT("EventInvalid", "Internal error: An event is invalid.").ToString());
|
|
bAreSourceContextsValid = false;
|
|
continue;
|
|
}
|
|
|
|
if (!EventPtr->bCompile)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!EventPtr->GetEventPath().HasPaths())
|
|
{
|
|
AddMessageForEvent(EventPtr, LOCTEXT("EventInvalidEventPath", "The event path is invalid."), UMVVMBlueprintViewEvent::EMessageType::Error, FName());
|
|
bAreSourceContextsValid = false;
|
|
continue;
|
|
}
|
|
|
|
auto RunGenerateCompilerSourceContext = [Self = this, &GenerateCompilerSourceContext, EventPtr, BlueprintView](const FMVVMBlueprintPropertyPath& PropertyPath, FName ArgumentName)
|
|
{
|
|
TValueOrError<void, FText> SourceContextResult = GenerateCompilerSourceContext(PropertyPath);
|
|
if (SourceContextResult.HasError())
|
|
{
|
|
Self->AddMessageForEvent(EventPtr, SourceContextResult.StealError(), UMVVMBlueprintViewEvent::EMessageType::Error, ArgumentName);
|
|
Self->bAreSourceContextsValid = false;
|
|
}
|
|
};
|
|
|
|
RunGenerateCompilerSourceContext(EventPtr->GetEventPath(), FName());
|
|
RunGenerateCompilerSourceContext(EventPtr->GetDestinationPath(), FName());
|
|
for (const FMVVMBlueprintPin& Pin : EventPtr->GetPins())
|
|
{
|
|
if (Pin.UsedPathAsValue())
|
|
{
|
|
RunGenerateCompilerSourceContext(Pin.GetPath(), Pin.GetName());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::CreateFunctionsDeclaration(const FWidgetBlueprintCompilerContext::FCreateVariableContext& Context, UMVVMBlueprintView* BlueprintView)
|
|
{
|
|
// Clean all previous intermediate function graph. It should stay alive. The graph lives on the Blueprint not on the class and it's used to generate the UFunction.
|
|
{
|
|
auto RenameObjectToTransientPackage = [](UObject* ObjectToRename)
|
|
{
|
|
const ERenameFlags RenFlags = REN_DontCreateRedirectors | REN_NonTransactional | REN_DoNotDirty;
|
|
ObjectToRename->Rename(nullptr, GetTransientPackage(), RenFlags);
|
|
ObjectToRename->SetFlags(RF_Transient);
|
|
ObjectToRename->ClearFlags(RF_Public | RF_Standalone | RF_ArchetypeObject);
|
|
FLinkerLoad::InvalidateExport(ObjectToRename);
|
|
};
|
|
|
|
for (UEdGraph* OldGraph : BlueprintView->TemporaryGraph)
|
|
{
|
|
if (OldGraph)
|
|
{
|
|
RenameObjectToTransientPackage(OldGraph);
|
|
}
|
|
}
|
|
BlueprintView->TemporaryGraph.Reset();
|
|
}
|
|
|
|
if (GetDefault<UMVVMDeveloperProjectSettings>()->bAllowGeneratedViewModelSetter)
|
|
{
|
|
for (FCompilerSourceCreatorContext& SourceCreator : CompilerSourceCreatorContexts)
|
|
{
|
|
if (!SourceCreator.SetterFunctionName.IsEmpty() && SourceCreator.Type == ECompilerSourceCreatorType::ViewModel)
|
|
{
|
|
ensure(SourceCreator.SetterGraph == nullptr);
|
|
|
|
SourceCreator.SetterGraph = UE::MVVM::FunctionGraphHelper::CreateIntermediateFunctionGraph(
|
|
WidgetBlueprintCompilerContext
|
|
, SourceCreator.SetterFunctionName
|
|
, (FUNC_BlueprintCallable | FUNC_Public | FUNC_Final)
|
|
, TEXT("Viewmodel")
|
|
, false);
|
|
BlueprintView->TemporaryGraph.Add(SourceCreator.SetterGraph);
|
|
|
|
if (SourceCreator.SetterGraph == nullptr || SourceCreator.SetterGraph->GetFName() != FName(*SourceCreator.SetterFunctionName))
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("SetterNameAlreadyExists", "The setter name {0} already exists and could not be autogenerated."),
|
|
FText::FromString(SourceCreator.SetterFunctionName)
|
|
).ToString()
|
|
);
|
|
}
|
|
|
|
UE::MVVM::FunctionGraphHelper::AddFunctionArgument(SourceCreator.SetterGraph, SourceCreator.ViewModelContext.GetViewModelClass(), "Viewmodel");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::CreateIntermediateGraphFunctions(const FWidgetBlueprintCompilerContext::FCreateVariableContext & Context, UMVVMBlueprintView * BlueprintView)
|
|
{
|
|
// Add Generated Conversion functions to the blueprint
|
|
const int32 NumBindings = BlueprintView->GetNumBindings();
|
|
for (int32 Index = 0; Index < NumBindings; ++Index)
|
|
{
|
|
FMVVMBlueprintViewBinding* BindingPtr = BlueprintView->GetBindingAt(Index);
|
|
if (BindingPtr == nullptr)
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("InvalidBindingIndex", "Internal error. Invalid binding index given."), Index).ToString());
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
|
|
FMVVMBlueprintViewBinding& Binding = *BindingPtr;
|
|
if (!Binding.bCompile)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
auto AddGeneratedConversionFunction = [&Context, BindingPtr, this](bool bIsForwardBinding)
|
|
{
|
|
UMVVMBlueprintViewConversionFunction* ConversionFunction = BindingPtr->Conversion.GetConversionFunction(bIsForwardBinding);
|
|
if (ConversionFunction && ConversionFunction->IsWrapperGraphTransient())
|
|
{
|
|
UEdGraph* WrapperGraph = ConversionFunction->GetOrCreateIntermediateWrapperGraph(WidgetBlueprintCompilerContext);
|
|
if (WrapperGraph)
|
|
{
|
|
bool bAlreadyContained = WidgetBlueprintCompilerContext.Blueprint->FunctionGraphs.Contains(WrapperGraph);
|
|
if (ensure(!bAlreadyContained))
|
|
{
|
|
Context.AddGeneratedFunctionGraph(WrapperGraph);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
if (IsForwardBinding(Binding.BindingType))
|
|
{
|
|
AddGeneratedConversionFunction(true);
|
|
}
|
|
if (IsBackwardBinding(Binding.BindingType))
|
|
{
|
|
AddGeneratedConversionFunction(false);
|
|
}
|
|
}
|
|
|
|
// Add Generated event to the blueprint
|
|
for (UMVVMBlueprintViewEvent* EventPtr : BlueprintView->GetEvents())
|
|
{
|
|
if (EventPtr == nullptr)
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*LOCTEXT("InvalidEvent", "Internal error. Invalid event.").ToString());
|
|
bAreEventsValid = false;
|
|
continue;
|
|
}
|
|
|
|
if (!EventPtr->bCompile)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
UEdGraph* WrapperGraph = EventPtr->GetOrCreateWrapperGraph();
|
|
if (WrapperGraph == nullptr)
|
|
{
|
|
AddMessageForEvent(EventPtr, LOCTEXT("InvalidEventGraph", "The event could not be generated"), UMVVMBlueprintViewEvent::EMessageType::Warning, FName());
|
|
continue;
|
|
}
|
|
|
|
bool bAlreadyContained = WidgetBlueprintCompilerContext.Blueprint->FunctionGraphs.Contains(WrapperGraph);
|
|
if (ensure(!bAlreadyContained))
|
|
{
|
|
Context.AddGeneratedFunctionGraph(WrapperGraph);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::CreateBindingDestinationContexts(UMVVMBlueprintView* BlueprintView)
|
|
{
|
|
if (!bAreSourcesCreatorValid || !bAreSourceContextsValid || !bAreBindingsValid || !bAreEventsValid)
|
|
{
|
|
return;
|
|
}
|
|
|
|
BindingDestinationContexts.Empty();
|
|
|
|
// Use the Skeleton class. The class bind and not all functions are generated yet
|
|
UWidgetBlueprintGeneratedClass* NewSkeletonClass = Cast<UWidgetBlueprintGeneratedClass>(WidgetBlueprintCompilerContext.Blueprint->SkeletonGeneratedClass);
|
|
if (NewSkeletonClass == nullptr)
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*LOCTEXT("InvalidNewClass", "Internal error. The skeleton class is not valid.").ToString());
|
|
return;
|
|
}
|
|
|
|
const int32 NumBindings = BlueprintView->GetNumBindings();
|
|
for (int32 Index = 0; Index < NumBindings; ++Index)
|
|
{
|
|
FMVVMBlueprintViewBinding* BindingPtr = BlueprintView->GetBindingAt(Index);
|
|
if (BindingPtr == nullptr)
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("InvalidBindingIndex", "Internal error. Invalid binding index given."), Index).ToString());
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
|
|
FMVVMBlueprintViewBinding& Binding = *BindingPtr;
|
|
if (!Binding.bCompile)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
auto AddDestination = [Index, BindingPtr, BlueprintView, NewSkeletonClass, this](bool bIsForwardBinding)
|
|
{
|
|
FBindingDestinationContext& NewDestinationContext = BindingDestinationContexts.AddDefaulted_GetRef();
|
|
NewDestinationContext.BindingIndex = Index;
|
|
NewDestinationContext.bIsForwardBinding = bIsForwardBinding;
|
|
|
|
const FMVVMBlueprintPropertyPath& DestinationPath = bIsForwardBinding ? BindingPtr->DestinationPath : BindingPtr->SourcePath;
|
|
TArray<UE::MVVM::FMVVMConstFieldVariant> SkeletalSetterPath = CreateBindingDestinationPath(BlueprintView, NewSkeletonClass, DestinationPath);
|
|
if (!IsPropertyPathValid(WidgetBlueprintCompilerContext.WidgetBlueprint(), SkeletalSetterPath))
|
|
{
|
|
AddMessageForBinding(*BindingPtr, BlueprintView, FText::Format(Private::PropertyPathIsInvalidFormat,
|
|
PropertyPathToText(NewSkeletonClass, BlueprintView, DestinationPath)),
|
|
EBindingMessageType::Error
|
|
);
|
|
bAreBindingsValid = false;
|
|
return;
|
|
}
|
|
|
|
// Generate the path with property converted to BP function
|
|
TValueOrError<TArray<FMVVMConstFieldVariant>, FText> SkeletalGeneratedField = FieldPathHelper::GenerateFieldPathList(SkeletalSetterPath, false);
|
|
if (SkeletalGeneratedField.HasError() || !IsPropertyPathValid(WidgetBlueprintCompilerContext.WidgetBlueprint(), SkeletalGeneratedField.GetValue()))
|
|
{
|
|
AddMessageForBinding(*BindingPtr, BlueprintView, FText::Format(Private::CouldNotCreateSourceFieldPathFormat
|
|
, ::UE::MVVM::FieldPathHelper::ToText(SkeletalSetterPath)
|
|
, SkeletalGeneratedField.GetError())
|
|
, EBindingMessageType::Error
|
|
);
|
|
bAreBindingsValid = false;
|
|
return;
|
|
}
|
|
|
|
NewDestinationContext.bCanBeSetInNative = CanBeSetInNative(SkeletalGeneratedField.GetValue());
|
|
|
|
// If the destination can't be set in cpp, we need to generate a BP function to set the value.
|
|
if (!NewDestinationContext.bCanBeSetInNative)
|
|
{
|
|
const FProperty* SetterProperty = nullptr;
|
|
if (SkeletalGeneratedField.GetValue().Num() > 0 && ensure(SkeletalGeneratedField.GetValue().Last().IsProperty()))
|
|
{
|
|
SetterProperty = SkeletalGeneratedField.GetValue().Last().GetProperty();
|
|
}
|
|
|
|
if (SetterProperty == nullptr)
|
|
{
|
|
AddMessageForBinding(*BindingPtr, BlueprintView, FText::Format(LOCTEXT("CantGetSetter", "Internal Error. The setter function was not created. {0}"),
|
|
PropertyPathToText(NewSkeletonClass, BlueprintView, DestinationPath)),
|
|
EBindingMessageType::Error
|
|
);
|
|
bAreBindingsValid = false;
|
|
return;
|
|
}
|
|
|
|
// create a setter function to be called from native. For now we follow the convention of Setter(Conversion(Getter))
|
|
UEdGraph* GeneratedSetterGraph = UE::MVVM::FunctionGraphHelper::CreateIntermediateFunctionGraph(WidgetBlueprintCompilerContext, FString::Printf(TEXT("__Setter_%s"), *SetterProperty->GetName()), EFunctionFlags::FUNC_None, TEXT("AutogeneratedSetter"), false);
|
|
if (GeneratedSetterGraph == nullptr)
|
|
{
|
|
AddMessageForBinding(*BindingPtr, BlueprintView, FText::Format(LOCTEXT("CantCreateSetter", "Internal Error. The setter function was not created. {0}"),
|
|
PropertyPathToText(NewSkeletonClass, BlueprintView, DestinationPath)),
|
|
EBindingMessageType::Error
|
|
);
|
|
bAreBindingsValid = false;
|
|
return;
|
|
}
|
|
|
|
UE::MVVM::FunctionGraphHelper::AddFunctionArgument(GeneratedSetterGraph, SetterProperty, "NewValue");
|
|
|
|
// set GeneratedSetterFunction. Use the SkeletalSetterPath here to use the setter will be generated when the function is generated.
|
|
if (!UE::MVVM::FunctionGraphHelper::GenerateIntermediateSetter(WidgetBlueprintCompilerContext, GeneratedSetterGraph, SkeletalSetterPath))
|
|
{
|
|
AddMessageForBinding(*BindingPtr, BlueprintView, FText::Format(LOCTEXT("CantGeneratedSetter", "Internal Error. The setter function was not generated. {0}"),
|
|
PropertyPathToText(NewSkeletonClass, BlueprintView, DestinationPath)),
|
|
EBindingMessageType::Error
|
|
);
|
|
bAreBindingsValid = false;
|
|
return;
|
|
}
|
|
|
|
// the new path can only be set later, once the function is compiled.
|
|
NewDestinationContext.GeneratedFunctionName = GeneratedSetterGraph->GetFName();
|
|
}
|
|
};
|
|
|
|
if (IsForwardBinding(Binding.BindingType))
|
|
{
|
|
AddDestination(true);
|
|
}
|
|
if (IsBackwardBinding(Binding.BindingType))
|
|
{
|
|
AddDestination(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool FMVVMViewBlueprintCompiler::PreCompile(UWidgetBlueprintGeneratedClass* Class, UMVVMBlueprintView* BlueprintView)
|
|
{
|
|
if (!bAreSourcesCreatorValid || !bAreSourceContextsValid || !bAreBindingsValid || !bAreEventsValid)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const int32 NumBindings = BlueprintView->GetNumBindings();
|
|
CompilerBindings.Reset(NumBindings*2);
|
|
BindingSourceContexts.Reset(NumBindings*2);
|
|
SimpleBindingContexts.Reset();
|
|
ComplexConversionFunctionContexts.Reset();
|
|
EventDelegateContexts.Reset();
|
|
|
|
PreCompileBindingSources(Class, BlueprintView);
|
|
PreCompileSourceCreators(Class, BlueprintView);
|
|
PreCompileBindings(Class, BlueprintView);
|
|
PreCompileEvents(Class, BlueprintView);
|
|
|
|
return bAreSourcesCreatorValid && bAreSourceContextsValid && bAreBindingsValid && bAreEventsValid;
|
|
}
|
|
|
|
|
|
bool FMVVMViewBlueprintCompiler::Compile(UWidgetBlueprintGeneratedClass* Class, UMVVMBlueprintView* BlueprintView, UMVVMViewClass* ViewExtension)
|
|
{
|
|
if (!bAreSourcesCreatorValid || !bAreSourceContextsValid || !bAreBindingsValid || !bAreEventsValid)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FCompileResult, FText> CompileResult = BindingLibraryCompiler.Compile(BlueprintView->GetCompiledBindingLibraryId());
|
|
if (CompileResult.HasError())
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("BindingCompilationFailed", "The binding compilation failed. {1}"), CompileResult.GetError()).ToString());
|
|
return false;
|
|
}
|
|
CompileSourceCreators(CompileResult.GetValue(), Class, BlueprintView, ViewExtension);
|
|
CompileBindings(CompileResult.GetValue(), Class, BlueprintView, ViewExtension);
|
|
CompileEvents(CompileResult.GetValue(), Class, BlueprintView, ViewExtension);
|
|
|
|
{
|
|
ViewExtension->bInitializeSourcesOnConstruct = BlueprintView->GetSettings()->bInitializeSourcesOnConstruct;
|
|
ViewExtension->bInitializeBindingsOnConstruct = ViewExtension->bInitializeSourcesOnConstruct ? BlueprintView->GetSettings()->bInitializeBindingsOnConstruct : false;
|
|
}
|
|
|
|
bool bResult = bAreSourcesCreatorValid && bAreSourceContextsValid && bAreBindingsValid && bAreEventsValid;
|
|
if (bResult)
|
|
{
|
|
ViewExtension->BindingLibrary = MoveTemp(CompileResult.GetValue().Library);
|
|
|
|
#if UE_WITH_MVVM_DEBUGGING
|
|
if (CVarLogViewCompliedResult->GetBool())
|
|
{
|
|
FMVVMViewClass_SourceCreator::FToStringArgs CreatorsToStringArgs = FMVVMViewClass_SourceCreator::FToStringArgs::All();
|
|
CreatorsToStringArgs.bUseDisplayName = false;
|
|
FMVVMViewClass_CompiledBinding::FToStringArgs BindingToStringArgs = FMVVMViewClass_CompiledBinding::FToStringArgs::All();
|
|
BindingToStringArgs.bUseDisplayName = false;
|
|
ViewExtension->Log(CreatorsToStringArgs, BindingToStringArgs);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
|
|
bool FMVVMViewBlueprintCompiler::PreCompileBindingSources(UWidgetBlueprintGeneratedClass* Class, UMVVMBlueprintView* BlueprintView)
|
|
{
|
|
const int32 NumBindings = BlueprintView->GetNumBindings(); // NB Binding can be added when creating a dynamic vm
|
|
for (int32 Index = 0; Index < NumBindings; ++Index)
|
|
{
|
|
FMVVMBlueprintViewBinding* BindingPtr = BlueprintView->GetBindingAt(Index);
|
|
if (BindingPtr == nullptr)
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("InvalidBindingIndex", "Internal error. Invalid binding index given."), Index).ToString());
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
|
|
FMVVMBlueprintViewBinding& Binding = *BindingPtr;
|
|
if (!Binding.bCompile)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool bIsOneTimeBinding = IsOneTimeBinding(Binding.BindingType);
|
|
|
|
auto CreateSourceContextForPropertyPath = [Self=this, &Binding, BlueprintView, Class, Index, bIsOneTimeBinding](const FMVVMBlueprintPropertyPath& Path, bool bForwardBinding, int32 ComplexConversionFunctionContextIndex, FName ArgumentName) -> bool
|
|
{
|
|
const TValueOrError<FBindingSourceContext, FText> CreatedBindingSourceContext = Self->CreateBindingSourceContext(BlueprintView, Class, Path, bIsOneTimeBinding);
|
|
if (CreatedBindingSourceContext.HasError())
|
|
{
|
|
Self->AddMessageForBinding(Binding, BlueprintView,
|
|
FText::Format(LOCTEXT("PropertyPathInvalidWithReason", "The property path '{0}' is invalid. {1}"),
|
|
PropertyPathToText(Class, BlueprintView, Binding.SourcePath),
|
|
CreatedBindingSourceContext.GetError()
|
|
),
|
|
EBindingMessageType::Error,
|
|
ArgumentName
|
|
);
|
|
return false;
|
|
}
|
|
|
|
FBindingSourceContext BindingSourceContext = CreatedBindingSourceContext.GetValue();
|
|
if (!IsPropertyPathValid(Self->WidgetBlueprintCompilerContext.WidgetBlueprint(), BindingSourceContext.PropertyPath))
|
|
{
|
|
Self->AddMessageForBinding(Binding,
|
|
BlueprintView,
|
|
FText::Format(Private::PropertyPathIsInvalidFormat, PropertyPathToText(Class, BlueprintView, Binding.SourcePath)),
|
|
EBindingMessageType::Error,
|
|
ArgumentName
|
|
);
|
|
return false;
|
|
}
|
|
|
|
if (BindingSourceContext.SourceClass == nullptr)
|
|
{
|
|
Self->AddMessageForBinding(Binding, BlueprintView, LOCTEXT("BindingInvalidSourceClass", "Internal error. The binding could not find its source class."), EBindingMessageType::Error, ArgumentName);
|
|
return false;
|
|
}
|
|
|
|
if (!BindingSourceContext.bIsRootWidget && BindingSourceContext.UserWidgetPropertyContextIndex == INDEX_NONE && BindingSourceContext.SourceCreatorContextIndex == INDEX_NONE)
|
|
{
|
|
Self->AddMessageForBinding(Binding, BlueprintView, LOCTEXT("BindingInvalidSource", "Internal error. The binding could not find its source."), EBindingMessageType::Error, ArgumentName);
|
|
return false;
|
|
}
|
|
|
|
BindingSourceContext.BindingIndex = Index;
|
|
BindingSourceContext.bIsForwardBinding = bForwardBinding;
|
|
BindingSourceContext.ComplexConversionFunctionContextIndex = ComplexConversionFunctionContextIndex;
|
|
|
|
Self->BindingSourceContexts.Add(MoveTemp(BindingSourceContext));
|
|
return true;
|
|
};
|
|
|
|
enum class ECreateSourcesForConversionFunctionResult : uint8 { Valid, Failed, Continue };
|
|
auto CreateSourcesForConversionFunction = [this, &Binding, BlueprintView, Index, &CreateSourceContextForPropertyPath](bool bForwardBinding)
|
|
{
|
|
ECreateSourcesForConversionFunctionResult Result = ECreateSourcesForConversionFunctionResult::Continue;
|
|
if (const UMVVMBlueprintViewConversionFunction* ConversionFunction = Binding.Conversion.GetConversionFunction(bForwardBinding))
|
|
{
|
|
if (ConversionFunction->NeedsWrapperGraph())
|
|
{
|
|
const int32 ComplexConversionFunctionContextIndex = ComplexConversionFunctionContexts.AddDefaulted();
|
|
{
|
|
FComplexConversionFunctionContext& NewConversionFunctionContext = ComplexConversionFunctionContexts[ComplexConversionFunctionContextIndex];
|
|
NewConversionFunctionContext.BindingIndex = Index;
|
|
NewConversionFunctionContext.bIsForwardBinding = bForwardBinding;
|
|
}
|
|
|
|
for (const FMVVMBlueprintPin& Pin : ConversionFunction->GetPins())
|
|
{
|
|
if (Pin.UsedPathAsValue())
|
|
{
|
|
if (Pin.GetName().IsNone())
|
|
{
|
|
AddMessageForBinding(Binding, BlueprintView,
|
|
FText::Format(LOCTEXT("InvalidArgumentPathName", "The conversion function {0} has an invalid argument"), FText::FromString(Binding.GetDisplayNameString(WidgetBlueprintCompilerContext.WidgetBlueprint()))),
|
|
EBindingMessageType::Error
|
|
);
|
|
Result = ECreateSourcesForConversionFunctionResult::Failed;
|
|
}
|
|
else
|
|
{
|
|
const FMVVMBlueprintPropertyPath& Path = Pin.GetPath();
|
|
if (CreateSourceContextForPropertyPath(Path, bForwardBinding, ComplexConversionFunctionContextIndex, Pin.GetName()))
|
|
{
|
|
Result = ECreateSourcesForConversionFunctionResult::Valid;
|
|
}
|
|
else
|
|
{
|
|
Result = ECreateSourcesForConversionFunctionResult::Failed;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Result == ECreateSourcesForConversionFunctionResult::Continue)
|
|
{
|
|
// The bindings doesn't have a path but could be onetime with hard codded value.
|
|
FSimpleBindingContext& NewBindingContext = SimpleBindingContexts.AddDefaulted_GetRef();
|
|
NewBindingContext.BindingIndex = Index;
|
|
NewBindingContext.bIsForwardBinding = bForwardBinding;
|
|
NewBindingContext.ComplexConversionFunctionContextIndex = ComplexConversionFunctionContextIndex;
|
|
ComplexConversionFunctionContexts[ComplexConversionFunctionContextIndex].bNeedsValidSource = false;
|
|
|
|
Result = ECreateSourcesForConversionFunctionResult::Valid;
|
|
}
|
|
}
|
|
}
|
|
return Result;
|
|
};
|
|
|
|
auto AddWarningForPropertyWithMVVMAndLegacyBinding = [this, &Binding, &BlueprintView, Class](const FMVVMBlueprintPropertyPath& Path)
|
|
{
|
|
if (!Path.HasPaths())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// There can't be a legacy binding in the local scope, so we can skip this if the MVVM binding refers to a property in local scope.
|
|
if (Path.HasFieldInLocalScope())
|
|
{
|
|
return;
|
|
}
|
|
|
|
TArrayView<FMVVMBlueprintFieldPath const> MVVMBindingPath = Path.GetFieldPaths();
|
|
TArray< FDelegateRuntimeBinding > LegacyBindings = Class->Bindings;
|
|
FName MVVMFieldName = Path.GetFieldNames(Class).Last();
|
|
FName MVVMObjectName = Path.GetWidgetName();
|
|
|
|
if (Path.GetFieldPaths().Last().GetBindingKind() == EBindingKind::Function)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If the first field is a UserWidget, we know this property resides in a nested UserWidget.
|
|
if (MVVMBindingPath[0].GetParentClass(Class) && MVVMBindingPath[0].GetParentClass(Class)->IsChildOf(UUserWidget::StaticClass()) && MVVMBindingPath.Num() > 1)
|
|
{
|
|
if (UWidgetBlueprintGeneratedClass* NestedBPGClass = Cast<UWidgetBlueprintGeneratedClass>(MVVMBindingPath[MVVMBindingPath.Num() - 2].GetParentClass(Class)))
|
|
{
|
|
LegacyBindings = NestedBPGClass->Bindings;
|
|
|
|
// We can't use Path.GetWidgetName() when we are dealing with nested UserWidgets, because it refers to the topmost UserWidget.
|
|
MVVMObjectName = MVVMBindingPath[MVVMBindingPath.Num() - 2].GetFieldName(Class);
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (const FDelegateRuntimeBinding& LegacyBinding : LegacyBindings)
|
|
{
|
|
if (LegacyBinding.ObjectName == MVVMObjectName)
|
|
{
|
|
if (LegacyBinding.PropertyName == MVVMFieldName)
|
|
{
|
|
AddMessageForBinding(Binding, BlueprintView, LOCTEXT("BindingConflictWithLegacy", "The binding is set on a property with legacy binding."), EBindingMessageType::Warning);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Add the forward binding. If the binding has a conversion function, use it instead of the regular binding.
|
|
if (IsForwardBinding(Binding.BindingType))
|
|
{
|
|
ECreateSourcesForConversionFunctionResult ConversionFunctionResult = CreateSourcesForConversionFunction(true);
|
|
if (ConversionFunctionResult == ECreateSourcesForConversionFunctionResult::Continue)
|
|
{
|
|
if (Binding.SourcePath.IsValid())
|
|
{
|
|
if (Binding.DestinationPath.IsValid())
|
|
{
|
|
AddWarningForPropertyWithMVVMAndLegacyBinding(Binding.DestinationPath);
|
|
}
|
|
|
|
if (!CreateSourceContextForPropertyPath(Binding.SourcePath, true, INDEX_NONE, FName()))
|
|
{
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AddMessageForBinding(Binding, BlueprintView, LOCTEXT("BindingEmptySourcePath", "The binding doesn't have a Source or a Conversion function."), EBindingMessageType::Error);
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
else if (ConversionFunctionResult == ECreateSourcesForConversionFunctionResult::Failed)
|
|
{
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Add the backward binding. If the binding has a conversion function, use it instead of the regular binding.
|
|
if (IsBackwardBinding(Binding.BindingType))
|
|
{
|
|
ECreateSourcesForConversionFunctionResult ConversionFunctionResult = CreateSourcesForConversionFunction(false);
|
|
if (ConversionFunctionResult == ECreateSourcesForConversionFunctionResult::Continue)
|
|
{
|
|
if (Binding.DestinationPath.IsValid())
|
|
{
|
|
if (Binding.SourcePath.IsValid())
|
|
{
|
|
AddWarningForPropertyWithMVVMAndLegacyBinding(Binding.SourcePath);
|
|
}
|
|
if (!CreateSourceContextForPropertyPath(Binding.DestinationPath, false, INDEX_NONE, FName()))
|
|
{
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AddMessageForBinding(Binding, BlueprintView, LOCTEXT("BindingEmptyDestinationPath", "The binding doesn't have a Destination or a Conversion function."), EBindingMessageType::Error);
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
else if (ConversionFunctionResult == ECreateSourcesForConversionFunctionResult::Failed)
|
|
{
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bAreBindingsValid;
|
|
}
|
|
|
|
|
|
bool FMVVMViewBlueprintCompiler::PreCompileSourceCreators(UWidgetBlueprintGeneratedClass* Class, UMVVMBlueprintView* BlueprintView)
|
|
{
|
|
if (!bAreSourcesCreatorValid)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (FCompilerSourceCreatorContext& SourceCreatorContext : CompilerSourceCreatorContexts)
|
|
{
|
|
FMVVMViewClass_SourceCreator CompiledSourceCreator;
|
|
|
|
if (SourceCreatorContext.Type == ECompilerSourceCreatorType::ViewModel)
|
|
{
|
|
const FMVVMBlueprintViewModelContext& ViewModelContext = SourceCreatorContext.ViewModelContext;
|
|
checkf(ViewModelContext.GetViewModelClass(), TEXT("The viewmodel class is invalid. It was checked in CreateSourceList"));
|
|
|
|
if (ViewModelContext.GetViewModelClass()->HasAllClassFlags(CLASS_Deprecated))
|
|
{
|
|
AddErrorForViewModel(ViewModelContext, FText::Format(LOCTEXT("ViewModelTypeDeprecated", "Viewmodel class '{0}' is deprecated and should not be used. Please update it in the View Models panel."),
|
|
ViewModelContext.GetViewModelClass()->GetDisplayNameText()
|
|
));
|
|
}
|
|
|
|
if (!GetAllowedContextCreationType(ViewModelContext.GetViewModelClass()).Contains(ViewModelContext.CreationType))
|
|
{
|
|
AddErrorForViewModel(ViewModelContext, FText::Format(LOCTEXT("ViewModelContextCreationTypeInvalid", "Viewmodel '{0}' has an invalidate creation type. You can change it in the View Models panel."),
|
|
ViewModelContext.GetViewModelClass()->GetDisplayNameText()
|
|
));
|
|
bAreSourcesCreatorValid = false;
|
|
continue;
|
|
}
|
|
|
|
if (ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::Manual)
|
|
{
|
|
}
|
|
else if (ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::CreateInstance)
|
|
{
|
|
if (ViewModelContext.GetViewModelClass()->HasAllClassFlags(CLASS_Abstract))
|
|
{
|
|
AddErrorForViewModel(ViewModelContext, FText::Format(LOCTEXT("ViewModelTypeAbstract", "Viewmodel class '{0}' is abstract and can't be created. You can change it in the View Models panel."),
|
|
ViewModelContext.GetViewModelClass()->GetDisplayNameText()
|
|
));
|
|
bAreSourcesCreatorValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
else if (ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::PropertyPath)
|
|
{
|
|
if (ViewModelContext.ViewModelPropertyPath.IsEmpty())
|
|
{
|
|
AddErrorForViewModel(ViewModelContext, LOCTEXT("ViewModelInvalidGetter", "Viewmodel has an invalid Getter. You can select a new one in the View Models panel."));
|
|
bAreSourcesCreatorValid = true;
|
|
continue;
|
|
}
|
|
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FFieldPathHandle, FText> ReadFieldPathResult = AddObjectFieldPath(BindingLibraryCompiler, Class, ViewModelContext.ViewModelPropertyPath, ViewModelContext.GetViewModelClass());
|
|
if (ReadFieldPathResult.HasError())
|
|
{
|
|
AddErrorForViewModel(ViewModelContext, ReadFieldPathResult.GetError());
|
|
bAreSourcesCreatorValid = false;
|
|
continue;
|
|
}
|
|
|
|
SourceCreatorContext.ReadPropertyPath = ReadFieldPathResult.StealValue();
|
|
}
|
|
else if (ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::GlobalViewModelCollection)
|
|
{
|
|
if (ViewModelContext.GlobalViewModelIdentifier.IsNone())
|
|
{
|
|
AddErrorForViewModel(ViewModelContext, LOCTEXT("ViewmodelInvalidGlobalIdentifier", "Viewmodel doesn't have a valid Global identifier. You can specify a new one in the Viewmodels panel."));
|
|
bAreSourcesCreatorValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
else if (ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::Resolver)
|
|
{
|
|
if (!ViewModelContext.Resolver)
|
|
{
|
|
AddErrorForViewModel(ViewModelContext, LOCTEXT("ViewmodelInvalidResolver", "Viewmodel doesn't have a valid Resolver. You can specify a new one in the Viewmodels panel."));
|
|
bAreSourcesCreatorValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AddErrorForViewModel(ViewModelContext, LOCTEXT("ViewmodelInvalidCreationType", "Viewmodel doesn't have a valid creation type. You can select one in the Viewmodels panel."));
|
|
bAreSourcesCreatorValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bAreSourcesCreatorValid;
|
|
}
|
|
|
|
|
|
bool FMVVMViewBlueprintCompiler::CompileSourceCreators(const FCompiledBindingLibraryCompiler::FCompileResult& CompileResult, UWidgetBlueprintGeneratedClass* Class, UMVVMBlueprintView* BlueprintView, UMVVMViewClass* ViewExtension)
|
|
{
|
|
if (!bAreSourcesCreatorValid)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TArray<FMVVMViewClass_SourceCreator> UnsortedSourceCreators;
|
|
for (const FCompilerSourceCreatorContext& SourceCreatorContext : CompilerSourceCreatorContexts)
|
|
{
|
|
const FMVVMBlueprintViewModelContext& ViewModelContext = SourceCreatorContext.ViewModelContext;
|
|
FMVVMViewClass_SourceCreator CompiledSourceCreator;
|
|
|
|
ensure(ViewModelContext.GetViewModelClass() && ViewModelContext.GetViewModelClass()->ImplementsInterface(UNotifyFieldValueChanged::StaticClass()));
|
|
CompiledSourceCreator.ExpectedSourceType = ViewModelContext.GetViewModelClass();
|
|
CompiledSourceCreator.PropertyName = ViewModelContext.GetViewModelName();
|
|
|
|
bool bCanBeSet = false;
|
|
bool CanBeEvaluated = false;
|
|
bool bIsOptional = false;
|
|
bool bCreateInstance = false;
|
|
bool IsUserWidgetProperty = false;
|
|
|
|
if (SourceCreatorContext.Type == ECompilerSourceCreatorType::ViewModel)
|
|
{
|
|
IsUserWidgetProperty = true;
|
|
bCanBeSet = ViewModelContext.bCreateSetterFunction;
|
|
|
|
if (ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::Manual)
|
|
{
|
|
bCanBeSet = true;
|
|
bIsOptional = true;
|
|
}
|
|
else if (ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::CreateInstance)
|
|
{
|
|
bCreateInstance = true;
|
|
}
|
|
else if (ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::PropertyPath)
|
|
{
|
|
const FMVVMVCompiledFieldPath* CompiledFieldPath = CompileResult.FieldPaths.Find(SourceCreatorContext.ReadPropertyPath);
|
|
if (CompiledFieldPath == nullptr)
|
|
{
|
|
AddErrorForViewModel(ViewModelContext, LOCTEXT("ViewModelInvalidInitializationBindingNotGenerated", "The viewmodel initialization binding was not generated."));
|
|
bAreSourcesCreatorValid = false;
|
|
continue;
|
|
}
|
|
|
|
CompiledSourceCreator.FieldPath = *CompiledFieldPath;
|
|
bIsOptional = ViewModelContext.bOptional;
|
|
}
|
|
else if (ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::GlobalViewModelCollection)
|
|
{
|
|
if (ViewModelContext.GlobalViewModelIdentifier.IsNone())
|
|
{
|
|
AddErrorForViewModel(ViewModelContext, LOCTEXT("ViewModelInvalidGlobalIdentifier", "The viewmodel doesn't have a valid Global identifier. You can specify a new one in the Viewmodels panel."));
|
|
bAreSourcesCreatorValid = false;
|
|
continue;
|
|
}
|
|
|
|
FMVVMViewModelContext GlobalViewModelInstance;
|
|
GlobalViewModelInstance.ContextClass = ViewModelContext.GetViewModelClass();
|
|
GlobalViewModelInstance.ContextName = ViewModelContext.GlobalViewModelIdentifier;
|
|
if (!GlobalViewModelInstance.IsValid())
|
|
{
|
|
AddErrorForViewModel(ViewModelContext, LOCTEXT("ViewModelCouldNotBeCreated", "The context for viewmodel could not be created. You can change the viewmodel in the Viewmodels panel."));
|
|
bAreSourcesCreatorValid = false;
|
|
continue;
|
|
}
|
|
|
|
CompiledSourceCreator.GlobalViewModelInstance = MoveTemp(GlobalViewModelInstance);
|
|
bIsOptional = ViewModelContext.bOptional;
|
|
}
|
|
else if (ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::Resolver)
|
|
{
|
|
UMVVMViewModelContextResolver* Resolver = DuplicateObject(ViewModelContext.Resolver.Get(), ViewExtension);
|
|
if (!Resolver)
|
|
{
|
|
AddErrorForViewModel(ViewModelContext, LOCTEXT("ViewmodelFailedResolverDuplicate", "Internal error. The resolver could not be dupliated."));
|
|
bAreSourcesCreatorValid = false;
|
|
continue;
|
|
}
|
|
|
|
CompiledSourceCreator.Resolver = Resolver;
|
|
bIsOptional = ViewModelContext.bOptional;
|
|
}
|
|
else
|
|
{
|
|
AddErrorForViewModel(ViewModelContext, LOCTEXT("ViewModelWithoutValidCreationType", "The viewmodel doesn't have a valid creation type."));
|
|
bAreSourcesCreatorValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
else if (SourceCreatorContext.Type == ECompilerSourceCreatorType::ViewModelDynamic)
|
|
{
|
|
const FMVVMVCompiledFieldPath* CompiledFieldPath = CompileResult.FieldPaths.Find(SourceCreatorContext.ReadPropertyPath);
|
|
if (CompiledFieldPath == nullptr)
|
|
{
|
|
AddErrorForViewModel(ViewModelContext, LOCTEXT("ViewModelInvalidInitializationBindingNotGenerated", "The viewmodel initialization binding was not generated."));
|
|
bAreSourcesCreatorValid = false;
|
|
continue;
|
|
}
|
|
|
|
CanBeEvaluated = true;
|
|
CompiledSourceCreator.FieldPath = *CompiledFieldPath;
|
|
CompiledSourceCreator.ParentSourceName = SourceCreatorContext.DynamicParentSourceName;
|
|
}
|
|
|
|
CompiledSourceCreator.Flags = 0;
|
|
CompiledSourceCreator.Flags |= bCreateInstance ? (uint8)FMVVMViewClass_SourceCreator::ESourceFlags::TypeCreateInstance : 0;
|
|
CompiledSourceCreator.Flags |= IsUserWidgetProperty ? (uint8)FMVVMViewClass_SourceCreator::ESourceFlags::IsUserWidgetProperty : 0;
|
|
CompiledSourceCreator.Flags |= bIsOptional ? (uint8)FMVVMViewClass_SourceCreator::ESourceFlags::IsOptional : 0;
|
|
CompiledSourceCreator.Flags |= bCanBeSet ? (uint8)FMVVMViewClass_SourceCreator::ESourceFlags::CanBeSet : 0;
|
|
CompiledSourceCreator.Flags |= CanBeEvaluated ? (uint8)FMVVMViewClass_SourceCreator::ESourceFlags::CanBeEvaluated : 0;
|
|
|
|
UnsortedSourceCreators.Add(MoveTemp(CompiledSourceCreator));
|
|
}
|
|
|
|
// sort the source creators by priority and then by name
|
|
if (UnsortedSourceCreators.Num() > 1)
|
|
{
|
|
struct FSortData
|
|
{
|
|
FSortData() = default;
|
|
FName SourceName;
|
|
FName ParentSourceName;
|
|
int32 SortIndex = -1;
|
|
|
|
void CalculateSortIndex(TMap<FName, FSortData>& Map)
|
|
{
|
|
if (SortIndex < 0)
|
|
{
|
|
if (ParentSourceName.IsNone())
|
|
{
|
|
SortIndex = 0;
|
|
}
|
|
else
|
|
{
|
|
Map[ParentSourceName].CalculateSortIndex(Map);
|
|
// calculate the Depth recursively
|
|
SortIndex = Map[ParentSourceName].SortIndex + 1;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
TMap<FName, FSortData> SortDatas;
|
|
SortDatas.Reserve(UnsortedSourceCreators.Num());
|
|
for (int32 Index = 0; Index < UnsortedSourceCreators.Num(); ++Index)
|
|
{
|
|
FSortData SortData;
|
|
SortData.SourceName = UnsortedSourceCreators[Index].GetSourceName();
|
|
SortData.ParentSourceName =UnsortedSourceCreators[Index].GetParentSourceName();
|
|
SortDatas.Emplace(SortData.SourceName, SortData);
|
|
}
|
|
for (auto& SortDataPair : SortDatas)
|
|
{
|
|
SortDataPair.Value.CalculateSortIndex(SortDatas);
|
|
}
|
|
|
|
UnsortedSourceCreators.Sort([&SortDatas](const FMVVMViewClass_SourceCreator& A, const FMVVMViewClass_SourceCreator& B)
|
|
{
|
|
int32 ASortIndex = SortDatas[A.GetSourceName()].SortIndex;
|
|
int32 BSortIndex = SortDatas[B.GetSourceName()].SortIndex;
|
|
if (ASortIndex == BSortIndex)
|
|
{
|
|
return A.GetSourceName().LexicalLess(B.GetSourceName());
|
|
}
|
|
return ASortIndex < BSortIndex;
|
|
});
|
|
}
|
|
|
|
// Add sorted array to the ViewExtension
|
|
{
|
|
ViewExtension->SourceCreators.Reset(UnsortedSourceCreators.Num());
|
|
for (FMVVMViewClass_SourceCreator& SourceCreator : UnsortedSourceCreators)
|
|
{
|
|
ViewExtension->SourceCreators.Add(MoveTemp(SourceCreator));
|
|
}
|
|
UnsortedSourceCreators.Reset();
|
|
}
|
|
|
|
return bAreSourcesCreatorValid;
|
|
}
|
|
|
|
|
|
bool FMVVMViewBlueprintCompiler::PreCompileBindings(UWidgetBlueprintGeneratedClass* Class, UMVVMBlueprintView* BlueprintView)
|
|
{
|
|
if (!bAreSourceContextsValid || !bAreBindingsValid)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
auto GetConversionFunction = [Self = this, Class, BlueprintView](FMVVMBlueprintViewBinding& Binding, bool bIsForwardBinding) -> TValueOrError<const UFunction*, void>
|
|
{
|
|
const UFunction* ConversionFunction = nullptr;
|
|
const UMVVMBlueprintViewConversionFunction* ViewConversionFunction = Binding.Conversion.GetConversionFunction(bIsForwardBinding);
|
|
if (ViewConversionFunction)
|
|
{
|
|
ConversionFunction = ViewConversionFunction->GetCompiledFunction(Class);
|
|
if (ConversionFunction == nullptr)
|
|
{
|
|
Self->AddMessageForBinding(Binding, BlueprintView, FText::Format(LOCTEXT("ConversionFunctionNotFound", "The conversion function '{0}' could not be found."),
|
|
FText::FromName(ViewConversionFunction->GetCompiledFunctionName())),
|
|
EBindingMessageType::Error
|
|
);
|
|
Self->bAreBindingsValid = false;
|
|
return MakeError();
|
|
}
|
|
|
|
TVariant<const UFunction*, TSubclassOf<UK2Node>> FunctionOrWrapperFunction = ViewConversionFunction->GetConversionFunction(Class);
|
|
if (FunctionOrWrapperFunction.IsType<const UFunction*>())
|
|
{
|
|
if (!GetDefault<UMVVMDeveloperProjectSettings>()->IsConversionFunctionAllowed(Self->WidgetBlueprintCompilerContext.WidgetBlueprint(), FunctionOrWrapperFunction.Get<const UFunction*>()))
|
|
{
|
|
Self->AddMessageForBinding(Binding, BlueprintView, FText::Format(LOCTEXT("ConversionFunctionNotAllow", "The conversion function {0} is not allowed."),
|
|
FText::FromName(FunctionOrWrapperFunction.Get<const UFunction*>()->GetFName())),
|
|
EBindingMessageType::Error
|
|
);
|
|
Self->bAreBindingsValid = false;
|
|
return MakeError();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Self->AddMessageForBinding(Binding, BlueprintView, LOCTEXT("ConversionFunctionNodeNotAllow", "The conversion function node is not allowed."), EBindingMessageType::Error);
|
|
Self->bAreBindingsValid = false;
|
|
return MakeError();
|
|
}
|
|
}
|
|
return MakeValue(ConversionFunction);
|
|
};
|
|
|
|
auto TestExecutionMode = [Self = this, BlueprintView](FMVVMBlueprintViewBinding& Binding)
|
|
{
|
|
if (Binding.bOverrideExecutionMode)
|
|
{
|
|
if (!GetDefault<UMVVMDeveloperProjectSettings>()->IsExecutionModeAllowed(Binding.OverrideExecutionMode))
|
|
{
|
|
Self->AddMessageForBinding(Binding, BlueprintView, LOCTEXT("NotAllowedExecutionMode", "The binding has a restricted execution mode."), EBindingMessageType::Error);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
auto GetSetterPath = [Self = this, Class, BlueprintView](FMVVMBlueprintViewBinding& Binding, int32 BindingIndex, bool bIsForwardBinding) -> TValueOrError<TArray<UE::MVVM::FMVVMConstFieldVariant>, void>
|
|
{
|
|
const FBindingDestinationContext* DestinationContext = Self->BindingDestinationContexts.FindByPredicate([BindingIndex, bIsForwardBinding](const FBindingDestinationContext& Other)
|
|
{
|
|
return Other.BindingIndex == BindingIndex && Other.bIsForwardBinding == bIsForwardBinding;
|
|
});
|
|
|
|
if (DestinationContext == nullptr)
|
|
{
|
|
Self->AddMessageForBinding(Binding, BlueprintView, LOCTEXT("CouldNotFindDestination", "Could not find the pre compiled destination."), EBindingMessageType::Error);
|
|
return MakeError();
|
|
}
|
|
|
|
TArray<UE::MVVM::FMVVMConstFieldVariant> SetterPath;
|
|
if (!DestinationContext->bCanBeSetInNative)
|
|
{
|
|
check(!DestinationContext->GeneratedFunctionName.IsNone());
|
|
UFunction* FoundFunction = Class->FindFunctionByName(DestinationContext->GeneratedFunctionName);
|
|
if (FoundFunction == nullptr)
|
|
{
|
|
Self->AddMessageForBinding(Binding, BlueprintView, LOCTEXT("CouldNotFindDestinationBPFunction", "Could not find the generated destination function."), EBindingMessageType::Error);
|
|
return MakeError();
|
|
}
|
|
FMVVMBlueprintPropertyPath DestinationPPropertyPath;
|
|
DestinationPPropertyPath.SetSelfContext();
|
|
DestinationPPropertyPath.SetPropertyPath(BlueprintView->GetOuterUMVVMWidgetBlueprintExtension_View()->GetWidgetBlueprint(), UE::MVVM::FMVVMConstFieldVariant(FoundFunction));
|
|
SetterPath = Self->CreateBindingDestinationPath(BlueprintView, Class, DestinationPPropertyPath);
|
|
}
|
|
else
|
|
{
|
|
const FMVVMBlueprintPropertyPath& DestinationPath = bIsForwardBinding ? Binding.DestinationPath : Binding.SourcePath;
|
|
SetterPath = Self->CreateBindingDestinationPath(BlueprintView, Class, DestinationPath);
|
|
}
|
|
|
|
if (!IsPropertyPathValid(Self->WidgetBlueprintCompilerContext.WidgetBlueprint(), SetterPath))
|
|
{
|
|
const FMVVMBlueprintPropertyPath& DestinationPath = bIsForwardBinding ? Binding.DestinationPath : Binding.SourcePath;
|
|
Self->AddMessageForBinding(Binding, BlueprintView, FText::Format(Private::PropertyPathIsInvalidFormat, PropertyPathToText(Class, BlueprintView, DestinationPath)), EBindingMessageType::Error);
|
|
return MakeError();
|
|
}
|
|
return MakeValue(SetterPath);
|
|
};
|
|
|
|
for (const FBindingSourceContext& BindingSourceContext : BindingSourceContexts)
|
|
{
|
|
FMVVMBlueprintViewBinding* BindingPtr = BlueprintView->GetBindingAt(BindingSourceContext.BindingIndex);
|
|
check(BindingPtr);
|
|
FMVVMBlueprintViewBinding& Binding = *BindingPtr;
|
|
|
|
FMVVMViewBlueprintCompiler* Self = this;
|
|
auto AddFieldId = [Self](const UClass* SourceContextClass, bool bNotifyFieldValueChangedRequired, EMVVMBindingMode BindingMode, FName FieldToListenTo) -> TValueOrError<FCompiledBindingLibraryCompiler::FFieldIdHandle, FText>
|
|
{
|
|
if (!IsOneTimeBinding(BindingMode) && bNotifyFieldValueChangedRequired)
|
|
{
|
|
return Self->BindingLibraryCompiler.AddFieldId(SourceContextClass, FieldToListenTo);
|
|
}
|
|
return MakeValue(FCompiledBindingLibraryCompiler::FFieldIdHandle());
|
|
};
|
|
|
|
if (!TestExecutionMode(Binding))
|
|
{
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
|
|
const bool bIsComplexBinding = BindingSourceContext.ComplexConversionFunctionContextIndex != INDEX_NONE;
|
|
bool bFieldIdNeeded = !IsOneTimeBinding(Binding.BindingType);
|
|
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FFieldIdHandle, FText> AddFieldResult = AddFieldId(BindingSourceContext.SourceClass, true, Binding.BindingType, BindingSourceContext.FieldId.GetFieldName());
|
|
if (AddFieldResult.HasError())
|
|
{
|
|
bFieldIdNeeded = false;
|
|
// For complex binding, at least one must be true
|
|
if (!bIsComplexBinding)
|
|
{
|
|
AddMessageForBinding(Binding, BlueprintView, FText::Format(LOCTEXT("CouldNotCreateSource", "Could not create source. {0}"),
|
|
AddFieldResult.GetError()), EBindingMessageType::Error);
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
else if (bIsComplexBinding)
|
|
{
|
|
if (ensure(ComplexConversionFunctionContexts.IsValidIndex(BindingSourceContext.ComplexConversionFunctionContextIndex)))
|
|
{
|
|
ComplexConversionFunctionContexts[BindingSourceContext.ComplexConversionFunctionContextIndex].bHasValidFieldId = true;
|
|
}
|
|
}
|
|
|
|
TArray<UE::MVVM::FMVVMConstFieldVariant> SetterPath;
|
|
{
|
|
TValueOrError<TArray<UE::MVVM::FMVVMConstFieldVariant>, void> SetterPathResult = GetSetterPath(Binding, BindingSourceContext.BindingIndex, BindingSourceContext.bIsForwardBinding);
|
|
if (SetterPathResult.HasError())
|
|
{
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
SetterPath = SetterPathResult.StealValue();
|
|
}
|
|
|
|
const UFunction* ConversionFunction = nullptr;
|
|
{
|
|
TValueOrError<const UFunction*, void> ConversionFunctionResult = GetConversionFunction(Binding, BindingSourceContext.bIsForwardBinding);
|
|
if (ConversionFunctionResult.HasError())
|
|
{
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
ConversionFunction = ConversionFunctionResult.StealValue();
|
|
}
|
|
|
|
TValueOrError<FCompiledBinding, FText> AddBindingResult = CreateCompiledBinding(Class, BindingSourceContext.PropertyPath, SetterPath, ConversionFunction, bIsComplexBinding);
|
|
if (AddBindingResult.HasError())
|
|
{
|
|
AddMessageForBinding(Binding, BlueprintView,
|
|
FText::Format(LOCTEXT("CouldNotCreateBinding", "Could not create binding. {0}"), AddBindingResult.GetError()),
|
|
EBindingMessageType::Error
|
|
);
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
|
|
FCompilerBinding NewBinding;
|
|
NewBinding.BindingIndex = BindingSourceContext.BindingIndex;
|
|
NewBinding.UserWidgetPropertyContextIndex = BindingSourceContext.UserWidgetPropertyContextIndex;
|
|
NewBinding.SourceCreatorContextIndex = BindingSourceContext.SourceCreatorContextIndex;
|
|
NewBinding.ComplexConversionFunctionContextIndex = BindingSourceContext.ComplexConversionFunctionContextIndex;
|
|
NewBinding.bSourceIsUserWidget = BindingSourceContext.bIsRootWidget;
|
|
NewBinding.bFieldIdNeeded = bFieldIdNeeded;
|
|
NewBinding.bIsForwardBinding = BindingSourceContext.bIsForwardBinding;
|
|
|
|
NewBinding.CompiledBinding = AddBindingResult.StealValue();
|
|
if (AddFieldResult.HasValue())
|
|
{
|
|
NewBinding.FieldIdHandle = AddFieldResult.StealValue();
|
|
}
|
|
|
|
CompilerBindings.Emplace(NewBinding);
|
|
}
|
|
|
|
|
|
for (const FSimpleBindingContext& BindingContext : SimpleBindingContexts)
|
|
{
|
|
FMVVMBlueprintViewBinding* BindingPtr = BlueprintView->GetBindingAt(BindingContext.BindingIndex);
|
|
check(BindingPtr);
|
|
FMVVMBlueprintViewBinding& Binding = *BindingPtr;
|
|
|
|
if (!TestExecutionMode(Binding))
|
|
{
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
|
|
if (!IsOneTimeBinding(Binding.BindingType))
|
|
{
|
|
AddMessageForBinding(Binding, BlueprintView, LOCTEXT("FunctionRequiresOneTimeBinding", "There is no source. The binding must be a OneTime binding."), EBindingMessageType::Error);
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
|
|
TArray<UE::MVVM::FMVVMConstFieldVariant> SetterPath;
|
|
{
|
|
TValueOrError<TArray<UE::MVVM::FMVVMConstFieldVariant>, void> SetterPathResult = GetSetterPath(Binding, BindingContext.BindingIndex, BindingContext.bIsForwardBinding);
|
|
if (SetterPathResult.HasError())
|
|
{
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
SetterPath = SetterPathResult.StealValue();
|
|
}
|
|
|
|
const UFunction* ConversionFunction = nullptr;
|
|
{
|
|
TValueOrError<const UFunction*, void> ConversionFunctionResult = GetConversionFunction(Binding, BindingContext.bIsForwardBinding);
|
|
if (ConversionFunctionResult.HasError())
|
|
{
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
ConversionFunction = ConversionFunctionResult.StealValue();
|
|
}
|
|
|
|
TArrayView<const UE::MVVM::FMVVMConstFieldVariant> GetterFields;
|
|
const bool bIsComplexBinding = BindingContext.ComplexConversionFunctionContextIndex != INDEX_NONE;
|
|
TValueOrError<FCompiledBinding, FText> AddBindingResult = CreateCompiledBinding(Class, GetterFields, SetterPath, ConversionFunction, bIsComplexBinding);
|
|
if (AddBindingResult.HasError())
|
|
{
|
|
AddMessageForBinding(Binding, BlueprintView,
|
|
FText::Format(LOCTEXT("CouldNotCreateBinding", "Could not create binding. {0}"), AddBindingResult.GetError()),
|
|
EBindingMessageType::Error
|
|
);
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
|
|
FCompilerBinding NewBinding;
|
|
NewBinding.BindingIndex = BindingContext.BindingIndex;
|
|
NewBinding.bFieldIdNeeded = !IsOneTimeBinding(Binding.BindingType);
|
|
NewBinding.bIsForwardBinding = BindingContext.bIsForwardBinding;
|
|
NewBinding.bNeedsValidSource = false;
|
|
|
|
NewBinding.CompiledBinding = AddBindingResult.StealValue();
|
|
|
|
CompilerBindings.Emplace(NewBinding);
|
|
}
|
|
|
|
if (bAreBindingsValid)
|
|
{
|
|
// Confirm that at least one source is valid for each pin of a complex conversion function.
|
|
for (const FComplexConversionFunctionContext& ComplexConversionFunctionContext : ComplexConversionFunctionContexts)
|
|
{
|
|
if (!ComplexConversionFunctionContext.bHasValidFieldId && ComplexConversionFunctionContext.bNeedsValidSource)
|
|
{
|
|
FMVVMBlueprintViewBinding* BindingPtr = BlueprintView->GetBindingAt(ComplexConversionFunctionContext.BindingIndex);
|
|
check(BindingPtr);
|
|
AddMessageForBinding(*BindingPtr, BlueprintView, LOCTEXT("CouldNotCreateSourceForConversionFunction", "There is no source. The binding must be a OneTime binding."), EBindingMessageType::Error);
|
|
bAreBindingsValid = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bAreBindingsValid;
|
|
}
|
|
|
|
|
|
bool FMVVMViewBlueprintCompiler::CompileBindings(const FCompiledBindingLibraryCompiler::FCompileResult& CompileResult, UWidgetBlueprintGeneratedClass* Class, UMVVMBlueprintView* BlueprintView, UMVVMViewClass* ViewExtension)
|
|
{
|
|
if (!bAreBindingsValid)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
struct FLocal
|
|
{
|
|
FLocal()
|
|
{
|
|
IConsoleVariable* CVarDefaultExecutionMode = IConsoleManager::Get().FindConsoleVariable(TEXT("MVVM.DefaultExecutionMode"));
|
|
if (ensure(CVarDefaultExecutionMode))
|
|
{
|
|
DefaultMode = (EMVVMExecutionMode)CVarDefaultExecutionMode->GetInt();
|
|
}
|
|
}
|
|
EMVVMExecutionMode DefaultMode = EMVVMExecutionMode::DelayedWhenSharedElseImmediate;
|
|
};
|
|
static FLocal LocalDefaultMode;
|
|
|
|
// Store bindings with corresponding ComplexConversionFunctionContextIndex to be sorted and mark the last binding
|
|
// in the complex conversion as bExecuteAtInitialization == true
|
|
TArray<TPair<int32, FMVVMViewClass_CompiledBinding>> TempBindingPairs;
|
|
TempBindingPairs.Reserve(CompilerBindings.Num());
|
|
|
|
for (const FCompilerBinding& CompileBinding : CompilerBindings)
|
|
{
|
|
// PropertyBinding needs a valid CompileBinding.BindingIndex.
|
|
check(CompileBinding.Type != ECompilerBindingType::PropertyBinding || (CompileBinding.BindingIndex >= 0 && CompileBinding.BindingIndex < BlueprintView->GetNumBindings()));
|
|
FMVVMBlueprintViewBinding* ViewBinding = CompileBinding.Type == ECompilerBindingType::PropertyBinding
|
|
? BlueprintView->GetBindingAt(CompileBinding.BindingIndex)
|
|
: nullptr;
|
|
|
|
|
|
FMVVMViewClass_CompiledBinding NewBinding;
|
|
const bool bIsSourceSelf = CompileBinding.bSourceIsUserWidget;
|
|
if (!bIsSourceSelf)
|
|
{
|
|
if (CompilerUserWidgetPropertyContexts.IsValidIndex(CompileBinding.UserWidgetPropertyContextIndex))
|
|
{
|
|
NewBinding.SourcePropertyName = CompilerUserWidgetPropertyContexts[CompileBinding.UserWidgetPropertyContextIndex].PropertyName;
|
|
}
|
|
else if (CompilerSourceCreatorContexts.IsValidIndex(CompileBinding.SourceCreatorContextIndex))
|
|
{
|
|
NewBinding.SourcePropertyName = CompilerSourceCreatorContexts[CompileBinding.SourceCreatorContextIndex].ViewModelContext.GetViewModelName();
|
|
}
|
|
else if (CompileBinding.bNeedsValidSource)
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*LOCTEXT("InvalidSourceInternal", "Internal error. The binding doesn't have a valid source.").ToString());
|
|
bAreBindingsValid = false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
auto AddErrorMessage = [this, ViewBinding, BlueprintView](const FText& ErrorMessage)
|
|
{
|
|
if (ViewBinding)
|
|
{
|
|
AddMessageForBinding(*ViewBinding, BlueprintView, ErrorMessage, EBindingMessageType::Error);
|
|
}
|
|
else
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*ErrorMessage.ToString());
|
|
}
|
|
};
|
|
|
|
const FMVVMVCompiledFieldId* CompiledFieldId = CompileResult.FieldIds.Find(CompileBinding.FieldIdHandle);
|
|
if (CompiledFieldId == nullptr && CompileBinding.bFieldIdNeeded)
|
|
{
|
|
AddErrorMessage(FText::Format(LOCTEXT("FieldIdNotGenerated", "Could not generate field ID for property '{0}'."), FText::FromName(NewBinding.SourcePropertyName)));
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
|
|
const FMVVMVCompiledBinding* CompiledBinding = CompileResult.Bindings.Find(CompileBinding.CompiledBinding.BindingHandle);
|
|
if (CompiledBinding == nullptr && CompileBinding.Type != ECompilerBindingType::ViewModelDynamic)
|
|
{
|
|
AddErrorMessage(LOCTEXT("CompiledBindingNotGenerated", "Could not generate compiled binding."));
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
|
|
bool bIsOptional = false;
|
|
if (!bIsSourceSelf)
|
|
{
|
|
const FMVVMBlueprintViewModelContext* ViewModelContext = nullptr;
|
|
if (CompilerUserWidgetPropertyContexts.IsValidIndex(CompileBinding.UserWidgetPropertyContextIndex))
|
|
{
|
|
if (CompilerUserWidgetPropertyContexts[CompileBinding.UserWidgetPropertyContextIndex].ViewModelId.IsValid())
|
|
{
|
|
ViewModelContext = BlueprintView->FindViewModel(CompilerUserWidgetPropertyContexts[CompileBinding.UserWidgetPropertyContextIndex].ViewModelId);
|
|
if (ViewModelContext == nullptr)
|
|
{
|
|
AddErrorMessage(LOCTEXT("CompiledBindingWithInvalidIVewModelId", "Internal error: the viewmodel became invalid."));
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
else if (CompilerSourceCreatorContexts.IsValidIndex(CompileBinding.SourceCreatorContextIndex))
|
|
{
|
|
ViewModelContext = &CompilerSourceCreatorContexts[CompileBinding.SourceCreatorContextIndex].ViewModelContext;
|
|
}
|
|
|
|
if (ViewModelContext)
|
|
{
|
|
bIsOptional = ViewModelContext->bOptional || ViewModelContext->CreationType == EMVVMBlueprintViewModelContextCreationType::Manual;
|
|
}
|
|
}
|
|
|
|
if (CompileBinding.Type == ECompilerBindingType::ViewModelDynamic)
|
|
{
|
|
int32 FoundSourceCreatorIndex = ViewExtension->SourceCreators.IndexOfByPredicate([LookFor = CompileBinding.DynamicViewModelName](const FMVVMViewClass_SourceCreator& Other)
|
|
{
|
|
return Other.GetSourceName() == LookFor;
|
|
});
|
|
|
|
check(FoundSourceCreatorIndex < std::numeric_limits<int8>::max()); // the index is saved as a int8
|
|
NewBinding.EvaluateSourceCreatorIndex = FoundSourceCreatorIndex;
|
|
if (NewBinding.EvaluateSourceCreatorIndex == INDEX_NONE)
|
|
{
|
|
FText ErrorMessage = FText::Format(LOCTEXT("CompiledViewModelDynamicBindingNotGenerated", "Was not able to find the source for {0}."), FText::FromName(CompileBinding.DynamicViewModelName));
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*ErrorMessage.ToString());
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
FText WrongBindingTypeInternalErrorMessage = LOCTEXT("WrongBindingTypeInternalError", "Internal Error. Wrong binding type.");
|
|
// FieldId can be null if it's a one time binding
|
|
NewBinding.FieldId = CompiledFieldId ? *CompiledFieldId : FMVVMVCompiledFieldId();
|
|
NewBinding.Binding = CompiledBinding ? *CompiledBinding : FMVVMVCompiledBinding();
|
|
NewBinding.Flags = 0;
|
|
|
|
int32 ComplexConversionFunctionContextIndex = INDEX_NONE;
|
|
|
|
if (ViewBinding)
|
|
{
|
|
if (CompileBinding.Type != ECompilerBindingType::PropertyBinding)
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*WrongBindingTypeInternalErrorMessage.ToString());
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
|
|
// If it is forward binding and simple conversion, mark it as bExecuteAtInitialization == true
|
|
bool bExecuteAtInitialization = CompileBinding.bIsForwardBinding && CompileBinding.ComplexConversionFunctionContextIndex == INDEX_NONE;
|
|
|
|
// If it is forward binding and complex conversion, store ComplexConversionFunctionContextIndex to be handled later in this function.
|
|
if (CompileBinding.bIsForwardBinding && CompileBinding.ComplexConversionFunctionContextIndex != INDEX_NONE)
|
|
{
|
|
// Only forward binding with complex conversion will have a valid ComplexConversionFunctionContextIndex
|
|
ComplexConversionFunctionContextIndex = CompileBinding.ComplexConversionFunctionContextIndex;
|
|
}
|
|
|
|
NewBinding.ExecutionMode = ViewBinding->bOverrideExecutionMode ? ViewBinding->OverrideExecutionMode : LocalDefaultMode.DefaultMode;
|
|
NewBinding.EditorId = ViewBinding->BindingId;
|
|
|
|
NewBinding.Flags |= (ViewBinding->bEnabled) ? FMVVMViewClass_CompiledBinding::EBindingFlags::EnabledByDefault : 0;
|
|
NewBinding.Flags |= (bExecuteAtInitialization) ? FMVVMViewClass_CompiledBinding::EBindingFlags::ExecuteAtInitialization : 0;
|
|
NewBinding.Flags |= (IsOneTimeBinding(ViewBinding->BindingType)) ? FMVVMViewClass_CompiledBinding::EBindingFlags::OneTime : 0;
|
|
NewBinding.Flags |= (bIsOptional) ? FMVVMViewClass_CompiledBinding::EBindingFlags::ViewModelOptional : 0;
|
|
NewBinding.Flags |= (ViewBinding->bOverrideExecutionMode) ? FMVVMViewClass_CompiledBinding::EBindingFlags::OverrideExecuteMode : 0;
|
|
NewBinding.Flags |= (bIsSourceSelf) ? FMVVMViewClass_CompiledBinding::EBindingFlags::SourceObjectIsSelf : 0;
|
|
}
|
|
else
|
|
{
|
|
if (CompileBinding.Type != ECompilerBindingType::ViewModelDynamic)
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*WrongBindingTypeInternalErrorMessage.ToString());
|
|
bAreBindingsValid = false;
|
|
continue;
|
|
}
|
|
NewBinding.ExecutionMode = EMVVMExecutionMode::Immediate;
|
|
NewBinding.EditorId = FGuid();
|
|
|
|
NewBinding.Flags |= FMVVMViewClass_CompiledBinding::EBindingFlags::EnabledByDefault;
|
|
NewBinding.Flags |= (bIsOptional) ? FMVVMViewClass_CompiledBinding::EBindingFlags::ViewModelOptional : 0;
|
|
NewBinding.Flags |= FMVVMViewClass_CompiledBinding::EBindingFlags::OverrideExecuteMode; // The mode needs to be Immediate.
|
|
NewBinding.Flags |= (bIsSourceSelf) ? FMVVMViewClass_CompiledBinding::EBindingFlags::SourceObjectIsSelf : 0;
|
|
}
|
|
|
|
TempBindingPairs.Emplace(ComplexConversionFunctionContextIndex, MoveTemp(NewBinding));
|
|
}
|
|
|
|
if (ViewExtension->SourceCreators.Num() > 1)
|
|
{
|
|
TMap<FName, int32> SourceCreatorSortOrder;
|
|
SourceCreatorSortOrder.Reserve(ViewExtension->SourceCreators.Num());
|
|
for (int32 Index = 0; Index < ViewExtension->SourceCreators.Num(); ++Index)
|
|
{
|
|
SourceCreatorSortOrder.Add(ViewExtension->SourceCreators[Index].GetSourceName(), Index);
|
|
}
|
|
|
|
TempBindingPairs.StableSort([&SourceCreatorSortOrder](const TPair<int32, FMVVMViewClass_CompiledBinding>& PairA, const TPair<int32, FMVVMViewClass_CompiledBinding>& PairB)
|
|
{
|
|
const FMVVMViewClass_CompiledBinding& A = PairA.Value;
|
|
const FMVVMViewClass_CompiledBinding& B = PairB.Value;
|
|
|
|
int32* FoundA = SourceCreatorSortOrder.Find(A.GetSourceName());
|
|
int32* FoundB = SourceCreatorSortOrder.Find(B.GetSourceName());
|
|
int32 AIndex = FoundA ? *FoundA : -1;
|
|
int32 BIndex = FoundB ? *FoundB : -1;
|
|
|
|
if (AIndex == BIndex)
|
|
{
|
|
if (A.GetEvaluateSourceCreatorBindingIndex() == B.GetEvaluateSourceCreatorBindingIndex())
|
|
{
|
|
return A.GetSourceName().LexicalLess(B.GetSourceName());
|
|
}
|
|
|
|
return A.GetEvaluateSourceCreatorBindingIndex() < B.GetEvaluateSourceCreatorBindingIndex();
|
|
}
|
|
|
|
return AIndex < BIndex;
|
|
});
|
|
}
|
|
|
|
// Go backward to be able to mark only the last binding in the complex conversion.
|
|
for (int32 Index = TempBindingPairs.Num() - 1; Index >= 0; --Index)
|
|
{
|
|
FMVVMViewClass_CompiledBinding& Binding = TempBindingPairs[Index].Value;
|
|
const int32 ComplexConversionFunctionContextIndex = TempBindingPairs[Index].Key;
|
|
|
|
// Only handle bindings with complex conversions.
|
|
if (ComplexConversionFunctionContextIndex != INDEX_NONE)
|
|
{
|
|
// Mark the complex conversion as bExecAtInitGenerated.
|
|
if (!ComplexConversionFunctionContexts[ComplexConversionFunctionContextIndex].bExecAtInitGenerated)
|
|
{
|
|
ComplexConversionFunctionContexts[ComplexConversionFunctionContextIndex].bExecAtInitGenerated = true;
|
|
|
|
// Mark the last binding with the complex conversion.
|
|
Binding.Flags |= FMVVMViewClass_CompiledBinding::EBindingFlags::ExecuteAtInitialization;
|
|
}
|
|
}
|
|
}
|
|
|
|
ViewExtension->CompiledBindings.Reserve(TempBindingPairs.Num());
|
|
for (TPair<int32, FMVVMViewClass_CompiledBinding>& BindingPair : TempBindingPairs)
|
|
{
|
|
ViewExtension->CompiledBindings.Emplace(MoveTemp(BindingPair.Value));
|
|
}
|
|
|
|
return bAreBindingsValid;
|
|
}
|
|
|
|
bool FMVVMViewBlueprintCompiler::PreCompileEvents(UWidgetBlueprintGeneratedClass* Class, UMVVMBlueprintView* BlueprintView)
|
|
{
|
|
if (!bAreSourceContextsValid || !bAreEventsValid)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!GetDefault<UMVVMDeveloperProjectSettings>()->bAllowBindingEvent && BlueprintView->GetEvents().Num() > 0)
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Warning(*LOCTEXT("EventsAreNotAllowed", "Binding events are not allowed in your project settings.").ToString());
|
|
}
|
|
|
|
for (UMVVMBlueprintViewEvent* EventPtr : BlueprintView->GetEvents())
|
|
{
|
|
if (EventPtr == nullptr || !EventPtr->bCompile)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
UEdGraph* GeneratedGraph = EventPtr->GetOrCreateWrapperGraph();
|
|
if (GeneratedGraph == nullptr)
|
|
{
|
|
AddMessageForEvent(EventPtr
|
|
, FText::Format(LOCTEXT("InvalidEventGeneratedGraph", "The generated graph for event {0} is invalid."), EventPtr->GetDisplayName(true))
|
|
, UMVVMBlueprintViewEvent::EMessageType::Error
|
|
, FName());
|
|
bAreEventsValid = false;
|
|
continue;
|
|
}
|
|
|
|
auto TestPath = [Self = this, BlueprintView, Class](UMVVMBlueprintViewEvent* EventPtr, const FMVVMBlueprintPropertyPath& PropertyPath, FName ArgumentName)
|
|
{
|
|
TArray<UE::MVVM::FMVVMConstFieldVariant> GeneratedPath = Self->CreateBindingDestinationPath(BlueprintView, Class, PropertyPath);
|
|
if (!Self->IsPropertyPathValid(Self->WidgetBlueprintCompilerContext.WidgetBlueprint(), GeneratedPath))
|
|
{
|
|
Self->AddMessageForEvent(EventPtr
|
|
, FText::Format(Private::PropertyPathIsInvalidFormat, PropertyPathToText(Class, BlueprintView, PropertyPath))
|
|
, UMVVMBlueprintViewEvent::EMessageType::Error
|
|
, ArgumentName);
|
|
Self->bAreEventsValid = false;
|
|
GeneratedPath.Empty();
|
|
}
|
|
return GeneratedPath;
|
|
};
|
|
|
|
// Does it resolve and are the field allowed
|
|
TArray<UE::MVVM::FMVVMConstFieldVariant> EventPath = TestPath(EventPtr, EventPtr->GetEventPath(), FName());
|
|
if (EventPath.Num() == 0)
|
|
{
|
|
continue;
|
|
}
|
|
if (TestPath(EventPtr, EventPtr->GetDestinationPath(), FName()).Num() == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (const FMVVMBlueprintPin& Pin : EventPtr->GetPins())
|
|
{
|
|
if (Pin.UsedPathAsValue())
|
|
{
|
|
if (TestPath(EventPtr, Pin.GetPath(), Pin.GetName()).Num() == 0)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Does EventPath resolves to a MulticastDelegateProperty
|
|
const FMulticastDelegateProperty* DelegateProperty = EventPath.Last().IsProperty() ? CastField<const FMulticastDelegateProperty>(EventPath.Last().GetProperty()) : nullptr;
|
|
if (DelegateProperty == nullptr)
|
|
{
|
|
AddMessageForEvent(EventPtr
|
|
, FText::Format(LOCTEXT("EventPathIsNotMulticastDelegate", "The event {0} is not a multicast delegate."), PropertyPathToText(Class, BlueprintView, EventPtr->GetEventPath()))
|
|
, UMVVMBlueprintViewEvent::EMessageType::Error
|
|
, FName());
|
|
bAreEventsValid = false;
|
|
continue;
|
|
}
|
|
|
|
// Does it still have the same signature
|
|
if (!UE::MVVM::FunctionGraphHelper::IsFunctionEntryMatchSignature(GeneratedGraph, DelegateProperty->SignatureFunction))
|
|
{
|
|
AddMessageForEvent(EventPtr
|
|
, FText::Format(LOCTEXT("EventPathFunctionSignatureError", "The event {0} doesn't match the function signature."), DelegateProperty->GetDisplayNameText())
|
|
, UMVVMBlueprintViewEvent::EMessageType::Error
|
|
, FName());
|
|
bAreEventsValid = false;
|
|
continue;
|
|
}
|
|
|
|
// Generate the FieldPath to get the delegate property at runtime
|
|
TValueOrError<TArray<FMVVMConstFieldVariant>, FText> GeneratedEventFields = FieldPathHelper::GenerateFieldPathList(EventPath, true);
|
|
if (GeneratedEventFields.HasError())
|
|
{
|
|
AddMessageForEvent(EventPtr
|
|
, FText::Format(Private::CouldNotCreateSourceFieldPathFormat, ::UE::MVVM::FieldPathHelper::ToText(EventPath), GeneratedEventFields.GetError())
|
|
, UMVVMBlueprintViewEvent::EMessageType::Error
|
|
, FName());
|
|
bAreEventsValid = false;
|
|
continue;
|
|
}
|
|
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FFieldPathHandle, FText> FieldPathResult = BindingLibraryCompiler.AddFieldPath(GeneratedEventFields.GetValue(), true);
|
|
if (FieldPathResult.HasError())
|
|
{
|
|
AddMessageForEvent(EventPtr
|
|
, FText::Format(Private::CouldNotCreateSourceFieldPathFormat, ::UE::MVVM::FieldPathHelper::ToText(EventPath), FieldPathResult.GetError())
|
|
, UMVVMBlueprintViewEvent::EMessageType::Error
|
|
, FName());
|
|
bAreEventsValid = false;
|
|
continue;
|
|
}
|
|
|
|
FName SourceName;
|
|
switch (EventPtr->GetEventPath().GetSource(WidgetBlueprintCompilerContext.WidgetBlueprint()))
|
|
{
|
|
case EMVVMBlueprintFieldPathSource::SelfContext:
|
|
SourceName = WidgetBlueprintCompilerContext.WidgetBlueprint()->GetFName();
|
|
break;
|
|
case EMVVMBlueprintFieldPathSource::Widget:
|
|
{
|
|
FName WidgetName = EventPtr->GetEventPath().GetWidgetName();
|
|
checkf(!WidgetName.IsNone(), TEXT("The destination should have been checked and set bAreSourceContextsValid."));
|
|
const bool bSourceIsUserWidget = WidgetName == Class->ClassGeneratedBy->GetFName();
|
|
ensure(!bSourceIsUserWidget);
|
|
|
|
const int32 VariableContextIndex = CompilerUserWidgetPropertyContexts.IndexOfByPredicate([WidgetName](const FCompilerUserWidgetPropertyContext& Other) { return Other.PropertyName == WidgetName; });
|
|
if (ensureAlwaysMsgf(VariableContextIndex != INDEX_NONE, TEXT("Could not find source context for destination '%s'"), *WidgetName.ToString()))
|
|
{
|
|
SourceName = CompilerUserWidgetPropertyContexts[VariableContextIndex].PropertyName;
|
|
}
|
|
break;
|
|
}
|
|
case EMVVMBlueprintFieldPathSource::ViewModel:
|
|
{
|
|
const FMVVMBlueprintViewModelContext* SourceViewModelContext = BlueprintView->FindViewModel(EventPtr->GetEventPath().GetViewModelId());
|
|
check(SourceViewModelContext);
|
|
FName ViewModelName = SourceViewModelContext->GetViewModelName();
|
|
const int32 DestinationVariableContextIndex = CompilerUserWidgetPropertyContexts.IndexOfByPredicate([ViewModelName](const FCompilerUserWidgetPropertyContext& Other) { return Other.PropertyName == ViewModelName; });
|
|
check(DestinationVariableContextIndex != INDEX_NONE);
|
|
SourceName = CompilerUserWidgetPropertyContexts[DestinationVariableContextIndex].PropertyName;
|
|
break;
|
|
}
|
|
default:
|
|
ensureAlwaysMsgf(false, TEXT("An EMVVMBlueprintFieldPathSource case was not checked."));
|
|
}
|
|
|
|
// No need to add the generated function to the field compiler.
|
|
//They are in the BP generated code.
|
|
|
|
FEventDelegateContext& NewContext = EventDelegateContexts.AddDefaulted_GetRef();
|
|
NewContext.BlueprintEvent = EventPtr;
|
|
NewContext.DelegateFieldPathHandle = FieldPathResult.StealValue();
|
|
NewContext.GeneratedGraphName = EventPtr->GetWrapperGraphName();
|
|
NewContext.SourceName = SourceName;
|
|
}
|
|
|
|
return bAreEventsValid;
|
|
}
|
|
|
|
|
|
bool FMVVMViewBlueprintCompiler::CompileEvents(const FCompiledBindingLibraryCompiler::FCompileResult& CompileResult, UWidgetBlueprintGeneratedClass* Class, UMVVMBlueprintView* BlueprintView, UMVVMViewClass* ViewExtension)
|
|
{
|
|
if (!bAreSourceContextsValid || !bAreEventsValid)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (FEventDelegateContext& Context : EventDelegateContexts)
|
|
{
|
|
check(Context.BlueprintEvent);
|
|
|
|
const FMVVMVCompiledFieldPath* CompiledFieldPath = CompileResult.FieldPaths.Find(Context.DelegateFieldPathHandle);
|
|
if (CompiledFieldPath == nullptr)
|
|
{
|
|
AddMessageForEvent(Context.BlueprintEvent, LOCTEXT("CompiledEventFieldPathNotGenerated", "Could not generate the event path."), UMVVMBlueprintViewEvent::EMessageType::Error, FName());
|
|
bAreEventsValid = false;
|
|
continue;
|
|
}
|
|
|
|
if (Context.GeneratedGraphName.IsNone() || Class->FindFunctionByName(Context.GeneratedGraphName) == nullptr)
|
|
{
|
|
AddMessageForEvent(Context.BlueprintEvent, LOCTEXT("CompiledEventFieldPathNotGenerated", "Could not generate the event path."), UMVVMBlueprintViewEvent::EMessageType::Error, FName());
|
|
bAreEventsValid = false;
|
|
continue;
|
|
}
|
|
|
|
FMVVMViewClass_CompiledEvent& NewBinding = ViewExtension->CompiledEvents.AddDefaulted_GetRef();
|
|
NewBinding.FieldPath = *CompiledFieldPath;
|
|
NewBinding.FunctionName = Context.GeneratedGraphName;
|
|
NewBinding.SourceName = Context.SourceName;
|
|
}
|
|
|
|
return bAreEventsValid;
|
|
}
|
|
|
|
|
|
TValueOrError<FMVVMViewBlueprintCompiler::FCompiledBinding, FText> FMVVMViewBlueprintCompiler::CreateCompiledBinding(const UWidgetBlueprintGeneratedClass* Class, TArrayView<const UE::MVVM::FMVVMConstFieldVariant> GetterFields, TArrayView<const UE::MVVM::FMVVMConstFieldVariant> SetterFields, const UFunction* ConversionFunction, bool bIsComplexBinding)
|
|
{
|
|
FCompiledBinding Result;
|
|
|
|
if (ConversionFunction != nullptr)
|
|
{
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FFieldPathHandle, FText> FieldPathResult = BindingLibraryCompiler.AddConversionFunctionFieldPath(Class, ConversionFunction);
|
|
if (FieldPathResult.HasError())
|
|
{
|
|
return MakeError(FText::Format(LOCTEXT("CouldNotCreateConversionFunctionFieldPath", "Couldn't create the conversion function field path '{0}'. {1}")
|
|
, FText::FromString(ConversionFunction->GetPathName())
|
|
, FieldPathResult.GetError()));
|
|
}
|
|
Result.ConversionFunction = FieldPathResult.StealValue();
|
|
Result.bIsConversionFunctionComplex = bIsComplexBinding;
|
|
|
|
// Sanity check
|
|
if (bIsComplexBinding && !BindingHelper::IsValidForComplexRuntimeConversion(ConversionFunction))
|
|
{
|
|
return MakeError(LOCTEXT("ConversionFunctionIsNotComplex", "Internal Error. The complex conversion function does not respect the prerequisite."));
|
|
}
|
|
}
|
|
|
|
if (!Result.bIsConversionFunctionComplex)
|
|
{
|
|
// Generate a path to read the value at runtime
|
|
TValueOrError<TArray<FMVVMConstFieldVariant>, FText> GeneratedField = FieldPathHelper::GenerateFieldPathList(GetterFields, true);
|
|
if (GeneratedField.HasError())
|
|
{
|
|
return MakeError(FText::Format(Private::CouldNotCreateSourceFieldPathFormat
|
|
, ::UE::MVVM::FieldPathHelper::ToText(GetterFields)
|
|
, GeneratedField.GetError()));
|
|
}
|
|
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FFieldPathHandle, FText> FieldPathResult = BindingLibraryCompiler.AddFieldPath(GeneratedField.GetValue(), true);
|
|
if (FieldPathResult.HasError())
|
|
{
|
|
return MakeError(FText::Format(Private::CouldNotCreateSourceFieldPathFormat
|
|
, ::UE::MVVM::FieldPathHelper::ToText(GetterFields)
|
|
, FieldPathResult.GetError()));
|
|
}
|
|
Result.SourceRead = FieldPathResult.StealValue();
|
|
}
|
|
|
|
{
|
|
static const FText CouldNotCreateDestinationFieldPathFormat = LOCTEXT("CouldNotCreateDestinationFieldPath", "Couldn't create the destination field path '{0}'. {1}");
|
|
|
|
TValueOrError<TArray<FMVVMConstFieldVariant>, FText> GeneratedField = FieldPathHelper::GenerateFieldPathList(SetterFields, false);
|
|
if (GeneratedField.HasError())
|
|
{
|
|
return MakeError(FText::Format(CouldNotCreateDestinationFieldPathFormat
|
|
, ::UE::MVVM::FieldPathHelper::ToText(SetterFields)
|
|
, GeneratedField.GetError()));
|
|
}
|
|
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FFieldPathHandle, FText> FieldPathResult = BindingLibraryCompiler.AddFieldPath(GeneratedField.GetValue(), false);
|
|
if (FieldPathResult.HasError())
|
|
{
|
|
return MakeError(FText::Format(CouldNotCreateDestinationFieldPathFormat
|
|
, ::UE::MVVM::FieldPathHelper::ToText(SetterFields)
|
|
, FieldPathResult.GetError()));
|
|
}
|
|
Result.DestinationWrite = FieldPathResult.StealValue();
|
|
}
|
|
|
|
// Generate the binding
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FBindingHandle, FText> BindingResult = Result.bIsConversionFunctionComplex
|
|
? BindingLibraryCompiler.AddComplexBinding(Result.DestinationWrite, Result.ConversionFunction)
|
|
: BindingLibraryCompiler.AddBinding(Result.SourceRead, Result.DestinationWrite, Result.ConversionFunction);
|
|
if (BindingResult.HasError())
|
|
{
|
|
return MakeError(BindingResult.StealError());
|
|
}
|
|
Result.BindingHandle = BindingResult.StealValue();
|
|
|
|
return MakeValue(Result);
|
|
}
|
|
|
|
|
|
const FMVVMViewBlueprintCompiler::FCompilerSourceCreatorContext* FMVVMViewBlueprintCompiler::FindViewModelSource(FGuid Id) const
|
|
{
|
|
return CompilerSourceCreatorContexts.FindByPredicate([Id](const FCompilerSourceCreatorContext& Other)
|
|
{
|
|
return Other.Type == ECompilerSourceCreatorType::ViewModel ? Other.ViewModelContext.GetViewModelId() == Id : false;
|
|
});
|
|
}
|
|
|
|
|
|
TValueOrError<FMVVMViewBlueprintCompiler::FBindingSourceContext, FText> FMVVMViewBlueprintCompiler::CreateBindingSourceContext(const UMVVMBlueprintView* BlueprintView, const UWidgetBlueprintGeneratedClass* Class, const FMVVMBlueprintPropertyPath& PropertyPath, bool bIsOneTimeBinding)
|
|
{
|
|
if (!PropertyPath.IsValid())
|
|
{
|
|
ensureAlways(false);
|
|
return MakeError(LOCTEXT("EmptyPropertyPath", "Empty property path found. This is ilegal."));
|
|
}
|
|
|
|
FBindingSourceContext Result;
|
|
switch (PropertyPath.GetSource(WidgetBlueprintCompilerContext.WidgetBlueprint()))
|
|
{
|
|
case EMVVMBlueprintFieldPathSource::ViewModel:
|
|
{
|
|
Result.bIsRootWidget = false;
|
|
|
|
const FMVVMBlueprintViewModelContext* SourceViewModelContext = BlueprintView->FindViewModel(PropertyPath.GetViewModelId());
|
|
check(SourceViewModelContext);
|
|
const FName SourceName = SourceViewModelContext->GetViewModelName();
|
|
Result.UserWidgetPropertyContextIndex = CompilerUserWidgetPropertyContexts.IndexOfByPredicate([SourceName](const FCompilerUserWidgetPropertyContext& Other) { return Other.PropertyName == SourceName; });
|
|
check(Result.UserWidgetPropertyContextIndex != INDEX_NONE);
|
|
|
|
Result.SourceClass = CompilerUserWidgetPropertyContexts[Result.UserWidgetPropertyContextIndex].Class;
|
|
Result.PropertyPath = CreatePropertyPath(Class, CompilerUserWidgetPropertyContexts[Result.UserWidgetPropertyContextIndex].PropertyName, PropertyPath.GetFields(Class));
|
|
break;
|
|
}
|
|
|
|
case EMVVMBlueprintFieldPathSource::SelfContext:
|
|
{
|
|
Result.bIsRootWidget = true;
|
|
Result.UserWidgetPropertyContextIndex = INDEX_NONE;
|
|
Result.SourceClass = const_cast<UWidgetBlueprintGeneratedClass*>(Class);
|
|
Result.PropertyPath = CreatePropertyPath(Class, FName(), PropertyPath.GetFields(Class));
|
|
break;
|
|
}
|
|
case EMVVMBlueprintFieldPathSource::Widget:
|
|
{
|
|
Result.bIsRootWidget = false;
|
|
|
|
const FName SourceName = PropertyPath.GetWidgetName();
|
|
Result.UserWidgetPropertyContextIndex = CompilerUserWidgetPropertyContexts.IndexOfByPredicate([SourceName](const FCompilerUserWidgetPropertyContext& Other) { return Other.PropertyName == SourceName; });
|
|
if (!CompilerUserWidgetPropertyContexts.IsValidIndex(Result.UserWidgetPropertyContextIndex))
|
|
{
|
|
return MakeError(LOCTEXT("InvalidUserWidgetPropertyContextIndexInternal", "Internal error. UserWidgetPropertyContextIndex is invalid."));
|
|
}
|
|
|
|
Result.SourceClass = CompilerUserWidgetPropertyContexts[Result.UserWidgetPropertyContextIndex].Class;
|
|
Result.PropertyPath = CreatePropertyPath(Class, CompilerUserWidgetPropertyContexts[Result.UserWidgetPropertyContextIndex].PropertyName, PropertyPath.GetFields(Class));
|
|
break;
|
|
}
|
|
|
|
default:
|
|
ensureAlwaysMsgf(false, TEXT("Not supported yet."));
|
|
}
|
|
|
|
// The path may contains another INotifyFieldValueChanged
|
|
TValueOrError<FieldPathHelper::FParsedNotifyBindingInfo, FText> BindingInfoResult = FieldPathHelper::GetNotifyBindingInfoFromFieldPath(Class, Result.PropertyPath);
|
|
if (BindingInfoResult.HasError())
|
|
{
|
|
return MakeError(BindingInfoResult.StealError());
|
|
}
|
|
const FieldPathHelper::FParsedNotifyBindingInfo& BindingInfo = BindingInfoResult.GetValue();
|
|
Result.FieldId = BindingInfo.NotifyFieldId;
|
|
|
|
if (BindingInfo.ViewModelIndex < 1 && BindingInfo.NotifyFieldClass && Result.SourceClass)
|
|
{
|
|
if (!Result.SourceClass->IsChildOf(BindingInfo.NotifyFieldClass))
|
|
{
|
|
return MakeError(LOCTEXT("InvalidNotifyFieldClassInternal", "Internal error. The viewmodel class doesn't matches."));
|
|
}
|
|
}
|
|
|
|
// The INotifyFieldValueChanged/viewmodel is not the first and only INotifyFieldValueChanged/viewmodel property path.
|
|
//Create a new source in PropertyPath creator mode. Create a special binding to update the viewmodel when it changes.
|
|
//This binding (calling this function) will use the new source.
|
|
if (BindingInfo.ViewModelIndex >= 1 && !bIsOneTimeBinding)
|
|
{
|
|
if (!GetDefault<UMVVMDeveloperProjectSettings>()->bAllowLongSourcePath)
|
|
{
|
|
return MakeError(LOCTEXT("DynamicSourceEntryNotSupport", "Long source entry is not supported. Add the viewmodel manually."));
|
|
}
|
|
|
|
//NB. The for loop order is important. The order is used in UMVVMView::SetViewModelInternal when enabling the bindings
|
|
int32 SourceCreatorContextIndex = INDEX_NONE;
|
|
for (int32 DynamicIndex = 1; DynamicIndex <= BindingInfo.ViewModelIndex; ++DynamicIndex)
|
|
{
|
|
if (!Result.PropertyPath.IsValidIndex(DynamicIndex))
|
|
{
|
|
return MakeError(LOCTEXT("DynamicSourceEntryInternalIndex", "Internal error. The source index is not valid."));
|
|
}
|
|
|
|
FName NewSourceName;
|
|
FName ParentSourceName;
|
|
FString NewSourcePropertyPath;
|
|
{
|
|
TStringBuilder<512> PropertyPathBuilder;
|
|
TStringBuilder<512> DynamicNameBuilder;
|
|
for (int32 Index = 0; Index <= DynamicIndex; ++Index)
|
|
{
|
|
if (Index > 0)
|
|
{
|
|
PropertyPathBuilder << TEXT('.');
|
|
DynamicNameBuilder << TEXT('_');
|
|
}
|
|
PropertyPathBuilder << Result.PropertyPath[Index].GetName();
|
|
DynamicNameBuilder << Result.PropertyPath[Index].GetName();
|
|
|
|
if (Index == DynamicIndex - 1)
|
|
{
|
|
ParentSourceName = FName(DynamicNameBuilder.ToString());
|
|
}
|
|
}
|
|
|
|
NewSourceName = FName(DynamicNameBuilder.ToString());
|
|
NewSourcePropertyPath = PropertyPathBuilder.ToString();
|
|
}
|
|
|
|
// Did we already create the new source?
|
|
int32 PreviousSourceCreatorContextIndex = SourceCreatorContextIndex;
|
|
SourceCreatorContextIndex = CompilerSourceCreatorContexts.IndexOfByPredicate([NewSourceName](const FCompilerSourceCreatorContext& Other)
|
|
{
|
|
return Other.Type == ECompilerSourceCreatorType::ViewModelDynamic
|
|
&& Other.ViewModelContext.GetViewModelName() == NewSourceName;
|
|
});
|
|
if (SourceCreatorContextIndex == INDEX_NONE)
|
|
{
|
|
// Create the new source
|
|
{
|
|
const UClass* ViewModelClass = Cast<const UClass>(Result.PropertyPath[DynamicIndex+1].GetOwner());
|
|
if (!ViewModelClass)
|
|
{
|
|
return MakeError(FText::GetEmpty());
|
|
}
|
|
|
|
FCompilerSourceCreatorContext SourceCreatorContext;
|
|
SourceCreatorContext.Type = ECompilerSourceCreatorType::ViewModelDynamic;
|
|
SourceCreatorContext.DynamicParentSourceName = ParentSourceName;
|
|
SourceCreatorContext.ViewModelContext = FMVVMBlueprintViewModelContext(ViewModelClass, NewSourceName);
|
|
SourceCreatorContext.ViewModelContext.bCreateSetterFunction = false;
|
|
SourceCreatorContext.ViewModelContext.bOptional = false;
|
|
SourceCreatorContext.ViewModelContext.CreationType = EMVVMBlueprintViewModelContextCreationType::PropertyPath;
|
|
SourceCreatorContext.ViewModelContext.ViewModelPropertyPath = NewSourcePropertyPath;
|
|
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FFieldPathHandle, FText> ReadFieldPathResult = AddObjectFieldPath(BindingLibraryCompiler, Class, SourceCreatorContext.ViewModelContext.ViewModelPropertyPath, SourceCreatorContext.ViewModelContext.GetViewModelClass());
|
|
if (ReadFieldPathResult.HasError())
|
|
{
|
|
return MakeError(FText::Format(LOCTEXT("DynamicSourceEntryInvalidPath", "Internal error. {0}."), ReadFieldPathResult.StealError()));
|
|
}
|
|
SourceCreatorContext.ReadPropertyPath = ReadFieldPathResult.StealValue();
|
|
|
|
SourceCreatorContextIndex = CompilerSourceCreatorContexts.Add(MoveTemp(SourceCreatorContext));
|
|
}
|
|
|
|
// Create the binding to update the source when it changes.
|
|
{
|
|
FCompilerBinding NewCompilerBinding;
|
|
NewCompilerBinding.Type = ECompilerBindingType::ViewModelDynamic;
|
|
NewCompilerBinding.bSourceIsUserWidget = false;
|
|
NewCompilerBinding.bIsForwardBinding = true;
|
|
NewCompilerBinding.bFieldIdNeeded = true;
|
|
NewCompilerBinding.DynamicViewModelName = NewSourceName;
|
|
|
|
if (PreviousSourceCreatorContextIndex == INDEX_NONE)
|
|
{
|
|
NewCompilerBinding.UserWidgetPropertyContextIndex = Result.UserWidgetPropertyContextIndex;
|
|
}
|
|
else
|
|
{
|
|
NewCompilerBinding.SourceCreatorContextIndex = PreviousSourceCreatorContextIndex;
|
|
}
|
|
|
|
{
|
|
const UClass* OwnerClass = Cast<const UClass>(Result.PropertyPath[DynamicIndex].GetOwner());
|
|
if (!OwnerClass)
|
|
{
|
|
return MakeError(FText::GetEmpty());
|
|
}
|
|
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FFieldIdHandle, FText> AddFieldResult = BindingLibraryCompiler.AddFieldId(OwnerClass, Result.PropertyPath[DynamicIndex].GetName());
|
|
if (AddFieldResult.HasError())
|
|
{
|
|
return MakeError(AddFieldResult.StealError());
|
|
}
|
|
NewCompilerBinding.FieldIdHandle = AddFieldResult.StealValue();
|
|
}
|
|
|
|
CompilerBindings.Add(MoveTemp(NewCompilerBinding));
|
|
}
|
|
}
|
|
}
|
|
|
|
Result.SourceClass = BindingInfo.NotifyFieldClass;
|
|
Result.FieldId = BindingInfo.NotifyFieldId;
|
|
Result.UserWidgetPropertyContextIndex = INDEX_NONE;
|
|
Result.SourceCreatorContextIndex = SourceCreatorContextIndex;
|
|
Result.bIsRootWidget = false;
|
|
}
|
|
|
|
return MakeValue(Result);
|
|
}
|
|
|
|
|
|
TArray<FMVVMConstFieldVariant> FMVVMViewBlueprintCompiler::CreateBindingDestinationPath(const UMVVMBlueprintView* BlueprintView, const UWidgetBlueprintGeneratedClass* Class, const FMVVMBlueprintPropertyPath& PropertyPath) const
|
|
{
|
|
if (!PropertyPath.IsValid())
|
|
{
|
|
ensureAlwaysMsgf(false, TEXT("Empty property path found. This is legal."));
|
|
return TArray<FMVVMConstFieldVariant>();
|
|
}
|
|
|
|
switch (PropertyPath.GetSource(WidgetBlueprintCompilerContext.WidgetBlueprint()))
|
|
{
|
|
case EMVVMBlueprintFieldPathSource::ViewModel:
|
|
{
|
|
const FMVVMBlueprintViewModelContext* SourceViewModelContext = BlueprintView->FindViewModel(PropertyPath.GetViewModelId());
|
|
check(SourceViewModelContext);
|
|
FName DestinationName = SourceViewModelContext->GetViewModelName();
|
|
const int32 DestinationVariableContextIndex = CompilerUserWidgetPropertyContexts.IndexOfByPredicate([DestinationName](const FCompilerUserWidgetPropertyContext& Other) { return Other.PropertyName == DestinationName; });
|
|
check(DestinationVariableContextIndex != INDEX_NONE);
|
|
|
|
return CreatePropertyPath(Class, CompilerUserWidgetPropertyContexts[DestinationVariableContextIndex].PropertyName, PropertyPath.GetFields(Class));
|
|
}
|
|
case EMVVMBlueprintFieldPathSource::SelfContext:
|
|
{
|
|
return CreatePropertyPath(Class, FName(), PropertyPath.GetFields(Class));
|
|
}
|
|
case EMVVMBlueprintFieldPathSource::Widget:
|
|
{
|
|
FName DestinationName = PropertyPath.GetWidgetName();
|
|
checkf(!DestinationName.IsNone(), TEXT("The destination should have been checked and set bAreSourceContextsValid."));
|
|
const int32 DestinationVariableContextIndex = CompilerUserWidgetPropertyContexts.IndexOfByPredicate([DestinationName](const FCompilerUserWidgetPropertyContext& Other) { return Other.PropertyName == DestinationName; });
|
|
|
|
if (ensureAlwaysMsgf(DestinationVariableContextIndex != INDEX_NONE, TEXT("Could not find source context for destination '%s'"), *DestinationName.ToString()))
|
|
{
|
|
return CreatePropertyPath(Class, CompilerUserWidgetPropertyContexts[DestinationVariableContextIndex].PropertyName, PropertyPath.GetFields(Class));
|
|
}
|
|
return TArray<FMVVMConstFieldVariant>();
|
|
}
|
|
default:
|
|
ensureAlwaysMsgf(false, TEXT("Not supported yet."));
|
|
return CreatePropertyPath(Class, FName(), PropertyPath.GetFields(Class));
|
|
}
|
|
}
|
|
|
|
TArray<FMVVMConstFieldVariant> FMVVMViewBlueprintCompiler::CreatePropertyPath(const UClass* Class, FName PropertyName, TArray<FMVVMConstFieldVariant> Properties)
|
|
{
|
|
if (PropertyName.IsNone())
|
|
{
|
|
return Properties;
|
|
}
|
|
|
|
check(Class);
|
|
FMVVMConstFieldVariant NewProperty = BindingHelper::FindFieldByName(Class, FMVVMBindingName(PropertyName));
|
|
Properties.Insert(NewProperty, 0);
|
|
return Properties;
|
|
}
|
|
|
|
|
|
bool FMVVMViewBlueprintCompiler::IsPropertyPathValid(const UBlueprint* Context, TArrayView<const FMVVMConstFieldVariant> PropertyPath)
|
|
{
|
|
const UStruct* CurrentContainer = Context->GeneratedClass ? Context->GeneratedClass : Context->SkeletonGeneratedClass;
|
|
int32 PathLength = PropertyPath.Num();
|
|
for (int32 Index = 0; Index < PathLength; Index++)
|
|
{
|
|
const FMVVMConstFieldVariant& Field = PropertyPath[Index];
|
|
|
|
if (CurrentContainer == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
if (Field.IsEmpty())
|
|
{
|
|
return false;
|
|
}
|
|
if (Field.IsProperty())
|
|
{
|
|
if (Field.GetProperty() == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!GetDefault<UMVVMDeveloperProjectSettings>()->IsPropertyAllowed(Context, CurrentContainer, Field.GetProperty()))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
if (Field.IsFunction())
|
|
{
|
|
if (Field.GetFunction() == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
const UClass* CurrentContainerAsClass = Cast<const UClass>(CurrentContainer);
|
|
if (CurrentContainerAsClass == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
if (!GetDefault<UMVVMDeveloperProjectSettings>()->IsFunctionAllowed(Context, CurrentContainerAsClass, Field.GetFunction()))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
TValueOrError<const UStruct*, void> FieldAsContainerResult = UE::MVVM::FieldPathHelper::GetFieldAsContainer(Field);
|
|
CurrentContainer = FieldAsContainerResult.HasValue() ? FieldAsContainerResult.GetValue() : nullptr;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FMVVMViewBlueprintCompiler::CanBeSetInNative(TArrayView<const FMVVMConstFieldVariant> PropertyPath)
|
|
{
|
|
for (int32 Index = PropertyPath.Num() - 1; Index >= 0; --Index)
|
|
{
|
|
const FMVVMConstFieldVariant& Variant = PropertyPath[Index];
|
|
// Stop the algo if the path is already a function.
|
|
if (Variant.IsFunction())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// If the BP is defined in BP and has Net flags or FieldNotify flag, then the VaraibleSet K2Node need to be used to generate the proper byte-code.
|
|
if (Variant.IsProperty())
|
|
{
|
|
// If it's an object then the path before the object doesn't matter.
|
|
if (const FObjectPropertyBase* PropertyBase = CastField<const FObjectPropertyBase>(Variant.GetProperty()))
|
|
{
|
|
bool bLastPath = Index >= PropertyPath.Num() - 1;
|
|
if (!bLastPath)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (Cast<UBlueprintGeneratedClass>(Variant.GetProperty()->GetOwnerStruct()))
|
|
{
|
|
if (Variant.GetProperty()->HasMetaData(FName("FieldNotify")) || Variant.GetProperty()->HasAnyPropertyFlags(CPF_Net))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void FMVVMViewBlueprintCompiler::TestGenerateSetter(const UBlueprint* Context, FStringView ObjectName, FStringView FieldPath, FStringView FunctionName)
|
|
{
|
|
#if UE_BUILD_DEVELOPMENT || UE_BUILD_DEBUG
|
|
UWidgetBlueprint* WidgetBlueprint = nullptr;
|
|
{
|
|
UObject* FoundObject = FindObject<UObject>(nullptr, ObjectName.GetData(), false);
|
|
WidgetBlueprint = Cast<UWidgetBlueprint>(FoundObject);
|
|
}
|
|
|
|
if (WidgetBlueprint == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UWidgetBlueprintGeneratedClass* NewSkeletonClass = Cast<UWidgetBlueprintGeneratedClass>(WidgetBlueprint->SkeletonGeneratedClass);
|
|
TValueOrError<TArray<FMVVMConstFieldVariant>, FText> SkeletalSetterPathResult = FieldPathHelper::GenerateFieldPathList(NewSkeletonClass, FieldPath, false);
|
|
if (SkeletalSetterPathResult.HasError())
|
|
{
|
|
return;
|
|
}
|
|
if (!IsPropertyPathValid(Context, SkeletalSetterPathResult.GetValue()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
UEdGraph* GeneratedSetterGraph = UE::MVVM::FunctionGraphHelper::CreateFunctionGraph(WidgetBlueprint, FunctionName, EFunctionFlags::FUNC_None, TEXT(""), false);
|
|
if (GeneratedSetterGraph == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FProperty* SetterProperty = nullptr;
|
|
if (SkeletalSetterPathResult.GetValue().Num() > 0 && ensure(SkeletalSetterPathResult.GetValue().Last().IsProperty()))
|
|
{
|
|
SetterProperty = SkeletalSetterPathResult.GetValue().Last().GetProperty();
|
|
}
|
|
if (SetterProperty == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UE::MVVM::FunctionGraphHelper::AddFunctionArgument(GeneratedSetterGraph, SetterProperty, "NewValue");
|
|
UE::MVVM::FunctionGraphHelper::GenerateSetter(WidgetBlueprint, GeneratedSetterGraph, SkeletalSetterPathResult.GetValue());
|
|
#endif
|
|
}
|
|
|
|
} //namespace
|
|
|
|
#undef LOCTEXT_NAMESPACE
|