// Copyright Epic Games, Inc. All Rights Reserved. #include "Bindings/MVVMCompiledBindingLibraryCompiler.h" #include "Bindings/MVVMBindingHelper.h" #include "Bindings/MVVMFieldPathHelper.h" #include "Engine/Blueprint.h" #include "Engine/Engine.h" #include "Kismet2/BlueprintEditorUtils.h" #include "MVVMDeveloperProjectSettings.h" #include "MVVMSubsystem.h" #include "Misc/TVariantMeta.h" #include // IWYU pragma: keep #define LOCTEXT_NAMESPACE "CompiledBindingLibraryCompiler" namespace UE::MVVM::Private { static const FName NAME_BlueprintGetter = "BlueprintGetter"; /** */ struct FRawFieldId { const UClass* NotifyFieldValueChangedClass; UE::FieldNotification::FFieldId FieldId; FCompiledBindingLibraryCompiler::FFieldIdHandle IdHandle; }; /** */ struct FRawField { FMVVMConstFieldVariant Field; bool bPropertyIsObjectProperty = false; // either the FProperty or the return value of the UFunction bool bPropertyIsStructProperty = false; int32 LoadedPropertyOrFunctionIndex = INDEX_NONE; public: bool IsSameField(const FRawField& Other) const { return Field == Other.Field; } }; /** */ struct FRawFieldPath { TArray RawFieldIndexes; bool bIsReadable = false; bool bIsWritable = false; FCompiledBindingLibraryCompiler::FFieldPathHandle PathHandle; FMVVMVCompiledFieldPath CompiledFieldPath; public: bool IsSameFieldPath(const FRawFieldPath& Other) const { return Other.RawFieldIndexes == RawFieldIndexes; } }; /** */ struct FRawBinding { FCompiledBindingLibraryCompiler::FFieldPathHandle SourcePathHandle; FCompiledBindingLibraryCompiler::FFieldPathHandle DestinationPathHandle; FCompiledBindingLibraryCompiler::FFieldPathHandle ConversionFunctionPathHandle; FCompiledBindingLibraryCompiler::FBindingHandle BindingHandle; FMVVMVCompiledBinding CompiledBinding; int32 BindingCount = 1; bool bIsConversionFunctionComplex = false; public: bool IsSameBinding(const FRawBinding& Binding) const { return Binding.SourcePathHandle == SourcePathHandle && Binding.DestinationPathHandle == DestinationPathHandle && Binding.ConversionFunctionPathHandle == ConversionFunctionPathHandle; } }; /** */ class FCompiledBindingLibraryCompilerImpl { public: FCompiledBindingLibraryCompilerImpl(const UBlueprint* Context) : BlueprintContext(Context) {} public: TArray FieldIds; TArray Fields; TArray FieldPaths; TArray Bindings; bool bCompiled = false; TWeakObjectPtr BlueprintContext; public: int32 AddUniqueField(FMVVMConstFieldVariant InFieldVariant) { int32 FoundFieldPath = Fields.IndexOfByPredicate([InFieldVariant](const Private::FRawField& Other) { return Other.Field == InFieldVariant; }); if (FoundFieldPath == INDEX_NONE) { FRawField RawField; RawField.Field = InFieldVariant; check(!InFieldVariant.IsEmpty()); const FProperty* FieldProperty = InFieldVariant.IsProperty() ? InFieldVariant.GetProperty() : BindingHelper::GetReturnProperty(InFieldVariant.GetFunction()); // FieldProperty can be null if it's a setter function RawField.bPropertyIsObjectProperty = CastField(FieldProperty) != nullptr; RawField.bPropertyIsStructProperty = CastField(FieldProperty) != nullptr; FoundFieldPath = Fields.Add(RawField); } return FoundFieldPath; } }; /** */ //FMVVMConstFieldVariant GetMostUpToDate(FMVVMConstFieldVariant Field) //{ // if (Field.IsValid()) // { // UClass* Class = Cast(Field.GetOwner()); // if (!Class) // { // return Field; // } // if (Field.IsProperty()) // { // return FMVVMConstFieldVariant(FBlueprintEditorUtils::GetMostUpToDateProperty(Field.GetProperty())); // } // else if (Field.IsFunction()) // { // return FMVVMConstFieldVariant(FBlueprintEditorUtils::GetMostUpToDateFunction(Field.GetFunction())); // } // } // return Field; //} /** */ const UStruct* GetSavedGeneratedStruct(FMVVMConstFieldVariant Field) { const UStruct* Result = Field.GetOwner(); const UClass* Class = Cast(Result); if (!Class || Class->HasAnyClassFlags(CLASS_Native) || Class->bCooked || !Field.IsValid()) { return Result; } const UBlueprint* Blueprint = Cast(Class->ClassGeneratedBy); if (!Blueprint) { return Result; } ensure(Blueprint->GeneratedClass); return Blueprint->GeneratedClass; } /** */ const UClass* GetSavedGeneratedStruct(const UClass* Class) { if (!Class || Class->HasAnyClassFlags(CLASS_Native) || Class->bCooked) { return Class; } UBlueprint* Blueprint = Cast(Class->ClassGeneratedBy); if (!Blueprint) { return Class; } ensure(Blueprint->GeneratedClass); return Blueprint->GeneratedClass; } } //namespace UE::MVVM::Private namespace UE::MVVM { int32 FCompiledBindingLibraryCompiler::FBindingHandle::IdGenerator = 0; int32 FCompiledBindingLibraryCompiler::FFieldPathHandle::IdGenerator = 0; int32 FCompiledBindingLibraryCompiler::FFieldIdHandle::IdGenerator = 0; /** * */ FCompiledBindingLibraryCompiler::FCompileResult::FCompileResult(FGuid LibraryId) : Library(LibraryId) { } /** * */ FCompiledBindingLibraryCompiler::FCompiledBindingLibraryCompiler(UBlueprint* GeneratingFor) : Impl(MakePimpl(GeneratingFor)) { } TValueOrError FCompiledBindingLibraryCompiler::AddFieldId(const UClass* InSourceClass, FName FieldId) { Impl->bCompiled = false; const UClass* SourceClass = FBlueprintEditorUtils::GetMostUpToDateClass(InSourceClass); if (FieldId.IsNone()) { return MakeError(LOCTEXT("FieldNotDefined", "The Field does not have the specifier FieldNotify or cannot be used as a binding source. You may want to use the 'One Time' binding mode or make sure it is readable.")); } if (!SourceClass->ImplementsInterface(UNotifyFieldValueChanged::StaticClass())) { return MakeError(FText::Format(LOCTEXT("ClassDoesNotImplementInterface", "'{0}' doesn't implement the NotifyFieldValueChanged interface.") , SourceClass->GetDisplayNameText())); } const TScriptInterface ScriptObject = SourceClass->GetDefaultObject(); if (ensure(ScriptObject.GetInterface())) { UE::FieldNotification::FFieldId FoundFieldId = ScriptObject->GetFieldNotificationDescriptor().GetField(SourceClass, FieldId); if (!FoundFieldId.IsValid()) { return MakeError(FText::Format(LOCTEXT("FieldNotifyNotSupported", "The FieldNotify '{0}' is not supported by '{1}'.") , FText::FromName(FieldId) , SourceClass->GetDisplayNameText())); } int32 FoundFieldIdIndex = Impl->FieldIds.IndexOfByPredicate([FoundFieldId, SourceClass](const Private::FRawFieldId& Other) { return Other.FieldId == FoundFieldId && Other.NotifyFieldValueChangedClass == SourceClass; }); if (FoundFieldIdIndex == INDEX_NONE) { Private::FRawFieldId RawFieldId; RawFieldId.NotifyFieldValueChangedClass = SourceClass; RawFieldId.FieldId = FoundFieldId; RawFieldId.IdHandle = FFieldIdHandle::MakeHandle(); FoundFieldIdIndex = Impl->FieldIds.Add(MoveTemp(RawFieldId)); } return MakeValue(Impl->FieldIds[FoundFieldIdIndex].IdHandle); } return MakeError(LOCTEXT("UnexpectedCaseInAddFieldId", "Unexpected case with AddFieldId.")); } TValueOrError FCompiledBindingLibraryCompiler::AddFieldPath(TArrayView InFieldPath, bool bInRead) { Impl->bCompiled = false; return AddFieldPathImpl(InFieldPath, bInRead); } TValueOrError FCompiledBindingLibraryCompiler::AddFieldPathImpl(TArrayView InFieldPath, bool bInRead) { Impl->bCompiled = false; auto ValidateContainer = [](const FProperty* Property, bool bShouldBeInsideContainer, bool bIsObjectOrScriptStruct) -> FText { const UStruct* OwnerStruct = Property->GetOwnerStruct(); if (OwnerStruct == nullptr) { return FText::Format(LOCTEXT("FieldHasInvalidOwner", "The field {0} has an invalid owner struct."), Property->GetDisplayNameText()); } if (bShouldBeInsideContainer) { if (!Cast(OwnerStruct) && !Cast(OwnerStruct)) { return FText::Format(LOCTEXT("FieldDoesNotHaveValidOwnerForPath", "The field {0} doesn't have a valid owner for that path."), Property->GetDisplayNameText()); } } if (bIsObjectOrScriptStruct) { if (const FObjectPropertyBase* ObjectProperty = CastField(Property)) { return FText::GetEmpty(); } else if (const FStructProperty* StructProperty = CastField(Property)) { if (StructProperty->HasGetter() || Property->HasMetaData(Private::NAME_BlueprintGetter)) { return FText::Format(LOCTEXT("GetterNotSupported", "Property {0} has getter accessor. Accessor not supported on FStructProperty since it would create a temporary structure and we would not able to return a valid container from that structure."), StructProperty->GetDisplayNameText()); } return FText::GetEmpty(); } return FText::Format(LOCTEXT("FieldCanOnlyBeObjectOrStruct", "Field can only be object properties or struct properties. {0} is a {1}"), Property->GetDisplayNameText(), Property->GetClass()->GetDisplayNameText()); } return FText::GetEmpty(); }; TArray RawFieldIndexes; RawFieldIndexes.Reserve(InFieldPath.Num()); const UBlueprint* BlueprintContext = Impl->BlueprintContext.Get(); if (BlueprintContext == nullptr) { return MakeError(LOCTEXT("InvalidBlueprint", "The Blueprint is invalid.")); } const UStruct* CurrentContainer = BlueprintContext->GeneratedClass ? BlueprintContext->GeneratedClass : BlueprintContext->SkeletonGeneratedClass; for (int32 Index = 0; Index < InFieldPath.Num(); ++Index) { if (CurrentContainer == nullptr) { return MakeError(LOCTEXT("InvalidContainer", "The path has an invalid container.")); } // Make sure the FieldVariant is not from a skeletalclass FMVVMConstFieldVariant FieldVariant = InFieldPath[Index]; if (!FieldVariant.IsValid()) { return MakeError(FText::Format(LOCTEXT("FieldDoesNotHaveValidOwner", "The field {0} doesn't have a valid owner."), FText::FromName(InFieldPath[Index].GetName()))); } const bool bIsLast = Index == InFieldPath.Num() - 1; if (FieldVariant.IsProperty()) { // They must all be readable except the last item if we are writing to the property. if (bIsLast && !bInRead) { if (!BindingHelper::IsValidForDestinationBinding(FieldVariant.GetProperty())) { return MakeError(FText::Format(LOCTEXT("PropertyNotWritableAtRuntime", "Property '{0}' is not writable at runtime."), FieldVariant.GetProperty()->GetDisplayNameText())); } } else if (!BindingHelper::IsValidForSourceBinding(FieldVariant.GetProperty())) { return MakeError(FText::Format(LOCTEXT("PropertyNotReadableAtRuntime", "Property '{0}' is not readable at runtime."), FieldVariant.GetProperty()->GetDisplayNameText())); } FText ValidatedStr = ValidateContainer(FieldVariant.GetProperty(), true, !bIsLast); if (!ValidatedStr.IsEmpty()) { return MakeError(ValidatedStr); } if (!GetDefault()->IsPropertyAllowed(Impl->BlueprintContext.Get(), CurrentContainer, FieldVariant.GetProperty())) { return MakeError(LOCTEXT("PropertyNotAllow", "A property is not allowed.")); } RawFieldIndexes.Add(Impl->AddUniqueField(FieldVariant)); } else if (FieldVariant.IsFunction()) { if (bIsLast && !bInRead) { if (!BindingHelper::IsValidForDestinationBinding(FieldVariant.GetFunction())) { return MakeError(FText::Format(LOCTEXT("FunctionNotWritableAtRuntime", "Function '{0}' is not writable at runtime."), FieldVariant.GetFunction()->GetDisplayNameText())); } } else if (!BindingHelper::IsValidForSourceBinding(FieldVariant.GetFunction())) { return MakeError(FText::Format(LOCTEXT("FunctionNotReadableAtRuntime", "Function '{0}' is not readable at runtime."), FieldVariant.GetFunction()->GetDisplayNameText())); } const UClass* CurrentContainerAsClass = Cast(CurrentContainer); if (CurrentContainerAsClass == nullptr) { return MakeError(LOCTEXT("InvalidContainer", "The path has an invalid container.")); } if (!GetDefault()->IsFunctionAllowed(Impl->BlueprintContext.Get(), CurrentContainerAsClass, FieldVariant.GetFunction())) { return MakeError(LOCTEXT("FunctionNotAllow", "A function is not allowed.")); } if (bIsLast && !bInRead) { const FProperty* FirstProperty = BindingHelper::GetFirstArgumentProperty(FieldVariant.GetFunction()); ValidateContainer(FirstProperty, false, bIsLast); RawFieldIndexes.Add(Impl->AddUniqueField(FieldVariant)); } else { const FProperty* ReturnProperty = BindingHelper::GetReturnProperty(FieldVariant.GetFunction()); ValidateContainer(ReturnProperty, false, bIsLast); RawFieldIndexes.Add(Impl->AddUniqueField(FieldVariant)); } } else { return MakeError(LOCTEXT("InvalidFieldInPath", "There is an invalid field in the field path.")); } TValueOrError FieldAsContainerResult = UE::MVVM::FieldPathHelper::GetFieldAsContainer(FieldVariant); CurrentContainer = FieldAsContainerResult.HasValue() ? FieldAsContainerResult.GetValue() : nullptr; } int32 FoundFieldPath = Impl->FieldPaths.IndexOfByPredicate([&RawFieldIndexes](const Private::FRawFieldPath& Other) { return Other.RawFieldIndexes == RawFieldIndexes; }); if (FoundFieldPath != INDEX_NONE) { Impl->FieldPaths[FoundFieldPath].bIsReadable = Impl->FieldPaths[FoundFieldPath].bIsReadable || bInRead; Impl->FieldPaths[FoundFieldPath].bIsWritable = Impl->FieldPaths[FoundFieldPath].bIsWritable || !bInRead; return MakeValue(Impl->FieldPaths[FoundFieldPath].PathHandle); } Private::FRawFieldPath RawFieldPath; RawFieldPath.RawFieldIndexes = RawFieldIndexes; RawFieldPath.PathHandle = FFieldPathHandle::MakeHandle(); RawFieldPath.bIsReadable = bInRead; RawFieldPath.bIsWritable = !bInRead; FoundFieldPath = Impl->FieldPaths.Add(MoveTemp(RawFieldPath)); return MakeValue(Impl->FieldPaths[FoundFieldPath].PathHandle); } TValueOrError FCompiledBindingLibraryCompiler::AddObjectFieldPath(TArrayView FieldPath, const UClass* ExpectedType, bool bInRead) { Impl->bCompiled = false; check(ExpectedType); if (FieldPath.Num() == 0) { return MakeError(FText::Format(LOCTEXT("FieldDoesNotReturnTypeEmptyPath", "The field does not return a '{0}', Field path was empty."), ExpectedType->GetDisplayNameText())); } UE::MVVM::FMVVMConstFieldVariant Last = FieldPath.Last(); const FObjectPropertyBase* ObjectPropertyBase = nullptr; if (Last.IsProperty()) { ObjectPropertyBase = CastField(Last.GetProperty()); } else if (Last.IsFunction()) { ObjectPropertyBase = CastField(BindingHelper::GetReturnProperty(Last.GetFunction())); } if (ObjectPropertyBase == nullptr) { return MakeError(FText::Format(LOCTEXT("FieldDoesNotReturnTypeNoProperty", "The field does not return a '{0}', Object Property not found."), ExpectedType->GetDisplayNameText())); } if (ObjectPropertyBase->PropertyClass == nullptr || !ObjectPropertyBase->PropertyClass->IsChildOf(ExpectedType)) { return MakeError(FText::Format(LOCTEXT("FieldDoesNotReturnType", "The field does not return a '{0}'."), ExpectedType->GetDisplayNameText())); } return AddFieldPathImpl(FieldPath, bInRead); } TValueOrError FCompiledBindingLibraryCompiler::AddConversionFunctionFieldPath(const UClass* InSourceClass, const UFunction* InFunction) { auto ValidateConversionFunction = [](const UFunction* Function) { const bool bIsSimpleFunction = BindingHelper::IsValidForSimpleRuntimeConversion(Function); const bool bIsComplexFunction = BindingHelper::IsValidForComplexRuntimeConversion(Function); if (!bIsSimpleFunction && !bIsComplexFunction) { return FText::Format(LOCTEXT("FunctionCannotBeUsedAsConversionFunction", "Function {0} cannot be used as a runtime conversion function."), Function->GetDisplayNameText()); } return FText::GetEmpty(); }; return AddFunctionFieldPathImpl(InSourceClass, InFunction, ValidateConversionFunction); } TValueOrError FCompiledBindingLibraryCompiler::AddDelegateSignatureFieldPath(const UClass* InSourceClass, const UFunction* InFunction) { auto ValidateDelegate = [](const UFunction* Function) { const bool bIsEvent = BindingHelper::IsValidForDelegateSignatureBinding(Function); if (!bIsEvent) { return FText::Format(LOCTEXT("EventCannotBeUsedAsDelegateSignature", "Event {0} cannot be used as a runtime delegate signature function."), Function->GetDisplayNameText()); } return FText::GetEmpty(); }; return AddFunctionFieldPathImpl(InSourceClass, InFunction, ValidateDelegate); } TValueOrError FCompiledBindingLibraryCompiler::AddBinding(FFieldPathHandle InSourceHandle, FFieldPathHandle InDestinationHandle) { return AddBindingImpl(InSourceHandle, InDestinationHandle, FFieldPathHandle(), false); } TValueOrError FCompiledBindingLibraryCompiler::AddBinding(FFieldPathHandle InSourceHandle, FFieldPathHandle InDestinationHandle, FFieldPathHandle InConversionFunctionHandle) { return AddBindingImpl(InSourceHandle, InDestinationHandle, InConversionFunctionHandle, false); } TValueOrError FCompiledBindingLibraryCompiler::AddComplexBinding(FFieldPathHandle InDestinationHandle, FFieldPathHandle InConversionFunctionHandle) { return AddBindingImpl(FFieldPathHandle(), InDestinationHandle, InConversionFunctionHandle, true); } TValueOrError FCompiledBindingLibraryCompiler::AddBindingImpl(FFieldPathHandle InSourceHandle, FFieldPathHandle InDestinationHandle, FFieldPathHandle InConversionFunctionHandle, bool bInIsComplexBinding) { Impl->bCompiled = false; UMVVMSubsystem::FConstDirectionalBindingArgs DirectionBindingArgs; // Complex Conversion function do not have input arguments. if (!bInIsComplexBinding) { const int32 FoundSourceFieldPath = Impl->FieldPaths.IndexOfByPredicate([InSourceHandle](const Private::FRawFieldPath& Other) { return Other.PathHandle == InSourceHandle; }); if (FoundSourceFieldPath == INDEX_NONE) { return MakeError(LOCTEXT("SourceHandleInvalid", "The source handle is invalid.")); } Private::FRawFieldPath& SourceRawFieldPath = Impl->FieldPaths[FoundSourceFieldPath]; if (!SourceRawFieldPath.bIsReadable) { return MakeError(LOCTEXT("SourceHandleNotReadable", "The source handle was not constructed as a readable path.")); } if (SourceRawFieldPath.RawFieldIndexes.Num() == 0) { return MakeError(LOCTEXT("SourceHandleNotRegistered", "The source handle was not registered correctly.")); } Private::FRawField& RawField = Impl->Fields[SourceRawFieldPath.RawFieldIndexes.Last()]; if (RawField.Field.IsEmpty()) { return MakeError(LOCTEXT("SourceHandleNotRegistered", "The source handle was not registered correctly.")); } DirectionBindingArgs.SourceBinding = RawField.Field; } { const int32 FoundDestinationFieldPath = Impl->FieldPaths.IndexOfByPredicate([InDestinationHandle](const Private::FRawFieldPath& Other) { return Other.PathHandle == InDestinationHandle; }); if (FoundDestinationFieldPath == INDEX_NONE) { return MakeError(LOCTEXT("DestinationHandleInvalid", "The destination handle is invalid.")); } Private::FRawFieldPath& DestinationRawFieldPath = Impl->FieldPaths[FoundDestinationFieldPath]; if (!DestinationRawFieldPath.bIsWritable) { return MakeError(LOCTEXT("DestinationHandleNotWritable", "The destination handle was not constructed as a writable path.")); } if (DestinationRawFieldPath.RawFieldIndexes.Num() == 0) { return MakeError(LOCTEXT("DestinationHandleNotRegistered", "The destination handle was not registered correctly.")); } Private::FRawField& RawField = Impl->Fields[DestinationRawFieldPath.RawFieldIndexes.Last()]; if (RawField.Field.IsEmpty()) { return MakeError(LOCTEXT("DestinationHandleNotRegistered", "The destination handle was not registered correctly.")); } DirectionBindingArgs.DestinationBinding = RawField.Field; } if (InConversionFunctionHandle.IsValid()) { const int32 FoundFunctionFieldPath = Impl->FieldPaths.IndexOfByPredicate([InConversionFunctionHandle](const Private::FRawFieldPath& Other) { return Other.PathHandle == InConversionFunctionHandle; }); if (FoundFunctionFieldPath == INDEX_NONE) { return MakeError(LOCTEXT("FunctionHandleInvalid", "The function handle is invalid.")); } Private::FRawFieldPath& ConversionFunctinRawFieldPath = Impl->FieldPaths[FoundFunctionFieldPath]; if (ConversionFunctinRawFieldPath.RawFieldIndexes.Num() == 0) { return MakeError(LOCTEXT("FunctionHandleNotRegistered", "The function handle was not registered as a function.")); } Private::FRawField& RawField = Impl->Fields[ConversionFunctinRawFieldPath.RawFieldIndexes.Last()]; if (!RawField.Field.IsFunction()) { return MakeError(LOCTEXT("FunctionHandleNotRegistered", "The function handle was not registered as a function.")); } DirectionBindingArgs.ConversionFunction = RawField.Field.GetFunction(); } TValueOrError IsValidBinding = GEngine->GetEngineSubsystem()->IsBindingValid(DirectionBindingArgs); if (IsValidBinding.HasError()) { return MakeError(IsValidBinding.StealError()); } Private::FRawBinding NewBinding; NewBinding.SourcePathHandle = InSourceHandle; NewBinding.DestinationPathHandle = InDestinationHandle; NewBinding.ConversionFunctionPathHandle = InConversionFunctionHandle; NewBinding.bIsConversionFunctionComplex = bInIsComplexBinding; const int32 FoundSameBindingIndex = Impl->Bindings.IndexOfByPredicate([&NewBinding](const Private::FRawBinding& Binding) { return NewBinding.IsSameBinding(Binding); }); if (FoundSameBindingIndex != INDEX_NONE) { if (!bInIsComplexBinding) { return MakeError(LOCTEXT("BindingAlreadyAdded", "The binding already exist.")); } else { ++Impl->Bindings[FoundSameBindingIndex].BindingCount; return MakeValue(Impl->Bindings[FoundSameBindingIndex].BindingHandle); } } else { FCompiledBindingLibraryCompiler::FBindingHandle ResultBindingHandle = FBindingHandle::MakeHandle(); NewBinding.BindingHandle = ResultBindingHandle; Impl->Bindings.Add(MoveTemp(NewBinding)); return MakeValue(ResultBindingHandle); } } TValueOrError FCompiledBindingLibraryCompiler::AddFunctionFieldPathImpl(const UClass* InSourceClass, const UFunction* InFunction, TFunctionRef ValidateFunctionCallback) { Impl->bCompiled = false; const UClass* SourceClass = FBlueprintEditorUtils::GetMostUpToDateClass(InSourceClass); const UFunction* Function = FBlueprintEditorUtils::GetMostUpToDateFunction(InFunction); // Transient Conversion function are only added to generated class and not to the skeletal class. bool bTransientConversionFunction = false; if (Function == nullptr && InFunction && InFunction->GetTypedOuter() == InSourceClass) { Function = InFunction; bTransientConversionFunction = true; } if (SourceClass == nullptr) { return MakeError(LOCTEXT("SourceClassInvalid", "The source class is invalid.")); } if (Function == nullptr) { return MakeError(LOCTEXT("FunctionPathEmpty", "The function path is empty.")); } FText FunctionValidationError = ValidateFunctionCallback(Function); if (!FunctionValidationError.IsEmpty()) { return MakeError(FunctionValidationError); } if (!Function->HasAllFunctionFlags(FUNC_Static)) { if (!SourceClass->IsChildOf(Function->GetOuterUClass()) && !bTransientConversionFunction) { return MakeError(FText::Format(LOCTEXT("FunctionHasInvalidSelf", "Function {0} is going to be executed with an invalid self."), Function->GetDisplayNameText())); } } TArray RawFieldIndexes; RawFieldIndexes.Add(Impl->AddUniqueField(FMVVMConstFieldVariant(Function))); const int32 FoundFieldPath = Impl->FieldPaths.IndexOfByPredicate([&RawFieldIndexes](const Private::FRawFieldPath& Other) { return Other.RawFieldIndexes == RawFieldIndexes; }); if (FoundFieldPath != INDEX_NONE) { return MakeValue(Impl->FieldPaths[FoundFieldPath].PathHandle); } Private::FRawFieldPath RawFieldPath; RawFieldPath.RawFieldIndexes = MoveTemp(RawFieldIndexes); RawFieldPath.PathHandle = FFieldPathHandle::MakeHandle(); RawFieldPath.bIsReadable = false; RawFieldPath.bIsWritable = false; const int32 NewFieldPathIndex = Impl->FieldPaths.Add(MoveTemp(RawFieldPath)); return MakeValue(Impl->FieldPaths[NewFieldPathIndex].PathHandle); } TValueOrError FCompiledBindingLibraryCompiler::Compile(FGuid LibraryId) { Impl->bCompiled = false; struct FCompiledClassInfo { TArray RawFieldIndex; TArray RawFieldIdIndex; }; // create the list of UClass. Uses the Generated class, not the SkeletalClass. TMap MapOfFieldInClass; { for (int32 Index = 0; Index < Impl->Fields.Num(); ++Index) { const Private::FRawField& RawField = Impl->Fields[Index]; check(!RawField.Field.IsEmpty()); const UStruct* FinalOwner = Private::GetSavedGeneratedStruct(RawField.Field); check(FinalOwner); FCompiledClassInfo& ClassInfo = MapOfFieldInClass.FindOrAdd(FinalOwner); // Test if the Field is there more than one { FMVVMConstFieldVariant FieldToTest = RawField.Field; const TArray& ListOfFields = Impl->Fields; const bool bContains = ClassInfo.RawFieldIndex.ContainsByPredicate([FieldToTest, &ListOfFields](int32 OtherIndex) { return ListOfFields[OtherIndex].Field == FieldToTest; }); check(!bContains); } ClassInfo.RawFieldIndex.Add(Index); } } // Todo optimize that list to group common type. ie UWidget::ToolTip == UProgressBar::ToolTip. We can merge UWidget in UProgressBar. //Algo: for each class entry // if: a class is child of another class in the list (ProgressBar is child of Widget all property inside Widget are also in ProgressBar) // then: merge the 2 and restart the algo FCompileResult Result = FCompileResult(LibraryId); // Create FMVVMCompiledBindingLibrary::CompiledFields and FMVVMCompiledBindingLibrary::CompiledFieldNames int32 TotalNumberOfProperties = 0; int32 TotalNumberOfFunctions = 0; for (TPair& StructCompiledFields : MapOfFieldInClass) { FMVVMVCompiledFields CompiledFields; CompiledFields.ClassOrScriptStruct = StructCompiledFields.Key; // The generated class not the skeletal class check(StructCompiledFields.Key); TArray PropertyNames; TArray FunctionNames; TArray FieldIdNames; for (const int32 FieldIndex : StructCompiledFields.Value.RawFieldIndex) { check(Impl->Fields.IsValidIndex(FieldIndex)); Private::FRawField& RawField = Impl->Fields[FieldIndex]; const FMVVMConstFieldVariant& Field = RawField.Field; if (Field.IsProperty()) { // N.B. no need to translate from skeletal to generated, we only use the the name or the index. Result.Library.LoadedProperties.Add(const_cast(Field.GetProperty())); PropertyNames.Add(Field.GetName()); RawField.LoadedPropertyOrFunctionIndex = TotalNumberOfProperties; ++TotalNumberOfProperties; } else { check(Field.IsFunction()); // N.B. no need to translate from skeletal to generated, we only use the the name or the index. Result.Library.LoadedFunctions.Emplace(Field.GetFunction()); FunctionNames.Add(Field.GetName()); RawField.LoadedPropertyOrFunctionIndex = TotalNumberOfFunctions; ++TotalNumberOfFunctions; } } if (PropertyNames.Num() > std::numeric_limits::max()) { return MakeError(FText::Format(LOCTEXT("TooManyPropertiesBound", "There are too many properties bound to struct '{0}'"), StructCompiledFields.Key->GetDisplayNameText())); } CompiledFields.NumberOfProperties = static_cast(PropertyNames.Num()); if (FunctionNames.Num() > std::numeric_limits::max()) { return MakeError(FText::Format(LOCTEXT("TooManyFunctionsBound", "There are too many functions bound to struct '{0}'"), StructCompiledFields.Key->GetDisplayNameText())); } CompiledFields.NumberOfFunctions = static_cast(FunctionNames.Num()); int32 LibraryStartIndex = Result.Library.CompiledFieldNames.Num(); if (LibraryStartIndex > std::numeric_limits::max()) { return MakeError(LOCTEXT("TooManyPropertiesAndFunctionsBound", "There are too many properties and functions bound in the library.")); } CompiledFields.LibraryStartIndex = static_cast(LibraryStartIndex); Result.Library.CompiledFieldNames.Append(PropertyNames); PropertyNames.Reset(); Result.Library.CompiledFieldNames.Append(FunctionNames); FunctionNames.Reset(); if (Result.Library.CompiledFieldNames.Num() > std::numeric_limits::max()) { return MakeError(LOCTEXT("TooManyPropertiesBoundInLibrary", "There are too many properties bound in the library.")); } Result.Library.CompiledFields.Add(CompiledFields); check(Result.Library.LoadedProperties.Num() + Result.Library.LoadedFunctions.Num() == Result.Library.CompiledFieldNames.Num()); check(Result.Library.LoadedProperties.Num() == TotalNumberOfProperties); check(Result.Library.LoadedFunctions.Num() == TotalNumberOfFunctions); } // Create FMVVMCompiledBindingLibrary::FieldPaths for (Private::FRawFieldPath& FieldPath : Impl->FieldPaths) { FieldPath.CompiledFieldPath.CompiledBindingLibraryId = Result.Library.CompiledBindingLibraryId; FieldPath.CompiledFieldPath.StartIndex = INDEX_NONE; FieldPath.CompiledFieldPath.Num = FieldPath.RawFieldIndexes.Num(); if (FieldPath.RawFieldIndexes.Num()) { FieldPath.CompiledFieldPath.StartIndex = Result.Library.FieldPaths.Num(); for (const int32 RawFieldIndex : FieldPath.RawFieldIndexes) { const Private::FRawField& RawField = Impl->Fields[RawFieldIndex]; check(!RawField.Field.IsEmpty()); FMVVMCompiledLoadedPropertyOrFunctionIndex FieldIndex; FieldIndex.Index = RawField.LoadedPropertyOrFunctionIndex; FieldIndex.bIsObjectProperty = RawField.bPropertyIsObjectProperty; FieldIndex.bIsScriptStructProperty = RawField.bPropertyIsStructProperty; FieldIndex.bIsProperty = RawField.Field.IsProperty(); Result.Library.FieldPaths.Add(FieldIndex); if (FieldIndex.bIsProperty) { check(Result.Library.LoadedProperties.IsValidIndex(FieldIndex.Index)); } else { check(Result.Library.LoadedFunctions.IsValidIndex(FieldIndex.Index)); } } } Result.FieldPaths.Add(FieldPath.PathHandle, FieldPath.CompiledFieldPath); } // Create FieldId for (Private::FRawFieldId& FieldId: Impl->FieldIds) { Result.FieldIds.Add(FieldId.IdHandle, FieldId.FieldId); } auto GetCompiledFieldPath = [this](const FFieldPathHandle Handle) { const Private::FRawFieldPath* FoundBinding = Impl->FieldPaths.FindByPredicate([Handle](const Private::FRawFieldPath& Other) { return Other.PathHandle == Handle; }); if (FoundBinding) { return FoundBinding->CompiledFieldPath; } return FMVVMVCompiledFieldPath(); }; // Create the requested FMVVMVCompiledBinding for (Private::FRawBinding& Binding : Impl->Bindings) { Binding.CompiledBinding.CompiledBindingLibraryId = Result.Library.CompiledBindingLibraryId; check(Binding.CompiledBinding.CompiledBindingLibraryId.IsValid()); Binding.CompiledBinding.SourceFieldPath = GetCompiledFieldPath(Binding.SourcePathHandle); check(Binding.bIsConversionFunctionComplex || Binding.CompiledBinding.SourceFieldPath.IsValid()); Binding.CompiledBinding.DestinationFieldPath = GetCompiledFieldPath(Binding.DestinationPathHandle); check(Binding.CompiledBinding.DestinationFieldPath.IsValid()); Binding.CompiledBinding.ConversionFunctionFieldPath = GetCompiledFieldPath(Binding.ConversionFunctionPathHandle); if (Binding.bIsConversionFunctionComplex) { Binding.CompiledBinding.Type = (uint8)FMVVMVCompiledBinding::EType::HasComplexConversionFunction; } else if (Binding.ConversionFunctionPathHandle.IsValid()) { Binding.CompiledBinding.Type = (uint8)FMVVMVCompiledBinding::EType::HasConversionFunction; } else { Binding.CompiledBinding.Type = (uint8)FMVVMVCompiledBinding::EType::None; } Result.Bindings.Add(Binding.BindingHandle, Binding.CompiledBinding); } Result.Library.LoadedProperties.Reset(); Result.Library.LoadedFunctions.Reset(); Impl->bCompiled = true; return MakeValue(MoveTemp(Result)); } } //namespace #undef LOCTEXT_NAMESPACE