4813 lines
195 KiB
C++
4813 lines
195 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "MVVMViewBlueprintCompiler.h"
|
|
|
|
#include "Bindings/MVVMBindingHelper.h"
|
|
#include "Bindings/MVVMConversionFunctionHelper.h"
|
|
#include "Bindings/MVVMFieldPathHelper.h"
|
|
#include "BlueprintEditorSettings.h"
|
|
#include "Blueprint/WidgetTree.h"
|
|
#include "Components/Widget.h"
|
|
#include "Extensions/MVVMBlueprintViewExtension.h"
|
|
#include "Extensions/UIComponent.h"
|
|
#include "HAL/IConsoleManager.h"
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
#include "K2Node_ComponentBoundEvent.h"
|
|
#include "Misc/NamePermissionList.h"
|
|
#include "MVVMBlueprintView.h"
|
|
#include "MVVMBlueprintViewConversionFunction.h"
|
|
#include "MVVMDeveloperProjectSettings.h"
|
|
#include "MVVMMessageLog.h"
|
|
#include "PropertyPermissionList.h"
|
|
#include "MVVMFunctionGraphHelper.h"
|
|
#include "MVVMWidgetBlueprintExtension_View.h"
|
|
#include "Node/MVVMK2Node_AreSourcesValidForBinding.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 "Algo/AnyOf.h"
|
|
#include "WidgetBlueprintEditorUtils.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "MVVMViewBlueprintCompiler"
|
|
|
|
namespace UE::MVVM::Compiler
|
|
{
|
|
int32 FCompilerBindingHandle::IdGenerator = 0;
|
|
}
|
|
|
|
/**
|
|
When compiling the skeletal class
|
|
CreateVariables()
|
|
Add the less amount of errors here to have a more responsive editor and have the variable available in the editor.
|
|
Create all the variables
|
|
Create the public functions
|
|
When compiling the full class
|
|
CreateVariables()
|
|
same as skeletal, it may compile the skeletal then the full class. This will be called twice with the same instance.
|
|
CreateFunctions()
|
|
Create the private functions for bindings and events.
|
|
Note The class is not linked
|
|
Once the full class is compiled, then add the bindings/events with
|
|
PreCompile()
|
|
Create the bindings and events data and send them to the library compiler.
|
|
Compile()
|
|
All bindings and events data are compiled. Create the view data with the compiled data.
|
|
*/
|
|
|
|
namespace UE::MVVM::Private
|
|
{
|
|
FAutoConsoleVariable CVarLogViewCompiledResult(
|
|
TEXT("MVVM.LogViewCompiledResult"),
|
|
false,
|
|
TEXT("After the view is compiled log the compiled bindings and sources.")
|
|
);
|
|
|
|
FAutoConsoleVariable CVarLogInvalidBindingVerbose(
|
|
TEXT("MVVM.LogInvalidBindingVerbose"),
|
|
true,
|
|
TEXT("If a binding is invalid. Log verbose amounts of various different content data.")
|
|
);
|
|
|
|
#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 CouldNotCreateDestinationFieldPathFormat = LOCTEXT("CouldNotCreateDestinationFieldPath", "Couldn't create the destination 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(FGuid Id)
|
|
{
|
|
return FText::FromString(Id.ToString(EGuidFormats::DigitsWithHyphensInBraces));
|
|
}
|
|
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FFieldPathHandle, FText> AddObjectFieldPath(FCompiledBindingLibraryCompiler& BindingLibraryCompiler, const UWidgetBlueprintGeneratedClass* Class, FStringView ObjectPath, const 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, UMVVMBlueprintView* InBlueprintView)
|
|
: WidgetBlueprintCompilerContext(InCreationContext)
|
|
, BlueprintView(InBlueprintView)
|
|
, BindingLibraryCompiler(InCreationContext.WidgetBlueprint())
|
|
{
|
|
check(BlueprintView);
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::AddMessage(const FText& MessageText, Compiler::EMessageType MessageType) const
|
|
{
|
|
switch (MessageType)
|
|
{
|
|
case Compiler::EMessageType::Info:
|
|
WidgetBlueprintCompilerContext.MessageLog.Note(*MessageText.ToString());
|
|
break;
|
|
case Compiler::EMessageType::Warning:
|
|
WidgetBlueprintCompilerContext.MessageLog.Warning(*MessageText.ToString());
|
|
break;
|
|
case Compiler::EMessageType::Error:
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*MessageText.ToString());
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::AddMessages(TArrayView<TWeakPtr<FCompilerBinding>> Bindings, TArrayView<TWeakPtr<FCompilerEvent>> Events, const FText& MessageText, Compiler::EMessageType MessageType) const
|
|
{
|
|
for (TWeakPtr<FCompilerBinding>& Binding : Bindings)
|
|
{
|
|
AddMessageForBinding(Binding.Pin(), MessageText, MessageType, FMVVMBlueprintPinId());
|
|
}
|
|
for (TWeakPtr<FCompilerEvent>& Event : Events)
|
|
{
|
|
AddMessageForEvent(Event.Pin(), MessageText, MessageType, FMVVMBlueprintPinId());
|
|
}
|
|
|
|
if (Bindings.Num() == 0 && Events.Num() == 0)
|
|
{
|
|
AddMessage(MessageText, MessageType);
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::AddMessageForBinding(const TSharedPtr<FCompilerBinding>& Binding, const FText& MessageText, Compiler::EMessageType MessageType, const FMVVMBlueprintPinId& PinId) const
|
|
{
|
|
const FMVVMBlueprintViewBinding* BindingPtr = Binding ? BlueprintView->GetBindingAt(Binding->Key.ViewBindingIndex) : nullptr;
|
|
if (BindingPtr)
|
|
{
|
|
AddMessageForBinding(*BindingPtr, MessageText, MessageType, PinId);
|
|
}
|
|
else
|
|
{
|
|
AddMessage(MessageText, MessageType);
|
|
}
|
|
}
|
|
|
|
|
|
FText GetJoinArgumentNames(const FMVVMBlueprintPinId& PinId)
|
|
{
|
|
TArray<FText> JoinedNames;
|
|
JoinedNames.Reserve(PinId.GetNames().Num());
|
|
for (FName Name : PinId.GetNames())
|
|
{
|
|
JoinedNames.Add(FText::FromName(Name));
|
|
}
|
|
return FText::Join(LOCTEXT("NameSeperator", "."), JoinedNames);
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::AddMessageForBinding(const FMVVMBlueprintViewBinding& Binding, const FText& MessageText, Compiler::EMessageType MessageType, const FMVVMBlueprintPinId& PinId) const
|
|
{
|
|
const FText BindingName = FText::FromString(Binding.GetDisplayNameString(WidgetBlueprintCompilerContext.WidgetBlueprint()));
|
|
|
|
FText FormattedError;
|
|
if (PinId.IsValid())
|
|
{
|
|
FormattedError = FText::Format(LOCTEXT("BindingFormatWithArgument", "Binding '{0}': Argument '{1}' - {2}"), BindingName, UE::MVVM::Private::GetJoinArgumentNames(PinId), MessageText);
|
|
}
|
|
else
|
|
{
|
|
FormattedError = FText::Format(LOCTEXT("BindingFormat", "Binding '{0}': {1}"), BindingName, MessageText);
|
|
}
|
|
AddMessage(FormattedError, MessageType);
|
|
|
|
static EBindingMessageType BindingMessageTypes[] = { EBindingMessageType ::Info, EBindingMessageType ::Warning, EBindingMessageType ::Error};
|
|
FBindingMessage NewMessage = { FormattedError, BindingMessageTypes[static_cast<int32>(MessageType)] };
|
|
BlueprintView->AddMessageToBinding(Binding.BindingId, NewMessage);
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::AddMessageForEvent(const TSharedPtr<FCompilerEvent>& Event, const FText& MessageText, Compiler::EMessageType MessageType, const FMVVMBlueprintPinId& PinId) const
|
|
{
|
|
const UMVVMBlueprintViewEvent* EventPtr = Event ? Event->Event.Get() : nullptr;
|
|
if (EventPtr)
|
|
{
|
|
AddMessageForEvent(EventPtr, MessageText, MessageType, PinId);
|
|
}
|
|
else
|
|
{
|
|
AddMessage(MessageText, MessageType);
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::AddMessageForEvent(const UMVVMBlueprintViewEvent* Event, const FText& MessageText, Compiler::EMessageType MessageType, const FMVVMBlueprintPinId& PinId) const
|
|
{
|
|
const FText EventName = Event->GetDisplayName(true);
|
|
|
|
FText FormattedError;
|
|
if (PinId.IsValid())
|
|
{
|
|
FormattedError = FText::Format(LOCTEXT("EventFormatWithArgument", "Event '{0}': Argument '{1}' - {2}"), EventName, UE::MVVM::Private::GetJoinArgumentNames(PinId), MessageText);
|
|
}
|
|
else
|
|
{
|
|
FormattedError = FText::Format(LOCTEXT("EventFormat", "Event '{0}': {1}"), EventName, MessageText);
|
|
}
|
|
AddMessage(FormattedError, MessageType);
|
|
|
|
static UMVVMBlueprintViewEvent::EMessageType BindingMessageTypes[] = { UMVVMBlueprintViewEvent::EMessageType::Info, UMVVMBlueprintViewEvent::EMessageType::Warning, UMVVMBlueprintViewEvent::EMessageType::Error };
|
|
UMVVMBlueprintViewEvent::FMessage NewMessage = { FormattedError, BindingMessageTypes[static_cast<int32>(MessageType)] };
|
|
Event->AddCompilationToBinding(NewMessage);
|
|
}
|
|
|
|
void FMVVMViewBlueprintCompiler::AddMessageForCondition(const TSharedPtr<FCompilerCondition>& Condition, const FText& MessageText, Compiler::EMessageType MessageType, const FMVVMBlueprintPinId& PinId) const
|
|
{
|
|
const UMVVMBlueprintViewCondition* ConditionPtr = Condition ? Condition->Condition.Get() : nullptr;
|
|
if (ConditionPtr)
|
|
{
|
|
AddMessageForCondition(ConditionPtr, MessageText, MessageType, PinId);
|
|
}
|
|
else
|
|
{
|
|
AddMessage(MessageText, MessageType);
|
|
}
|
|
}
|
|
|
|
void FMVVMViewBlueprintCompiler::AddMessageForCondition(const UMVVMBlueprintViewCondition* Condition, const FText& MessageText, Compiler::EMessageType MessageType, const FMVVMBlueprintPinId& PinId) const
|
|
{
|
|
const FText ConditionName = Condition->GetDisplayName(true);
|
|
|
|
FText FormattedError;
|
|
if (PinId.IsValid())
|
|
{
|
|
FormattedError = FText::Format(LOCTEXT("ConditionFormatWithArgument", "Condition '{0}': Argument '{1}' - {2}"), ConditionName, UE::MVVM::Private::GetJoinArgumentNames(PinId), MessageText);
|
|
}
|
|
else
|
|
{
|
|
FormattedError = FText::Format(LOCTEXT("ConditionFormat", "Condition '{0}': {1}"), ConditionName, MessageText);
|
|
}
|
|
AddMessage(FormattedError, MessageType);
|
|
|
|
static UMVVMBlueprintViewCondition::EMessageType BindingMessageTypes[] = { UMVVMBlueprintViewCondition::EMessageType::Info, UMVVMBlueprintViewCondition::EMessageType::Warning, UMVVMBlueprintViewCondition::EMessageType::Error };
|
|
UMVVMBlueprintViewCondition::FMessage NewMessage = { FormattedError, BindingMessageTypes[static_cast<int32>(MessageType)] };
|
|
Condition->AddCompilationToBinding(NewMessage);
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::AddMessageForViewModel(const FMVVMBlueprintViewModelContext& ViewModel, const FText& Message, Compiler::EMessageType MessageType) const
|
|
{
|
|
const FText FormattedError = FText::Format(LOCTEXT("ViewModelFormat", "Viewodel '{0}': {1}"), ViewModel.GetDisplayName(), Message);
|
|
AddMessage(FormattedError, MessageType);
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::AddMessageForViewModel(const FText& ViewModelDisplayName, const FText& Message, Compiler::EMessageType MessageType) const
|
|
{
|
|
const FText FormattedError = FText::Format(LOCTEXT("ViewModelFormat", "Viewodel '{0}': {1}"), ViewModelDisplayName, Message);
|
|
AddMessage(FormattedError, MessageType);
|
|
}
|
|
|
|
|
|
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)
|
|
{
|
|
TArray<UObject*> Children;
|
|
const bool bIncludeNestedObjects = false;
|
|
ForEachObjectWithOuter(ClassToClean, [&Children](UObject* Child)
|
|
{
|
|
if (Cast<UMVVMViewClass>(Child))
|
|
{
|
|
Children.Add(Child);
|
|
}
|
|
}, bIncludeNestedObjects);
|
|
|
|
for (UObject* Child : Children)
|
|
{
|
|
FunctionGraphHelper::RenameObjectToTransientPackage(Child);
|
|
}
|
|
|
|
// Clean up the stale viewmodel instances.
|
|
TMap<FGuid, TWeakObjectPtr<UObject>>& SavedViewModelInstances = BlueprintView->GetOuterUMVVMWidgetBlueprintExtension_View()->TemporaryViewModelInstances;
|
|
TArray<FGuid> DeleteViewModelInstances;
|
|
|
|
for (auto It = SavedViewModelInstances.CreateIterator(); It; ++It)
|
|
{
|
|
uint32 ViewModelIndex = ViewModelCreatorContexts.IndexOfByPredicate([It](const FCompilerViewModelCreatorContext& SourceCreatorContext)
|
|
{
|
|
return SourceCreatorContext.ViewModelContext.GetViewModelId() == It->Key;
|
|
});
|
|
|
|
TWeakObjectPtr<UObject> StaticViewModelInstance = nullptr;
|
|
if (ViewModelIndex != INDEX_NONE)
|
|
{
|
|
const FMVVMBlueprintViewModelContext& ViewModelContext = ViewModelCreatorContexts[ViewModelIndex].ViewModelContext;
|
|
if (It->Value.IsValid())
|
|
{
|
|
if (ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::CreateInstance
|
|
&& ViewModelContext.bExposeInstanceInEditor)
|
|
{
|
|
StaticViewModelInstance = It->Value;
|
|
}
|
|
else
|
|
{
|
|
FunctionGraphHelper::RenameObjectToTransientPackage(It->Value.Get());
|
|
}
|
|
}
|
|
}
|
|
if (!StaticViewModelInstance.IsValid())
|
|
{
|
|
DeleteViewModelInstances.Add(It->Key);
|
|
SavedViewModelInstances[It->Key] = nullptr;
|
|
}
|
|
}
|
|
|
|
for (FGuid Guid : DeleteViewModelInstances)
|
|
{
|
|
SavedViewModelInstances.Remove(Guid);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::GatherGeneratedVariables(const FWidgetBlueprintCompilerContext::FPopulateGeneratedVariablesContext& Context)
|
|
{
|
|
if (!BlueprintView)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!AreStepsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
CreateWidgetMap(Context);
|
|
CreateBindingList(Context);
|
|
CreateEventList(Context);
|
|
CreateConditionList(Context);
|
|
CreateExtensionList(Context);
|
|
CreateRequiredProperties(Context);
|
|
|
|
// Add Generated Variables
|
|
{
|
|
auto AddObjectVariable = [&Context](const FCompilerUserWidgetProperty& UserWidgetProperty)
|
|
{
|
|
FBPVariableDescription ObjectVariableDesc;
|
|
ObjectVariableDesc.VarName = UserWidgetProperty.Name;
|
|
ObjectVariableDesc.VarGuid = UserWidgetProperty.Guid;
|
|
ObjectVariableDesc.VarType = FEdGraphPinType(UEdGraphSchema_K2::PC_Object, NAME_None, UserWidgetProperty.AuthoritativeClass, EPinContainerType::None, false, FEdGraphTerminalType());
|
|
ObjectVariableDesc.FriendlyName = UserWidgetProperty.DisplayName.ToString();
|
|
ObjectVariableDesc.PropertyFlags = (CPF_BlueprintVisible | CPF_RepSkip);
|
|
|
|
const bool bIsInstanceExposed = UserWidgetProperty.bInstanced && UserWidgetProperty.bInstanceExposed;
|
|
if (bIsInstanceExposed)
|
|
{
|
|
ObjectVariableDesc.PropertyFlags |= (CPF_Edit | CPF_ExportObject | CPF_PersistentInstance | CPF_InstancedReference | CPF_NonNullable | CPF_NoClear);
|
|
|
|
if (UserWidgetProperty.AuthoritativeClass->HasAnyClassFlags(CLASS_HasInstancedReference))
|
|
{
|
|
ObjectVariableDesc.PropertyFlags |= (CPF_ContainsInstancedReference);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ObjectVariableDesc.PropertyFlags |= (CPF_Transient | CPF_DuplicateTransient);
|
|
}
|
|
|
|
if (UserWidgetProperty.bExposeOnSpawn)
|
|
{
|
|
ObjectVariableDesc.PropertyFlags |= (CPF_ExposeOnSpawn);
|
|
}
|
|
else if (!bIsInstanceExposed)
|
|
{
|
|
ObjectVariableDesc.PropertyFlags |= (CPF_DisableEditOnInstance);
|
|
}
|
|
|
|
if (UserWidgetProperty.bReadOnly)
|
|
{
|
|
ObjectVariableDesc.PropertyFlags |= (CPF_BlueprintReadOnly);
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if (!UserWidgetProperty.BlueprintSetter.IsEmpty())
|
|
{
|
|
ObjectVariableDesc.SetMetaData(FBlueprintMetadata::MD_PropertySetFunction, *UserWidgetProperty.BlueprintSetter);
|
|
}
|
|
if (!UserWidgetProperty.DisplayName.IsEmpty())
|
|
{
|
|
ObjectVariableDesc.SetMetaData(FBlueprintMetadata::MD_DisplayName, *UserWidgetProperty.DisplayName.ToString());
|
|
}
|
|
if (!UserWidgetProperty.CategoryName.IsEmpty())
|
|
{
|
|
ObjectVariableDesc.SetMetaData(FBlueprintMetadata::MD_FunctionCategory, *UserWidgetProperty.CategoryName);
|
|
}
|
|
if (UserWidgetProperty.bExposeOnSpawn)
|
|
{
|
|
ObjectVariableDesc.SetMetaData(FBlueprintMetadata::MD_ExposeOnSpawn, TEXT("true"));
|
|
}
|
|
if (UserWidgetProperty.bPrivate)
|
|
{
|
|
ObjectVariableDesc.SetMetaData(FBlueprintMetadata::MD_Private, TEXT("true"));
|
|
}
|
|
if (bIsInstanceExposed)
|
|
{
|
|
ObjectVariableDesc.SetMetaData(FName("EditInline"), TEXT("true"));
|
|
}
|
|
#endif
|
|
|
|
Context.AddGeneratedVariable(MoveTemp(ObjectVariableDesc));
|
|
};
|
|
|
|
for (FCompilerUserWidgetProperty& UserWidgetProperty : NeededUserWidgetProperties)
|
|
{
|
|
check(UserWidgetProperty.AuthoritativeClass);
|
|
check(!UserWidgetProperty.Name.IsNone());
|
|
|
|
// Check if the property already exists in parent classes
|
|
FMVVMConstFieldVariant UserWidgetPropertyField = BindingHelper::FindFieldByName(Context.GetWidgetBlueprint()->ParentClass, FMVVMBindingName(UserWidgetProperty.Name));
|
|
if (UserWidgetPropertyField.IsEmpty())
|
|
{
|
|
for (FField* Field = Context.GetWidgetBlueprint()->ParentClass->ChildProperties; Field != nullptr; Field = Field->Next)
|
|
{
|
|
if (Field->GetFName() == UserWidgetProperty.Name)
|
|
{
|
|
if (CastField<FProperty>(Field))
|
|
{
|
|
UserWidgetPropertyField = FMVVMFieldVariant(CastField<FProperty>(Field));
|
|
}
|
|
else
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("FieldIsNotProperty", "The field for source '{0}' exists but is not a property."), UserWidgetProperty.DisplayName).ToString());
|
|
bIsGatherGeneratedVariablesStepValid = false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
for (UField* Field = Context.GetWidgetBlueprint()->ParentClass->Children; Field != nullptr; Field = Field->Next)
|
|
{
|
|
if (Field->GetFName() == UserWidgetProperty.Name)
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("FieldIsNotProperty", "The field for source '{0}' exists but is not a property."), UserWidgetProperty.DisplayName).ToString());
|
|
bIsGatherGeneratedVariablesStepValid = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const FBPVariableDescription* ExistingGeneratedVariablePtr = Context.GetWidgetBlueprint()->GeneratedVariables.FindByPredicate([&UserWidgetProperty](const FBPVariableDescription& VariableDesc)
|
|
{
|
|
return VariableDesc.VarName == UserWidgetProperty.Name;
|
|
});
|
|
|
|
// 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.
|
|
if (UserWidgetProperty.CreationType == FCompilerUserWidgetProperty::ECreationType::CreateOnlyIfDoesntExist)
|
|
{
|
|
if (ExistingGeneratedVariablePtr != nullptr)
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("VariableAlreadyExistsInBP", "There is already a variable named '{0}' in blueprint '{1}'."), UserWidgetProperty.DisplayName, FText::FromString(Context.GetWidgetBlueprint()->GetFriendlyName())).ToString());
|
|
bIsGatherGeneratedVariablesStepValid = false;
|
|
continue;
|
|
}
|
|
else if (!UserWidgetPropertyField.IsEmpty())
|
|
{
|
|
// Viewmodel property cannot already exist. It will create issues with initialization and with View::SetViewModel.
|
|
const UClass* OwnerClass = Cast<UClass>(UserWidgetPropertyField.GetOwner());
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("PropertyAlreadyExistInParent", "There is already a property named '{0}' in scope '{1}'."), UserWidgetProperty.DisplayName, (OwnerClass ? OwnerClass->GetDisplayNameText() : FText::GetEmpty())).ToString());
|
|
bIsGatherGeneratedVariablesStepValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (ExistingGeneratedVariablePtr != nullptr && (ExistingGeneratedVariablePtr->VarType.PinCategory != UEdGraphSchema_K2::PC_Object || ExistingGeneratedVariablePtr->VarType.PinSubCategoryObject != UserWidgetProperty.AuthoritativeClass))
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("VariableExistsWithWrongType", "There is already a variable named '{0}' in blueprint '{1}' that does not have the expected type."), UserWidgetProperty.DisplayName, FText::FromString(Context.GetWidgetBlueprint()->GetFriendlyName())).ToString());
|
|
bIsGatherGeneratedVariablesStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
const bool bCreateVariable = UserWidgetProperty.CreationType == FCompilerUserWidgetProperty::ECreationType::CreateOnlyIfDoesntExist
|
|
|| (UserWidgetProperty.CreationType == FCompilerUserWidgetProperty::ECreationType::CreateIfDoesntExist && UserWidgetPropertyField.IsEmpty());
|
|
if (bCreateVariable && ExistingGeneratedVariablePtr == nullptr)
|
|
{
|
|
AddObjectVariable(UserWidgetProperty);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::CreateVariables(const FWidgetBlueprintCompilerContext::FCreateVariableContext& Context)
|
|
{
|
|
if (!BlueprintView)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!AreStepsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (Context.GetCompileType() == EKismetCompileType::SkeletonOnly)
|
|
{
|
|
CreatePublicFunctionsDeclaration(Context);
|
|
}
|
|
|
|
// Create variable
|
|
{
|
|
for (FCompilerUserWidgetProperty& UserWidgetProperty : NeededUserWidgetProperties)
|
|
{
|
|
check(UserWidgetProperty.AuthoritativeClass);
|
|
check(!UserWidgetProperty.Name.IsNone());
|
|
UserWidgetProperty.Property = nullptr; // Skeletal set the property, Full needs the new property
|
|
|
|
FMVVMConstFieldVariant UserWidgetPropertyField = BindingHelper::FindFieldByName(Context.GetGeneratedClass(), FMVVMBindingName(UserWidgetProperty.Name));
|
|
|
|
// The class is not linked yet. It may not be available yet.
|
|
if (UserWidgetPropertyField.IsEmpty())
|
|
{
|
|
for (FField* Field = Context.GetGeneratedClass()->ChildProperties; Field != nullptr; Field = Field->Next)
|
|
{
|
|
if (Field->GetFName() == UserWidgetProperty.Name)
|
|
{
|
|
if (CastField<FProperty>(Field))
|
|
{
|
|
UserWidgetPropertyField = FMVVMFieldVariant(CastField<FProperty>(Field));
|
|
}
|
|
else
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("FieldIsNotProperty", "The field for source '{0}' exists but is not a property."), UserWidgetProperty.DisplayName).ToString());
|
|
bIsCreateVariableStepValid = false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
for (UField* Field = Context.GetGeneratedClass()->Children; Field != nullptr; Field = Field->Next)
|
|
{
|
|
if (Field->GetFName() == UserWidgetProperty.Name)
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("FieldIsNotProperty", "The field for source '{0}' exists but is not a property."), UserWidgetProperty.DisplayName).ToString());
|
|
bIsCreateVariableStepValid = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (UserWidgetPropertyField.IsEmpty())
|
|
{
|
|
UClass* ParentClass = Context.GetGeneratedClass()->GetSuperClass();
|
|
if (const FProperty* Property = ParentClass->FindPropertyByName(UserWidgetProperty.Name))
|
|
{
|
|
UserWidgetPropertyField = FMVVMFieldVariant(Property);
|
|
}
|
|
}
|
|
|
|
if (!UserWidgetPropertyField.IsEmpty())
|
|
{
|
|
if (UserWidgetPropertyField.IsFunction())
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("FunctionCanBeSource", "Function can't be source. '{0}'."), UserWidgetProperty.DisplayName).ToString());
|
|
bIsCreateVariableStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
if (!BindingHelper::IsValidForSourceBinding(UserWidgetPropertyField))
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("FieldNotAccessibleAtRuntime", "The field for source '{0}' exists but is not accessible at runtime."), UserWidgetProperty.DisplayName).ToString());
|
|
bIsCreateVariableStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
ensure(UserWidgetPropertyField.IsProperty());
|
|
const FProperty* Property = UserWidgetPropertyField.IsProperty() ? UserWidgetPropertyField.GetProperty() : nullptr;
|
|
const FObjectProperty* ObjectProperty = CastField<const FObjectProperty>(Property);
|
|
const bool bIsCompatible = ObjectProperty && UserWidgetProperty.AuthoritativeClass->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."), UserWidgetProperty.DisplayName).ToString());
|
|
bIsCreateVariableStepValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (UserWidgetPropertyField.IsProperty())
|
|
{
|
|
UserWidgetProperty.Property = UserWidgetPropertyField.GetProperty();
|
|
}
|
|
|
|
if (UserWidgetProperty.Property == nullptr)
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("VariableCouldNotBeCreated", "The variable for '{0}' could not be created."), UserWidgetProperty.DisplayName).ToString());
|
|
bIsCreateVariableStepValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add multicast delegate variables for any events / ubergraph pages that need them to fire
|
|
{
|
|
for (const TSharedRef<FCompilerBinding>& ValidBinding : ValidBindings)
|
|
{
|
|
FMVVMBlueprintViewBinding& Binding = *BlueprintView->GetBindingAt(ValidBinding->Key.ViewBindingIndex);
|
|
|
|
// Functions are not yet generated / valid, but their graphs exist & we need to create delegates events that may be added to the ubergraph
|
|
if (ValidBinding->Type == FCompilerBinding::EType::Unknown)
|
|
{
|
|
if (UMVVMBlueprintViewConversionFunction* ConversionFunction = Binding.Conversion.GetConversionFunction(ValidBinding->Key.bIsForwardBinding))
|
|
{
|
|
if (ConversionFunction->IsUbergraphPage() && !ConversionFunction->GetWrapperGraphName().IsNone())
|
|
{
|
|
// Create a multicast delegate variable that the event node will bind to
|
|
Context.CreateMulticastDelegateVariable(ConversionFunction->GetWrapperGraphName());
|
|
|
|
// Functions / Graphs should only be compiled in full compilations
|
|
if (Context.GetCompileType() == EKismetCompileType::Full)
|
|
{
|
|
ConversionFunction->SetDestinationPath(Binding.DestinationPath);
|
|
UEdGraph* WrapperGraph = ConversionFunction->GetOrCreateIntermediateWrapperGraph(WidgetBlueprintCompilerContext);
|
|
|
|
ensure(WrapperGraph);
|
|
ensure(ConversionFunction->IsWrapperGraphTransient());
|
|
ensure(!WidgetBlueprintCompilerContext.Blueprint->FunctionGraphs.Contains(WrapperGraph));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Public function permissions
|
|
{
|
|
FPathPermissionList& FunctionPermissions = GetMutableDefault<UBlueprintEditorSettings>()->GetFunctionPermissions();
|
|
FPathPermissionList& GeneratedFunctionPermissions = GetMutableDefault<UMVVMDeveloperProjectSettings>()->GetGeneratedFunctionPermissions();
|
|
|
|
FName ContextName;
|
|
{
|
|
TStringBuilder<512> ContextNameStr;
|
|
Context.GetGeneratedClass()->GetPathName(nullptr, ContextNameStr);
|
|
ContextName = ContextNameStr.ToString();
|
|
}
|
|
|
|
FunctionPermissions.UnregisterOwner(ContextName);
|
|
GeneratedFunctionPermissions.UnregisterOwner(ContextName);
|
|
|
|
const bool bHasFiltering = GetMutableDefault<UBlueprintEditorSettings>()->GetFunctionPermissions().HasFiltering();
|
|
for (FName FunctionName : FunctionPermissionsToAdd)
|
|
{
|
|
TStringBuilder<512> FunctionPath;
|
|
FunctionPath << ContextName;
|
|
FunctionPath << TEXT(':');
|
|
FunctionPath << FunctionName;
|
|
|
|
GeneratedFunctionPermissions.AddAllowListItem(ContextName, FunctionPath);
|
|
|
|
if (bHasFiltering)
|
|
{
|
|
FunctionPermissions.AddAllowListItem(ContextName, FunctionPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::CreateWidgetMap(const FWidgetBlueprintCompilerContext::FPopulateGeneratedVariablesContext& Context)
|
|
{
|
|
// 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::CreateBindingList(const FWidgetBlueprintCompilerContext::FPopulateGeneratedVariablesContext& Context)
|
|
{
|
|
ValidBindings.Reset();
|
|
|
|
// Build the list of bindings that we should compile.
|
|
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());
|
|
bIsGatherGeneratedVariablesStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
FMVVMBlueprintViewBinding& Binding = *BindingPtr;
|
|
if (!Binding.bCompile)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const bool bHasConversionFunction = Binding.Conversion.GetConversionFunction(true) || Binding.Conversion.GetConversionFunction(false);
|
|
if (bHasConversionFunction && Binding.BindingType == EMVVMBindingMode::TwoWay)
|
|
{
|
|
AddMessageForBinding(Binding, LOCTEXT("TwoWayBindingsWithConversion", "Two-way bindings are not allowed to use conversion functions."), Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
bIsGatherGeneratedVariablesStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
if (IsForwardBinding(Binding.BindingType))
|
|
{
|
|
TSharedRef<FCompilerBinding> ValidBinding = MakeShared<FCompilerBinding>();
|
|
ValidBinding->Key.ViewBindingIndex = Index;
|
|
ValidBinding->Key.bIsForwardBinding = true;
|
|
ValidBinding->bIsOneTimeBinding = IsOneTimeBinding(Binding.BindingType);
|
|
|
|
ValidBindings.Add(ValidBinding);
|
|
}
|
|
if (IsBackwardBinding(Binding.BindingType))
|
|
{
|
|
TSharedRef<FCompilerBinding> ValidBinding = MakeShared<FCompilerBinding>();
|
|
ValidBinding->Key.ViewBindingIndex = Index;
|
|
ValidBinding->Key.bIsForwardBinding = false;
|
|
ValidBinding->bIsOneTimeBinding = IsOneTimeBinding(Binding.BindingType);
|
|
|
|
ValidBindings.Add(ValidBinding);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::CreateEventList(const FWidgetBlueprintCompilerContext::FPopulateGeneratedVariablesContext& Context)
|
|
{
|
|
for (UMVVMBlueprintViewEvent* EventPtr : BlueprintView->GetEvents())
|
|
{
|
|
if (EventPtr == nullptr)
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*LOCTEXT("EventInvalid", "Internal error: An event is invalid.").ToString());
|
|
bIsGatherGeneratedVariablesStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
if (!EventPtr->bCompile)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!EventPtr->GetEventPath().HasPaths())
|
|
{
|
|
AddMessageForEvent(EventPtr, LOCTEXT("EventInvalidEventPath", "The event path is invalid."), Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
bIsGatherGeneratedVariablesStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
TSharedRef<FCompilerEvent> ValidEvent = MakeShared<FCompilerEvent>();
|
|
ValidEvent->Event = EventPtr;
|
|
|
|
ValidEvents.Add(ValidEvent);
|
|
}
|
|
}
|
|
|
|
void FMVVMViewBlueprintCompiler::CreateConditionList(const FWidgetBlueprintCompilerContext::FPopulateGeneratedVariablesContext& Context)
|
|
{
|
|
bool bAllowConditionBinding = GetDefault<UMVVMDeveloperProjectSettings>()->bAllowConditionBinding;
|
|
|
|
for (UMVVMBlueprintViewCondition* ConditionPtr : BlueprintView->GetConditions())
|
|
{
|
|
if (ConditionPtr == nullptr)
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*LOCTEXT("ConditionInvalid", "Internal error: A condition is invalid.").ToString());
|
|
bIsGatherGeneratedVariablesStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
if (!ConditionPtr->bCompile || !bAllowConditionBinding)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!ConditionPtr->GetConditionPath().HasPaths())
|
|
{
|
|
AddMessageForCondition(ConditionPtr, LOCTEXT("ConditionInvalidConditionPath", "The condition path is invalid."), Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
bIsGatherGeneratedVariablesStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
TSharedRef<FCompilerCondition> ValidCondition = MakeShared<FCompilerCondition>();
|
|
ValidCondition->Condition = ConditionPtr;
|
|
|
|
ValidConditions.Add(ValidCondition);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::CreateExtensionList(const FWidgetBlueprintCompilerContext::FPopulateGeneratedVariablesContext& Context)
|
|
{
|
|
for (int32 Index = BlueprintView->GetOuterUMVVMWidgetBlueprintExtension_View()->BlueprintExtensions.Num() - 1; Index >= 0; --Index)
|
|
{
|
|
FMVVMExtensionItem& Extension = BlueprintView->GetOuterUMVVMWidgetBlueprintExtension_View()->BlueprintExtensions[Index];
|
|
if (Extension.ExtensionObj == nullptr)
|
|
{
|
|
BlueprintView->GetOuterUMVVMWidgetBlueprintExtension_View()->BlueprintExtensions.RemoveAtSwap(Index);
|
|
break;
|
|
}
|
|
|
|
TSharedRef<FCompilerExtension> ValidExtension = MakeShared<FCompilerExtension>();
|
|
ValidExtension->Extension = Extension.ExtensionObj;
|
|
|
|
ValidExtensions.Add(ValidExtension);
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::CreateRequiredProperties(const FWidgetBlueprintCompilerContext::FPopulateGeneratedVariablesContext& Context)
|
|
{
|
|
NeededBindingSources.Reset();
|
|
NeededUserWidgetProperties.Reset();
|
|
ViewModelCreatorContexts.Reset();
|
|
ViewModelSettersToGenerate.Reset();
|
|
|
|
TSet<FGuid> ViewModelGuids;
|
|
for (const FMVVMBlueprintViewModelContext& ViewModelContext : BlueprintView->GetViewModels())
|
|
{
|
|
if (!ViewModelContext.GetViewModelId().IsValid())
|
|
{
|
|
AddMessageForViewModel(ViewModelContext, LOCTEXT("ViewmodelInvalidGuid", "GUID is invalid."), Compiler::EMessageType::Error);
|
|
bIsGatherGeneratedVariablesStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
if (ViewModelGuids.Contains(ViewModelContext.GetViewModelId()))
|
|
{
|
|
AddMessageForViewModel(ViewModelContext, LOCTEXT("ViewmodelAlreadyAdded", "Identical viewmodel has already been added."), Compiler::EMessageType::Error);
|
|
bIsGatherGeneratedVariablesStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
ViewModelGuids.Add(ViewModelContext.GetViewModelId());
|
|
|
|
if (ViewModelContext.GetViewModelClass() == nullptr || !ViewModelContext.IsValid())
|
|
{
|
|
AddMessageForViewModel(ViewModelContext, LOCTEXT("ViewmodelInvalidClass", "Invalid class."), Compiler::EMessageType::Error);
|
|
bIsGatherGeneratedVariablesStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
FName PropertyName = ViewModelContext.GetViewModelName();
|
|
|
|
const bool bCreateSetterFunction = GetDefault<UMVVMDeveloperProjectSettings>()->bAllowGeneratedViewModelSetter
|
|
&& (ViewModelContext.bCreateSetterFunction || ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::Manual);
|
|
FString SetterFunctionName;
|
|
if (bCreateSetterFunction)
|
|
{
|
|
SetterFunctionName = TEXT("Set") + ViewModelContext.GetViewModelName().ToString();
|
|
|
|
FCompilerViewModelSetter& ViewModelSetter = ViewModelSettersToGenerate.AddDefaulted_GetRef();
|
|
ViewModelSetter.Class = ViewModelContext.GetViewModelClass();
|
|
ViewModelSetter.PropertyName = PropertyName;
|
|
ViewModelSetter.BlueprintSetter = SetterFunctionName;
|
|
ViewModelSetter.DisplayName = ViewModelContext.GetDisplayName();
|
|
}
|
|
|
|
TSharedRef<FCompilerBindingSource> SourceContext = MakeShared<FCompilerBindingSource>();
|
|
{
|
|
SourceContext->AuthoritativeClass = ViewModelContext.GetViewModelClass();
|
|
SourceContext->Name = PropertyName;
|
|
SourceContext->Type = FCompilerBindingSource::EType::ViewModel;
|
|
SourceContext->bIsOptional = ViewModelContext.bOptional;
|
|
|
|
if (ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::Manual)
|
|
{
|
|
SourceContext->bIsOptional = true;
|
|
}
|
|
else if (ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::CreateInstance)
|
|
{
|
|
SourceContext->bIsOptional = false;
|
|
}
|
|
|
|
NeededBindingSources.Add(SourceContext);
|
|
}
|
|
|
|
{
|
|
bool bIsPublicReadable = ViewModelContext.InstancedViewModel == nullptr && ViewModelContext.bCreateGetterFunction;
|
|
|
|
FCompilerUserWidgetProperty& SourceVariable = NeededUserWidgetProperties.AddDefaulted_GetRef();
|
|
SourceVariable.AuthoritativeClass = ViewModelContext.GetViewModelClass();
|
|
SourceVariable.Name = PropertyName;
|
|
SourceVariable.DisplayName = ViewModelContext.GetDisplayName();
|
|
SourceVariable.CategoryName = TEXT("Viewmodel");
|
|
SourceVariable.Guid = ViewModelContext.GetViewModelId();
|
|
SourceVariable.BlueprintSetter = SetterFunctionName;
|
|
SourceVariable.CreationType = FCompilerUserWidgetProperty::ECreationType::CreateOnlyIfDoesntExist;
|
|
SourceVariable.bExposeOnSpawn = bCreateSetterFunction;
|
|
SourceVariable.bPrivate = !bIsPublicReadable;
|
|
SourceVariable.bReadOnly = !bCreateSetterFunction;
|
|
SourceVariable.bInstanced = ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::CreateInstance;
|
|
SourceVariable.bInstanceExposed = ViewModelContext.bExposeInstanceInEditor;
|
|
}
|
|
|
|
{
|
|
FCompilerViewModelCreatorContext& CreatorContext = ViewModelCreatorContexts.AddDefaulted_GetRef();
|
|
CreatorContext.ViewModelContext = ViewModelContext;
|
|
CreatorContext.Source = SourceContext;
|
|
}
|
|
}
|
|
|
|
TSet<FName> WidgetSourcesCreated;
|
|
TSet<FName> WidgetUserPropertyCreated;
|
|
bool bSelfBindingSourceCreated = NeededBindingSources.ContainsByPredicate([](const TSharedRef<FCompilerBindingSource>& Other)
|
|
{
|
|
return Other->Type == FCompilerBindingSource::EType::Self;
|
|
});
|
|
|
|
auto GenerateCompilerContext = [Self = this, WidgetBP = Context.GetWidgetBlueprint(), &ViewModelGuids, &WidgetSourcesCreated, &WidgetUserPropertyCreated, &bSelfBindingSourceCreated](bool bInCreateSource, const FMVVMBlueprintPropertyPath& PropertyPath) -> TValueOrError<void, FText>
|
|
{
|
|
auto CreateSourceForSelf = [Self, &bSelfBindingSourceCreated]() -> TSharedRef<FCompilerBindingSource>
|
|
{
|
|
TSharedRef<FCompilerBindingSource> SourceContext = MakeShared<FCompilerBindingSource>();
|
|
SourceContext->AuthoritativeClass = nullptr;
|
|
SourceContext->Name = Self->WidgetBlueprintCompilerContext.WidgetBlueprint()->GetFName();
|
|
SourceContext->Type = FCompilerBindingSource::EType::Self;
|
|
SourceContext->bIsOptional = false;
|
|
|
|
if (!bSelfBindingSourceCreated)
|
|
{
|
|
Self->NeededBindingSources.Add(SourceContext);
|
|
bSelfBindingSourceCreated = true;
|
|
}
|
|
return SourceContext;
|
|
};
|
|
FName PropertyPathWidgetName = PropertyPath.GetWidgetName();
|
|
FGuid PropertyPathViewModelId = PropertyPath.GetViewModelId();
|
|
|
|
bool bIsComponentOnSelf = false;
|
|
if (PropertyPath.IsComponent())
|
|
{
|
|
const UWidgetBlueprintGeneratedClass* WidgetBPGeneratedClass = Cast<UWidgetBlueprintGeneratedClass>(WidgetBP->GeneratedClass);
|
|
const UWidgetBlueprintGeneratedClass* ComponentOwningWidgetBlueprint = UE::MVVM::FieldPathHelper::GetComponentPropertyPathSource(PropertyPath.GetFields(WidgetBPGeneratedClass), WidgetBPGeneratedClass);
|
|
bIsComponentOnSelf = ComponentOwningWidgetBlueprint == WidgetBPGeneratedClass;
|
|
}
|
|
|
|
// If the path is "self.widget.property", "self.viewmodel.property", then remove the "self".
|
|
EMVVMBlueprintFieldPathSource FieldPathSource = PropertyPath.GetSource(Self->WidgetBlueprintCompilerContext.WidgetBlueprint());
|
|
if (FieldPathSource == EMVVMBlueprintFieldPathSource::SelfContext)
|
|
{
|
|
const TArrayView<const FMVVMBlueprintFieldPath> FieldPaths = PropertyPath.GetFieldPaths();
|
|
if (FieldPaths.Num() >= 2 && !bIsComponentOnSelf)
|
|
{
|
|
if (FieldPaths[0].GetBindingKind() == EBindingKind::Property && FieldPaths[0].IsFieldSelfContext())
|
|
{
|
|
FName RawFieldName = FieldPaths[0].GetRawFieldName();
|
|
if (Self->WidgetNameToWidgetPointerMap.Contains(RawFieldName))
|
|
{
|
|
FieldPathSource = EMVVMBlueprintFieldPathSource::Widget;
|
|
PropertyPathWidgetName = RawFieldName;
|
|
}
|
|
else if (const FMVVMBlueprintViewModelContext* SourceViewModelContext = Self->BlueprintView->FindViewModel(RawFieldName))
|
|
{
|
|
FieldPathSource = EMVVMBlueprintFieldPathSource::ViewModel;
|
|
PropertyPathViewModelId = SourceViewModelContext->GetViewModelId();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (FieldPathSource)
|
|
{
|
|
case EMVVMBlueprintFieldPathSource::SelfContext:
|
|
{
|
|
CreateSourceForSelf();
|
|
return MakeValue();
|
|
}
|
|
case EMVVMBlueprintFieldPathSource::Widget:
|
|
{
|
|
// Only do this once
|
|
bool bNewCreateSource = !WidgetSourcesCreated.Contains(PropertyPathWidgetName) && bInCreateSource;
|
|
bool bNewAddWidgetProperty = !WidgetUserPropertyCreated.Contains(PropertyPathWidgetName);
|
|
if (bNewCreateSource || bNewAddWidgetProperty)
|
|
{
|
|
UWidget** WidgetPtr = Self->WidgetNameToWidgetPointerMap.Find(PropertyPathWidgetName);
|
|
if (WidgetPtr == nullptr || *WidgetPtr == nullptr)
|
|
{
|
|
return MakeError(FText::Format(LOCTEXT("InvalidWidgetFormat", "Could not find the targeted widget: {0}"), FText::FromName(PropertyPathWidgetName)));
|
|
}
|
|
UWidget* Widget = *WidgetPtr;
|
|
|
|
if (bNewCreateSource)
|
|
{
|
|
FName PropertyName = PropertyPathWidgetName;
|
|
{
|
|
TSharedRef<FCompilerBindingSource>* FoundCompilerSource = Self->NeededBindingSources.FindByPredicate([PropertyName](const TSharedRef<FCompilerBindingSource>& Other)
|
|
{
|
|
return Other->Name == PropertyName;
|
|
});
|
|
if (FoundCompilerSource != nullptr)
|
|
{
|
|
// It should be in the TSet<FName> WidgetSources
|
|
return MakeError(FText::Format(LOCTEXT("ExistingWidgetSourceFormat", "Internal error. A widget source already exist: {0}"), FText::FromName(PropertyPathWidgetName)));
|
|
}
|
|
}
|
|
|
|
TSharedRef<FCompilerBindingSource> SourceContext = MakeShared<FCompilerBindingSource>();
|
|
SourceContext->AuthoritativeClass = Widget->GetClass();
|
|
SourceContext->Name = PropertyName;
|
|
SourceContext->Type = FCompilerBindingSource::EType::Widget;
|
|
SourceContext->bIsOptional = false;
|
|
Self->NeededBindingSources.Add(SourceContext);
|
|
|
|
WidgetSourcesCreated.Add(Widget->GetFName());
|
|
}
|
|
|
|
if (bNewAddWidgetProperty)
|
|
{
|
|
FCompilerUserWidgetProperty& SourceVariable = Self->NeededUserWidgetProperties.AddDefaulted_GetRef();
|
|
SourceVariable.AuthoritativeClass = Widget->GetClass();
|
|
SourceVariable.Name = PropertyPathWidgetName;
|
|
SourceVariable.Guid = WidgetBP->WidgetVariableNameToGuidMap.FindRef(Widget->GetFName());
|
|
SourceVariable.DisplayName = Widget->IsGeneratedName() ? FText::FromString(Widget->GetName()) : Widget->GetLabelText();
|
|
SourceVariable.CategoryName = TEXT("Widget");
|
|
SourceVariable.CreationType = FCompilerUserWidgetProperty::ECreationType::CreateIfDoesntExist;
|
|
SourceVariable.bExposeOnSpawn = false;
|
|
SourceVariable.bPrivate = true;
|
|
SourceVariable.bReadOnly = true;
|
|
SourceVariable.bInstanced = false;
|
|
SourceVariable.bInstanceExposed = false;
|
|
|
|
WidgetUserPropertyCreated.Add(Widget->GetFName());
|
|
}
|
|
|
|
if (bIsComponentOnSelf)
|
|
{
|
|
CreateSourceForSelf();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case EMVVMBlueprintFieldPathSource::ViewModel:
|
|
{
|
|
const FMVVMBlueprintViewModelContext* SourceViewModelContext = Self->BlueprintView->FindViewModel(PropertyPathViewModelId);
|
|
if (SourceViewModelContext == nullptr)
|
|
{
|
|
return MakeError(FText::Format(LOCTEXT("BindingViewModelNotFound", "Could not find viewmodel with GUID {0}."), GetViewModelIdText(PropertyPathViewModelId)));
|
|
}
|
|
|
|
if (!ViewModelGuids.Contains(SourceViewModelContext->GetViewModelId()))
|
|
{
|
|
return MakeError(FText::Format(LOCTEXT("BindingViewModelInvalid", "Viewmodel {0} {1} was invalid."), SourceViewModelContext->GetDisplayName(), GetViewModelIdText(PropertyPathViewModelId)));
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
return MakeError(LOCTEXT("SourcePathNotSet", "A source path is required, but not set."));
|
|
}
|
|
return MakeValue();
|
|
};
|
|
|
|
// Find the start property for each path.
|
|
//The full path will be tested later. We want to build the list of property needed before generating the graphs.
|
|
for (const TSharedRef<FCompilerBinding>& ValidBinding : ValidBindings)
|
|
{
|
|
const FMVVMBlueprintViewBinding& Binding = *(BlueprintView->GetBindingAt(ValidBinding->Key.ViewBindingIndex));
|
|
|
|
auto RunGenerateCompilerSourceContext = [Self = this, &GenerateCompilerContext, &Binding](bool bCreateSource, const FMVVMBlueprintPropertyPath& PropertyPath, const FMVVMBlueprintPinId& PinId)
|
|
{
|
|
TValueOrError<void, FText> SourceContextResult = GenerateCompilerContext(bCreateSource, PropertyPath);
|
|
if (SourceContextResult.HasError())
|
|
{
|
|
Self->AddMessageForBinding(Binding, SourceContextResult.StealError(), Compiler::EMessageType::Error, PinId);
|
|
Self->bIsGatherGeneratedVariablesStepValid = false;
|
|
}
|
|
};
|
|
|
|
UMVVMBlueprintViewConversionFunction* ConversionFunction = Binding.Conversion.GetConversionFunction(ValidBinding->Key.bIsForwardBinding);
|
|
if (ConversionFunction)
|
|
{
|
|
// validate the sources
|
|
for (const FMVVMBlueprintPin& Pin : ConversionFunction->GetPins())
|
|
{
|
|
if (Pin.UsedPathAsValue())
|
|
{
|
|
if (!Pin.IsValid())
|
|
{
|
|
AddMessageForBinding(Binding
|
|
, FText::Format(LOCTEXT("InvalidFunctionArgumentPathName", "The conversion function {0} has an invalid argument."), FText::FromString(Binding.GetDisplayNameString(WidgetBlueprintCompilerContext.WidgetBlueprint())))
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId()
|
|
);
|
|
bIsGatherGeneratedVariablesStepValid = false;
|
|
continue;
|
|
}
|
|
RunGenerateCompilerSourceContext(true, Pin.GetPath(), Pin.GetId());
|
|
}
|
|
}
|
|
|
|
// validate the destination
|
|
const FMVVMBlueprintPropertyPath& BindingDestinationPath = ValidBinding->Key.bIsForwardBinding ? Binding.DestinationPath : Binding.SourcePath;
|
|
RunGenerateCompilerSourceContext(false, BindingDestinationPath, FMVVMBlueprintPinId());
|
|
}
|
|
else
|
|
{
|
|
// if we aren't using a conversion function, validate the source and destination paths
|
|
RunGenerateCompilerSourceContext(ValidBinding->Key.bIsForwardBinding, Binding.SourcePath, FMVVMBlueprintPinId());
|
|
RunGenerateCompilerSourceContext(!ValidBinding->Key.bIsForwardBinding, Binding.DestinationPath, FMVVMBlueprintPinId());
|
|
}
|
|
}
|
|
|
|
// Find the event property.
|
|
//All inputs must also exist (for the BP to compile).
|
|
for (TSharedRef<FCompilerEvent>& ValidEvent : ValidEvents)
|
|
{
|
|
UMVVMBlueprintViewEvent* EventPtr = ValidEvent->Event.Get();
|
|
check(EventPtr);
|
|
auto RunGenerateCompilerSourceContext = [Self = this, &GenerateCompilerContext, EventPtr](bool bCreateSource, const FMVVMBlueprintPropertyPath& PropertyPath, const FMVVMBlueprintPinId& PinId)
|
|
{
|
|
TValueOrError<void, FText> SourceContextResult = GenerateCompilerContext(bCreateSource, PropertyPath);
|
|
if (SourceContextResult.HasError())
|
|
{
|
|
Self->AddMessageForEvent(EventPtr, SourceContextResult.StealError(), Compiler::EMessageType::Error, PinId);
|
|
Self->bIsGatherGeneratedVariablesStepValid = false;
|
|
}
|
|
};
|
|
|
|
RunGenerateCompilerSourceContext(false, EventPtr->GetEventPath(), FMVVMBlueprintPinId());
|
|
RunGenerateCompilerSourceContext(false, EventPtr->GetDestinationPath(), FMVVMBlueprintPinId());
|
|
for (const FMVVMBlueprintPin& Pin : EventPtr->GetPins())
|
|
{
|
|
if (Pin.UsedPathAsValue())
|
|
{
|
|
if (!Pin.IsValid())
|
|
{
|
|
AddMessageForEvent(EventPtr
|
|
, LOCTEXT("InvalidEventArgumentPathName", "The event has an invalid argument.")
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId()
|
|
);
|
|
bIsGatherGeneratedVariablesStepValid = false;
|
|
continue;
|
|
}
|
|
RunGenerateCompilerSourceContext(true, Pin.GetPath(), Pin.GetId());
|
|
}
|
|
}
|
|
}
|
|
|
|
for (TSharedRef<FCompilerCondition>& ValidCondition : ValidConditions)
|
|
{
|
|
UMVVMBlueprintViewCondition* ConditionPtr = ValidCondition->Condition.Get();
|
|
check(ConditionPtr);
|
|
auto RunGenerateCompilerSourceContext = [Self = this, &GenerateCompilerContext, ConditionPtr](bool bCreateSource, const FMVVMBlueprintPropertyPath& PropertyPath, const FMVVMBlueprintPinId& PinId)
|
|
{
|
|
TValueOrError<void, FText> SourceContextResult = GenerateCompilerContext(bCreateSource, PropertyPath);
|
|
if (SourceContextResult.HasError())
|
|
{
|
|
Self->AddMessageForCondition(ConditionPtr, SourceContextResult.StealError(), Compiler::EMessageType::Error, PinId);
|
|
Self->bIsGatherGeneratedVariablesStepValid = false;
|
|
}
|
|
};
|
|
|
|
RunGenerateCompilerSourceContext(false, ConditionPtr->GetConditionPath(), FMVVMBlueprintPinId());
|
|
for (const FMVVMBlueprintPin& Pin : ConditionPtr->GetPins())
|
|
{
|
|
if (Pin.UsedPathAsValue())
|
|
{
|
|
if (!Pin.IsValid())
|
|
{
|
|
AddMessageForCondition(ConditionPtr
|
|
, LOCTEXT("InvalidConditionArgumentPathName", "The condition has an invalid argument.")
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId()
|
|
);
|
|
bIsGatherGeneratedVariablesStepValid = false;
|
|
continue;
|
|
}
|
|
RunGenerateCompilerSourceContext(true, Pin.GetPath(), Pin.GetId());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Extension can define properties.
|
|
for (TSharedRef<FCompilerExtension>& Extension : ValidExtensions)
|
|
{
|
|
UMVVMBlueprintViewExtension* ExtensionPtr = Extension->Extension.Get();
|
|
if (ensure(ExtensionPtr))
|
|
{
|
|
TArray<Compiler::FBlueprintViewUserWidgetProperty> Properties = ExtensionPtr->AddProperties();
|
|
for (const Compiler::FBlueprintViewUserWidgetProperty& Property : Properties)
|
|
{
|
|
FCompilerUserWidgetProperty& CompilerUserWidgetProperty = NeededUserWidgetProperties.AddDefaulted_GetRef();
|
|
|
|
CompilerUserWidgetProperty.AuthoritativeClass = Property.AuthoritativeClass;
|
|
CompilerUserWidgetProperty.Name = Property.Name;
|
|
CompilerUserWidgetProperty.DisplayName = Property.DisplayName;
|
|
CompilerUserWidgetProperty.bExposeOnSpawn = Property.bExposeOnSpawn;
|
|
CompilerUserWidgetProperty.bPrivate = Property.bPrivate;
|
|
CompilerUserWidgetProperty.bReadOnly = Property.bReadOnly;
|
|
CompilerUserWidgetProperty.CategoryName = Property.CategoryName;
|
|
CompilerUserWidgetProperty.Guid = Property.Guid;
|
|
CompilerUserWidgetProperty.CreationType = FCompilerUserWidgetProperty::ECreationType::CreateIfDoesntExist;
|
|
}
|
|
|
|
TArray<Compiler::FBlueprintViewUserWidgetWidgetProperty> WidgetProperties = ExtensionPtr->AddWidgetProperties();
|
|
for (const Compiler::FBlueprintViewUserWidgetWidgetProperty& WidgetProperty : WidgetProperties)
|
|
{
|
|
if (WidgetNameToWidgetPointerMap.Find(WidgetProperty.WidgetName))
|
|
{
|
|
FMVVMBlueprintPropertyPath WidgetPropertyPath;
|
|
WidgetPropertyPath.SetWidgetName(WidgetProperty.WidgetName);
|
|
TValueOrError<void, FText> SourceContextResult = GenerateCompilerContext(false, WidgetPropertyPath);
|
|
if (SourceContextResult.HasError())
|
|
{
|
|
AddMessage(SourceContextResult.StealError(), Compiler::EMessageType::Error);
|
|
bIsGatherGeneratedVariablesStepValid = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AddMessage(FText::Format(LOCTEXT("InvalidWidgetPropertyRequested", "Could not find the widget : {0} in the widget tree to make the property requested by viewmodel extension."), FText::FromName(WidgetProperty.WidgetName)), Compiler::EMessageType::Error);
|
|
bIsGatherGeneratedVariablesStepValid = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::CreatePublicFunctionsDeclaration(const FWidgetBlueprintCompilerContext::FCreateVariableContext& Context)
|
|
{
|
|
// 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.
|
|
for (UEdGraph* OldGraph : BlueprintView->TemporaryGraph)
|
|
{
|
|
if (OldGraph)
|
|
{
|
|
FunctionGraphHelper::RenameObjectToTransientPackage(OldGraph);
|
|
}
|
|
}
|
|
// During deserialization for undo/redo, the existing graphs can get renamed to leave room for the serialized objects with the same name. When this happens, we basically don't have
|
|
// any pointers left on the graphs and nobody can clean them anymore.
|
|
for (FName OldGraphName : BlueprintView->TemporaryGraphNames)
|
|
{
|
|
UEdGraph* OldGraph = static_cast<UEdGraph*>(StaticFindObjectFastSafe(UEdGraph::StaticClass(), WidgetBlueprintCompilerContext.Blueprint, OldGraphName));
|
|
if (OldGraph)
|
|
{
|
|
FunctionGraphHelper::RenameObjectToTransientPackage(OldGraph);
|
|
}
|
|
}
|
|
BlueprintView->TemporaryGraph.Reset();
|
|
BlueprintView->TemporaryGraphNames.Reset();
|
|
|
|
if (GetDefault<UMVVMDeveloperProjectSettings>()->bAllowGeneratedViewModelSetter)
|
|
{
|
|
for (FCompilerViewModelSetter& Setter : ViewModelSettersToGenerate)
|
|
{
|
|
ensure(Setter.SetterGraph == nullptr);
|
|
|
|
Setter.SetterGraph = UE::MVVM::FunctionGraphHelper::CreateIntermediateFunctionGraph(
|
|
WidgetBlueprintCompilerContext
|
|
, Setter.BlueprintSetter
|
|
, (FUNC_BlueprintCallable | FUNC_Public | FUNC_Final)
|
|
, TEXT("Viewmodel")
|
|
, false);
|
|
|
|
if (Setter.SetterGraph == nullptr || Setter.SetterGraph->GetFName() != FName(*Setter.BlueprintSetter))
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("SetterNameAlreadyExists", "The setter name {0} already exists and could not be autogenerated."),
|
|
FText::FromString(Setter.BlueprintSetter)
|
|
).ToString()
|
|
);
|
|
|
|
if (Setter.SetterGraph != nullptr)
|
|
{
|
|
FunctionGraphHelper::RenameObjectToTransientPackage(Setter.SetterGraph);
|
|
Setter.SetterGraph = nullptr;
|
|
}
|
|
bIsCreateVariableStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
Setter.SetterGraph->ClearFlags(RF_Transactional);
|
|
BlueprintView->TemporaryGraph.Add(Setter.SetterGraph);
|
|
FName GraphName = Setter.SetterGraph->GetFName();
|
|
BlueprintView->TemporaryGraphNames.Add(GraphName);
|
|
FunctionPermissionsToAdd.AddUnique(GraphName);
|
|
GeneratedFunctions.AddUnique(GraphName);
|
|
|
|
UE::MVVM::FunctionGraphHelper::AddFunctionArgument(Setter.SetterGraph, const_cast<UClass*>(Setter.Class), "Viewmodel");
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMVVMViewBlueprintCompiler::CreateFunctions(const FWidgetBlueprintCompilerContext::FCreateFunctionContext& Context)
|
|
{
|
|
if (!AreStepsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
SourceViewModelDynamicCreatorContexts.Reset();
|
|
|
|
CategorizeBindings(Context);
|
|
CategorizeEvents(Context);
|
|
CategorizeConditions(Context);
|
|
CreateWriteFieldContexts(Context);
|
|
CreateViewModelSetters(Context);
|
|
CreateIntermediateGraphFunctions(Context);
|
|
CategorizeAsyncFunctions(Context);
|
|
}
|
|
|
|
void FMVVMViewBlueprintCompiler::CategorizeBindings(const FWidgetBlueprintCompilerContext::FCreateFunctionContext& Context)
|
|
{
|
|
// Find the type of the bindings
|
|
for (TSharedRef<FCompilerBinding>& ValidBinding : ValidBindings)
|
|
{
|
|
FMVVMBlueprintViewBinding& Binding = *BlueprintView->GetBindingAt(ValidBinding->Key.ViewBindingIndex);
|
|
|
|
check(ValidBinding->Type == FCompilerBinding::EType::Unknown);
|
|
ValidBinding->Type = FCompilerBinding::EType::Invalid;
|
|
|
|
UMVVMBlueprintViewConversionFunction* ConversionFunction = Binding.Conversion.GetConversionFunction(ValidBinding->Key.bIsForwardBinding);
|
|
if (ConversionFunction)
|
|
{
|
|
UClass* NewClass = WidgetBlueprintCompilerContext.NewClass;
|
|
if (!ConversionFunction->IsValid(WidgetBlueprintCompilerContext.WidgetBlueprint()))
|
|
{
|
|
AddMessageForBinding(Binding, LOCTEXT("InvalidConversionFunction", "The conversion function is invalid."), Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
|
|
if (CVarLogInvalidBindingVerbose->GetBool())
|
|
{
|
|
FText ConversionFuncInfo = FText::Format(LOCTEXT("ConversionFuncInfo", "Conversion Function Info: Is Ubergraph: {0}, Conv Type: {1}, Conv Func: {2}")
|
|
, FText::FromString(ConversionFunction->IsUbergraphPage() ? "Y" : "N")
|
|
, FText::FromString(ConversionFunction->GetConversionFunction().GetType() == EMVVMBlueprintFunctionReferenceType::Function ? "Function" : "Node")
|
|
, FText::FromString(ConversionFunction->GetConversionFunction().ToString())
|
|
);
|
|
AddMessageForBinding(Binding, ConversionFuncInfo, Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
|
|
FText DeveloperSettingsInfo = FText::Format(LOCTEXT("DeveloperSettingsInfo", "Developer Settings Info: Filter Type: {0}")
|
|
, FText::FromString(GetDefault<UMVVMDeveloperProjectSettings>()->ConversionFunctionFilter == EMVVMDeveloperConversionFunctionFilterType::AllowedList ? "AllowedList" : "BlueprintActionRegistry")
|
|
);
|
|
AddMessageForBinding(Binding, DeveloperSettingsInfo, Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
|
|
for (const FSoftClassPath& AllowedClassForConversionFunction : GetDefault<UMVVMDeveloperProjectSettings>()->AllowedClassForConversionFunctions)
|
|
{
|
|
FText AllowedClass = FText::FromString("AllowedClass: " + AllowedClassForConversionFunction.ToString());
|
|
AddMessageForBinding(Binding, AllowedClass, Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
}
|
|
|
|
for (const FSoftClassPath& DeniedClassForConversionFunction : GetDefault<UMVVMDeveloperProjectSettings>()->DeniedClassForConversionFunctions)
|
|
{
|
|
FText DeniedClass = FText::FromString("DeniedClass: " + DeniedClassForConversionFunction.ToString());
|
|
AddMessageForBinding(Binding, DeniedClass, Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
}
|
|
|
|
for (const FName DeniedModuleForConversionFunction : GetDefault<UMVVMDeveloperProjectSettings>()->DeniedModuleForConversionFunctions)
|
|
{
|
|
FText DeniedModule = FText::FromString("DeniedModule: " + DeniedModuleForConversionFunction.ToString());
|
|
AddMessageForBinding(Binding, DeniedModule, Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
}
|
|
|
|
if (TSubclassOf<UK2Node> InvalidNode = ConversionFunction->GetConversionFunction().GetNode())
|
|
{
|
|
TStringBuilder<512> FunctionClassPath;
|
|
InvalidNode->GetPathName(nullptr, FunctionClassPath);
|
|
|
|
FText NodeInfo = FText::Format(LOCTEXT("NodeInfo", "Conversion Node Info: FName: {0}, ClassPathName: {1}, Module: {2}, FunctionClassPath: {3}")
|
|
, FText::FromString(InvalidNode->GetFName().ToString())
|
|
, FText::FromString(InvalidNode->GetClassPathName().ToString())
|
|
, FText::FromString(InvalidNode->GetClassPathName().GetPackageName().ToString())
|
|
, FText::FromString(FunctionClassPath.ToString())
|
|
);
|
|
AddMessageForBinding(Binding, NodeInfo, Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
}
|
|
}
|
|
|
|
bIsCreateFunctionsStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
// Make sure the graph is up to date
|
|
UEdGraph* WrapperGraph = ConversionFunction->GetOrCreateIntermediateWrapperGraph(WidgetBlueprintCompilerContext);
|
|
if (WrapperGraph == nullptr)
|
|
{
|
|
AddMessageForBinding(Binding, LOCTEXT("InvalidConversionFunctionGraph", "The conversion function graph could not be generated.")
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId()
|
|
);
|
|
bIsCreateFunctionsStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
ConversionFunction->UpdatePinValues(WidgetBlueprintCompilerContext.WidgetBlueprint());
|
|
|
|
if (ConversionFunction->HasOrphanedPin())
|
|
{
|
|
AddMessageForBinding(Binding, LOCTEXT("InvalidConversionFunctionGraphOrphaned", "The conversion function has an orphaned pin.")
|
|
, Compiler::EMessageType::Warning
|
|
, FMVVMBlueprintPinId()
|
|
);
|
|
bIsCreateFunctionsStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
if (ConversionFunction->GetPins().Num() == 0)
|
|
{
|
|
AddMessageForBinding(Binding
|
|
, FText::Format(LOCTEXT("InvalidNumberOfFunctionPin", "The conversion function {0} has no source."), FText::FromString(Binding.GetDisplayNameString(WidgetBlueprintCompilerContext.WidgetBlueprint())))
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId()
|
|
);
|
|
bIsCreateFunctionsStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
if (ConversionFunction->NeedsWrapperGraph(WidgetBlueprintCompilerContext.WidgetBlueprint()))
|
|
{
|
|
ValidBinding->Type = FCompilerBinding::EType::ComplexConversionFunction;
|
|
}
|
|
else
|
|
{
|
|
ValidBinding->Type = FCompilerBinding::EType::SimpleConversionFunction;
|
|
}
|
|
ValidBinding->ConversionFunction = ConversionFunction;
|
|
|
|
// Because the editor use the destination to order the bindings, the destination path can be "valid". Pointing only to the WidgetName.
|
|
//const FMVVMBlueprintPropertyPath& BindingSourcePath = ValidBinding->Key.bIsForwardBinding ? Binding.SourcePath : Binding.DestinationPath;
|
|
//if (BindingSourcePath.IsValid())
|
|
//{
|
|
// AddMessageForBinding(Binding, LOCTEXT("ShouldNotHaveSourceWarning", "Internal Error. The binding should not have a source."), EMessageType::Warning, FMVVMBlueprintPinId());
|
|
//}
|
|
}
|
|
else
|
|
{
|
|
ValidBinding->Type = FCompilerBinding::EType::Assignment;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMVVMViewBlueprintCompiler::CategorizeEvents(const FWidgetBlueprintCompilerContext::FCreateFunctionContext& Context)
|
|
{
|
|
TArray<TSharedRef<FCompilerEvent>> TemporaryEvent = MoveTemp(ValidEvents);
|
|
ValidEvents.Reset(TemporaryEvent.Num());
|
|
|
|
for (TSharedRef<FCompilerEvent>& Event : TemporaryEvent)
|
|
{
|
|
UMVVMBlueprintViewEvent* EventPtr = Event->Event.Get();
|
|
UEdGraph* WrapperGraph = EventPtr->GetOrCreateWrapperGraph();
|
|
if (WrapperGraph == nullptr)
|
|
{
|
|
AddMessageForEvent(EventPtr, LOCTEXT("InvalidEventGraph", "The event could not be generated."), Compiler::EMessageType::Warning, FMVVMBlueprintPinId());
|
|
bIsCreateFunctionsStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
EventPtr->UpdatePinValues();
|
|
|
|
if (EventPtr->HasOrphanedPin())
|
|
{
|
|
AddMessageForEvent(EventPtr, LOCTEXT("InvalidEventGraphOrphaned", "The event has an orphaned pin."), Compiler::EMessageType::Warning, FMVVMBlueprintPinId());
|
|
bIsCreateFunctionsStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
int32 Index = ValidEvents.Add(Event);
|
|
EventPtr->UpdateEventKey(FMVVMViewClass_EventKey(Index));
|
|
}
|
|
}
|
|
|
|
void FMVVMViewBlueprintCompiler::CategorizeConditions(const FWidgetBlueprintCompilerContext::FCreateFunctionContext& Context)
|
|
{
|
|
TArray<TSharedRef<FCompilerCondition>> TemporaryConditions = MoveTemp(ValidConditions);
|
|
ValidConditions.Reset(TemporaryConditions.Num());
|
|
|
|
for (TSharedRef<FCompilerCondition>& Condition : TemporaryConditions)
|
|
{
|
|
UMVVMBlueprintViewCondition* ConditionPtr = Condition->Condition.Get();
|
|
UEdGraph* WrapperGraph = ConditionPtr->GetOrCreateWrapperGraph();
|
|
if (WrapperGraph == nullptr)
|
|
{
|
|
AddMessageForCondition(ConditionPtr, LOCTEXT("InvalidConditionGraph", "The condition could not be generated."), Compiler::EMessageType::Warning, FMVVMBlueprintPinId());
|
|
bIsCreateFunctionsStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
ConditionPtr->UpdatePinValues();
|
|
|
|
if (ConditionPtr->HasOrphanedPin())
|
|
{
|
|
AddMessageForCondition(ConditionPtr, LOCTEXT("InvalidConditionGraphOrphaned", "The condition has an orphaned pin."), Compiler::EMessageType::Warning, FMVVMBlueprintPinId());
|
|
bIsCreateFunctionsStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
int32 Index = ValidConditions.Add(Condition);
|
|
ConditionPtr->UpdateConditionKey(FMVVMViewClass_ConditionKey(Index));
|
|
}
|
|
}
|
|
|
|
void FMVVMViewBlueprintCompiler::CreateWriteFieldContexts(const FWidgetBlueprintCompilerContext::FCreateFunctionContext& Context)
|
|
{
|
|
// 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());
|
|
bIsCreateFunctionsStepValid = false;
|
|
return;
|
|
}
|
|
|
|
for (TSharedRef<FCompilerBinding>& ValidBinding : ValidBindings)
|
|
{
|
|
const FMVVMBlueprintViewBinding& Binding = *BlueprintView->GetBindingAt(ValidBinding->Key.ViewBindingIndex);
|
|
|
|
if (ValidBinding->Type == FCompilerBinding::EType::Assignment
|
|
|| ValidBinding->Type == FCompilerBinding::EType::SimpleConversionFunction
|
|
|| ValidBinding->Type == FCompilerBinding::EType::ComplexConversionFunction)
|
|
{
|
|
const FMVVMBlueprintPropertyPath& DestinationPropertyPath = ValidBinding->Key.bIsForwardBinding ? Binding.DestinationPath : Binding.SourcePath;
|
|
TValueOrError<FCreateFieldsResult, FText> FieldContextResult = CreateFieldContext(NewSkeletonClass, DestinationPropertyPath, false);
|
|
if (FieldContextResult.HasError())
|
|
{
|
|
AddMessageForBinding(Binding, FieldContextResult.StealError(), Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
bIsCreateFunctionsStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
// Test if it already exist
|
|
TSharedPtr<FGeneratedWriteFieldPathContext> WriteFieldPath;
|
|
{
|
|
const TArray<UE::MVVM::FMVVMConstFieldVariant>& SkeletalGeneratedFieldsResult = FieldContextResult.GetValue().SkeletalGeneratedFields;
|
|
TSharedRef<FGeneratedWriteFieldPathContext>* Found = GeneratedWriteFieldPaths.FindByPredicate([&SkeletalGeneratedFieldsResult](const TSharedRef<FGeneratedWriteFieldPathContext>& Other)
|
|
{
|
|
return Other->SkeletalGeneratedFields == SkeletalGeneratedFieldsResult;
|
|
});
|
|
if (Found)
|
|
{
|
|
WriteFieldPath = *Found;
|
|
|
|
// Backward binding are reactive and are not trigger on initialization.
|
|
//It is valid for a backward binding to reuse the same write field path.
|
|
if (ValidBinding->Key.bIsForwardBinding)
|
|
{
|
|
AddMessageForBinding(Binding
|
|
, FText::Format(LOCTEXT("PropertyPathAlreadyUsed", "Note: the property path '{0}' is already used by another binding. Binding initialization order may not be deterministic."), PropertyPathToText(NewSkeletonClass, BlueprintView.Get(), DestinationPropertyPath))
|
|
, Compiler::EMessageType::Info
|
|
, FMVVMBlueprintPinId()
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WriteFieldPath = MakeWriteFieldPath(
|
|
FieldContextResult.GetValue().GeneratedFrom
|
|
, MoveTemp(FieldContextResult.GetValue().GeneratedFields)
|
|
, MoveTemp(FieldContextResult.GetValue().SkeletalGeneratedFields));
|
|
GeneratedWriteFieldPaths.Add(WriteFieldPath.ToSharedRef());
|
|
}
|
|
}
|
|
|
|
WriteFieldPath->UsedByBindings.AddUnique(ValidBinding);
|
|
WriteFieldPath->bUseByNativeBinding = true; // the setter function can be in BP but the destination will be set in native
|
|
|
|
// Assign the Destination to the binding
|
|
ValidBinding->WritePath = WriteFieldPath;
|
|
}
|
|
}
|
|
|
|
//The destination for event must also exist (for the BP to compile).
|
|
for (TSharedRef<FCompilerEvent>& ValidEvent : ValidEvents)
|
|
{
|
|
UMVVMBlueprintViewEvent* EventPtr = ValidEvent->Event.Get();
|
|
check(EventPtr);
|
|
|
|
TValueOrError<FCreateFieldsResult, FText> FieldContextResult = CreateFieldContext(NewSkeletonClass, EventPtr->GetDestinationPath(), false);
|
|
if (FieldContextResult.HasError())
|
|
{
|
|
AddMessageForEvent(EventPtr
|
|
, FText::Format(Private::PropertyPathIsInvalidFormat, PropertyPathToText(NewSkeletonClass, BlueprintView.Get(), EventPtr->GetDestinationPath()))
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId()
|
|
);
|
|
bIsCreateFunctionsStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
// Test if it already exist
|
|
TSharedPtr<FGeneratedWriteFieldPathContext> WriteFieldPath;
|
|
{
|
|
const TArray<UE::MVVM::FMVVMConstFieldVariant>& SkeletalGeneratedFieldsResult = FieldContextResult.GetValue().SkeletalGeneratedFields;
|
|
TSharedRef<FGeneratedWriteFieldPathContext>* Found = GeneratedWriteFieldPaths.FindByPredicate([&SkeletalGeneratedFieldsResult](const TSharedRef<FGeneratedWriteFieldPathContext>& Other) { return Other->SkeletalGeneratedFields == SkeletalGeneratedFieldsResult; });
|
|
if (Found)
|
|
{
|
|
WriteFieldPath = (*Found);
|
|
}
|
|
else
|
|
{
|
|
WriteFieldPath = MakeWriteFieldPath(
|
|
FieldContextResult.GetValue().GeneratedFrom
|
|
, MoveTemp(FieldContextResult.GetValue().GeneratedFields)
|
|
, MoveTemp(FieldContextResult.GetValue().SkeletalGeneratedFields));
|
|
GeneratedWriteFieldPaths.Add(WriteFieldPath.ToSharedRef());
|
|
}
|
|
}
|
|
|
|
WriteFieldPath->UsedByEvents.AddUnique(ValidEvent);
|
|
ValidEvent->WritePath = WriteFieldPath;
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::CreateViewModelSetters(const FWidgetBlueprintCompilerContext::FCreateFunctionContext& Context)
|
|
{
|
|
if (GetDefault<UMVVMDeveloperProjectSettings>()->bAllowGeneratedViewModelSetter)
|
|
{
|
|
for (FCompilerViewModelSetter& Setter : ViewModelSettersToGenerate)
|
|
{
|
|
if (ensure(Setter.SetterGraph != nullptr))
|
|
{
|
|
if (!UE::MVVM::FunctionGraphHelper::GenerateViewModelSetter(WidgetBlueprintCompilerContext, Setter.SetterGraph, Setter.PropertyName))
|
|
{
|
|
AddMessageForViewModel(Setter.DisplayName, LOCTEXT("SetterFunctionCouldNotBeGenerated", "The setter function could not be generated."), Compiler::EMessageType::Warning);
|
|
bIsCreateFunctionsStepValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::CreateIntermediateGraphFunctions(const FWidgetBlueprintCompilerContext::FCreateFunctionContext& Context)
|
|
{
|
|
// Add function to set destination if needed
|
|
for (TSharedRef<FGeneratedWriteFieldPathContext>& GeneratedDestination : GeneratedWriteFieldPaths)
|
|
{
|
|
// If the destination can't be set in cpp, we need to generate a BP function to set the value.
|
|
if (!GeneratedDestination->bCanBeSetInNative && GeneratedDestination->bUseByNativeBinding)
|
|
{
|
|
auto AddErrorMessage = [Self = this, &GeneratedDestination](const FText& ErrorMsg)
|
|
{
|
|
Self->AddMessages(GeneratedDestination->UsedByBindings, GeneratedDestination->UsedByEvents, ErrorMsg, Compiler::EMessageType::Error);
|
|
};
|
|
|
|
const FProperty* SetterProperty = nullptr;
|
|
if (ensure(GeneratedDestination->SkeletalGeneratedFields.Num() > 0 && GeneratedDestination->SkeletalGeneratedFields.Last().IsProperty()))
|
|
{
|
|
SetterProperty = GeneratedDestination->SkeletalGeneratedFields.Last().GetProperty();
|
|
}
|
|
|
|
if (SetterProperty == nullptr)
|
|
{
|
|
AddErrorMessage(FText::Format(LOCTEXT("CantGetSetter", "Internal Error. The setter function was not created. {0}"), ::UE::MVVM::FieldPathHelper::ToText(GeneratedDestination->GeneratedFields)));
|
|
bIsCreateFunctionsStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
AddErrorMessage(FText::Format(LOCTEXT("CantCreateSetter", "Internal Error. The setter function was not created. {0}"), ::UE::MVVM::FieldPathHelper::ToText(GeneratedDestination->GeneratedFields)));
|
|
bIsCreateFunctionsStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
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, GeneratedDestination->SkeletalGeneratedFields))
|
|
{
|
|
AddErrorMessage(FText::Format(LOCTEXT("CantGeneratedSetter", "Internal Error. The setter function was not generated. {0}"), ::UE::MVVM::FieldPathHelper::ToText(GeneratedDestination->GeneratedFields)));
|
|
bIsCreateFunctionsStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
// the new path can only be set later, once the function is compiled.
|
|
GeneratedDestination->GeneratedFunctionName = GeneratedSetterGraph->GetFName();
|
|
GeneratedFunctions.Add(GeneratedSetterGraph->GetFName());
|
|
}
|
|
}
|
|
|
|
// Add Generated Conversion functions to the blueprint
|
|
for (const TSharedRef<FCompilerBinding>& ValidBinding : ValidBindings)
|
|
{
|
|
FMVVMBlueprintViewBinding& Binding = *BlueprintView->GetBindingAt(ValidBinding->Key.ViewBindingIndex);
|
|
|
|
if (ValidBinding->Type == FCompilerBinding::EType::ComplexConversionFunction)
|
|
{
|
|
UMVVMBlueprintViewConversionFunction* ConversionFunction = Binding.Conversion.GetConversionFunction(ValidBinding->Key.bIsForwardBinding);
|
|
check(ConversionFunction);
|
|
|
|
UEdGraph* WrapperGraph = ConversionFunction->GetOrCreateIntermediateWrapperGraph(WidgetBlueprintCompilerContext);
|
|
if (ensure(WrapperGraph) && ConversionFunction->IsWrapperGraphTransient())
|
|
{
|
|
bool bAlreadyContained = WidgetBlueprintCompilerContext.Blueprint->FunctionGraphs.Contains(WrapperGraph);
|
|
if (ensure(!bAlreadyContained))
|
|
{
|
|
if (ConversionFunction->IsUbergraphPage())
|
|
{
|
|
// Calls to Latent Functions require a mapping to a stable, non-transient UObject for Latent Manager to use as UUID for execution
|
|
WidgetBlueprintCompilerContext.MessageLog.NotifyIntermediateObjectCreation(ConversionFunction->GetWrapperNode(), ConversionFunction->GetLatentNodeUUID());
|
|
Context.AddGeneratedUbergraphPage(WrapperGraph);
|
|
}
|
|
else
|
|
{
|
|
Context.AddGeneratedFunctionGraph(WrapperGraph);
|
|
}
|
|
}
|
|
|
|
if (ConversionFunction->IsUbergraphPage())
|
|
{
|
|
// Append the delegate signature suffix, note that we can't name the graph this as the delegate itself uses this name.
|
|
GeneratedFunctions.Add(UE::MVVM::BindingHelper::GetDelegateSignatureName(WrapperGraph->GetFName()));
|
|
}
|
|
else
|
|
{
|
|
GeneratedFunctions.Add(WrapperGraph->GetFName());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add Generated event to the blueprint
|
|
for (TSharedRef<FCompilerEvent>& Event : ValidEvents)
|
|
{
|
|
UMVVMBlueprintViewEvent* EventPtr = Event->Event.Get();
|
|
UEdGraph* WrapperGraph = EventPtr->GetOrCreateWrapperGraph();
|
|
if (ensure(WrapperGraph))
|
|
{
|
|
bool bAlreadyContained = WidgetBlueprintCompilerContext.Blueprint->FunctionGraphs.Contains(WrapperGraph);
|
|
if (ensure(!bAlreadyContained))
|
|
{
|
|
Context.AddGeneratedFunctionGraph(WrapperGraph);
|
|
GeneratedFunctions.Add(WrapperGraph->GetFName());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add Generated condition to the blueprint
|
|
for (TSharedRef<FCompilerCondition>& Condition : ValidConditions)
|
|
{
|
|
UMVVMBlueprintViewCondition* ConditionPtr = Condition->Condition.Get();
|
|
UEdGraph* WrapperGraph = ConditionPtr->GetOrCreateWrapperGraph();
|
|
if (ensure(WrapperGraph))
|
|
{
|
|
bool bAlreadyContained = WidgetBlueprintCompilerContext.Blueprint->FunctionGraphs.Contains(WrapperGraph);
|
|
if (ensure(!bAlreadyContained))
|
|
{
|
|
Context.AddGeneratedFunctionGraph(WrapperGraph);
|
|
GeneratedFunctions.Add(WrapperGraph->GetFName());
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void FMVVMViewBlueprintCompiler::CategorizeAsyncFunctions(const FWidgetBlueprintCompilerContext::FCreateFunctionContext& Context)
|
|
{
|
|
// Populate source binding keys for async functions
|
|
int32 Index = 0;
|
|
for (const TSharedRef<FCompilerBinding>& ValidBinding : ValidBindings)
|
|
{
|
|
FMVVMBlueprintViewBinding& Binding = *BlueprintView->GetBindingAt(ValidBinding->Key.ViewBindingIndex);
|
|
|
|
if (ValidBinding->Type == FCompilerBinding::EType::ComplexConversionFunction)
|
|
{
|
|
UMVVMBlueprintViewConversionFunction* ConversionFunction = Binding.Conversion.GetConversionFunction(ValidBinding->Key.bIsForwardBinding);
|
|
if (ensure(ConversionFunction) && ConversionFunction->IsUbergraphPage())
|
|
{
|
|
UEdGraph* WrapperGraph = ConversionFunction->GetOrCreateIntermediateWrapperGraph(WidgetBlueprintCompilerContext);
|
|
if (ensure(WrapperGraph))
|
|
{
|
|
TArray<UMVVMK2Node_AreSourcesValidForBinding*> AreSourcesValidNodes;
|
|
WrapperGraph->GetNodesOfClass<UMVVMK2Node_AreSourcesValidForBinding>(AreSourcesValidNodes);
|
|
|
|
// Like for events, this is a temp index to avoid log errors. The real index will get set after compilation
|
|
for (UMVVMK2Node_AreSourcesValidForBinding* AreSourcesValidNode : AreSourcesValidNodes)
|
|
{
|
|
AreSourcesValidNode->BindingKey = FMVVMViewClass_BindingKey(Index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Index++;
|
|
}
|
|
}
|
|
|
|
|
|
bool FMVVMViewBlueprintCompiler::PreCompile(UWidgetBlueprintGeneratedClass* Class)
|
|
{
|
|
if (!AreStepsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FixCompilerBindingSelfSource(Class);
|
|
AddWarningForPropertyWithMVVMAndLegacyBinding(Class);
|
|
|
|
GeneratedReadFieldPaths.Reset();
|
|
CreateReadFieldContexts(Class);
|
|
CreateCreatorContentFromBindingSource(Class);
|
|
|
|
// NB. The dynamic sources are created.
|
|
FixFieldPathContext(Class);
|
|
|
|
if (!AreStepsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
PreCompileViewModelCreatorContexts(Class);
|
|
PreCompileBindings(Class);
|
|
PreCompileEvents(Class);
|
|
PreCompileConditions(Class);
|
|
PreCompileViewExtensions(Class);
|
|
PreCompileSourceDependencies(Class);
|
|
|
|
return AreStepsValid();
|
|
}
|
|
|
|
|
|
bool FMVVMViewBlueprintCompiler::Compile(UWidgetBlueprintGeneratedClass* Class, UMVVMViewClass* ViewExtension)
|
|
{
|
|
if (!AreStepsValid())
|
|
{
|
|
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;
|
|
}
|
|
CompileSources(CompileResult.GetValue(), Class, ViewExtension);
|
|
CompileBindings(CompileResult.GetValue(), Class, ViewExtension);
|
|
CompileEvaluateSources(CompileResult.GetValue(), Class, ViewExtension);
|
|
CompileEvents(CompileResult.GetValue(), Class, ViewExtension);
|
|
CompileConditions(CompileResult.GetValue(), Class, ViewExtension);
|
|
CompileViewExtensions(CompileResult.GetValue(), Class, ViewExtension);
|
|
SortSourceFields(CompileResult.GetValue(), Class, ViewExtension);
|
|
|
|
{
|
|
ViewExtension->bInitializeSourcesOnConstruct = BlueprintView->GetSettings()->bInitializeSourcesOnConstruct;
|
|
ViewExtension->bInitializeBindingsOnConstruct = ViewExtension->bInitializeSourcesOnConstruct ? BlueprintView->GetSettings()->bInitializeBindingsOnConstruct : false;
|
|
ViewExtension->bInitializeEventsOnConstruct = BlueprintView->GetSettings()->bInitializeEventsOnConstruct;
|
|
ViewExtension->bListenToViewModelCollectionChanged = Algo::AnyOf(ViewExtension->Sources, [](const FMVVMViewClass_Source& Source){ return Source.RequireGlobalViewModelCollectionUpdate(); });
|
|
ViewExtension->bCreateViewWithoutBindings = BlueprintView->GetSettings()->bCreateViewWithoutBindings;
|
|
}
|
|
{
|
|
ViewExtension->OptionalSources = 0;
|
|
for (int32 Index = 0; Index < ViewExtension->Sources.Num(); ++Index)
|
|
{
|
|
const FMVVMViewClass_Source& Source = ViewExtension->Sources[Index];
|
|
if (Source.IsOptional())
|
|
{
|
|
FMVVMViewClass_SourceKey ClassSourceKey = FMVVMViewClass_SourceKey(Index);
|
|
ViewExtension->OptionalSources |= ClassSourceKey.GetBit();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bResult = AreStepsValid();
|
|
if (bResult)
|
|
{
|
|
ViewExtension->BindingLibrary = MoveTemp(CompileResult.GetValue().Library);
|
|
|
|
#if UE_WITH_MVVM_DEBUGGING
|
|
if (CVarLogViewCompiledResult->GetBool())
|
|
{
|
|
UMVVMViewClass::FToStringArgs ToStringArgs = UMVVMViewClass::FToStringArgs::All();
|
|
ToStringArgs.Source.bUseDisplayName = false;
|
|
ToStringArgs.Binding.bUseDisplayName = false;
|
|
ToStringArgs.Evaluate.bUseDisplayName = false;
|
|
ToStringArgs.Event.bUseDisplayName = false;
|
|
UE_LOG(LogMVVM, Log, TEXT("%s"), *ViewExtension->ToString(ToStringArgs));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return AreStepsValid();
|
|
}
|
|
|
|
|
|
TArray<FName> FMVVMViewBlueprintCompiler::GetGeneratedFunctions() const
|
|
{
|
|
return GeneratedFunctions;
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::FixFieldPathContext(UWidgetBlueprintGeneratedClass* Class)
|
|
{
|
|
TSharedRef<FCompilerBindingSource>* SelfSourcePtr = NeededBindingSources.FindByPredicate([](const TSharedRef<FCompilerBindingSource>& Other)
|
|
{
|
|
return Other->Type == FCompilerBindingSource::EType::Self;
|
|
});
|
|
|
|
auto FindProperty = [](FMVVMConstFieldVariant Field) -> const FProperty*
|
|
{
|
|
if (Field.IsProperty())
|
|
{
|
|
return Field.GetProperty();
|
|
}
|
|
else if (Field.IsFunction() && Field.GetFunction())
|
|
{
|
|
return BindingHelper::GetReturnProperty(Field.GetFunction());
|
|
}
|
|
return nullptr;
|
|
};
|
|
|
|
auto FindDynamicCreatorContext = [Self = this, &FindProperty](TArray<UE::MVVM::FMVVMConstFieldVariant>& GeneratedFields, int32 Index, TSharedPtr<FCompilerBindingSource>& InOutSource) -> bool
|
|
{
|
|
bool bBreak = true;
|
|
|
|
UE::MVVM::FMVVMConstFieldVariant NextField = GeneratedFields[Index];
|
|
if (const FObjectProperty* FieldObjectProperty = CastField<FObjectProperty>(FindProperty(NextField)))
|
|
{
|
|
const UClass* NextFieldObjectPtrClass = FieldObjectProperty->PropertyClass;
|
|
for (const TSharedRef<FCompilerSourceViewModelDynamicCreatorContext>& DynamicCreatorContext : Self->SourceViewModelDynamicCreatorContexts)
|
|
{
|
|
if (DynamicCreatorContext->ParentSource == InOutSource
|
|
&& DynamicCreatorContext->NotificationId.GetFieldName() == NextField.GetName()
|
|
&& DynamicCreatorContext->Source->AuthoritativeClass == NextFieldObjectPtrClass)
|
|
{
|
|
InOutSource = DynamicCreatorContext->Source;
|
|
bBreak = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bBreak;
|
|
};
|
|
|
|
//// Sanity check. Set Source to FGeneratedReadFieldPathContext
|
|
//{
|
|
// for (TSharedRef<FGeneratedReadFieldPathContext>& GeneratedRead : GeneratedReadFieldPaths)
|
|
// {
|
|
// // ReadFieldPath should already be valid. Only confirm it here.
|
|
// TSharedPtr<FCompilerBindingSource> NewSource = SelfSourcePtr ? *SelfSourcePtr : TSharedPtr<FCompilerBindingSource>();
|
|
// if (GeneratedRead->GeneratedFields.Num() > 0)
|
|
// {
|
|
// UE::MVVM::FMVVMConstFieldVariant NextField = GeneratedRead->GeneratedFields[0];
|
|
// if (const FObjectProperty* FieldObjectProperty = CastField<FObjectProperty>(FindProperty(NextField)))
|
|
// {
|
|
// UClass* NextFieldObjectPtrClass = FieldObjectProperty->PropertyClass;
|
|
// TSharedRef<FCompilerBindingSource>* BindingSource = NeededBindingSources.FindByPredicate(
|
|
// [NextField, NextFieldObjectPtrClass](TSharedRef<FCompilerBindingSource>& Other)
|
|
// {
|
|
// return Other->Name == NextField.GetName()
|
|
// && NextFieldObjectPtrClass == Other->AuthoritativeClass;
|
|
// }
|
|
// );
|
|
|
|
// if (BindingSource)
|
|
// {
|
|
// NewSource = *BindingSource;
|
|
// for (int32 GeneratedFieldIndex = 1; GeneratedFieldIndex < GeneratedRead->GeneratedFields.Num() - 1; ++GeneratedFieldIndex)
|
|
// {
|
|
// if (FindDynamicCreatorContext(GeneratedRead->GeneratedFields, GeneratedFieldIndex, NewSource))
|
|
// {
|
|
// break;
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// if (GeneratedRead->Source != NewSource)
|
|
// {
|
|
// // We only do this test as a sanity check and because it's the same algo for FGeneratedWriteFieldPathContext
|
|
// AddMessage(FText::Format(LOCTEXT("InternalErrorNotSameSource", "Internal error. The read path {0} source do not matches with the previous calculated source."), ::UE::MVVM::FieldPathHelper::ToText(GeneratedRead->GeneratedFields))
|
|
// , EMessageType::Info
|
|
// );
|
|
// }
|
|
// }
|
|
//}
|
|
|
|
// Set OptionalSource and OptionalDependencySource to FGeneratedWriteFieldPathContext
|
|
for (TSharedRef<FGeneratedWriteFieldPathContext>& GeneratedDestination : GeneratedWriteFieldPaths)
|
|
{
|
|
//ViewmodelA.ViewmodelB = ViewmodelC.Value -> ViewmodelA needs to init before ViewmodelA_ViewmodelB, ViewmodelA before ViewmodelC, ViewmodelC before ViewmodelA_ViewmodelB
|
|
// OptionSource = ViewmodelA
|
|
// OptionalDependencySource = ViewmodelA_ViewmodelB. We do not have the info for ViewmodelC (only in the binding) and there could be more than one read path. Set the flag and add the dependency later.
|
|
//ViewmodelA.ViewmodelB.Value = ViewmodelC.Value -> ViewmodelA needs to init before ViewmodelA_ViewmodelB, ViewmodelA before ViewmodelC, ViewmodelA_ViewmodelB before ViewmodelC
|
|
// OptionSource = ViewmodelA_ViewmodelB
|
|
// OptionalDependencySource = null. We set a value, not a source.
|
|
|
|
if (GeneratedDestination->GeneratedFields.Num() > 0)
|
|
{
|
|
UE::MVVM::FMVVMConstFieldVariant NextField = GeneratedDestination->GeneratedFields[0];
|
|
if (const FObjectProperty* FieldObjectProperty = CastField<FObjectProperty>(FindProperty(NextField)))
|
|
{
|
|
const UClass* NextFieldObjectPtrClass = FieldObjectProperty->PropertyClass;
|
|
TSharedRef<FCompilerBindingSource>* BindingSource = NeededBindingSources.FindByPredicate(
|
|
[NextField, NextFieldObjectPtrClass](TSharedRef<FCompilerBindingSource>& Other)
|
|
{
|
|
return Other->Name == NextField.GetName()
|
|
&& NextFieldObjectPtrClass == Other->AuthoritativeClass;
|
|
}
|
|
);
|
|
|
|
if (BindingSource)
|
|
{
|
|
if (GeneratedDestination->GeneratedFields.Num() > 1)
|
|
{
|
|
GeneratedDestination->OptionalSource = *BindingSource;
|
|
int32 GeneratedFieldIndex = 1;
|
|
for (; GeneratedFieldIndex < GeneratedDestination->GeneratedFields.Num() - 1; ++GeneratedFieldIndex)
|
|
{
|
|
if (FindDynamicCreatorContext(GeneratedDestination->GeneratedFields, GeneratedFieldIndex, GeneratedDestination->OptionalSource))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (GeneratedFieldIndex == GeneratedDestination->GeneratedFields.Num() - 1)
|
|
{
|
|
FindDynamicCreatorContext(GeneratedDestination->GeneratedFields, GeneratedFieldIndex, GeneratedDestination->OptionalDependencySource);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GeneratedDestination->OptionalSource = SelfSourcePtr ? *SelfSourcePtr : TSharedPtr<FCompilerBindingSource>();
|
|
GeneratedDestination->OptionalDependencySource = *BindingSource;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If a graph was generated for the FGeneratedWriteFieldPathContext, then use it instead for the SkeletalGeneratedFields
|
|
for (TSharedRef<FGeneratedWriteFieldPathContext>& GeneratedDestination : GeneratedWriteFieldPaths)
|
|
{
|
|
if (!GeneratedDestination->GeneratedFunctionName.IsNone())
|
|
{
|
|
UFunction* GeneratedFunction = Class->FindFunctionByName(GeneratedDestination->GeneratedFunctionName);
|
|
if (GeneratedFunction == nullptr)
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*LOCTEXT("CantFindGeneratedBindingSetterFunction", "Internal Error. The setter function was not generated.").ToString());
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
GeneratedDestination->GeneratedFields.Reset();
|
|
GeneratedDestination->GeneratedFields.Add(UE::MVVM::FMVVMConstFieldVariant(GeneratedFunction));
|
|
// note the function is not added on the skeletal class
|
|
GeneratedDestination->SkeletalGeneratedFields.Reset();
|
|
GeneratedDestination->SkeletalGeneratedFields.Add(UE::MVVM::FMVVMConstFieldVariant(GeneratedFunction));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::CreateReadFieldContexts(UWidgetBlueprintGeneratedClass* Class)
|
|
{
|
|
auto AlreadyExist = [Self = this](const TArray<UE::MVVM::FMVVMConstFieldVariant>& SkeletalGeneratedFields) -> TSharedPtr<FGeneratedReadFieldPathContext>
|
|
{
|
|
// Test if it already exist
|
|
TSharedRef<FGeneratedReadFieldPathContext>* Found = Self->GeneratedReadFieldPaths.FindByPredicate([&SkeletalGeneratedFields](const TSharedRef<FGeneratedReadFieldPathContext>& Other) { return Other->SkeletalGeneratedFields == SkeletalGeneratedFields; });
|
|
return Found != nullptr ? *Found : TSharedPtr<FGeneratedReadFieldPathContext>();
|
|
};
|
|
|
|
for (TSharedRef<FCompilerBinding>& ValidBinding : ValidBindings)
|
|
{
|
|
const FMVVMBlueprintViewBinding& Binding = *BlueprintView->GetBindingAt(ValidBinding->Key.ViewBindingIndex);
|
|
|
|
auto CreateBindingSourceContext = [Self = this, Class, &ValidBinding, &Binding](const FMVVMBlueprintPropertyPath& PropertyPath, const FMVVMBlueprintPinId& PinId) -> TValueOrError<FCreateFieldsResult, void>
|
|
{
|
|
TValueOrError<FCreateFieldsResult, FText> FieldContextResult = Self->CreateFieldContext(Class, PropertyPath, true);
|
|
if (FieldContextResult.HasError())
|
|
{
|
|
Self->AddMessageForBinding(Binding
|
|
, FText::Format(Private::PropertyPathIsInvalidFormat, PropertyPathToText(Class, Self->BlueprintView.Get(), PropertyPath))
|
|
, Compiler::EMessageType::Error
|
|
, PinId
|
|
);
|
|
Self->bIsPreCompileStepValid = false;
|
|
return MakeError();
|
|
}
|
|
|
|
return MakeValue(FieldContextResult.StealValue());
|
|
};
|
|
|
|
auto CreateFieldId = [Self = this, Class, &Binding, bIsOneTimeBinding = ValidBinding->bIsOneTimeBinding](const FMVVMBlueprintPropertyPath& PropertyPath, TSharedPtr<FGeneratedReadFieldPathContext>& ReadFieldContext, const FMVVMBlueprintPinId& PinId) -> TValueOrError<void, void>
|
|
{
|
|
if (!ReadFieldContext->NotificationField.IsValid())
|
|
{
|
|
TValueOrError<TSharedPtr<FCompilerNotifyFieldId>, FText> CreateFieldResult = Self->CreateNotifyFieldId(Class, ReadFieldContext);
|
|
if (CreateFieldResult.HasError())
|
|
{
|
|
Self->AddMessageForBinding(Binding
|
|
, FText::Format(LOCTEXT("CreateNotifyFieldIdFailedInvalidSelfContext", "The property path '{0}' is invalid. {1}"), PropertyPathToText(Class, Self->BlueprintView.Get(), PropertyPath), CreateFieldResult.StealError())
|
|
, Compiler::EMessageType::Error
|
|
, PinId
|
|
);
|
|
return MakeError();
|
|
}
|
|
|
|
if (CreateFieldResult.GetValue())
|
|
{
|
|
// Sanity check
|
|
{
|
|
// if there is a FieldId associated with the read property
|
|
if (CreateFieldResult.GetValue()->Source)
|
|
{
|
|
EMVVMBlueprintFieldPathSource PathSource = ReadFieldContext->GeneratedFrom;
|
|
bool bValidViewModel = CreateFieldResult.GetValue()->Source->Type == FCompilerBindingSource::EType::ViewModel && PathSource == EMVVMBlueprintFieldPathSource::ViewModel;
|
|
bool bValidWidget = CreateFieldResult.GetValue()->Source->Type == FCompilerBindingSource::EType::Widget && PathSource == EMVVMBlueprintFieldPathSource::Widget;
|
|
bool bDynamic = CreateFieldResult.GetValue()->Source->Type == FCompilerBindingSource::EType::DynamicViewmodel;
|
|
bool bSelf = CreateFieldResult.GetValue()->Source->Type == FCompilerBindingSource::EType::Self && PathSource == EMVVMBlueprintFieldPathSource::SelfContext;
|
|
bool bIsComponentOnSelf = false;
|
|
if (ReadFieldContext->bIsComponent)
|
|
{
|
|
const UWidgetBlueprintGeneratedClass* ComponentOwningWidgetBlueprint = UE::MVVM::FieldPathHelper::GetComponentPropertyPathSource(ReadFieldContext->SkeletalGeneratedFields, Class);
|
|
bIsComponentOnSelf = ComponentOwningWidgetBlueprint == Class;
|
|
}
|
|
bIsComponentOnSelf = bIsComponentOnSelf && CreateFieldResult.GetValue()->Source->Type == FCompilerBindingSource::EType::Self && PathSource == EMVVMBlueprintFieldPathSource::Widget;
|
|
if (!(bValidViewModel || bValidWidget || bDynamic || bSelf || bIsComponentOnSelf))
|
|
{
|
|
Self->AddMessageForBinding(Binding
|
|
, FText::Format(LOCTEXT("CreateNotifyFieldIdFailedInvalidInvalidContext", "Internal error. The property path '{0}' is invalid. The context is invalid."), PropertyPathToText(Class, Self->BlueprintView.Get(), PropertyPath))
|
|
, Compiler::EMessageType::Error
|
|
, PinId
|
|
);
|
|
return MakeError();
|
|
}
|
|
}
|
|
}
|
|
|
|
ReadFieldContext->NotificationField = CreateFieldResult.GetValue();
|
|
ReadFieldContext->Source = ReadFieldContext->NotificationField->Source;
|
|
}
|
|
}
|
|
return MakeValue();
|
|
};
|
|
|
|
if (ValidBinding->Type == FCompilerBinding::EType::Assignment)
|
|
{
|
|
const FMVVMBlueprintPropertyPath& BindingSourcePath = ValidBinding->Key.bIsForwardBinding ? Binding.SourcePath : Binding.DestinationPath;
|
|
TValueOrError<FCreateFieldsResult, void> CreateSourceResult = CreateBindingSourceContext(BindingSourcePath, FMVVMBlueprintPinId());
|
|
if (CreateSourceResult.HasError())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TSharedPtr<FGeneratedReadFieldPathContext> Found = AlreadyExist(CreateSourceResult.GetValue().SkeletalGeneratedFields);
|
|
if (!Found)
|
|
{
|
|
Found = MakeShared<FGeneratedReadFieldPathContext>();
|
|
Found->Source = MoveTemp(CreateSourceResult.GetValue().OptionalSource);
|
|
Found->GeneratedFields = MoveTemp(CreateSourceResult.GetValue().GeneratedFields);
|
|
Found->SkeletalGeneratedFields = MoveTemp(CreateSourceResult.GetValue().SkeletalGeneratedFields);
|
|
Found->GeneratedFrom = MoveTemp(CreateSourceResult.GetValue().GeneratedFrom);
|
|
Found->bIsComponent = MoveTemp(CreateSourceResult.GetValue().bIsComponent);
|
|
|
|
if (CreateFieldId(BindingSourcePath, Found, FMVVMBlueprintPinId()).HasError())
|
|
{
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
GeneratedReadFieldPaths.Add(Found.ToSharedRef());
|
|
}
|
|
|
|
Found->UsedByBindings.AddUnique(ValidBinding);
|
|
ValidBinding->ReadPaths.Add(Found.ToSharedRef());
|
|
}
|
|
else if (ValidBinding->Type == FCompilerBinding::EType::SimpleConversionFunction
|
|
|| ValidBinding->Type == FCompilerBinding::EType::ComplexConversionFunction)
|
|
{
|
|
UMVVMBlueprintViewConversionFunction* ConversionFunction = Binding.Conversion.GetConversionFunction(ValidBinding->Key.bIsForwardBinding);
|
|
if (ensure(ConversionFunction))
|
|
{
|
|
for (const FMVVMBlueprintPin& Pin : ConversionFunction->GetPins())
|
|
{
|
|
if (Pin.UsedPathAsValue())
|
|
{
|
|
TValueOrError<FCreateFieldsResult, void> CreateSourceResult = CreateBindingSourceContext(Pin.GetPath(), Pin.GetId());
|
|
if (CreateSourceResult.HasError())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TSharedPtr<FGeneratedReadFieldPathContext> Found = AlreadyExist(CreateSourceResult.GetValue().SkeletalGeneratedFields);
|
|
if (!Found)
|
|
{
|
|
Found = MakeShared<FGeneratedReadFieldPathContext>();
|
|
Found->Source = MoveTemp(CreateSourceResult.GetValue().OptionalSource);
|
|
Found->GeneratedFields = MoveTemp(CreateSourceResult.GetValue().GeneratedFields);
|
|
Found->SkeletalGeneratedFields = MoveTemp(CreateSourceResult.GetValue().SkeletalGeneratedFields);
|
|
Found->GeneratedFrom = MoveTemp(CreateSourceResult.GetValue().GeneratedFrom);
|
|
Found->bIsComponent = MoveTemp(CreateSourceResult.GetValue().bIsComponent);
|
|
|
|
if (CreateFieldId(Pin.GetPath(), Found, Pin.GetId()).HasError())
|
|
{
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
GeneratedReadFieldPaths.Add(Found.ToSharedRef());
|
|
}
|
|
|
|
Found->UsedByBindings.AddUnique(TWeakPtr<FCompilerBinding>(ValidBinding));
|
|
ValidBinding->ReadPaths.Add(Found.ToSharedRef());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//The pins for event
|
|
for (TSharedRef<FCompilerEvent>& ValidEvent : ValidEvents)
|
|
{
|
|
UMVVMBlueprintViewEvent* EventPtr = ValidEvent->Event.Get();
|
|
check(EventPtr);
|
|
for (const FMVVMBlueprintPin& Pin : EventPtr->GetPins())
|
|
{
|
|
if (Pin.UsedPathAsValue())
|
|
{
|
|
TValueOrError<FCreateFieldsResult, FText> CreateSourceResult = CreateFieldContext(Class, Pin.GetPath(), true);
|
|
if (CreateSourceResult.HasError())
|
|
{
|
|
AddMessageForEvent(ValidEvent
|
|
, FText::Format(Private::PropertyPathIsInvalidFormat, PropertyPathToText(Class, BlueprintView.Get(), Pin.GetPath()))
|
|
, Compiler::EMessageType::Error
|
|
, Pin.GetId()
|
|
);
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
TSharedPtr<FGeneratedReadFieldPathContext> Found = AlreadyExist(CreateSourceResult.GetValue().SkeletalGeneratedFields);
|
|
if (!Found)
|
|
{
|
|
Found = MakeShared<FGeneratedReadFieldPathContext>();
|
|
Found->Source = MoveTemp(CreateSourceResult.GetValue().OptionalSource);
|
|
Found->GeneratedFields = MoveTemp(CreateSourceResult.GetValue().GeneratedFields);
|
|
Found->GeneratedFrom = MoveTemp(CreateSourceResult.GetValue().GeneratedFrom);
|
|
Found->SkeletalGeneratedFields = MoveTemp(CreateSourceResult.GetValue().SkeletalGeneratedFields);
|
|
Found->bIsComponent = MoveTemp(CreateSourceResult.GetValue().bIsComponent);
|
|
GeneratedReadFieldPaths.Add(Found.ToSharedRef());
|
|
}
|
|
|
|
Found->UsedByEvents.AddUnique(ValidEvent);
|
|
ValidEvent->ReadPaths.Add(Found.ToSharedRef());
|
|
}
|
|
}
|
|
}
|
|
|
|
//The pins for condition
|
|
for (TSharedRef<FCompilerCondition>& ValidCondition : ValidConditions)
|
|
{
|
|
UMVVMBlueprintViewCondition* ConditionPtr = ValidCondition->Condition.Get();
|
|
check(ConditionPtr);
|
|
|
|
{
|
|
TValueOrError<FCreateFieldsResult, FText> CreateConditionSourceResult = CreateFieldContext(Class, ConditionPtr->GetConditionPath(), true);
|
|
if (CreateConditionSourceResult.HasError())
|
|
{
|
|
AddMessageForCondition(ValidCondition
|
|
, FText::Format(Private::PropertyPathIsInvalidFormat, PropertyPathToText(Class, BlueprintView.Get(), ConditionPtr->GetConditionPath()))
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId());
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
TSharedPtr<FGeneratedReadFieldPathContext> FoundReadFieldPath = AlreadyExist(CreateConditionSourceResult.GetValue().SkeletalGeneratedFields);
|
|
if (!FoundReadFieldPath)
|
|
{
|
|
FoundReadFieldPath = MakeShared<FGeneratedReadFieldPathContext>();
|
|
FoundReadFieldPath->Source = MoveTemp(CreateConditionSourceResult.GetValue().OptionalSource);
|
|
FoundReadFieldPath->GeneratedFrom = MoveTemp(CreateConditionSourceResult.GetValue().GeneratedFrom);
|
|
FoundReadFieldPath->GeneratedFields = MoveTemp(CreateConditionSourceResult.GetValue().GeneratedFields);
|
|
FoundReadFieldPath->SkeletalGeneratedFields = MoveTemp(CreateConditionSourceResult.GetValue().SkeletalGeneratedFields);
|
|
FoundReadFieldPath->bIsComponent = MoveTemp(CreateConditionSourceResult.GetValue().bIsComponent);
|
|
|
|
GeneratedReadFieldPaths.Add(FoundReadFieldPath.ToSharedRef());
|
|
}
|
|
|
|
if (!FoundReadFieldPath->NotificationField.IsValid())
|
|
{
|
|
TValueOrError<TSharedPtr<FCompilerNotifyFieldId>, FText> CreateFieldResult = CreateNotifyFieldId(Class, FoundReadFieldPath);
|
|
if (CreateFieldResult.HasError())
|
|
{
|
|
AddMessageForCondition(ValidCondition
|
|
, FText::Format(LOCTEXT("CreateNotifyFieldIdFailedInvalidSelfContext", "The property path '{0}' is invalid. {1}"), PropertyPathToText(Class, BlueprintView.Get(), ConditionPtr->GetConditionPath()), CreateFieldResult.StealError())
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId()
|
|
);
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
if (CreateFieldResult.GetValue())
|
|
{
|
|
// Sanity check
|
|
{
|
|
// if there is a FieldId associated with the read property
|
|
if (CreateFieldResult.GetValue()->Source)
|
|
{
|
|
EMVVMBlueprintFieldPathSource PathSource = FoundReadFieldPath->GeneratedFrom;
|
|
bool bValidViewModel = CreateFieldResult.GetValue()->Source->Type == FCompilerBindingSource::EType::ViewModel && PathSource == EMVVMBlueprintFieldPathSource::ViewModel;
|
|
bool bValidWidget = CreateFieldResult.GetValue()->Source->Type == FCompilerBindingSource::EType::Widget && PathSource == EMVVMBlueprintFieldPathSource::Widget;
|
|
bool bDynamic = CreateFieldResult.GetValue()->Source->Type == FCompilerBindingSource::EType::DynamicViewmodel;
|
|
bool bSelf = CreateFieldResult.GetValue()->Source->Type == FCompilerBindingSource::EType::Self && PathSource == EMVVMBlueprintFieldPathSource::SelfContext;
|
|
if (!(bValidViewModel || bValidWidget || bDynamic || bSelf))
|
|
{
|
|
AddMessageForCondition(ValidCondition
|
|
, FText::Format(LOCTEXT("CreateNotifyFieldIdFailedInvalidInvalidContext", "Internal error. The property path '{0}' is invalid. The context is invalid."), PropertyPathToText(Class, BlueprintView.Get(), ConditionPtr->GetConditionPath()))
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId()
|
|
);
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
FoundReadFieldPath->NotificationField = CreateFieldResult.GetValue();
|
|
FoundReadFieldPath->Source = FoundReadFieldPath->NotificationField->Source;
|
|
}
|
|
}
|
|
|
|
|
|
FoundReadFieldPath->UsedByConditions.AddUnique(ValidCondition);
|
|
ValidCondition->ReadPaths.Add(FoundReadFieldPath.ToSharedRef());
|
|
|
|
for (const FMVVMBlueprintPin& Pin : ConditionPtr->GetPins())
|
|
{
|
|
if (Pin.UsedPathAsValue())
|
|
{
|
|
TValueOrError<FCreateFieldsResult, FText> CreateSourceResult = CreateFieldContext(Class, Pin.GetPath(), true);
|
|
if (CreateSourceResult.HasError())
|
|
{
|
|
AddMessageForCondition(ValidCondition
|
|
, FText::Format(Private::PropertyPathIsInvalidFormat, PropertyPathToText(Class, BlueprintView.Get(), Pin.GetPath()))
|
|
, Compiler::EMessageType::Error
|
|
, Pin.GetId()
|
|
);
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::CreateCreatorContentFromBindingSource(UWidgetBlueprintGeneratedClass* Class)
|
|
{
|
|
// Add all the needed sources that are not viewmodel (so not in the SourceCreators)
|
|
for (const TSharedRef<FCompilerBindingSource>& Source : NeededBindingSources)
|
|
{
|
|
{
|
|
const bool bSourceIsViewModel = (Source->Type == FCompilerBindingSource::EType::ViewModel || Source->Type == FCompilerBindingSource::EType::DynamicViewmodel);
|
|
const FCompilerViewModelCreatorContext* FoundSourceCreator = ViewModelCreatorContexts.FindByPredicate([Source](const FCompilerViewModelCreatorContext& Other) { return Other.Source == Source; });
|
|
if (bSourceIsViewModel && FoundSourceCreator == nullptr)
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*LOCTEXT("ViewmodelSourceNotAdded", "Internal error. A viewmodel was not added to the compiled list.").ToString());
|
|
bIsPreCompileStepValid = false;
|
|
}
|
|
}
|
|
|
|
const bool bSourceIsWidget = Source->Type == FCompilerBindingSource::EType::Widget || Source->Type == FCompilerBindingSource::EType::Self;
|
|
if (bSourceIsWidget)
|
|
{
|
|
FCompilerWidgetCreatorContext& CompiledSourceCreator = WidgetCreatorContexts.AddDefaulted_GetRef();
|
|
CompiledSourceCreator.Source = Source;
|
|
CompiledSourceCreator.bSelfReference = Source->Type == FCompilerBindingSource::EType::Self;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::FixCompilerBindingSelfSource(UWidgetBlueprintGeneratedClass* Class)
|
|
{
|
|
int32 Counter = 0;
|
|
for (TSharedRef<FCompilerBindingSource>& Source : NeededBindingSources)
|
|
{
|
|
if (Source->Type == FCompilerBindingSource::EType::Self)
|
|
{
|
|
ensure(Source->AuthoritativeClass == nullptr);
|
|
Source->AuthoritativeClass = Class;
|
|
++Counter;
|
|
}
|
|
}
|
|
|
|
if (Counter > 1)
|
|
{
|
|
AddMessage(LOCTEXT("MoreThanSeldContext", "Internal error. There is more than self context.")
|
|
, Compiler::EMessageType::Warning
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::AddWarningForPropertyWithMVVMAndLegacyBinding(UWidgetBlueprintGeneratedClass* Class)
|
|
{
|
|
const TArray<FDelegateRuntimeBinding>& LegacyBindings = Class->Bindings;
|
|
for (const TSharedRef<FGeneratedWriteFieldPathContext>& WriteFieldPath : GeneratedWriteFieldPaths)
|
|
{
|
|
FName MVVMObjectName;
|
|
FName MVVMFieldName;
|
|
if (WriteFieldPath->GeneratedFrom == EMVVMBlueprintFieldPathSource::SelfContext)
|
|
{
|
|
MVVMObjectName = Class->ClassGeneratedBy ? Class->ClassGeneratedBy->GetFName() : FName();
|
|
MVVMFieldName = WriteFieldPath->SkeletalGeneratedFields.Num() > 0 ? WriteFieldPath->SkeletalGeneratedFields[0].GetName() : FName();
|
|
}
|
|
|
|
for (int32 Index = 0; Index < WriteFieldPath->SkeletalGeneratedFields.Num(); ++Index)
|
|
{
|
|
const UE::MVVM::FMVVMConstFieldVariant& Field = WriteFieldPath->SkeletalGeneratedFields[Index];
|
|
const FObjectPropertyBase* ObjectProperty = Field.IsProperty() ? CastField<const FObjectPropertyBase>(Field.GetProperty()) : nullptr;
|
|
if (ObjectProperty && ObjectProperty->PropertyClass->IsChildOf(UWidget::StaticClass()))
|
|
{
|
|
MVVMObjectName = ObjectProperty->GetFName();
|
|
MVVMFieldName = WriteFieldPath->SkeletalGeneratedFields.IsValidIndex(Index+1) ? WriteFieldPath->SkeletalGeneratedFields[Index+1].GetName() : FName();
|
|
}
|
|
}
|
|
|
|
for (const FDelegateRuntimeBinding& LegacyBinding : LegacyBindings)
|
|
{
|
|
if (LegacyBinding.ObjectName == MVVMObjectName && LegacyBinding.PropertyName == MVVMFieldName)
|
|
{
|
|
if (WriteFieldPath->UsedByBindings.Num())
|
|
{
|
|
AddMessages(WriteFieldPath->UsedByBindings
|
|
, TArrayView<TWeakPtr<FCompilerEvent>>()
|
|
, LOCTEXT("BindingConflictWithLegacy", "The binding is set on a property with legacy binding.")
|
|
, Compiler::EMessageType::Warning
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::PreCompileViewModelCreatorContexts(UWidgetBlueprintGeneratedClass* Class)
|
|
{
|
|
for (FCompilerViewModelCreatorContext& SourceCreatorContext : ViewModelCreatorContexts)
|
|
{
|
|
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))
|
|
{
|
|
AddMessageForViewModel(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())
|
|
, Compiler::EMessageType::Warning
|
|
);
|
|
}
|
|
|
|
if (!ViewModelContext.GetViewModelClass()->ImplementsInterface(UNotifyFieldValueChanged::StaticClass()))
|
|
{
|
|
AddMessageForViewModel(ViewModelContext
|
|
, LOCTEXT("ViewmodelInvalidInterface", "The class doesn't implement the interface NotifyFieldValueChanged.")
|
|
, Compiler::EMessageType::Error
|
|
);
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
// Test the creation mode. Dynamic source are always using Path.
|
|
if (SourceCreatorContext.DynamicContext == nullptr)
|
|
{
|
|
if (!GetAllowedContextCreationType(ViewModelContext.GetViewModelClass()).Contains(ViewModelContext.CreationType))
|
|
{
|
|
AddMessageForViewModel(ViewModelContext
|
|
, LOCTEXT("ViewModelContextCreationTypeInvalid", "It has an invalidate creation type. You can change it in the View Models panel.")
|
|
, Compiler::EMessageType::Error
|
|
);
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::Manual)
|
|
{
|
|
}
|
|
else if (ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::CreateInstance)
|
|
{
|
|
if (ViewModelContext.GetViewModelClass()->HasAllClassFlags(CLASS_Abstract))
|
|
{
|
|
AddMessageForViewModel(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())
|
|
, Compiler::EMessageType::Error
|
|
);
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
UObject* CDOInstance = Class->GetDefaultObject(true);
|
|
|
|
// If requested, create an instance of the viewmodel to be edited through the details panel
|
|
if (ViewModelContext.bExposeInstanceInEditor)
|
|
{
|
|
FObjectPropertyBase* ViewModelProperty = FindFProperty<FObjectPropertyBase>(Class, ViewModelContext.GetViewModelName());
|
|
if (!ViewModelProperty->GetObjectPropertyValue_InContainer(CDOInstance))
|
|
{
|
|
UObject* StaticInstance = NewObject<UObject>(CDOInstance, ViewModelContext.GetViewModelClass(), NAME_None, RF_Transactional | RF_Public);
|
|
BlueprintView->GetOuterUMVVMWidgetBlueprintExtension_View()->TemporaryViewModelInstances.Add(ViewModelContext.GetViewModelId(), StaticInstance);
|
|
if (ensure(StaticInstance->GetClass()->IsChildOf(ViewModelProperty->PropertyClass)))
|
|
{
|
|
ViewModelProperty->SetObjectPropertyValue_InContainer(CDOInstance, StaticInstance);
|
|
}
|
|
}
|
|
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FFieldPathHandle, FText> ReadFieldPathResult = AddObjectFieldPath(BindingLibraryCompiler, Class, ViewModelContext.GetViewModelName().ToString(), ViewModelContext.GetViewModelClass());
|
|
if (ReadFieldPathResult.HasError())
|
|
{
|
|
AddMessageForViewModel(ViewModelContext
|
|
, ReadFieldPathResult.GetError()
|
|
, Compiler::EMessageType::Error
|
|
);
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
SourceCreatorContext.ReadPropertyPathHandle = ReadFieldPathResult.StealValue();
|
|
}
|
|
else
|
|
{
|
|
FObjectPropertyBase* ViewModelProperty = FindFProperty<FObjectPropertyBase>(Class, ViewModelContext.GetViewModelName());
|
|
ViewModelProperty->SetObjectPropertyValue_InContainer(CDOInstance, nullptr);
|
|
}
|
|
}
|
|
else if (ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::PropertyPath)
|
|
{
|
|
if (ViewModelContext.ViewModelPropertyPath.IsEmpty())
|
|
{
|
|
AddMessageForViewModel(ViewModelContext
|
|
, LOCTEXT("ViewModelInvalidGetter", "Viewmodel has an invalid Getter. You can select a new one in the View Models panel.")
|
|
, Compiler::EMessageType::Error
|
|
);
|
|
bIsPreCompileStepValid = true;
|
|
continue;
|
|
}
|
|
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FFieldPathHandle, FText> ReadFieldPathResult = AddObjectFieldPath(BindingLibraryCompiler, Class, ViewModelContext.ViewModelPropertyPath, ViewModelContext.GetViewModelClass());
|
|
if (ReadFieldPathResult.HasError())
|
|
{
|
|
AddMessageForViewModel(ViewModelContext
|
|
, ReadFieldPathResult.GetError()
|
|
, Compiler::EMessageType::Error
|
|
);
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
SourceCreatorContext.ReadPropertyPathHandle = ReadFieldPathResult.StealValue();
|
|
}
|
|
else if (ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::GlobalViewModelCollection)
|
|
{
|
|
if (ViewModelContext.GlobalViewModelIdentifier.IsNone())
|
|
{
|
|
AddMessageForViewModel(ViewModelContext
|
|
, LOCTEXT("ViewmodelInvalidGlobalIdentifier", "Viewmodel doesn't have a valid Global identifier. You can specify a new one in the Viewmodels panel.")
|
|
, Compiler::EMessageType::Error
|
|
);
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
else if (ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::Resolver)
|
|
{
|
|
if (!ViewModelContext.Resolver)
|
|
{
|
|
AddMessageForViewModel(ViewModelContext
|
|
, LOCTEXT("ViewmodelInvalidResolver", "Viewmodel doesn't have a valid Resolver. You can specify a new one in the Viewmodels panel.")
|
|
, Compiler::EMessageType::Error
|
|
);
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AddMessageForViewModel(ViewModelContext
|
|
, LOCTEXT("ViewmodelInvalidCreationType", "Viewmodel doesn't have a valid creation type. You can select one in the Viewmodels panel.")
|
|
, Compiler::EMessageType::Error
|
|
);
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
for (FCompilerWidgetCreatorContext& WidgetCreator : WidgetCreatorContexts)
|
|
{
|
|
if (!WidgetCreator.Source->AuthoritativeClass->ImplementsInterface(UNotifyFieldValueChanged::StaticClass()))
|
|
{
|
|
AddMessage(FText::Format(LOCTEXT("WidgetInvalidInterface", "The widget {0} class doesn't implement the interface NotifyFieldValueChanged."), FText::FromName(WidgetCreator.Source->Name))
|
|
, Compiler::EMessageType::Error
|
|
);
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
if (!WidgetCreator.bSelfReference)
|
|
{
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FFieldPathHandle, FText> ReadFieldPathResult = AddObjectFieldPath(BindingLibraryCompiler, Class, WidgetCreator.Source->Name.ToString(), WidgetCreator.Source->AuthoritativeClass);
|
|
if (ReadFieldPathResult.HasError())
|
|
{
|
|
AddMessage(FText::Format(LOCTEXT("WidgetAddObjectFieldPathFailFormat", "The widget {0} creator failed. {1}")
|
|
, FText::FromName(WidgetCreator.Source->Name)
|
|
, ReadFieldPathResult.GetError()
|
|
)
|
|
, Compiler::EMessageType::Error
|
|
);
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
WidgetCreator.ReadPropertyPathHandle = ReadFieldPathResult.StealValue();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::CompileSources(const FCompiledBindingLibraryCompiler::FCompileResult& CompileResult, UWidgetBlueprintGeneratedClass* Class, UMVVMViewClass* ViewExtension)
|
|
{
|
|
struct FSortData
|
|
{
|
|
FSortData() = default;
|
|
TSharedPtr<FCompilerBindingSource> Source;
|
|
TArray<FName, TInlineAllocator<8>> Errors;
|
|
int32 SortIndex = -1;
|
|
bool bCalculating = false;
|
|
|
|
int32 CalculateSortIndex(FName RequestedBy, TMap<FName, FSortData>& Map)
|
|
{
|
|
if (SortIndex < 0)
|
|
{
|
|
if (bCalculating)
|
|
{
|
|
SortIndex = 0;
|
|
Errors.Add(RequestedBy);
|
|
}
|
|
else
|
|
{
|
|
bCalculating = true;
|
|
if (Source->Dependencies.Num() == 0)
|
|
{
|
|
SortIndex = 0;
|
|
}
|
|
else
|
|
{
|
|
for (TWeakPtr<FCompilerBindingSource> WeakDependency : Source->Dependencies)
|
|
{
|
|
TSharedPtr<FCompilerBindingSource> Dependency = WeakDependency.Pin();
|
|
if (ensure(Dependency))
|
|
{
|
|
SortIndex = FMath::Max(Map[Dependency->Name].CalculateSortIndex(Source->Name, Map) + 1, SortIndex);
|
|
}
|
|
}
|
|
}
|
|
bCalculating = false;
|
|
}
|
|
}
|
|
return SortIndex;
|
|
}
|
|
};
|
|
|
|
TMap<FName, FSortData> SortDatas;
|
|
SortDatas.Reserve(ViewModelCreatorContexts.Num() + WidgetCreatorContexts.Num());
|
|
TArray<FMVVMViewClass_Source> UnsortedSourceCreators;
|
|
UnsortedSourceCreators.Reserve(ViewModelCreatorContexts.Num() + WidgetCreatorContexts.Num());
|
|
|
|
for (FCompilerViewModelCreatorContext& SourceCreatorContext : ViewModelCreatorContexts)
|
|
{
|
|
const FMVVMBlueprintViewModelContext& ViewModelContext = SourceCreatorContext.ViewModelContext;
|
|
FMVVMViewClass_Source CompiledSourceCreator;
|
|
|
|
ensure(ViewModelContext.GetViewModelClass() && ViewModelContext.GetViewModelClass()->ImplementsInterface(UNotifyFieldValueChanged::StaticClass()));
|
|
CompiledSourceCreator.ExpectedSourceType = ViewModelContext.GetViewModelClass();
|
|
CompiledSourceCreator.PropertyName = ViewModelContext.GetViewModelName();
|
|
|
|
bool bCanBeSet = ViewModelContext.bCreateSetterFunction;
|
|
bool bCanBeEvaluated = SourceCreatorContext.DynamicContext.IsValid();
|
|
bool bIsOptional = SourceCreatorContext.Source->bIsOptional;
|
|
bool bCreateInstance = false;
|
|
bool bIsUserWidgetProperty = !SourceCreatorContext.DynamicContext.IsValid();
|
|
bool bExposeInstanceInEditor = false;
|
|
bool bGlobalViewModelCollectionUpdate = false;
|
|
bool bAlwaysExecuteBindingsOnSetSource = GetDefault<UMVVMDeveloperProjectSettings>()->bForceExecuteBindingsOnSetSource;
|
|
|
|
if (ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::Manual)
|
|
{
|
|
bCanBeSet = true;
|
|
ensure(bIsOptional == true);
|
|
}
|
|
else if (ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::CreateInstance)
|
|
{
|
|
bCreateInstance = true;
|
|
bExposeInstanceInEditor = ViewModelContext.bExposeInstanceInEditor;
|
|
|
|
if (bExposeInstanceInEditor)
|
|
{
|
|
const FMVVMVCompiledFieldPath* CompiledFieldPath = CompileResult.FieldPaths.Find(SourceCreatorContext.ReadPropertyPathHandle);
|
|
if (CompiledFieldPath == nullptr)
|
|
{
|
|
AddMessageForViewModel(ViewModelContext
|
|
, LOCTEXT("ViewModelInvalidInstanceNotFound", "The path for the created viewmodel instance was not found.")
|
|
, Compiler::EMessageType::Error
|
|
);
|
|
bIsCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
CompiledSourceCreator.FieldPath = *CompiledFieldPath;
|
|
}
|
|
ensure(bIsOptional == false);
|
|
}
|
|
else if (ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::PropertyPath)
|
|
{
|
|
const FMVVMVCompiledFieldPath* CompiledFieldPath = CompileResult.FieldPaths.Find(SourceCreatorContext.ReadPropertyPathHandle);
|
|
if (CompiledFieldPath == nullptr)
|
|
{
|
|
AddMessageForViewModel(ViewModelContext
|
|
, LOCTEXT("ViewModelInvalidInitializationBindingNotGenerated", "The viewmodel initialization binding was not generated.")
|
|
, Compiler::EMessageType::Error
|
|
);
|
|
bIsCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
CompiledSourceCreator.FieldPath = *CompiledFieldPath;
|
|
ensure(bIsOptional == ViewModelContext.bOptional);
|
|
}
|
|
else if (ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::GlobalViewModelCollection)
|
|
{
|
|
ensure(!ViewModelContext.GlobalViewModelIdentifier.IsNone());
|
|
|
|
FMVVMViewModelContext GlobalViewModelInstance;
|
|
GlobalViewModelInstance.ContextClass = ViewModelContext.GetViewModelClass();
|
|
GlobalViewModelInstance.ContextName = ViewModelContext.GlobalViewModelIdentifier;
|
|
if (!GlobalViewModelInstance.IsValid())
|
|
{
|
|
AddMessageForViewModel(ViewModelContext
|
|
, LOCTEXT("ViewmodelGlobalContextIdentifier", "The context for viewmodel could not be created. Change the identifier.")
|
|
, Compiler::EMessageType::Warning
|
|
);
|
|
}
|
|
|
|
CompiledSourceCreator.GlobalViewModelInstance = MoveTemp(GlobalViewModelInstance);
|
|
bGlobalViewModelCollectionUpdate = ViewModelContext.bGlobalViewModelCollectionUpdate;
|
|
ensure(bIsOptional == ViewModelContext.bOptional);
|
|
}
|
|
else if (ViewModelContext.CreationType == EMVVMBlueprintViewModelContextCreationType::Resolver)
|
|
{
|
|
UMVVMViewModelContextResolver* Resolver = DuplicateObject(ViewModelContext.Resolver.Get(), ViewExtension);
|
|
if (!Resolver)
|
|
{
|
|
AddMessageForViewModel(ViewModelContext
|
|
, LOCTEXT("ViewmodelFailedResolverDuplicate", "Internal error. The resolver could not be dupliated.")
|
|
, Compiler::EMessageType::Error
|
|
);
|
|
bIsCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
CompiledSourceCreator.Resolver = Resolver;
|
|
ensure(bIsOptional == ViewModelContext.bOptional);
|
|
}
|
|
else
|
|
{
|
|
AddMessageForViewModel(ViewModelContext
|
|
, LOCTEXT("ViewModelWithoutValidCreationType", "The viewmodel doesn't have a valid creation type.")
|
|
, Compiler::EMessageType::Error
|
|
);
|
|
bIsCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
if (SourceCreatorContext.DynamicContext)
|
|
{
|
|
if (!SourceCreatorContext.DynamicContext->ParentSource)
|
|
{
|
|
AddMessageForViewModel(ViewModelContext
|
|
, LOCTEXT("ViewModelWithoutValidDyanmicParentSource", "The viewmodel doesn't have a valid parent source.")
|
|
, Compiler::EMessageType::Error
|
|
);
|
|
bIsCompileStepValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (ViewModelContext.bOverrideForceExecuteBindingsOnSetSource)
|
|
{
|
|
bAlwaysExecuteBindingsOnSetSource = ViewModelContext.bForceExecuteBindingsOnSetSource;
|
|
}
|
|
bAlwaysExecuteBindingsOnSetSource = bCanBeSet && bAlwaysExecuteBindingsOnSetSource;
|
|
|
|
CompiledSourceCreator.Flags = 0;
|
|
CompiledSourceCreator.Flags |= bCreateInstance ? (uint16)FMVVMViewClass_Source::EFlags::TypeCreateInstance : 0;
|
|
CompiledSourceCreator.Flags |= bIsUserWidgetProperty ? (uint16)FMVVMViewClass_Source::EFlags::IsUserWidgetProperty : 0;
|
|
CompiledSourceCreator.Flags |= bIsUserWidgetProperty ? (uint16)FMVVMViewClass_Source::EFlags::SetUserWidgetProperty : 0;
|
|
CompiledSourceCreator.Flags |= bIsOptional ? (uint16)FMVVMViewClass_Source::EFlags::IsOptional : 0;
|
|
CompiledSourceCreator.Flags |= bCanBeSet ? (uint16)FMVVMViewClass_Source::EFlags::CanBeSet : 0;
|
|
CompiledSourceCreator.Flags |= bCanBeEvaluated || bGlobalViewModelCollectionUpdate ? (uint16)FMVVMViewClass_Source::EFlags::CanBeEvaluated : 0;
|
|
CompiledSourceCreator.Flags |= (uint16)FMVVMViewClass_Source::EFlags::IsViewModel;
|
|
CompiledSourceCreator.Flags |= bExposeInstanceInEditor ? (uint16)FMVVMViewClass_Source::EFlags::IsViewModelInstanceExposed : 0;
|
|
CompiledSourceCreator.Flags |= bGlobalViewModelCollectionUpdate ? (uint16)FMVVMViewClass_Source::EFlags::GlobalViewModelCollectionUpdate : 0;
|
|
CompiledSourceCreator.Flags |= bAlwaysExecuteBindingsOnSetSource ? (uint16)FMVVMViewClass_Source::EFlags::AlwaysExecuteBindingsOnSetSource : 0;
|
|
|
|
{
|
|
FSortData SortData;
|
|
SortData.Source = SourceCreatorContext.Source.ToSharedRef();
|
|
SortDatas.Add(CompiledSourceCreator.GetName(), SortData);
|
|
|
|
ensure(SortData.Source->Name == CompiledSourceCreator.GetName());
|
|
}
|
|
|
|
UnsortedSourceCreators.Add(MoveTemp(CompiledSourceCreator));
|
|
}
|
|
|
|
// The other sources needed by the view
|
|
for (FCompilerWidgetCreatorContext& WidgetCreator : WidgetCreatorContexts)
|
|
{
|
|
FMVVMViewClass_Source CompiledSourceCreator;
|
|
ensure(WidgetCreator.Source->AuthoritativeClass && WidgetCreator.Source->AuthoritativeClass->ImplementsInterface(UNotifyFieldValueChanged::StaticClass()));
|
|
CompiledSourceCreator.ExpectedSourceType = const_cast<UClass*>(WidgetCreator.Source->AuthoritativeClass);
|
|
CompiledSourceCreator.PropertyName = WidgetCreator.Source->Name;
|
|
CompiledSourceCreator.Flags = 0;
|
|
CompiledSourceCreator.Flags |= WidgetCreator.bSelfReference ? (uint16)FMVVMViewClass_Source::EFlags::SelfReference : 0;
|
|
|
|
if (!WidgetCreator.bSelfReference)
|
|
{
|
|
const FMVVMVCompiledFieldPath* CompiledFieldPath = CompileResult.FieldPaths.Find(WidgetCreator.ReadPropertyPathHandle);
|
|
if (CompiledFieldPath == nullptr)
|
|
{
|
|
AddMessage(FText::Format(LOCTEXT("WidgetInvalidInitializationBindingNotGenerated", "The widget {0} initialization binding was not generated."), FText::FromName(WidgetCreator.Source->Name))
|
|
, Compiler::EMessageType::Error
|
|
);
|
|
bIsCompileStepValid = false;
|
|
continue;
|
|
}
|
|
CompiledSourceCreator.FieldPath = *CompiledFieldPath;
|
|
}
|
|
|
|
{
|
|
FSortData SortData;
|
|
SortData.Source = WidgetCreator.Source;
|
|
SortDatas.Add(CompiledSourceCreator.GetName(), SortData);
|
|
}
|
|
|
|
UnsortedSourceCreators.Add(MoveTemp(CompiledSourceCreator));
|
|
}
|
|
|
|
// Sanity check
|
|
{
|
|
// Test if all NeededBindingSources in inside the UnsortedSourceCreators
|
|
for (const TSharedRef<FCompilerBindingSource>& BindingSource : NeededBindingSources)
|
|
{
|
|
const FMVVMViewClass_Source* FoundClassSource = UnsortedSourceCreators.FindByPredicate([ToFindName = BindingSource->Name](const FMVVMViewClass_Source& Other){ return Other.GetName() == ToFindName; });
|
|
if (FoundClassSource == nullptr)
|
|
{
|
|
AddMessage(FText::Format(LOCTEXT("CompileSources_MissingSources", "Internal error. The source {0} was not compiled."), FText::FromName(BindingSource->Name))
|
|
, Compiler::EMessageType::Warning
|
|
);
|
|
}
|
|
}
|
|
|
|
ensure(SortDatas.Num() == UnsortedSourceCreators.Num());
|
|
}
|
|
|
|
// Sort the source creators by priority and then by name
|
|
if (UnsortedSourceCreators.Num() > 1)
|
|
{
|
|
// Calculate dependencies
|
|
for (auto& SortDataPair : SortDatas)
|
|
{
|
|
SortDataPair.Value.CalculateSortIndex(SortDataPair.Key, SortDatas);
|
|
}
|
|
|
|
// Report errors
|
|
for (const auto& SortDataPair : SortDatas)
|
|
{
|
|
for (FName OtherSource : SortDataPair.Value.Errors)
|
|
{
|
|
AddMessage(
|
|
FText::Format(LOCTEXT("CompileSources_Dependencies", "The source {0} circularly depends on the source {1}"), FText::FromName(SortDataPair.Key), FText::FromName(OtherSource))
|
|
, Compiler::EMessageType::Info
|
|
);
|
|
}
|
|
}
|
|
|
|
// Sort the sources
|
|
UnsortedSourceCreators.Sort([&SortDatas](const FMVVMViewClass_Source& A, const FMVVMViewClass_Source& B)
|
|
{
|
|
int32 ASortIndex = SortDatas[A.GetName()].SortIndex;
|
|
int32 BSortIndex = SortDatas[B.GetName()].SortIndex;
|
|
if (ASortIndex == BSortIndex)
|
|
{
|
|
return A.GetName().LexicalLess(B.GetName());
|
|
}
|
|
return ASortIndex < BSortIndex;
|
|
});
|
|
}
|
|
|
|
// Add the sorted viewmodel array to the ViewExtension
|
|
ViewExtension->Sources = MoveTemp(UnsortedSourceCreators);
|
|
|
|
if (ViewExtension->Sources.Num() > 64)
|
|
{
|
|
// The view use a uint64 bitfield to filter which viewmodel/source is valid/initialized.
|
|
//You can use the MVVM.LogViewCompiledResult command to display the compile result and see the name of the sources.
|
|
AddMessage(LOCTEXT("TooManySources", "There is too many sources for the view. Try spliting your widget into other widgets."), Compiler::EMessageType::Error);
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::PreCompileBindings(UWidgetBlueprintGeneratedClass* Class)
|
|
{
|
|
auto TestExecutionMode = [Self = this](const FMVVMBlueprintViewBinding& Binding) -> bool
|
|
{
|
|
if (Binding.bOverrideExecutionMode)
|
|
{
|
|
if (!GetDefault<UMVVMDeveloperProjectSettings>()->IsExecutionModeAllowed(Binding.OverrideExecutionMode))
|
|
{
|
|
Self->AddMessageForBinding(Binding
|
|
, LOCTEXT("NotAllowedExecutionMode", "The binding has a restricted execution mode.")
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId()
|
|
);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
auto AddFieldIds = [Self = this, Class](FCompilerBinding& ValidBinding, const FMVVMBlueprintViewBinding& Binding) -> bool
|
|
{
|
|
// Add FieldId
|
|
bool bHasFieldId = false;
|
|
if (!ValidBinding.bIsOneTimeBinding)
|
|
{
|
|
for (const TSharedPtr<FGeneratedReadFieldPathContext>& ReadPath : ValidBinding.ReadPaths)
|
|
{
|
|
if (ReadPath->NotificationField)
|
|
{
|
|
if (ReadPath->NotificationField->LibraryCompilerHandle.IsValid())
|
|
{
|
|
bHasFieldId = true;
|
|
}
|
|
else if (ReadPath->NotificationField->Source) // it is maybe OneTIme
|
|
{
|
|
const UClass* SourceContextClass = ReadPath->NotificationField->Source->AuthoritativeClass;
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FFieldIdHandle, FText> FieldIdResult = Self->BindingLibraryCompiler.AddFieldId(SourceContextClass, ReadPath->NotificationField->NotificationId.GetFieldName());
|
|
if (FieldIdResult.HasError() || !FieldIdResult.GetValue().IsValid())
|
|
{
|
|
Self->AddMessageForBinding(Binding
|
|
, FText::Format(LOCTEXT("CouldNotCreateFieldId", "Could not create Field. {0}"), FieldIdResult.GetError())
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId()
|
|
);
|
|
return false;
|
|
}
|
|
|
|
ReadPath->NotificationField->LibraryCompilerHandle = FieldIdResult.StealValue();
|
|
bHasFieldId = ReadPath->NotificationField->LibraryCompilerHandle.IsValid();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test correct numbers of FieldIds
|
|
bool bRequiresValidFieldId = !ValidBinding.bIsOneTimeBinding;
|
|
if (bRequiresValidFieldId && !bHasFieldId)
|
|
{
|
|
Self->AddMessageForBinding(Binding
|
|
, LOCTEXT("CouldNotCreateSourceFields", "There is no field to bind to. The binding must be a OneTime binding.")
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId()
|
|
);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
auto AddReadPaths = [Self = this, Class](FCompilerBinding& ValidBinding, const FMVVMBlueprintViewBinding& Binding) -> bool
|
|
{
|
|
bool bHasValidReadHandle = false;
|
|
if (ValidBinding.Type != FCompilerBinding::EType::ComplexConversionFunction)
|
|
{
|
|
for (const TSharedPtr<FGeneratedReadFieldPathContext>& ReadPath : ValidBinding.ReadPaths)
|
|
{
|
|
if (ReadPath->LibraryCompilerHandle.IsValid())
|
|
{
|
|
bHasValidReadHandle = true;
|
|
}
|
|
else
|
|
{
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FFieldPathHandle, FText> FieldPathResult = Self->BindingLibraryCompiler.AddFieldPath(ReadPath->SkeletalGeneratedFields, true);
|
|
if (FieldPathResult.HasError() || !FieldPathResult.GetValue().IsValid())
|
|
{
|
|
Self->AddMessageForBinding(Binding
|
|
, FText::Format(Private::CouldNotCreateSourceFieldPathFormat, ::UE::MVVM::FieldPathHelper::ToText(ReadPath->GeneratedFields), FieldPathResult.GetError())
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId()
|
|
);
|
|
return false;
|
|
}
|
|
|
|
ReadPath->LibraryCompilerHandle = FieldPathResult.StealValue();
|
|
bHasValidReadHandle = ReadPath->LibraryCompilerHandle.IsValid();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test read
|
|
bool bRequiresReadHandle = ValidBinding.Type != FCompilerBinding::EType::ComplexConversionFunction;
|
|
if (bRequiresReadHandle && !bHasValidReadHandle)
|
|
{
|
|
Self->AddMessageForBinding(Binding
|
|
, LOCTEXT("CouldNotCreateSourceReadIsPresent", "Internal error. There should be no property to read from.")
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId()
|
|
);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
auto AddWritePaths = [Self = this, Class](FCompilerBinding& ValidBinding, const FMVVMBlueprintViewBinding& Binding) -> bool
|
|
{
|
|
if (ValidBinding.WritePath->LibraryCompilerHandle.IsValid())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (!ValidBinding.WritePath)
|
|
{
|
|
Self->AddMessageForBinding(Binding
|
|
, LOCTEXT("InvalidWritePath", "Internal Error. The write path is invalid.")
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId()
|
|
);
|
|
return false;
|
|
}
|
|
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FFieldPathHandle, FText> FieldPathResult = Self->BindingLibraryCompiler.AddFieldPath(ValidBinding.WritePath->SkeletalGeneratedFields, false);
|
|
if (FieldPathResult.HasError())
|
|
{
|
|
Self->AddMessageForBinding(Binding
|
|
, FText::Format(Private::CouldNotCreateDestinationFieldPathFormat, ::UE::MVVM::FieldPathHelper::ToText(ValidBinding.WritePath->GeneratedFields), FieldPathResult.GetError())
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId()
|
|
);
|
|
return false;
|
|
}
|
|
ValidBinding.WritePath->LibraryCompilerHandle = FieldPathResult.StealValue();
|
|
|
|
// test write
|
|
|
|
return true;
|
|
};
|
|
|
|
auto AddConversionFunction = [Self = this, Class](FCompilerBinding& ValidBinding, const FMVVMBlueprintViewBinding& Binding) -> bool
|
|
{
|
|
const UFunction* ConversionFunction = nullptr;
|
|
UMVVMBlueprintViewConversionFunction* ViewConversionFunction = ValidBinding.ConversionFunction.Get();
|
|
{
|
|
if (ViewConversionFunction)
|
|
{
|
|
ConversionFunction = ViewConversionFunction->GetCompiledFunction(Class);
|
|
if (ConversionFunction == nullptr)
|
|
{
|
|
Self->AddMessageForBinding(Binding
|
|
, FText::Format(LOCTEXT("ConversionFunctionNotFound", "The conversion function '{0}' could not be found."), FText::FromName(ViewConversionFunction->GetCompiledFunctionName(Class)))
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId()
|
|
);
|
|
return false;
|
|
}
|
|
|
|
FMVVMBlueprintFunctionReference FunctionOrWrapperFunction = ViewConversionFunction->GetConversionFunction();
|
|
if (FunctionOrWrapperFunction.GetType() == EMVVMBlueprintFunctionReferenceType::Function)
|
|
{
|
|
const UFunction* WrapperFunction = FunctionOrWrapperFunction.GetFunction(Self->WidgetBlueprintCompilerContext.WidgetBlueprint());
|
|
if (!GetDefault<UMVVMDeveloperProjectSettings>()->IsConversionFunctionAllowed(Self->WidgetBlueprintCompilerContext.WidgetBlueprint(), WrapperFunction))
|
|
{
|
|
Self->AddMessageForBinding(Binding
|
|
, FText::Format(LOCTEXT("ConversionFunctionNotAllow", "The conversion function {0} is not allowed."), FText::FromName(WrapperFunction ? WrapperFunction->GetFName() : FName()))
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId()
|
|
);
|
|
return false;
|
|
}
|
|
}
|
|
else if (FunctionOrWrapperFunction.GetType() == EMVVMBlueprintFunctionReferenceType::Node)
|
|
{
|
|
if (!GetDefault<UMVVMDeveloperProjectSettings>()->IsConversionFunctionAllowed(Self->WidgetBlueprintCompilerContext.WidgetBlueprint(), FunctionOrWrapperFunction.GetNode()))
|
|
{
|
|
Self->AddMessageForBinding(Binding
|
|
, FText::Format(LOCTEXT("ConversionFunctionNotAllow", "The conversion function {0} is not allowed."), FText::FromName(FunctionOrWrapperFunction.GetNode().Get() ? FunctionOrWrapperFunction.GetNode()->GetFName() : FName()))
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId()
|
|
);
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Self->AddMessageForBinding(Binding
|
|
, LOCTEXT("ConversionFunctionNodeNotAllow", "The conversion function node is not allowed.")
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId()
|
|
);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bIsUbergraphPage = ViewConversionFunction && ViewConversionFunction->IsUbergraphPage();
|
|
if (ConversionFunction != nullptr)
|
|
{
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FFieldPathHandle, FText> FieldPathResult = bIsUbergraphPage
|
|
? Self->BindingLibraryCompiler.AddDelegateSignatureFieldPath(Class, ConversionFunction)
|
|
: Self->BindingLibraryCompiler.AddConversionFunctionFieldPath(Class, ConversionFunction);
|
|
if (FieldPathResult.HasError())
|
|
{
|
|
Self->AddMessageForBinding(Binding
|
|
, FText::Format(LOCTEXT("CouldNotCreateConversionFunctionFieldPath", "Couldn't create the conversion function field path '{0}'. {1}")
|
|
, FText::FromString(ConversionFunction->GetPathName())
|
|
, FieldPathResult.GetError())
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId()
|
|
);
|
|
return false;
|
|
}
|
|
|
|
ValidBinding.ConversionFunctionHandle = FieldPathResult.StealValue();
|
|
}
|
|
|
|
// Sanity check
|
|
if (!bIsUbergraphPage)
|
|
{
|
|
const bool bShouldHaveConversionFunction = ValidBinding.Type == FCompilerBinding::EType::ComplexConversionFunction || ValidBinding.Type == FCompilerBinding::EType::SimpleConversionFunction;
|
|
if ((ConversionFunction != nullptr) != bShouldHaveConversionFunction)
|
|
{
|
|
Self->AddMessageForBinding(Binding, LOCTEXT("ConversionFunctionShouldExist", "Internal error. The conversion function should exist."), Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
return false;
|
|
}
|
|
|
|
const bool bShouldHaveComplexConversionFunction = ValidBinding.Type == FCompilerBinding::EType::ComplexConversionFunction;
|
|
if (bShouldHaveComplexConversionFunction != BindingHelper::IsValidForComplexRuntimeConversion(ConversionFunction))
|
|
{
|
|
Self->AddMessageForBinding(Binding, LOCTEXT("ConversionFunctionIsNotComplex", "Internal Error. The complex conversion function does not respect the prerequisite."), Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
for (TSharedRef<FCompilerBinding>& ValidBinding : ValidBindings)
|
|
{
|
|
const FMVVMBlueprintViewBinding& Binding = *(BlueprintView->GetBindingAt(ValidBinding->Key.ViewBindingIndex));
|
|
if (ValidBinding->Type != FCompilerBinding::EType::Assignment
|
|
&& ValidBinding->Type != FCompilerBinding::EType::ComplexConversionFunction
|
|
&& ValidBinding->Type != FCompilerBinding::EType::SimpleConversionFunction)
|
|
{
|
|
AddMessageForBinding(Binding, LOCTEXT("UnsupportedBindingType", "The binding is invalid."), Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
if (!TestExecutionMode(Binding))
|
|
{
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
if (!AddFieldIds(ValidBinding.Get(), Binding)
|
|
|| !AddReadPaths(ValidBinding.Get(), Binding)
|
|
|| !AddWritePaths(ValidBinding.Get(), Binding)
|
|
|| !AddConversionFunction(ValidBinding.Get(), Binding))
|
|
{
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
// Generate the binding
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FBindingHandle, FText> BindingResult = ValidBinding->Type == FCompilerBinding::EType::ComplexConversionFunction
|
|
? BindingLibraryCompiler.AddComplexBinding(ValidBinding->WritePath->LibraryCompilerHandle, ValidBinding->ConversionFunctionHandle)
|
|
: BindingLibraryCompiler.AddBinding(ValidBinding->ReadPaths[0]->LibraryCompilerHandle, ValidBinding->WritePath->LibraryCompilerHandle, ValidBinding->ConversionFunctionHandle);
|
|
|
|
if (BindingResult.HasError())
|
|
{
|
|
AddMessageForBinding(Binding
|
|
, FText::Format(LOCTEXT("CouldNotCreateBinding", "Could not create binding. {0}"), BindingResult.StealError())
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId()
|
|
);
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
ValidBinding->BindingHandle = BindingResult.StealValue();
|
|
ValidBinding->CompilerBindingHandle = Compiler::FCompilerBindingHandle::MakeHandle();
|
|
}
|
|
|
|
// Add bindings for the dynamic viewmodels
|
|
for (TSharedRef<FCompilerSourceViewModelDynamicCreatorContext>& ViewModelDynamic : SourceViewModelDynamicCreatorContexts)
|
|
{
|
|
if (ensure(ViewModelDynamic->Source.IsValid() && ViewModelDynamic->ParentSource.IsValid()))
|
|
{
|
|
if (!ViewModelDynamic->NotificationId.IsValid())
|
|
{
|
|
AddMessage(FText::Format(LOCTEXT("InvalidNotificationFieldId", "{0} doesn't have a field Id."), FText::FromName(ViewModelDynamic->Source->Name)), Compiler::EMessageType::Error);
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
const UClass* SourceContextClass = ViewModelDynamic->ParentSource->AuthoritativeClass;
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FFieldIdHandle, FText> FieldIdResult = BindingLibraryCompiler.AddFieldId(SourceContextClass, ViewModelDynamic->NotificationId.GetFieldName());
|
|
if (FieldIdResult.HasError() || !FieldIdResult.GetValue().IsValid())
|
|
{
|
|
AddMessage(FText::Format(LOCTEXT("CouldNotCreateFieldIdForDynamic", "Could not create Field for {0}. {1}"), FText::FromName(ViewModelDynamic->Source->Name), FieldIdResult.GetError()), Compiler::EMessageType::Error);
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
ViewModelDynamic->NotificationIdLibraryCompilerHandle = FieldIdResult.GetValue();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::CompileBindings(const FCompiledBindingLibraryCompiler::FCompileResult& CompileResult, UWidgetBlueprintGeneratedClass* Class, UMVVMViewClass* ViewExtension)
|
|
{
|
|
static IConsoleVariable* CVarDefaultExecutionMode = IConsoleManager::Get().FindConsoleVariable(TEXT("MVVM.DefaultExecutionMode"));
|
|
ensure(CVarDefaultExecutionMode);
|
|
if (!CVarDefaultExecutionMode)
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Error(*LOCTEXT("CantFindDefaultExecutioMode", "The default execution mode cannot be found.").ToString());
|
|
return;
|
|
}
|
|
|
|
// Sort the array to have a predictable list
|
|
{
|
|
UMVVMBlueprintView* LocalBlueprintView = BlueprintView.Get();
|
|
ValidBindings.StableSort([LocalBlueprintView](const TSharedRef<FCompilerBinding>& A, const TSharedRef<FCompilerBinding>& B)
|
|
{
|
|
const FMVVMBlueprintViewBinding& BindingA = *(LocalBlueprintView->GetBindingAt(A->Key.ViewBindingIndex));
|
|
const FMVVMBlueprintViewBinding& BindingB = *(LocalBlueprintView->GetBindingAt(B->Key.ViewBindingIndex));
|
|
return BindingA.BindingId < BindingB.BindingId;
|
|
});
|
|
}
|
|
|
|
ensure(ViewExtension->Bindings.Num() == 0);
|
|
ViewExtension->Bindings.Reset(ValidBindings.Num());
|
|
for (int32 ValidBindingIndex = 0; ValidBindingIndex < ValidBindings.Num(); ++ValidBindingIndex)
|
|
{
|
|
const TSharedRef<FCompilerBinding>& ValidBinding = ValidBindings[ValidBindingIndex];
|
|
const FMVVMBlueprintViewBinding& Binding = *(BlueprintView->GetBindingAt(ValidBinding->Key.ViewBindingIndex));
|
|
|
|
const FMVVMVCompiledBinding* CompiledBinding = CompileResult.Bindings.Find(ValidBinding->BindingHandle);
|
|
if (CompiledBinding == nullptr)
|
|
{
|
|
AddMessageForBinding(Binding, LOCTEXT("CompiledBindingNotGenerated", "Could not generate compiled binding."), Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
bIsCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
FMVVMViewClass_BindingKey BindingKey = FMVVMViewClass_BindingKey(ViewExtension->Bindings.AddDefaulted());
|
|
FMVVMViewClass_Binding& NewBinding = ViewExtension->Bindings[BindingKey.GetIndex()];
|
|
NewBinding.Binding = CompiledBinding ? *CompiledBinding : FMVVMVCompiledBinding();
|
|
NewBinding.Flags = 0;
|
|
NewBinding.ExecutionMode = Binding.bOverrideExecutionMode ? Binding.OverrideExecutionMode : (EMVVMExecutionMode)CVarDefaultExecutionMode->GetInt();
|
|
NewBinding.SourceBitField = 0;
|
|
NewBinding.EditorId = Binding.BindingId;
|
|
|
|
// Add the write source.
|
|
if (ValidBinding->WritePath && ValidBinding->WritePath->OptionalSource)
|
|
{
|
|
int32 ViewExtensionSourceCreatorsIndex = ViewExtension->Sources.IndexOfByPredicate([LookFor = ValidBinding->WritePath->OptionalSource->Name](const FMVVMViewClass_Source& Other)
|
|
{
|
|
return Other.GetName() == LookFor;
|
|
});
|
|
if (ViewExtension->Sources.IsValidIndex(ViewExtensionSourceCreatorsIndex))
|
|
{
|
|
|
|
FMVVMViewClass_SourceKey FieldClassSourceKey = FMVVMViewClass_SourceKey(ViewExtensionSourceCreatorsIndex);
|
|
NewBinding.SourceBitField |= FieldClassSourceKey.GetBit();
|
|
}
|
|
}
|
|
|
|
TArray<FMVVMViewClass_SourceKey, TInlineAllocator<16>> SharedExecuteAtInitializationBindings;
|
|
int32 SharedBindingsCount = 0;
|
|
// Find the source needed by the binding. Also generate every bindings on that source (that need to register to the FieldNotify).
|
|
for (TSharedPtr<FGeneratedReadFieldPathContext> ReadPath : ValidBinding->ReadPaths)
|
|
{
|
|
if (ReadPath->Source == nullptr)
|
|
{
|
|
AddMessageForBinding(Binding, LOCTEXT("InvalidSourceInternal", "Internal error. The binding has an invalid source."), Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
bIsCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
int32 ViewExtensionSourceCreatorsIndex = ViewExtension->Sources.IndexOfByPredicate([LookFor = ReadPath->Source->Name](const FMVVMViewClass_Source& Other)
|
|
{
|
|
return Other.GetName() == LookFor;
|
|
});
|
|
if (!ViewExtension->Sources.IsValidIndex(ViewExtensionSourceCreatorsIndex))
|
|
{
|
|
AddMessageForBinding(Binding, LOCTEXT("CompiledSourceCreatorNotGenerated", "Internal error. The source creator was not generated."), Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
bIsCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
// Add the needed source.
|
|
FMVVMViewClass_SourceKey FieldClassSourceKey = FMVVMViewClass_SourceKey(ViewExtensionSourceCreatorsIndex);
|
|
NewBinding.SourceBitField |= FieldClassSourceKey.GetBit();
|
|
|
|
// FieldId
|
|
const bool bHasField = (ReadPath->NotificationField && !ValidBinding->bIsOneTimeBinding);
|
|
const UE::FieldNotification::FFieldId* CompiledFieldId = bHasField ? CompileResult.FieldIds.Find(ReadPath->NotificationField->LibraryCompilerHandle) : nullptr;
|
|
if (CompiledFieldId == nullptr && bHasField)
|
|
{
|
|
AddMessageForBinding(Binding, LOCTEXT("CompiledFieldNotGenerated", "Internal error. The FieldId was not generated."), Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
bIsCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
bool bExecuteAtInitialization = ValidBinding->Key.bIsForwardBinding;
|
|
|
|
FMVVMViewClass_Source& ClassSource = ViewExtension->Sources[ViewExtensionSourceCreatorsIndex];
|
|
FMVVMViewClass_SourceBinding& NewSourceBinding = ClassSource.Bindings.AddDefaulted_GetRef();
|
|
NewSourceBinding.BindingKey = BindingKey;
|
|
|
|
NewSourceBinding.Flags = 0;
|
|
// Set the ExecuteAtInitialization but remove it later if it's not the last of the group.
|
|
NewSourceBinding.Flags |= (bExecuteAtInitialization) ? (uint8)FMVVMViewClass_SourceBinding::EFlags::ExecuteAtInitialization : 0;
|
|
|
|
if (CompiledFieldId)
|
|
{
|
|
NewSourceBinding.FieldId = FFieldNotificationId(CompiledFieldId->GetName());
|
|
ClassSource.FieldToRegisterTo.AddUnique(FMVVMViewClass_FieldId(*CompiledFieldId));
|
|
|
|
// Count the number of shared instance of that binding. (they can come from the same Source)
|
|
//This flag is used at runtime to know if we should delay the binding execution.
|
|
//We only delay when the field changes. It should only be shared if it has field.
|
|
++SharedBindingsCount;
|
|
}
|
|
|
|
if (bExecuteAtInitialization)
|
|
{
|
|
SharedExecuteAtInitializationBindings.Add(FieldClassSourceKey);
|
|
}
|
|
}
|
|
|
|
// Set binding flags.
|
|
NewBinding.Flags |= (!ValidBinding->bIsOneTimeBinding) ? (uint8)FMVVMViewClass_Binding::EFlags::OneWay : 0;
|
|
NewBinding.Flags |= (SharedBindingsCount > 1) ? (uint8)FMVVMViewClass_Binding::EFlags::Shared : 0;
|
|
NewBinding.Flags |= (Binding.bOverrideExecutionMode) ? (uint8)FMVVMViewClass_Binding::EFlags::OverrideExecuteMode : 0;
|
|
NewBinding.Flags |= (Binding.bEnabled) ? (uint8)FMVVMViewClass_Binding::EFlags::EnabledByDefault : 0;
|
|
|
|
// Only the last binding in the complex conversion has ExecuteAtInitialization.
|
|
if (SharedExecuteAtInitializationBindings.Num() > 1)
|
|
{
|
|
SharedExecuteAtInitializationBindings.Sort([](const FMVVMViewClass_SourceKey& A, const FMVVMViewClass_SourceKey&B)
|
|
{
|
|
return A.GetIndex() < B.GetIndex();
|
|
});
|
|
|
|
//Remove the flag on all the binding on all the sources ecept the last one.
|
|
for (int32 Index = 0; Index < SharedExecuteAtInitializationBindings.Num() - 1; ++Index)
|
|
{
|
|
if (SharedExecuteAtInitializationBindings[Index] != SharedExecuteAtInitializationBindings.Last())
|
|
{
|
|
FMVVMViewClass_Source& ClassSource = ViewExtension->Sources[SharedExecuteAtInitializationBindings[Index].GetIndex()];
|
|
for (FMVVMViewClass_SourceBinding& SourceBinding : ClassSource.Bindings)
|
|
{
|
|
if (SourceBinding.GetBindingKey() == BindingKey)
|
|
{
|
|
SourceBinding.Flags &= ~(uint8)FMVVMViewClass_SourceBinding::EFlags::ExecuteAtInitialization;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Keep only one ExecuteAtInitialization on the last source.
|
|
{
|
|
FMVVMViewClass_Source& ClassSource = ViewExtension->Sources[SharedExecuteAtInitializationBindings.Last().GetIndex()];
|
|
bool bFound = false;
|
|
for (int32 Index = ClassSource.Bindings.Num() - 1; Index >= 0; --Index)
|
|
{
|
|
FMVVMViewClass_SourceBinding& SourceBinding = ClassSource.Bindings[Index];
|
|
if (SourceBinding.GetBindingKey() == BindingKey)
|
|
{
|
|
if (bFound)
|
|
{
|
|
SourceBinding.Flags &= ~(uint8)FMVVMViewClass_SourceBinding::EFlags::ExecuteAtInitialization;
|
|
}
|
|
bFound = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set the real binding key for the async validation nodes
|
|
if (ValidBinding->Type == FCompilerBinding::EType::ComplexConversionFunction)
|
|
{
|
|
UMVVMBlueprintViewConversionFunction* ConversionFunction = Binding.Conversion.GetConversionFunction(ValidBinding->Key.bIsForwardBinding);
|
|
if (ensure(ConversionFunction) && ConversionFunction->IsUbergraphPage())
|
|
{
|
|
UEdGraph* WrapperGraph = ConversionFunction->GetOrCreateIntermediateWrapperGraph(WidgetBlueprintCompilerContext);
|
|
if (ensure(WrapperGraph))
|
|
{
|
|
TArray<UMVVMK2Node_AreSourcesValidForBinding*> AreSourcesValidNodes;
|
|
WrapperGraph->GetNodesOfClass<UMVVMK2Node_AreSourcesValidForBinding>(AreSourcesValidNodes);
|
|
|
|
for (UMVVMK2Node_AreSourcesValidForBinding* AreSourcesValidNode : AreSourcesValidNodes)
|
|
{
|
|
AreSourcesValidNode->BindingKey = BindingKey;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::CompileEvaluateSources(const FCompiledBindingLibraryCompiler::FCompileResult& CompileResult, UWidgetBlueprintGeneratedClass* Class, UMVVMViewClass* ViewExtension)
|
|
{
|
|
for (const TSharedRef<FCompilerSourceViewModelDynamicCreatorContext>& ViewModelDynamic : SourceViewModelDynamicCreatorContexts)
|
|
{
|
|
int32 ViewExtensionSourceCreatorsIndex = ViewExtension->Sources.IndexOfByPredicate([LookFor = ViewModelDynamic->Source->Name](const FMVVMViewClass_Source& Other)
|
|
{
|
|
return Other.GetName() == LookFor;
|
|
});
|
|
if (!ViewExtension->Sources.IsValidIndex(ViewExtensionSourceCreatorsIndex))
|
|
{
|
|
AddMessage(LOCTEXT("CompiledSourceCreatorNotGenerated", "Internal error. The source creator was not generated."), Compiler::EMessageType::Error);
|
|
bIsCompileStepValid = false;
|
|
continue;
|
|
}
|
|
int32 ViewExtensionParentCreatorsIndex = ViewExtension->Sources.IndexOfByPredicate([LookFor = ViewModelDynamic->ParentSource->Name](const FMVVMViewClass_Source& Other)
|
|
{
|
|
return Other.GetName() == LookFor;
|
|
});
|
|
if (!ViewExtension->Sources.IsValidIndex(ViewExtensionParentCreatorsIndex))
|
|
{
|
|
AddMessage(LOCTEXT("CompiledParentSourceCreatorNotGenerated", "Internal error. The parent source creator was not generated."), Compiler::EMessageType::Error);
|
|
bIsCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
const UE::FieldNotification::FFieldId* CompiledFieldId = CompileResult.FieldIds.Find(ViewModelDynamic->NotificationIdLibraryCompilerHandle);
|
|
if (CompiledFieldId == nullptr)
|
|
{
|
|
AddMessage(LOCTEXT("CompiledFieldNotifyNotGenerated", "Internal error. The field notify was not generated."), Compiler::EMessageType::Error);
|
|
bIsCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
FMVVMViewClass_EvaluateSource& NewBinding = ViewExtension->EvaluateSources.AddDefaulted_GetRef();
|
|
NewBinding.ParentFieldId = FFieldNotificationId(CompiledFieldId->GetName());
|
|
NewBinding.ParentSource = FMVVMViewClass_SourceKey(ViewExtensionParentCreatorsIndex);
|
|
NewBinding.ToEvaluate = FMVVMViewClass_SourceKey(ViewExtensionSourceCreatorsIndex);
|
|
|
|
FMVVMViewClass_Source& ClassSource = ViewExtension->Sources[ViewExtensionParentCreatorsIndex];
|
|
ClassSource.FieldToRegisterTo.AddUnique(FMVVMViewClass_FieldId(*CompiledFieldId));
|
|
|
|
ClassSource.Flags |= (uint16)FMVVMViewClass_Source::EFlags::HasEvaluatedBindings;
|
|
}
|
|
|
|
ViewExtension->EvaluateSources.StableSort([](const FMVVMViewClass_EvaluateSource& A, const FMVVMViewClass_EvaluateSource& B)
|
|
{
|
|
if (A.GetParentSource() == B.GetParentSource())
|
|
{
|
|
return A.GetFieldId().GetFieldName().Compare(B.GetFieldId().GetFieldName()) < 0;
|
|
}
|
|
return A.GetParentSource().GetIndex() < B.GetParentSource().GetIndex();
|
|
});
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::PreCompileEvents(UWidgetBlueprintGeneratedClass* Class)
|
|
{
|
|
if (!GetDefault<UMVVMDeveloperProjectSettings>()->bAllowBindingEvent && BlueprintView->GetEvents().Num() > 0)
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Warning(*LOCTEXT("EventsAreNotAllowed", "Binding events are not allowed in your project settings.").ToString());
|
|
}
|
|
|
|
for (TSharedRef<FCompilerEvent>& ValidEvent : ValidEvents)
|
|
{
|
|
UMVVMBlueprintViewEvent* EventPtr = ValidEvent->Event.Get();
|
|
check(EventPtr);
|
|
UEdGraph* GeneratedGraph = EventPtr->GetOrCreateWrapperGraph();
|
|
check(GeneratedGraph);
|
|
|
|
// Does it resolve and are the field allowed
|
|
TValueOrError<FCreateFieldsResult, FText> EventPathResult = CreateFieldContext(Class, EventPtr->GetEventPath(), true);
|
|
if (EventPathResult.HasError())
|
|
{
|
|
AddMessageForEvent(EventPtr
|
|
, FText::Format(Private::PropertyPathIsInvalidFormat, PropertyPathToText(Class, BlueprintView.Get(), EventPtr->GetEventPath()))
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId());
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
//Does EventPath resolves to a MulticastDelegateProperty
|
|
const FMulticastDelegateProperty* DelegateProperty = EventPathResult.GetValue().SkeletalGeneratedFields.Last().IsProperty() ? CastField<const FMulticastDelegateProperty>(EventPathResult.GetValue().SkeletalGeneratedFields.Last().GetProperty()) : nullptr;
|
|
if (DelegateProperty == nullptr)
|
|
{
|
|
AddMessageForEvent(EventPtr
|
|
, FText::Format(LOCTEXT("EventPathIsNotMulticastDelegate", "The event {0} is not a multicast delegate."), PropertyPathToText(Class, BlueprintView.Get(), EventPtr->GetEventPath()))
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId());
|
|
bIsPreCompileStepValid = 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())
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId());
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
// Generate the FieldPath to get the delegate property at runtime
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FFieldPathHandle, FText> EventFieldPathResult = BindingLibraryCompiler.AddFieldPath(EventPathResult.GetValue().SkeletalGeneratedFields, true);
|
|
if (EventFieldPathResult.HasError())
|
|
{
|
|
AddMessageForEvent(EventPtr
|
|
, FText::Format(Private::CouldNotCreateSourceFieldPathFormat, PropertyPathToText(Class, BlueprintView.Get(), EventPtr->GetEventPath()), EventFieldPathResult.GetError())
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId());
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
//todo ? this doesn't support long path?
|
|
|
|
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);
|
|
|
|
SourceName = WidgetName;
|
|
break;
|
|
}
|
|
case EMVVMBlueprintFieldPathSource::ViewModel:
|
|
{
|
|
const FMVVMBlueprintViewModelContext* SourceViewModelContext = BlueprintView->FindViewModel(EventPtr->GetEventPath().GetViewModelId());
|
|
check(SourceViewModelContext);
|
|
FName ViewModelName = SourceViewModelContext->GetViewModelName();
|
|
SourceName = ViewModelName;
|
|
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.
|
|
|
|
ValidEvent->DelegateFieldPathHandle = EventFieldPathResult.StealValue();
|
|
ValidEvent->GeneratedGraphName = EventPtr->GetWrapperGraphName();
|
|
ValidEvent->SourceName = SourceName;
|
|
}
|
|
}
|
|
|
|
void FMVVMViewBlueprintCompiler::CompileEvents(const FCompiledBindingLibraryCompiler::FCompileResult& CompileResult, UWidgetBlueprintGeneratedClass* Class, UMVVMViewClass* ViewExtension)
|
|
{
|
|
// NB. The order is important. The index is used in the generated function to identify the event.
|
|
for (const TSharedRef<FCompilerEvent>& ValidEvent : ValidEvents)
|
|
{
|
|
const FMVVMVCompiledFieldPath* CompiledFieldPath = CompileResult.FieldPaths.Find(ValidEvent->DelegateFieldPathHandle);
|
|
if (CompiledFieldPath == nullptr)
|
|
{
|
|
AddMessageForEvent(ValidEvent->Event.Get(), LOCTEXT("CompiledEventFieldPathNotGenerated", "Could not generate the event path."), Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
bIsCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
if (ValidEvent->GeneratedGraphName.IsNone() || Class->FindFunctionByName(ValidEvent->GeneratedGraphName) == nullptr)
|
|
{
|
|
AddMessageForEvent(ValidEvent->Event.Get(), LOCTEXT("CompiledEventFieldPathNotGenerated", "Could not generate the event path."), Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
bIsCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
int32 FoundSourceIndex = INDEX_NONE;
|
|
if (!ValidEvent->SourceName.IsNone())
|
|
{
|
|
FoundSourceIndex = ViewExtension->Sources.IndexOfByPredicate([ToFind = ValidEvent->SourceName](const FMVVMViewClass_Source& Other)
|
|
{
|
|
return Other.GetName() == ToFind;
|
|
});
|
|
}
|
|
|
|
uint64 SourceBitField = 0;
|
|
{
|
|
if (ValidEvent->WritePath && ValidEvent->WritePath->OptionalSource)
|
|
{
|
|
int32 ViewExtensionSourceCreatorsIndex = ViewExtension->Sources.IndexOfByPredicate([LookFor = ValidEvent->WritePath->OptionalSource->Name](const FMVVMViewClass_Source& Other)
|
|
{
|
|
return Other.GetName() == LookFor;
|
|
});
|
|
if (ViewExtension->Sources.IsValidIndex(ViewExtensionSourceCreatorsIndex))
|
|
{
|
|
|
|
FMVVMViewClass_SourceKey FieldClassSourceKey = FMVVMViewClass_SourceKey(ViewExtensionSourceCreatorsIndex);
|
|
SourceBitField |= FieldClassSourceKey.GetBit();
|
|
}
|
|
}
|
|
for (TSharedPtr<FGeneratedReadFieldPathContext> ReadPath : ValidEvent->ReadPaths)
|
|
{
|
|
if (ReadPath->Source == nullptr)
|
|
{
|
|
AddMessageForEvent(ValidEvent->Event.Get(), LOCTEXT("InvalidEventSourceInternal", "Internal error. The event has an invalid source."), Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
bIsCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
int32 ViewExtensionSourceCreatorsIndex = ViewExtension->Sources.IndexOfByPredicate([LookFor = ReadPath->Source->Name](const FMVVMViewClass_Source& Other)
|
|
{
|
|
return Other.GetName() == LookFor;
|
|
});
|
|
if (!ViewExtension->Sources.IsValidIndex(ViewExtensionSourceCreatorsIndex))
|
|
{
|
|
AddMessageForEvent(ValidEvent->Event.Get(), LOCTEXT("CompiledEventSourceCreatorNotGenerated", "Internal error. The source creator was not generated."), Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
bIsCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
// Add the needed source.
|
|
FMVVMViewClass_SourceKey FieldClassSourceKey = FMVVMViewClass_SourceKey(ViewExtensionSourceCreatorsIndex);
|
|
SourceBitField |= FieldClassSourceKey.GetBit();
|
|
}
|
|
}
|
|
|
|
FMVVMViewClass_Event& NewBinding = ViewExtension->Events.AddDefaulted_GetRef();
|
|
NewBinding.FieldPath = *CompiledFieldPath;
|
|
NewBinding.UserWidgetFunctionName = ValidEvent->GeneratedGraphName;
|
|
NewBinding.SourceToReevaluate = FoundSourceIndex != INDEX_NONE ? FMVVMViewClass_SourceKey(FoundSourceIndex) : FMVVMViewClass_SourceKey();
|
|
NewBinding.SourceBitField = SourceBitField;
|
|
}
|
|
}
|
|
|
|
void FMVVMViewBlueprintCompiler::PreCompileConditions(UWidgetBlueprintGeneratedClass* Class)
|
|
{
|
|
if (!GetDefault<UMVVMDeveloperProjectSettings>()->bAllowConditionBinding && BlueprintView->GetConditions().Num() > 0)
|
|
{
|
|
WidgetBlueprintCompilerContext.MessageLog.Warning(*LOCTEXT("ConditionsAreNotAllowed", "Condition bindings are not allowed in your project settings.").ToString());
|
|
}
|
|
|
|
for (TSharedRef<FCompilerCondition>& ValidCondition : ValidConditions)
|
|
{
|
|
UMVVMBlueprintViewCondition* ConditionPtr = ValidCondition->Condition.Get();
|
|
check(ConditionPtr);
|
|
UEdGraph* GeneratedGraph = ConditionPtr->GetOrCreateWrapperGraph();
|
|
check(GeneratedGraph);
|
|
|
|
// Does it resolve and are the field allowed
|
|
TValueOrError<FCreateFieldsResult, FText> ConditionPathResult = CreateFieldContext(Class, ConditionPtr->GetConditionPath(), true);
|
|
if (ConditionPathResult.HasError())
|
|
{
|
|
AddMessageForCondition(ConditionPtr
|
|
, FText::Format(Private::PropertyPathIsInvalidFormat, PropertyPathToText(Class, BlueprintView.Get(), ConditionPtr->GetConditionPath()))
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId());
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
FName SourceName;
|
|
switch (ConditionPtr->GetConditionPath().GetSource(WidgetBlueprintCompilerContext.WidgetBlueprint()))
|
|
{
|
|
case EMVVMBlueprintFieldPathSource::SelfContext:
|
|
SourceName = WidgetBlueprintCompilerContext.WidgetBlueprint()->GetFName();
|
|
break;
|
|
case EMVVMBlueprintFieldPathSource::Widget:
|
|
{
|
|
FName WidgetName = ConditionPtr->GetConditionPath().GetWidgetName();
|
|
checkf(!WidgetName.IsNone(), TEXT("The destination should have been checked and set bAreSourceContextsValid."));
|
|
const bool bSourceIsUserWidget = WidgetName == Class->ClassGeneratedBy->GetFName();
|
|
ensure(!bSourceIsUserWidget);
|
|
|
|
SourceName = WidgetName;
|
|
break;
|
|
}
|
|
case EMVVMBlueprintFieldPathSource::ViewModel:
|
|
{
|
|
const FMVVMBlueprintViewModelContext* SourceViewModelContext = BlueprintView->FindViewModel(ConditionPtr->GetConditionPath().GetViewModelId());
|
|
check(SourceViewModelContext);
|
|
FName ViewModelName = SourceViewModelContext->GetViewModelName();
|
|
SourceName = ViewModelName;
|
|
break;
|
|
}
|
|
default:
|
|
ensureAlwaysMsgf(false, TEXT("An EMVVMBlueprintFieldPathSource case was not checked."));
|
|
}
|
|
|
|
for (TSharedPtr<FGeneratedReadFieldPathContext> ReadPath : ValidCondition->ReadPaths)
|
|
{
|
|
if (ReadPath->NotificationField)
|
|
{
|
|
if (ReadPath->NotificationField->Source)
|
|
{
|
|
const UClass* SourceContextClass = ReadPath->NotificationField->Source->AuthoritativeClass;
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FFieldIdHandle, FText> FieldIdResult = BindingLibraryCompiler.AddFieldId(SourceContextClass, ReadPath->NotificationField->NotificationId.GetFieldName());
|
|
if (FieldIdResult.HasError() || !FieldIdResult.GetValue().IsValid())
|
|
{
|
|
AddMessageForCondition(ValidCondition
|
|
, FText::Format(LOCTEXT("CouldNotCreateFieldId", "Could not create Field. {0}"), FieldIdResult.GetError())
|
|
, Compiler::EMessageType::Error
|
|
, FMVVMBlueprintPinId()
|
|
);
|
|
bIsPreCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
ReadPath->NotificationField->LibraryCompilerHandle = FieldIdResult.StealValue();
|
|
}
|
|
}
|
|
}
|
|
|
|
// No need to add the generated function to the field compiler.
|
|
//They are in the BP generated code.
|
|
|
|
ValidCondition->GeneratedGraphName = ConditionPtr->GetWrapperGraphName();
|
|
ValidCondition->SourceName = SourceName;
|
|
}
|
|
}
|
|
|
|
void FMVVMViewBlueprintCompiler::CompileConditions(const FCompiledBindingLibraryCompiler::FCompileResult& CompileResult, UWidgetBlueprintGeneratedClass* Class, UMVVMViewClass* ViewExtension)
|
|
{
|
|
// NB. The order is important. The index is used in the generated function to identify the event.
|
|
for (const TSharedRef<FCompilerCondition>& ValidCondition : ValidConditions)
|
|
{
|
|
if (ValidCondition->GeneratedGraphName.IsNone() || Class->FindFunctionByName(ValidCondition->GeneratedGraphName) == nullptr)
|
|
{
|
|
AddMessageForCondition(ValidCondition->Condition.Get(), LOCTEXT("CompiledConditionFieldPathNotGenerated", "Could not generate the condition path."), Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
bIsCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
int32 FoundSourceIndex = INDEX_NONE;
|
|
if (!ValidCondition->SourceName.IsNone())
|
|
{
|
|
FoundSourceIndex = ViewExtension->Sources.IndexOfByPredicate([ToFind = ValidCondition->SourceName](const FMVVMViewClass_Source& Other)
|
|
{
|
|
return Other.GetName() == ToFind;
|
|
});
|
|
}
|
|
|
|
|
|
FMVVMViewClass_ConditionKey ConditionKey = FMVVMViewClass_ConditionKey(ViewExtension->Conditions.AddDefaulted());
|
|
FMVVMViewClass_Condition& NewCondition = ViewExtension->Conditions[ConditionKey.GetIndex()];
|
|
|
|
uint64 SourceBitField = 0;
|
|
{
|
|
for (TSharedPtr<FGeneratedReadFieldPathContext> ReadPath : ValidCondition->ReadPaths)
|
|
{
|
|
if (ReadPath->Source == nullptr)
|
|
{
|
|
AddMessageForCondition(ValidCondition->Condition.Get(), LOCTEXT("InvalidConditionSourceInternal", "Internal error. The condition has an invalid source."), Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
bIsCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
int32 ViewExtensionSourceCreatorsIndex = ViewExtension->Sources.IndexOfByPredicate([LookFor = ReadPath->Source->Name](const FMVVMViewClass_Source& Other)
|
|
{
|
|
return Other.GetName() == LookFor;
|
|
});
|
|
if (!ViewExtension->Sources.IsValidIndex(ViewExtensionSourceCreatorsIndex))
|
|
{
|
|
AddMessageForCondition(ValidCondition->Condition.Get(), LOCTEXT("CompiledConditionSourceCreatorNotGenerated", "Internal error. The source creator was not generated."), Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
bIsCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
// Add the needed source.
|
|
FMVVMViewClass_SourceKey FieldClassSourceKey = FMVVMViewClass_SourceKey(ViewExtensionSourceCreatorsIndex);
|
|
SourceBitField |= FieldClassSourceKey.GetBit();
|
|
|
|
const bool bHasField = ReadPath->NotificationField != nullptr;
|
|
const UE::FieldNotification::FFieldId* CompiledFieldId = ReadPath->NotificationField != nullptr ? CompileResult.FieldIds.Find(ReadPath->NotificationField->LibraryCompilerHandle) : nullptr;
|
|
if (CompiledFieldId == nullptr && bHasField)
|
|
{
|
|
AddMessageForCondition(ValidCondition, LOCTEXT("CompiledFieldNotGenerated", "Internal error. The FieldId was not generated."), Compiler::EMessageType::Error, FMVVMBlueprintPinId());
|
|
bIsCompileStepValid = false;
|
|
continue;
|
|
}
|
|
|
|
FMVVMViewClass_Source& ClassSource = ViewExtension->Sources[ViewExtensionSourceCreatorsIndex];
|
|
FMVVMViewClass_SourceCondition& NewSourceCondition = ClassSource.Conditions.AddDefaulted_GetRef();
|
|
NewSourceCondition.ConditionKey = ConditionKey;
|
|
|
|
if (CompiledFieldId != nullptr)
|
|
{
|
|
NewSourceCondition.FieldId = FFieldNotificationId(CompiledFieldId->GetName());
|
|
ClassSource.FieldToRegisterTo.AddUnique(FMVVMViewClass_FieldId(*CompiledFieldId));
|
|
}
|
|
}
|
|
}
|
|
|
|
NewCondition.UserWidgetFunctionName = ValidCondition->GeneratedGraphName;
|
|
NewCondition.SourceToReevaluate = FoundSourceIndex != INDEX_NONE ? FMVVMViewClass_SourceKey(FoundSourceIndex) : FMVVMViewClass_SourceKey();
|
|
NewCondition.SourceBitField = SourceBitField;
|
|
}
|
|
}
|
|
|
|
void FMVVMViewBlueprintCompiler::PreCompileViewExtensions(UWidgetBlueprintGeneratedClass* Class)
|
|
{
|
|
struct FExposedCompiler : Compiler::IMVVMBlueprintViewPrecompile
|
|
{
|
|
FExposedCompiler(FMVVMViewBlueprintCompiler* InSelf, UWidgetBlueprintGeneratedClass* InClass)
|
|
: Self(InSelf),
|
|
Class(InClass)
|
|
{}
|
|
|
|
virtual const UMVVMBlueprintView* GetBlueprintView() const override
|
|
{
|
|
return Self->BlueprintView.Get();
|
|
}
|
|
|
|
virtual const TMap<FName, UWidget*>& GetWidgetNameToWidgetPointerMap() const override
|
|
{
|
|
return Self->WidgetNameToWidgetPointerMap;
|
|
}
|
|
|
|
virtual TArray<Compiler::FCompilerBindingHandle> GetAllBindings() const override
|
|
{
|
|
TArray<Compiler::FCompilerBindingHandle> BindingHandles;
|
|
for (const TSharedRef<FCompilerBinding>& Binding : Self->ValidBindings)
|
|
{
|
|
BindingHandles.Add(Binding->CompilerBindingHandle);
|
|
}
|
|
|
|
return BindingHandles;
|
|
}
|
|
|
|
virtual TArray<TArray<UE::MVVM::FMVVMConstFieldVariant>> GetBindingReadFields(Compiler::FCompilerBindingHandle BindingHandle) override
|
|
{
|
|
TArray<TArray<UE::MVVM::FMVVMConstFieldVariant>> BindingReadFields;
|
|
const TSharedRef<FCompilerBinding>* Binding = Self->ValidBindings.FindByPredicate([BindingHandle](const TSharedRef<FCompilerBinding>& OtherBinding) { return OtherBinding->CompilerBindingHandle == BindingHandle; });
|
|
if (Binding)
|
|
{
|
|
for (const TSharedPtr<FGeneratedReadFieldPathContext>& ReadPath : Binding->Get().ReadPaths)
|
|
{
|
|
BindingReadFields.Add(ReadPath->SkeletalGeneratedFields);
|
|
}
|
|
}
|
|
|
|
return BindingReadFields;
|
|
}
|
|
|
|
virtual TArray<UE::MVVM::FMVVMConstFieldVariant> GetBindingWriteFields(Compiler::FCompilerBindingHandle BindingHandle) override
|
|
{
|
|
const TSharedRef<FCompilerBinding>* Binding = Self->ValidBindings.FindByPredicate([BindingHandle](const TSharedRef<FCompilerBinding>& OtherBinding) { return OtherBinding->CompilerBindingHandle == BindingHandle; });
|
|
if (Binding)
|
|
{
|
|
if (Binding->Get().WritePath)
|
|
{
|
|
return Binding->Get().WritePath->SkeletalGeneratedFields;
|
|
}
|
|
}
|
|
|
|
return TArray<UE::MVVM::FMVVMConstFieldVariant>();
|
|
}
|
|
|
|
virtual const FProperty* GetBindingSourceProperty(Compiler::FCompilerBindingHandle BindingHandle) override
|
|
{
|
|
const TSharedRef<FCompilerBinding>* Binding = Self->ValidBindings.FindByPredicate([BindingHandle](const TSharedRef<FCompilerBinding>& OtherBinding) { return OtherBinding->CompilerBindingHandle == BindingHandle; });
|
|
if (Binding)
|
|
{
|
|
if (Binding->Get().Type == FCompilerBinding::EType::ComplexConversionFunction
|
|
|| Binding->Get().Type == FCompilerBinding::EType::SimpleConversionFunction)
|
|
{
|
|
if (const UFunction* CompiledFunction = Binding->Get().ConversionFunction->GetCompiledFunction(Class))
|
|
{
|
|
TValueOrError<const FProperty*, FText> ReturnProperty = UE::MVVM::BindingHelper::TryGetReturnTypeForConversionFunction(CompiledFunction);
|
|
if (ReturnProperty.HasValue())
|
|
{
|
|
return ReturnProperty.GetValue();
|
|
}
|
|
}
|
|
}
|
|
else if (Binding->Get().Type == FCompilerBinding::EType::Assignment)
|
|
{
|
|
if (Binding->Get().ReadPaths.Num() > 0)
|
|
{
|
|
if (const TSharedPtr<FGeneratedReadFieldPathContext>& ReadPath = Binding->Get().ReadPaths[0])
|
|
{
|
|
if (ReadPath->SkeletalGeneratedFields.Num() > 0)
|
|
{
|
|
UE::MVVM::FMVVMConstFieldVariant LastSourceField = ReadPath->SkeletalGeneratedFields.Last();
|
|
if (LastSourceField.IsProperty())
|
|
{
|
|
return LastSourceField.GetProperty();
|
|
}
|
|
else if (LastSourceField.IsFunction())
|
|
{
|
|
return BindingHelper::GetReturnProperty(LastSourceField.GetFunction());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
virtual TValueOrError<FCompiledBindingLibraryCompiler::FFieldPathHandle, FText> AddObjectFieldPath(const UE::MVVM::Compiler::IMVVMBlueprintViewPrecompile::FObjectFieldPathArgs& Args) override
|
|
{
|
|
return UE::MVVM::Private::AddObjectFieldPath(Self->BindingLibraryCompiler, Args.Class, Args.ObjectPath, Args.ExpectedType);
|
|
}
|
|
|
|
virtual TValueOrError<FCompiledBindingLibraryCompiler::FFieldPathHandle, FText> AddFieldPath(TArrayView<const FMVVMConstFieldVariant> InFieldPath, bool bInRead) override
|
|
{
|
|
return Self->BindingLibraryCompiler.AddFieldPath(InFieldPath, bInRead);
|
|
}
|
|
|
|
virtual void AddMessageForBinding(Compiler::FCompilerBindingHandle BindingHandle, const FText& MessageText, Compiler::EMessageType MessageType) const override
|
|
{
|
|
const TSharedRef<FCompilerBinding>* Binding = Self->ValidBindings.FindByPredicate([BindingHandle](const TSharedRef<FCompilerBinding>& OtherBinding) { return OtherBinding->CompilerBindingHandle == BindingHandle; });
|
|
if (Binding)
|
|
{
|
|
Self->AddMessageForBinding(*Binding, MessageText, MessageType, FMVVMBlueprintPinId());
|
|
}
|
|
}
|
|
|
|
virtual void AddMessage(const FText& MessageText, Compiler::EMessageType MessageType) override
|
|
{
|
|
Self->AddMessage(MessageText, MessageType);
|
|
}
|
|
|
|
virtual void MarkPrecompileStepInvalid() override
|
|
{
|
|
Self->bIsPreCompileStepValid = false;
|
|
}
|
|
|
|
virtual ~FExposedCompiler() {};
|
|
|
|
private:
|
|
FMVVMViewBlueprintCompiler* Self = nullptr;
|
|
UWidgetBlueprintGeneratedClass* Class = nullptr;
|
|
};
|
|
|
|
FExposedCompiler ExposedCompiler(this, Class);
|
|
|
|
for (TSharedRef<FCompilerExtension>& Extension : ValidExtensions)
|
|
{
|
|
UMVVMBlueprintViewExtension* ExtensionPtr = Extension->Extension.Get();
|
|
if (ensure(ExtensionPtr))
|
|
{
|
|
ExtensionPtr->Precompile(&ExposedCompiler, Class);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMVVMViewBlueprintCompiler::CompileViewExtensions(const FCompiledBindingLibraryCompiler::FCompileResult& CompileResult, UWidgetBlueprintGeneratedClass* Class, UMVVMViewClass* ViewExtension)
|
|
{
|
|
struct FExposedCompiler : Compiler::IMVVMBlueprintViewCompile
|
|
{
|
|
FExposedCompiler(FMVVMViewBlueprintCompiler* InSelf, const FCompiledBindingLibraryCompiler::FCompileResult& InCompileResult, UMVVMViewClass* InViewExtension)
|
|
: Self(InSelf)
|
|
, CompileResult(InCompileResult)
|
|
, ViewClass(InViewExtension)
|
|
{}
|
|
|
|
virtual const UMVVMBlueprintView* GetBlueprintView() const override
|
|
{
|
|
return Self->BlueprintView.Get();
|
|
}
|
|
|
|
virtual const TMap<FName, UWidget*>& GetWidgetNameToWidgetPointerMap() const override
|
|
{
|
|
return Self->WidgetNameToWidgetPointerMap;
|
|
}
|
|
|
|
virtual TValueOrError<FMVVMVCompiledFieldPath, void> GetFieldPath(FCompiledBindingLibraryCompiler::FFieldPathHandle FieldPath) override
|
|
{
|
|
if (FMVVMVCompiledFieldPath* CompiledPath = CompileResult.FieldPaths.Find(FieldPath))
|
|
{
|
|
return MakeValue(*CompiledPath);
|
|
}
|
|
else
|
|
{
|
|
return MakeError();
|
|
}
|
|
}
|
|
|
|
virtual void AddMessageForBinding(Compiler::FCompilerBindingHandle BindingHandle, const FText& MessageText, Compiler::EMessageType MessageType) const override
|
|
{
|
|
const TSharedRef<FCompilerBinding>* Binding = Self->ValidBindings.FindByPredicate([BindingHandle](const TSharedRef<FCompilerBinding>& OtherBinding) { return OtherBinding->CompilerBindingHandle == BindingHandle; });
|
|
if (Binding)
|
|
{
|
|
Self->AddMessageForBinding(*Binding, MessageText, MessageType, FMVVMBlueprintPinId());
|
|
}
|
|
}
|
|
|
|
virtual void AddMessage(const FText& MessageText, Compiler::EMessageType MessageType) override
|
|
{
|
|
Self->AddMessage(MessageText, MessageType);
|
|
}
|
|
|
|
virtual void MarkCompileStepInvalid() override
|
|
{
|
|
Self->bIsCompileStepValid = false;
|
|
}
|
|
|
|
virtual UMVVMViewClassExtension* CreateViewClassExtension(TSubclassOf<UMVVMViewClassExtension> ExtensionClass)
|
|
{
|
|
return Self->CreateViewClassExtension(ExtensionClass, ViewClass);
|
|
}
|
|
|
|
virtual ~FExposedCompiler() {};
|
|
|
|
private:
|
|
FMVVMViewBlueprintCompiler* Self = nullptr;
|
|
FCompiledBindingLibraryCompiler::FCompileResult CompileResult;
|
|
UMVVMViewClass* ViewClass = nullptr;
|
|
};
|
|
|
|
FExposedCompiler ExposedCompiler(this, CompileResult, ViewExtension);
|
|
|
|
for (TSharedRef<FCompilerExtension>& Extension : ValidExtensions)
|
|
{
|
|
UMVVMBlueprintViewExtension* ExtensionPtr = Extension->Extension.Get();
|
|
if (ensure(ExtensionPtr))
|
|
{
|
|
ExtensionPtr->Compile(&ExposedCompiler, Class, ViewExtension);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMVVMViewBlueprintCompiler::PreCompileSourceDependencies(UWidgetBlueprintGeneratedClass* Class)
|
|
{
|
|
//i. ViewmodelA.ViewmodelB = ViewmodelC.Value -> ViewmodelA needs to init before ViewmodelA_ViewmodelB, ViewmodelA before ViewmodelC, ViewmodelC before ViewmodelA_ViewmodelB
|
|
//ii. ViewmodelA.ViewmodelB.Value = ViewmodelC.Value -> ViewmodelA needs to init before ViewmodelA_ViewmodelB, ViewmodelA before ViewmodelC, ViewmodelA_ViewmodelB before ViewmodelC
|
|
|
|
// Dynamic sources depends on their parent. (i and ii ViewmodelA before ViewmodelA_ViewmodelB)
|
|
for (const TSharedRef<FCompilerSourceViewModelDynamicCreatorContext>& DynamicContext : SourceViewModelDynamicCreatorContexts)
|
|
{
|
|
ensure(DynamicContext->Source);
|
|
ensure(DynamicContext->ParentSource);
|
|
DynamicContext->Source->Dependencies.AddUnique(DynamicContext->ParentSource);
|
|
}
|
|
|
|
for (const TSharedRef<FCompilerBinding>& ValidBinding : ValidBindings)
|
|
{
|
|
if (ValidBinding->WritePath)
|
|
{
|
|
for (const TSharedPtr<FGeneratedReadFieldPathContext>& ReadPath : ValidBinding->ReadPaths)
|
|
{
|
|
// All the read depends on the write (i and ii ViewmodelA before ViewmodelC)
|
|
if (ensure(ReadPath->Source))
|
|
{
|
|
if (ValidBinding->WritePath->OptionalSource)
|
|
{
|
|
ReadPath->Source->Dependencies.AddUnique(ValidBinding->WritePath->OptionalSource);
|
|
}
|
|
|
|
// The write depends on the read (i ViewmodelC before ViewmodelA_ViewmodelB)
|
|
if (ValidBinding->WritePath->OptionalDependencySource)
|
|
{
|
|
ValidBinding->WritePath->OptionalDependencySource->Dependencies.AddUnique(ReadPath->Source);
|
|
}
|
|
}
|
|
|
|
// NB (ii ViewmodelA_ViewmodelB before ViewmodelC) is implicit
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMVVMViewBlueprintCompiler::SortSourceFields(const FCompiledBindingLibraryCompiler::FCompileResult& CompileResult, UWidgetBlueprintGeneratedClass* Class, UMVVMViewClass* ViewExtension)
|
|
{
|
|
for (FMVVMViewClass_Source& Source : ViewExtension->Sources)
|
|
{
|
|
for (const FMVVMViewClass_FieldId& Field : Source.FieldToRegisterTo)
|
|
{
|
|
if (Field.GetFieldId().GetIndex() == INDEX_NONE)
|
|
{
|
|
AddMessage(LOCTEXT("SortSourceFieldsInvalidId", "Internal error. The id is invalid."), Compiler::EMessageType::Error);
|
|
bIsCompileStepValid = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
Source.FieldToRegisterTo.StableSort([](const FMVVMViewClass_FieldId& A, const FMVVMViewClass_FieldId& B)
|
|
{
|
|
return A.GetFieldId().GetIndex() < B.GetFieldId().GetIndex();
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
TValueOrError<FMVVMViewBlueprintCompiler::FGetFieldsResult, FText> FMVVMViewBlueprintCompiler::GetFields(const UWidgetBlueprintGeneratedClass* Class, const FMVVMBlueprintPropertyPath& PropertyPath) const
|
|
{
|
|
FGetFieldsResult Result;
|
|
if (!PropertyPath.IsValid())
|
|
{
|
|
ensureAlwaysMsgf(false, TEXT("Empty property path found. It should have been catch before."));
|
|
return MakeError(FText::GetEmpty());
|
|
}
|
|
|
|
auto FindSource = [Self = this](const FName PropertyName, FCompilerBindingSource::EType ExpectedType) -> TValueOrError<TSharedPtr<FCompilerBindingSource>, FText>
|
|
{
|
|
const TSharedRef<FCompilerBindingSource>* Found = Self->NeededBindingSources.FindByPredicate([PropertyName](const TSharedRef<FCompilerBindingSource>& Other) { return Other->Name == PropertyName; });
|
|
if (Found)
|
|
{
|
|
if ((*Found)->Type != ExpectedType)
|
|
{
|
|
return MakeError(LOCTEXT("NotExpectedSourceType", "Internal error. The source of the path is not of the expected type."));
|
|
}
|
|
return MakeValue(*Found);
|
|
}
|
|
// It can be valid to not have a BindingSource, if it's a widget in a WritePath
|
|
return MakeValue(TSharedPtr<FCompilerBindingSource>());
|
|
};
|
|
|
|
// If the path is "self.widget.property", "self.viewmodel.property", then remove the "self".
|
|
EMVVMBlueprintFieldPathSource FieldPathSource = PropertyPath.GetSource(WidgetBlueprintCompilerContext.WidgetBlueprint());
|
|
TArray<UE::MVVM::FMVVMConstFieldVariant> PropertyPathFields = PropertyPath.GetFields(Class);
|
|
FGuid ViewModelId = PropertyPath.GetViewModelId();
|
|
FName DestinationWidgetName = PropertyPath.GetWidgetName();
|
|
bool bIsComponentOnSelf = false;
|
|
if (PropertyPath.IsComponent())
|
|
{
|
|
const UWidgetBlueprintGeneratedClass* ComponentOwningWidgetBlueprint = UE::MVVM::FieldPathHelper::GetComponentPropertyPathSource(PropertyPath.GetFields(Class), Class);
|
|
bIsComponentOnSelf = ComponentOwningWidgetBlueprint == Class;
|
|
}
|
|
|
|
if (FieldPathSource == EMVVMBlueprintFieldPathSource::SelfContext)
|
|
{
|
|
if (PropertyPathFields.Num() >= 2 && PropertyPathFields[0].IsProperty() && !bIsComponentOnSelf)
|
|
{
|
|
FName FieldName = PropertyPathFields[0].GetName();
|
|
if (WidgetNameToWidgetPointerMap.Contains(FieldName))
|
|
{
|
|
FieldPathSource = EMVVMBlueprintFieldPathSource::Widget;
|
|
DestinationWidgetName = FieldName;
|
|
PropertyPathFields.RemoveAt(0);
|
|
}
|
|
else if (const FMVVMBlueprintViewModelContext* SourceViewModelContext = BlueprintView->FindViewModel(FieldName))
|
|
{
|
|
FieldPathSource = EMVVMBlueprintFieldPathSource::ViewModel;
|
|
ViewModelId = SourceViewModelContext->GetViewModelId();
|
|
PropertyPathFields.RemoveAt(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build the full path.
|
|
switch (FieldPathSource)
|
|
{
|
|
case EMVVMBlueprintFieldPathSource::ViewModel:
|
|
{
|
|
const FCompilerViewModelCreatorContext* FoundViewModelCreator = ViewModelCreatorContexts.FindByPredicate([ViewModelId](const FCompilerViewModelCreatorContext& Other){ return Other.ViewModelContext.GetViewModelId() == ViewModelId; });
|
|
check(FoundViewModelCreator);
|
|
const FName PropertyName = FoundViewModelCreator->ViewModelContext.GetViewModelName();
|
|
const FCompilerUserWidgetProperty* FoundUserWidgetProperty = NeededUserWidgetProperties.FindByPredicate([PropertyName](const FCompilerUserWidgetProperty& Other){ return Other.Name == PropertyName; });
|
|
check(FoundUserWidgetProperty);
|
|
check(FoundViewModelCreator->ViewModelContext.GetViewModelClass());
|
|
check(FoundUserWidgetProperty->AuthoritativeClass == FoundViewModelCreator->ViewModelContext.GetViewModelClass());
|
|
|
|
TValueOrError<TSharedPtr<FCompilerBindingSource>, FText> FindSourceResult = FindSource(PropertyName, FCompilerBindingSource::EType::ViewModel);
|
|
if (FindSourceResult.HasError())
|
|
{
|
|
return MakeError(FindSourceResult.StealError());
|
|
}
|
|
if (!FindSourceResult.GetValue().IsValid())
|
|
{
|
|
return MakeError(LOCTEXT("ViewModelShouldHaveSource", "Internal error. Viewmodel should have a source."));
|
|
}
|
|
|
|
Result.GeneratedFields = AppendBaseField(Class, PropertyName, PropertyPathFields);
|
|
Result.OptionalSource = FindSourceResult.StealValue();
|
|
break;
|
|
}
|
|
case EMVVMBlueprintFieldPathSource::SelfContext:
|
|
{
|
|
TValueOrError<TSharedPtr<FCompilerBindingSource>, FText> FindSourceResult = FindSource(WidgetBlueprintCompilerContext.WidgetBlueprint()->GetFName(), FCompilerBindingSource::EType::Self);
|
|
if (FindSourceResult.HasError())
|
|
{
|
|
return MakeError(FindSourceResult.StealError());
|
|
}
|
|
if (!FindSourceResult.GetValue().IsValid())
|
|
{
|
|
return MakeError(LOCTEXT("WidgetBlueprintShouldHaveSource", "Internal error. The blueprint should have a source."));
|
|
}
|
|
|
|
Result.GeneratedFields = AppendBaseField(Class, FName(), PropertyPathFields);
|
|
Result.OptionalSource = FindSourceResult.StealValue();
|
|
break;
|
|
}
|
|
case EMVVMBlueprintFieldPathSource::Widget:
|
|
{
|
|
check(WidgetNameToWidgetPointerMap.Contains(DestinationWidgetName));
|
|
checkf(!DestinationWidgetName.IsNone(), TEXT("The destination should have been checked and set bAreSourceContextsValid."));
|
|
|
|
const FCompilerUserWidgetProperty* FoundUserWidgetProperty = NeededUserWidgetProperties.FindByPredicate([DestinationWidgetName](const FCompilerUserWidgetProperty& Other) { return Other.Name == DestinationWidgetName; });
|
|
check(FoundUserWidgetProperty);
|
|
|
|
TValueOrError<TSharedPtr<FCompilerBindingSource>, FText> FindSourceResult = bIsComponentOnSelf ? FindSource(WidgetBlueprintCompilerContext.WidgetBlueprint()->GetFName(), FCompilerBindingSource::EType::Self) : FindSource(DestinationWidgetName, FCompilerBindingSource::EType::Widget);
|
|
if (FindSourceResult.HasError())
|
|
{
|
|
return MakeError(FindSourceResult.StealError());
|
|
}
|
|
|
|
Result.GeneratedFields = bIsComponentOnSelf ? AppendBaseField(Class, FName(), PropertyPathFields) : AppendBaseField(Class, DestinationWidgetName, PropertyPathFields);
|
|
Result.OptionalSource = FindSourceResult.StealValue();
|
|
break;
|
|
}
|
|
default:
|
|
ensureAlwaysMsgf(false, TEXT("Not supported."));
|
|
Result.GeneratedFields = AppendBaseField(Class, FName(), PropertyPathFields);
|
|
break;
|
|
}
|
|
|
|
Result.GeneratedFrom = FieldPathSource;
|
|
return MakeValue(MoveTemp(Result));
|
|
}
|
|
|
|
|
|
TArray<FMVVMConstFieldVariant> FMVVMViewBlueprintCompiler::AppendBaseField(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;
|
|
}
|
|
|
|
|
|
TValueOrError<FMVVMViewBlueprintCompiler::FCreateFieldsResult, FText> FMVVMViewBlueprintCompiler::CreateFieldContext(const UWidgetBlueprintGeneratedClass* Class, const FMVVMBlueprintPropertyPath& PropertyPath, bool bForSourceReading) const
|
|
{
|
|
FMVVMViewBlueprintCompiler::FCreateFieldsResult Result;
|
|
|
|
// Evaluate the getter/setter path.
|
|
{
|
|
TValueOrError<FGetFieldsResult, FText> GetFieldResult = GetFields(Class, PropertyPath);
|
|
if (GetFieldResult.HasError())
|
|
{
|
|
return MakeError(GetFieldResult.StealError());
|
|
}
|
|
|
|
Result.GeneratedFrom = MoveTemp(GetFieldResult.GetValue().GeneratedFrom);
|
|
Result.OptionalSource = MoveTemp(GetFieldResult.GetValue().OptionalSource);
|
|
Result.GeneratedFields = MoveTemp(GetFieldResult.GetValue().GeneratedFields);
|
|
Result.bIsComponent = PropertyPath.IsComponent();
|
|
}
|
|
|
|
if (!IsPropertyPathValid(WidgetBlueprintCompilerContext.WidgetBlueprint(), Result.GeneratedFields))
|
|
{
|
|
return MakeError(FText::Format(Private::PropertyPathIsInvalidFormat, PropertyPathToText(Class, BlueprintView.Get(), PropertyPath)));
|
|
}
|
|
|
|
// Generate the path with property converted to BP function
|
|
TValueOrError<TArray<FMVVMConstFieldVariant>, FText> SkeletalGeneratedFieldsResult = FieldPathHelper::GenerateFieldPathList(Result.GeneratedFields, bForSourceReading);
|
|
if (SkeletalGeneratedFieldsResult.HasError())
|
|
{
|
|
return MakeError(FText::Format(Private::CouldNotCreateSourceFieldPathFormat, PropertyPathToText(Class, BlueprintView.Get(), PropertyPath), SkeletalGeneratedFieldsResult.GetError()));
|
|
}
|
|
if (!IsPropertyPathValid(WidgetBlueprintCompilerContext.WidgetBlueprint(), SkeletalGeneratedFieldsResult.GetValue()))
|
|
{
|
|
return MakeError(FText::Format(Private::CouldNotCreateSourceFieldPathFormat, PropertyPathToText(Class, BlueprintView.Get(), PropertyPath), LOCTEXT("NotAValidPropertyPath", "The path is not valid.")));
|
|
}
|
|
Result.SkeletalGeneratedFields = SkeletalGeneratedFieldsResult.StealValue();
|
|
|
|
return MakeValue(Result);
|
|
}
|
|
|
|
|
|
TValueOrError<TSharedPtr<FMVVMViewBlueprintCompiler::FCompilerNotifyFieldId>, FText> FMVVMViewBlueprintCompiler::CreateNotifyFieldId(const UWidgetBlueprintGeneratedClass* Class, const TSharedPtr<FGeneratedReadFieldPathContext>& ReadFieldContext)
|
|
{
|
|
check(ReadFieldContext->SkeletalGeneratedFields.Num() > 0);
|
|
|
|
// The path may contains another INotifyFieldValueChanged
|
|
TValueOrError<FieldPathHelper::FParsedNotifyBindingInfo, FText> BindingInfoResult = FieldPathHelper::GetNotifyBindingInfoFromFieldPath(Class, ReadFieldContext->SkeletalGeneratedFields);
|
|
if (BindingInfoResult.HasError())
|
|
{
|
|
return MakeError(BindingInfoResult.StealError());
|
|
}
|
|
|
|
const FieldPathHelper::FParsedNotifyBindingInfo& BindingInfo = BindingInfoResult.GetValue();
|
|
if (!BindingInfo.NotifyFieldId.IsValid() || ReadFieldContext->Source == nullptr)
|
|
{
|
|
return MakeValue(TSharedPtr<FCompilerNotifyFieldId>());
|
|
}
|
|
|
|
// the path is UserWidget->Widget->Property. THe source is the UserWidget but it should not, UserWidget shuould be remove from the path.
|
|
FCompilerNotifyFieldId Result;
|
|
Result.NotificationId = BindingInfo.NotifyFieldId;
|
|
Result.Source = ReadFieldContext->Source;
|
|
Result.ViewModelDynamic.Reset();
|
|
|
|
auto GetClassFromField = [](UE::MVVM::FMVVMConstFieldVariant Field) -> const UClass*
|
|
{
|
|
if (Field.IsProperty())
|
|
{
|
|
const FObjectPropertyBase* ObjectProperty = CastField<const FObjectPropertyBase>(Field.GetProperty());
|
|
if (ObjectProperty)
|
|
{
|
|
return ObjectProperty->PropertyClass;
|
|
}
|
|
}
|
|
else if (Field.IsFunction())
|
|
{
|
|
const FObjectPropertyBase* ReturnValue = CastField<const FObjectPropertyBase>(BindingHelper::GetReturnProperty(Field.GetFunction()));
|
|
if (ReturnValue)
|
|
{
|
|
return ReturnValue->PropertyClass;
|
|
}
|
|
}
|
|
return nullptr;
|
|
};
|
|
|
|
// If the source type is Self, we don't include "self" as a field in SkeletalGeneratedFields, so the ViewModelIndex will not consider that. Meanwhile, we do include the widget if source type is Widget.
|
|
// So a ViewModelIndex of 0 with source type Self is equivalent to a ViewModelIndex of 1 with source type widget.
|
|
bool bIsSelf = ReadFieldContext->Source->Type == FCompilerBindingSource::EType::Self && ReadFieldContext->SkeletalGeneratedFields.Num() > 1;
|
|
bool bIsComponentOnSelf = false;
|
|
if (ReadFieldContext->bIsComponent)
|
|
{
|
|
const UWidgetBlueprintGeneratedClass* ComponentOwningWidgetBlueprint = UE::MVVM::FieldPathHelper::GetComponentPropertyPathSource(ReadFieldContext->SkeletalGeneratedFields, Class);
|
|
bIsComponentOnSelf = ComponentOwningWidgetBlueprint == Class;
|
|
}
|
|
|
|
int32 ViewModelIndexIncludingSource = (bIsSelf || bIsComponentOnSelf) ? BindingInfo.ViewModelIndex + 1 : BindingInfo.ViewModelIndex;
|
|
// Sanity check
|
|
{
|
|
const UClass* ExpectedClass = nullptr;
|
|
if (ViewModelIndexIncludingSource < 1 && BindingInfo.NotifyFieldClass)
|
|
{
|
|
ExpectedClass = ReadFieldContext->Source->AuthoritativeClass;
|
|
}
|
|
else if (ViewModelIndexIncludingSource >= 0 && BindingInfo.ViewModelIndex != INDEX_NONE)
|
|
{
|
|
ExpectedClass = GetClassFromField(ReadFieldContext->SkeletalGeneratedFields[BindingInfo.ViewModelIndex]);
|
|
}
|
|
|
|
if (ExpectedClass == nullptr || BindingInfo.NotifyFieldClass == nullptr || !ExpectedClass->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 (ViewModelIndexIncludingSource >= 1)
|
|
{
|
|
if (!GetDefault<UMVVMDeveloperProjectSettings>()->bAllowLongSourcePath)
|
|
{
|
|
return MakeError(LOCTEXT("DynamicSourceEntryNotSupport", "Long source entry is not supported. Add the viewmodel manually."));
|
|
}
|
|
|
|
for (int32 DynamicIndex = (bIsSelf || bIsComponentOnSelf) && BindingInfo.ViewModelIndex == 0 ? 0 : 1 ; DynamicIndex <= BindingInfo.ViewModelIndex; ++DynamicIndex)
|
|
{
|
|
if (!ReadFieldContext->SkeletalGeneratedFields.IsValidIndex(DynamicIndex))
|
|
{
|
|
return MakeError(LOCTEXT("DynamicSourceEntryInternalIndex", "Internal error. The source index is not valid."));
|
|
}
|
|
|
|
FName NewSourceName;
|
|
FName NewParentSourceName;
|
|
FString NewSourcePropertyPath;
|
|
const UClass* NewSourceAuthoritativeClass = nullptr;
|
|
{
|
|
TStringBuilder<512> PropertyPathBuilder;
|
|
TStringBuilder<512> DynamicNameBuilder;
|
|
for (int32 Index = 0; Index <= DynamicIndex; ++Index)
|
|
{
|
|
if (Index > 0)
|
|
{
|
|
PropertyPathBuilder << TEXT('.');
|
|
DynamicNameBuilder << TEXT('_');
|
|
}
|
|
PropertyPathBuilder << ReadFieldContext->SkeletalGeneratedFields[Index].GetName();
|
|
DynamicNameBuilder << ReadFieldContext->SkeletalGeneratedFields[Index].GetName();
|
|
|
|
if (Index == DynamicIndex - 1)
|
|
{
|
|
NewParentSourceName = FName(DynamicNameBuilder.ToString());
|
|
}
|
|
}
|
|
|
|
NewSourceName = FName(DynamicNameBuilder.ToString());
|
|
NewSourcePropertyPath = PropertyPathBuilder.ToString();
|
|
|
|
const UClass* OwnerSkeletalClass = GetClassFromField(ReadFieldContext->SkeletalGeneratedFields[DynamicIndex]);
|
|
if (OwnerSkeletalClass == nullptr)
|
|
{
|
|
return MakeError(LOCTEXT("DVM_GeneratedFieldInvalid", "Internal error. The GeneratedFiled is invalid."));
|
|
}
|
|
NewSourceAuthoritativeClass = OwnerSkeletalClass->GetAuthoritativeClass();
|
|
if (NewSourceAuthoritativeClass == nullptr)
|
|
{
|
|
return MakeError(LOCTEXT("DVM_AuthoritativeClassInvalid", "Internal error. No authoritative class."));
|
|
}
|
|
}
|
|
|
|
if (bIsComponentOnSelf || bIsSelf)
|
|
{
|
|
NewParentSourceName = NewParentSourceName.IsNone() ? Class->ClassGeneratedBy->GetFName() : NewParentSourceName;
|
|
}
|
|
|
|
// Does the parent exist
|
|
TSharedPtr<FCompilerBindingSource> ParentBindingSource;
|
|
{
|
|
const TSharedRef<FCompilerBindingSource>* FoundParentBindingSource = NeededBindingSources.FindByPredicate([NewParentSourceName](const TSharedRef<FCompilerBindingSource>& Other)
|
|
{
|
|
return Other->Name == NewParentSourceName;
|
|
});
|
|
if (FoundParentBindingSource == nullptr)
|
|
{
|
|
return MakeError(LOCTEXT("DVM_InvalidParentBindingSource", "Internal error. Can't find the parent binding source."));
|
|
}
|
|
ParentBindingSource = *FoundParentBindingSource;
|
|
}
|
|
|
|
// Did we already create the new source. It could be a dynamic or one added by the user
|
|
{
|
|
const TSharedRef<FCompilerBindingSource>* FoundBindingSource = NeededBindingSources.FindByPredicate([NewSourceName](const TSharedRef<FCompilerBindingSource>& Other)
|
|
{
|
|
return Other->Name == NewSourceName;
|
|
});
|
|
if (FoundBindingSource)
|
|
{
|
|
if ((*FoundBindingSource)->Type != FCompilerBindingSource::EType::DynamicViewmodel)
|
|
{
|
|
return MakeError(LOCTEXT("DVM_ViewmodelOfSameName", "The dynamic viewmodel cannot be added. There is already a source with that name."));
|
|
}
|
|
// is the class the same
|
|
if (!(*FoundBindingSource)->AuthoritativeClass->IsChildOf(NewSourceAuthoritativeClass))
|
|
{
|
|
return MakeError(LOCTEXT("DVM_ExistingNotSameClass", "Internal error. The viewmodel was already added and is not the same type."));
|
|
}
|
|
}
|
|
|
|
const TSharedRef<FCompilerSourceViewModelDynamicCreatorContext>* FoundViewModelDynamicCreatorContext = SourceViewModelDynamicCreatorContexts.FindByPredicate([NewSourceName](const TSharedRef<FCompilerSourceViewModelDynamicCreatorContext>& Other)
|
|
{
|
|
return Other->Source->Name == NewSourceName;
|
|
});
|
|
if (FoundViewModelDynamicCreatorContext)
|
|
{
|
|
if (FoundBindingSource == nullptr)
|
|
{
|
|
return MakeError(LOCTEXT("DVM_BindingSourceShouldExist", "Internal error. The binding source should exist."));
|
|
}
|
|
if ((*FoundViewModelDynamicCreatorContext)->Source != *FoundBindingSource)
|
|
{
|
|
return MakeError(LOCTEXT("DVM_BindingSourceShouldBeTheSame", "Internal error. The source should be the same."));
|
|
}
|
|
if ((*FoundViewModelDynamicCreatorContext)->ParentSource->Name != NewParentSourceName)
|
|
{
|
|
return MakeError(LOCTEXT("DVM_BindingSourceParentSameName", "Internal error. The parent name should be the same."));
|
|
}
|
|
}
|
|
|
|
const FCompilerViewModelCreatorContext* FoundViewModelCreatorContext = ViewModelCreatorContexts.FindByPredicate([NewSourceName](const FCompilerViewModelCreatorContext& Other)
|
|
{
|
|
return Other.ViewModelContext.GetViewModelName() == NewSourceName;
|
|
});
|
|
if (FoundViewModelCreatorContext)
|
|
{
|
|
if (FoundBindingSource == nullptr)
|
|
{
|
|
return MakeError(LOCTEXT("DVM_BindingSourceShouldExist", "Internal error. The binding source should exist."));
|
|
}
|
|
if (FoundViewModelCreatorContext->Source != *FoundBindingSource)
|
|
{
|
|
return MakeError(LOCTEXT("DVM_BindingSourceShouldBeTheSame", "Internal error. The source should be the same."));
|
|
}
|
|
if (FoundViewModelDynamicCreatorContext == nullptr)
|
|
{
|
|
return MakeError(LOCTEXT("DVM_DynamicCreatorContextShouldExit", "Internal error. The creator context should exist."));
|
|
}
|
|
if (FoundViewModelCreatorContext->DynamicContext != *FoundViewModelDynamicCreatorContext)
|
|
{
|
|
return MakeError(LOCTEXT("DVM_DynamicContextSHouldBeSame", "Internal error. The creator context should be the same."));
|
|
}
|
|
if (FoundViewModelCreatorContext->ViewModelContext.CreationType != EMVVMBlueprintViewModelContextCreationType::PropertyPath)
|
|
{
|
|
return MakeError(LOCTEXT("DVM_DynamicContextShouldBePropertyPath", "Internal error. The existing creator context should use a property path."));
|
|
}
|
|
if (FoundViewModelCreatorContext->ViewModelContext.ViewModelPropertyPath != NewSourcePropertyPath)
|
|
{
|
|
return MakeError(LOCTEXT("DVM_DynamicContextSamePropertyPath", "Internal error. The existing creator context use the same property path."));
|
|
}
|
|
}
|
|
|
|
if (FoundBindingSource && FoundViewModelCreatorContext == nullptr && FoundViewModelDynamicCreatorContext == nullptr)
|
|
{
|
|
return MakeError(LOCTEXT("DVM_MissingDefinition", "Internal error. There are missing definition for the dynamic viewmodel."));
|
|
}
|
|
|
|
if (FoundBindingSource)
|
|
{
|
|
Result.Source = *FoundBindingSource;
|
|
Result.ViewModelDynamic = *FoundViewModelDynamicCreatorContext;
|
|
continue; // already exist and correct to use.
|
|
}
|
|
}
|
|
|
|
if (!NewSourceAuthoritativeClass->ImplementsInterface(UNotifyFieldValueChanged::StaticClass()))
|
|
{
|
|
return MakeError(LOCTEXT("DVM_NewDynamicNotViewmodel", "The dynamic viewmodel is not an actual viewmodel."));
|
|
}
|
|
|
|
// Create the new source
|
|
TSharedRef<FCompilerBindingSource> NewBindingSource = MakeShared<FCompilerBindingSource>();
|
|
NewBindingSource->AuthoritativeClass = NewSourceAuthoritativeClass;
|
|
NewBindingSource->Name = NewSourceName;
|
|
NewBindingSource->Type = FCompilerBindingSource::EType::DynamicViewmodel;
|
|
NewBindingSource->bIsOptional = ParentBindingSource->bIsOptional;
|
|
NeededBindingSources.Add(NewBindingSource);
|
|
|
|
Result.Source = NewBindingSource;
|
|
|
|
TSharedRef<FCompilerSourceViewModelDynamicCreatorContext> NewViewModelDynamic = MakeShared<FCompilerSourceViewModelDynamicCreatorContext>();
|
|
NewViewModelDynamic->Source = NewBindingSource;
|
|
NewViewModelDynamic->ParentSource = ParentBindingSource;
|
|
NewViewModelDynamic->NotificationId = FFieldNotificationId(ReadFieldContext->SkeletalGeneratedFields[DynamicIndex].GetName());
|
|
SourceViewModelDynamicCreatorContexts.Add(NewViewModelDynamic);
|
|
|
|
Result.ViewModelDynamic = NewViewModelDynamic;
|
|
|
|
FCompilerViewModelCreatorContext& NewViewModelCreatorContext = ViewModelCreatorContexts.AddDefaulted_GetRef();
|
|
NewViewModelCreatorContext.ViewModelContext = FMVVMBlueprintViewModelContext(NewSourceAuthoritativeClass, NewSourceName);
|
|
NewViewModelCreatorContext.ViewModelContext.bCreateSetterFunction = false;
|
|
NewViewModelCreatorContext.ViewModelContext.bOptional = NewBindingSource->bIsOptional;
|
|
NewViewModelCreatorContext.ViewModelContext.CreationType = EMVVMBlueprintViewModelContextCreationType::PropertyPath;
|
|
NewViewModelCreatorContext.ViewModelContext.ViewModelPropertyPath = NewSourcePropertyPath;
|
|
NewViewModelCreatorContext.Source = NewBindingSource;
|
|
NewViewModelCreatorContext.DynamicContext = NewViewModelDynamic;
|
|
}
|
|
}
|
|
|
|
return MakeValue(MakeShared<FCompilerNotifyFieldId>(Result));
|
|
}
|
|
|
|
UMVVMViewClassExtension* FMVVMViewBlueprintCompiler::CreateViewClassExtension(TSubclassOf<UMVVMViewClassExtension> ExtensionClass, UMVVMViewClass* ViewClass)
|
|
{
|
|
if (ensure(ExtensionClass.Get()))
|
|
{
|
|
UObject* ExtensionObj = NewObject<UObject>(ViewClass, ExtensionClass.Get(), NAME_None);
|
|
UMVVMViewClassExtension* Extension = CastChecked<UMVVMViewClassExtension>(ExtensionObj);
|
|
ViewClass->ViewClassExtensions.Add(Extension);
|
|
return Extension;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
TSharedRef<FMVVMViewBlueprintCompiler::FGeneratedWriteFieldPathContext> FMVVMViewBlueprintCompiler::MakeWriteFieldPath(EMVVMBlueprintFieldPathSource GeneratedFrom, TArray<UE::MVVM::FMVVMConstFieldVariant>&& GeneratedFields, TArray<UE::MVVM::FMVVMConstFieldVariant>&& SkeletalGeneratedFields)
|
|
{
|
|
TSharedRef<FGeneratedWriteFieldPathContext> WriteFieldPath = MakeShared<FGeneratedWriteFieldPathContext>();
|
|
//WriteFieldPath->OptionalSource;
|
|
WriteFieldPath->GeneratedFields = MoveTemp(GeneratedFields);
|
|
WriteFieldPath->SkeletalGeneratedFields = MoveTemp(SkeletalGeneratedFields);
|
|
WriteFieldPath->GeneratedFrom = GeneratedFrom;
|
|
WriteFieldPath->bCanBeSetInNative = CanBeSetInNative(WriteFieldPath->SkeletalGeneratedFields);
|
|
return WriteFieldPath;
|
|
}
|
|
|
|
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 |