924 lines
33 KiB
C++
924 lines
33 KiB
C++
// 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 <limits> // 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<int32> 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<FRawFieldId> FieldIds;
|
|
TArray<FRawField> Fields;
|
|
TArray<FRawFieldPath> FieldPaths;
|
|
TArray<FRawBinding> Bindings;
|
|
bool bCompiled = false;
|
|
TWeakObjectPtr<const UBlueprint> 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<FObjectPropertyBase>(FieldProperty) != nullptr;
|
|
RawField.bPropertyIsStructProperty = CastField<FStructProperty>(FieldProperty) != nullptr;
|
|
|
|
FoundFieldPath = Fields.Add(RawField);
|
|
}
|
|
return FoundFieldPath;
|
|
}
|
|
};
|
|
|
|
/** */
|
|
//FMVVMConstFieldVariant GetMostUpToDate(FMVVMConstFieldVariant Field)
|
|
//{
|
|
// if (Field.IsValid())
|
|
// {
|
|
// UClass* Class = Cast<UClass>(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<UClass>(Result);
|
|
if (!Class || Class->HasAnyClassFlags(CLASS_Native) || Class->bCooked || !Field.IsValid())
|
|
{
|
|
return Result;
|
|
}
|
|
|
|
const UBlueprint* Blueprint = Cast<const UBlueprint>(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<UBlueprint>(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<Private::FCompiledBindingLibraryCompilerImpl>(GeneratingFor))
|
|
{
|
|
|
|
}
|
|
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FFieldIdHandle, FText> 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<INotifyFieldValueChanged> 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::FFieldPathHandle, FText> FCompiledBindingLibraryCompiler::AddFieldPath(TArrayView<const FMVVMConstFieldVariant> InFieldPath, bool bInRead)
|
|
{
|
|
Impl->bCompiled = false;
|
|
|
|
return AddFieldPathImpl(InFieldPath, bInRead);
|
|
}
|
|
|
|
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FFieldPathHandle, FText> FCompiledBindingLibraryCompiler::AddFieldPathImpl(TArrayView<const FMVVMConstFieldVariant> 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<UScriptStruct>(OwnerStruct) && !Cast<UClass>(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<const FObjectPropertyBase>(Property))
|
|
{
|
|
return FText::GetEmpty();
|
|
}
|
|
else if (const FStructProperty* StructProperty = CastField<const FStructProperty>(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<int32> 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<UMVVMDeveloperProjectSettings>()->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<const UClass>(CurrentContainer);
|
|
if (CurrentContainerAsClass == nullptr)
|
|
{
|
|
return MakeError(LOCTEXT("InvalidContainer", "The path has an invalid container."));
|
|
}
|
|
|
|
if (!GetDefault<UMVVMDeveloperProjectSettings>()->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<const UStruct*, void> 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::FFieldPathHandle, FText> FCompiledBindingLibraryCompiler::AddObjectFieldPath(TArrayView<const UE::MVVM::FMVVMConstFieldVariant> 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<const FObjectPropertyBase>(Last.GetProperty());
|
|
}
|
|
else if (Last.IsFunction())
|
|
{
|
|
ObjectPropertyBase = CastField<const FObjectPropertyBase>(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::FFieldPathHandle, FText> 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::FFieldPathHandle, FText> 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::FBindingHandle, FText> FCompiledBindingLibraryCompiler::AddBinding(FFieldPathHandle InSourceHandle, FFieldPathHandle InDestinationHandle)
|
|
{
|
|
return AddBindingImpl(InSourceHandle, InDestinationHandle, FFieldPathHandle(), false);
|
|
}
|
|
|
|
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FBindingHandle, FText> FCompiledBindingLibraryCompiler::AddBinding(FFieldPathHandle InSourceHandle, FFieldPathHandle InDestinationHandle, FFieldPathHandle InConversionFunctionHandle)
|
|
{
|
|
return AddBindingImpl(InSourceHandle, InDestinationHandle, InConversionFunctionHandle, false);
|
|
}
|
|
|
|
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FBindingHandle, FText> FCompiledBindingLibraryCompiler::AddComplexBinding(FFieldPathHandle InDestinationHandle, FFieldPathHandle InConversionFunctionHandle)
|
|
{
|
|
return AddBindingImpl(FFieldPathHandle(), InDestinationHandle, InConversionFunctionHandle, true);
|
|
}
|
|
|
|
|
|
TValueOrError<FCompiledBindingLibraryCompiler::FBindingHandle, FText> 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<bool, FText> IsValidBinding = GEngine->GetEngineSubsystem<UMVVMSubsystem>()->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::FFieldPathHandle, FText> FCompiledBindingLibraryCompiler::AddFunctionFieldPathImpl(const UClass* InSourceClass, const UFunction* InFunction, TFunctionRef<FText(const UFunction*)> 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<UClass>() == 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<int32> 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::FCompileResult, FText> FCompiledBindingLibraryCompiler::Compile(FGuid LibraryId)
|
|
{
|
|
Impl->bCompiled = false;
|
|
|
|
struct FCompiledClassInfo
|
|
{
|
|
TArray<int32> RawFieldIndex;
|
|
TArray<int32> RawFieldIdIndex;
|
|
};
|
|
|
|
// create the list of UClass. Uses the Generated class, not the SkeletalClass.
|
|
TMap<const UStruct*, FCompiledClassInfo> 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<Private::FRawField>& 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<const UStruct*, FCompiledClassInfo>& StructCompiledFields : MapOfFieldInClass)
|
|
{
|
|
FMVVMVCompiledFields CompiledFields;
|
|
CompiledFields.ClassOrScriptStruct = StructCompiledFields.Key; // The generated class not the skeletal class
|
|
check(StructCompiledFields.Key);
|
|
|
|
TArray<FName> PropertyNames;
|
|
TArray<FName> FunctionNames;
|
|
TArray<FName> 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<FProperty*>(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<FMVVMVCompiledBinding::IndexType>::max())
|
|
{
|
|
return MakeError(FText::Format(LOCTEXT("TooManyPropertiesBound", "There are too many properties bound to struct '{0}'"), StructCompiledFields.Key->GetDisplayNameText()));
|
|
}
|
|
CompiledFields.NumberOfProperties = static_cast<int16>(PropertyNames.Num());
|
|
|
|
if (FunctionNames.Num() > std::numeric_limits<FMVVMVCompiledBinding::IndexType>::max())
|
|
{
|
|
return MakeError(FText::Format(LOCTEXT("TooManyFunctionsBound", "There are too many functions bound to struct '{0}'"), StructCompiledFields.Key->GetDisplayNameText()));
|
|
}
|
|
CompiledFields.NumberOfFunctions = static_cast<int16>(FunctionNames.Num());
|
|
|
|
int32 LibraryStartIndex = Result.Library.CompiledFieldNames.Num();
|
|
if (LibraryStartIndex > std::numeric_limits<FMVVMVCompiledBinding::IndexType>::max())
|
|
{
|
|
return MakeError(LOCTEXT("TooManyPropertiesAndFunctionsBound", "There are too many properties and functions bound in the library."));
|
|
}
|
|
CompiledFields.LibraryStartIndex = static_cast<int16>(LibraryStartIndex);
|
|
|
|
Result.Library.CompiledFieldNames.Append(PropertyNames);
|
|
PropertyNames.Reset();
|
|
Result.Library.CompiledFieldNames.Append(FunctionNames);
|
|
FunctionNames.Reset();
|
|
if (Result.Library.CompiledFieldNames.Num() > std::numeric_limits<FMVVMVCompiledBinding::IndexType>::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
|