|
@@ -2,3 +2,1865 @@
|
|
|
|
|
|
|
|
|
|
#include "EzAbilityPropertyBindings.h"
|
|
#include "EzAbilityPropertyBindings.h"
|
|
|
|
+
|
|
|
|
+#if WITH_EDITOR
|
|
|
|
+#include "UObject/CoreRedirects.h"
|
|
|
|
+#include "UObject/Package.h"
|
|
|
|
+#include "Engine/BlueprintGeneratedClass.h"
|
|
|
|
+#include "Engine/UserDefinedStruct.h"
|
|
|
|
+#include "Kismet2/StructureEditorUtils.h"
|
|
|
|
+#include "UObject/Field.h"
|
|
|
|
+#endif
|
|
|
|
+
|
|
|
|
+#include "EzAbilityLog.h"
|
|
|
|
+#include "EzAbilityPropertyRef.h"
|
|
|
|
+#include "PropertyPathHelpers.h"
|
|
|
|
+#include "Misc/EnumerateRange.h"
|
|
|
|
+
|
|
|
|
+#include UE_INLINE_GENERATED_CPP_BY_NAME(EzAbilityPropertyBindings)
|
|
|
|
+
|
|
|
|
+namespace UE::EzAbility
|
|
|
|
+{
|
|
|
|
+ FString GetDescAndPathAsString(const FEzAbilityBindableStructDesc& Desc, const FEzAbilityPropertyPath& Path)
|
|
|
|
+ {
|
|
|
|
+ FStringBuilderBase Result;
|
|
|
|
+
|
|
|
|
+ Result += Desc.ToString();
|
|
|
|
+
|
|
|
|
+ if (!Path.IsPathEmpty())
|
|
|
|
+ {
|
|
|
|
+ Result += TEXT(" ");
|
|
|
|
+ Result += Path.ToString();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return Result.ToString();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+#if WITH_EDITOR
|
|
|
|
+ EEzAbilityPropertyUsage GetUsageFromMetaData(const FProperty* Property)
|
|
|
|
+ {
|
|
|
|
+ static const FName CategoryName(TEXT("Category"));
|
|
|
|
+
|
|
|
|
+ if (Property == nullptr)
|
|
|
|
+ {
|
|
|
|
+ return EEzAbilityPropertyUsage::Invalid;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const FString Category = Property->GetMetaData(CategoryName);
|
|
|
|
+
|
|
|
|
+ if (Category == TEXT("Input"))
|
|
|
|
+ {
|
|
|
|
+ return EEzAbilityPropertyUsage::Input;
|
|
|
|
+ }
|
|
|
|
+ if (Category == TEXT("Inputs"))
|
|
|
|
+ {
|
|
|
|
+ return EEzAbilityPropertyUsage::Input;
|
|
|
|
+ }
|
|
|
|
+ if (Category == TEXT("Output"))
|
|
|
|
+ {
|
|
|
|
+ return EEzAbilityPropertyUsage::Output;
|
|
|
|
+ }
|
|
|
|
+ if (Category == TEXT("Outputs"))
|
|
|
|
+ {
|
|
|
|
+ return EEzAbilityPropertyUsage::Output;
|
|
|
|
+ }
|
|
|
|
+ if (Category == TEXT("Context"))
|
|
|
|
+ {
|
|
|
|
+ return EEzAbilityPropertyUsage::Context;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return EEzAbilityPropertyUsage::Parameter;
|
|
|
|
+ }
|
|
|
|
+#endif
|
|
|
|
+
|
|
|
|
+} // UE::EzAbility
|
|
|
|
+
|
|
|
|
+namespace UE::EzAbility::Private
|
|
|
|
+{
|
|
|
|
+
|
|
|
|
+#if WITH_EDITORONLY_DATA
|
|
|
|
+PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
|
|
+ FEzAbilityPropertyPath ConvertEditorPath(const FEzAbilityEditorPropertyPath& InEditorPath)
|
|
|
|
+ {
|
|
|
|
+ FEzAbilityPropertyPath Path;
|
|
|
|
+ Path.SetStructID(InEditorPath.StructID);
|
|
|
|
+
|
|
|
|
+ for (const FString& Segment : InEditorPath.Path)
|
|
|
|
+ {
|
|
|
|
+ const TCHAR* PropertyNamePtr = nullptr;
|
|
|
|
+ int32 PropertyNameLength = 0;
|
|
|
|
+ int32 ArrayIndex = INDEX_NONE;
|
|
|
|
+ PropertyPathHelpers::FindFieldNameAndArrayIndex(Segment.Len(), *Segment, PropertyNameLength, &PropertyNamePtr, ArrayIndex);
|
|
|
|
+ FString PropertyNameString(PropertyNameLength, PropertyNamePtr);
|
|
|
|
+ const FName PropertyName(*PropertyNameString, FNAME_Find);
|
|
|
|
+ Path.AddPathSegment(PropertyName, ArrayIndex);
|
|
|
|
+ }
|
|
|
|
+ return Path;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ FEzAbilityEditorPropertyPath ConvertEditorPath(const FEzAbilityPropertyPath& InPath)
|
|
|
|
+ {
|
|
|
|
+ FEzAbilityEditorPropertyPath Path;
|
|
|
|
+ Path.StructID = InPath.GetStructID();
|
|
|
|
+ for (const FEzAbilityPropertyPathSegment& Segment : InPath.GetSegments())
|
|
|
|
+ {
|
|
|
|
+ if (Segment.GetArrayIndex() != INDEX_NONE)
|
|
|
|
+ {
|
|
|
|
+ Path.Path.Add(FString::Printf(TEXT("%s[%d]"), *Segment.GetName().ToString(), Segment.GetArrayIndex()));
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ Path.Path.Add(Segment.GetName().ToString());
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return Path;
|
|
|
|
+ }
|
|
|
|
+PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
+#endif // WITH_EDITORONLY_DATA
|
|
|
|
+} // UE::EzAbility::Private
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+//----------------------------------------------------------------//
|
|
|
|
+// FEzAbilityBindableStructDesc
|
|
|
|
+//----------------------------------------------------------------//
|
|
|
|
+
|
|
|
|
+FString FEzAbilityBindableStructDesc::ToString() const
|
|
|
|
+{
|
|
|
|
+ FStringBuilderBase Result;
|
|
|
|
+
|
|
|
|
+ Result += UEnum::GetDisplayValueAsText(DataSource).ToString();
|
|
|
|
+ Result += TEXT(" '");
|
|
|
|
+ Result += Name.ToString();
|
|
|
|
+ Result += TEXT("'");
|
|
|
|
+
|
|
|
|
+ return Result.ToString();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//----------------------------------------------------------------//
|
|
|
|
+// FEzAbilityPropertyPathBinding
|
|
|
|
+//----------------------------------------------------------------//
|
|
|
|
+void FEzAbilityPropertyPathBinding::PostSerialize(const FArchive& Ar)
|
|
|
|
+{
|
|
|
|
+#if WITH_EDITORONLY_DATA
|
|
|
|
+PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
|
|
+ if (SourcePath_DEPRECATED.IsValid())
|
|
|
|
+ {
|
|
|
|
+ SourcePropertyPath = UE::EzAbility::Private::ConvertEditorPath(SourcePath_DEPRECATED);
|
|
|
|
+ SourcePath_DEPRECATED.StructID = FGuid();
|
|
|
|
+ SourcePath_DEPRECATED.Path.Reset();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (TargetPath_DEPRECATED.IsValid())
|
|
|
|
+ {
|
|
|
|
+ TargetPropertyPath = UE::EzAbility::Private::ConvertEditorPath(TargetPath_DEPRECATED);
|
|
|
|
+ TargetPath_DEPRECATED.StructID = FGuid();
|
|
|
|
+ TargetPath_DEPRECATED.Path.Reset();
|
|
|
|
+ }
|
|
|
|
+PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
+#endif // WITH_EDITORONLY_DATA
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+//----------------------------------------------------------------//
|
|
|
|
+// FEzAbilityPropertyBindings
|
|
|
|
+//----------------------------------------------------------------//
|
|
|
|
+
|
|
|
|
+void FEzAbilityPropertyBindings::Reset()
|
|
|
|
+{
|
|
|
|
+ SourceStructs.Reset();
|
|
|
|
+ CopyBatches.Reset();
|
|
|
|
+ PropertyPathBindings.Reset();
|
|
|
|
+ PropertyCopies.Reset();
|
|
|
|
+ PropertyAccesses.Reset();
|
|
|
|
+ PropertyReferencePaths.Reset();
|
|
|
|
+ PropertyIndirections.Reset();
|
|
|
|
+
|
|
|
|
+ bBindingsResolved = false;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const FEzAbilityBindableStructDesc* FEzAbilityPropertyBindings::GetSourceDescByHandle(const FEzAbilityDataHandle SourceDataHandle)
|
|
|
|
+{
|
|
|
|
+ TArray<FEzAbilityBindableStructDesc> FoundDescs;
|
|
|
|
+ for (const FEzAbilityBindableStructDesc& Desc : SourceStructs)
|
|
|
|
+ {
|
|
|
|
+ if (Desc.DataHandle == SourceDataHandle)
|
|
|
|
+ {
|
|
|
|
+ FoundDescs.Add(Desc);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (FoundDescs.Num() > 1)
|
|
|
|
+ {
|
|
|
|
+ UE_LOG(LogEzAbility, Error, TEXT("%hs: Found %d entries for handle %s."), __FUNCTION__, FoundDescs.Num(), *SourceDataHandle.Describe());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return SourceStructs.FindByPredicate([SourceDataHandle](const FEzAbilityBindableStructDesc& Desc)
|
|
|
|
+ {
|
|
|
|
+ return Desc.DataHandle == SourceDataHandle;
|
|
|
|
+ });
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+bool FEzAbilityPropertyBindings::ResolvePaths()
|
|
|
|
+{
|
|
|
|
+ PropertyIndirections.Reset();
|
|
|
|
+ PropertyCopies.SetNum(PropertyPathBindings.Num());
|
|
|
|
+
|
|
|
|
+ bBindingsResolved = true;
|
|
|
|
+
|
|
|
|
+ bool bResult = true;
|
|
|
|
+
|
|
|
|
+ for (const FEzAbilityPropertyCopyBatch& Batch : CopyBatches)
|
|
|
|
+ {
|
|
|
|
+ for (int32 i = Batch.BindingsBegin; i != Batch.BindingsEnd; i++)
|
|
|
|
+ {
|
|
|
|
+ const FEzAbilityPropertyPathBinding& Binding = PropertyPathBindings[i];
|
|
|
|
+
|
|
|
|
+ FEzAbilityPropertyCopy& Copy = PropertyCopies[i];
|
|
|
|
+ Copy.SourceDataHandle = Binding.GetSourceDataHandle();
|
|
|
|
+
|
|
|
|
+ if (!Binding.GetSourceDataHandle().IsValid())
|
|
|
|
+ {
|
|
|
|
+ UE_LOG(LogEzAbility, Error, TEXT("%hs: Invalid source struct for property binding %s."), __FUNCTION__, *Binding.GetSourcePath().ToString());
|
|
|
|
+ Copy.Type = EEzAbilityPropertyCopyType::None;
|
|
|
|
+ bBindingsResolved = false;
|
|
|
|
+ bResult = false;
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const FEzAbilityBindableStructDesc* SourceDesc = GetSourceDescByHandle(Copy.SourceDataHandle);
|
|
|
|
+ if (!SourceDesc)
|
|
|
|
+ {
|
|
|
|
+ UE_LOG(LogEzAbility, Error, TEXT("%hs: Could not find data source for binding %s."), __FUNCTION__, *Binding.GetSourcePath().ToString());
|
|
|
|
+ Copy.Type = EEzAbilityPropertyCopyType::None;
|
|
|
|
+ bBindingsResolved = false;
|
|
|
|
+ bResult = false;
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const UStruct* SourceStruct = SourceDesc->Struct;
|
|
|
|
+ const UStruct* TargetStruct = Batch.TargetStruct.Struct;
|
|
|
|
+ if (!SourceStruct || !TargetStruct)
|
|
|
|
+ {
|
|
|
|
+ Copy.Type = EEzAbilityPropertyCopyType::None;
|
|
|
|
+ bBindingsResolved = false;
|
|
|
|
+ bResult = false;
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Copy.SourceStructType = SourceStruct;
|
|
|
|
+
|
|
|
|
+ // Resolve paths and validate the copy. Stops on first failure.
|
|
|
|
+ bool bSuccess = true;
|
|
|
|
+ FEzAbilityPropertyPathIndirection SourceLeafIndirection;
|
|
|
|
+ FEzAbilityPropertyPathIndirection TargetLeafIndirection;
|
|
|
|
+ bSuccess = bSuccess && ResolvePath(SourceStruct, Binding.GetSourcePath(), Copy.SourceIndirection, SourceLeafIndirection);
|
|
|
|
+ bSuccess = bSuccess && ResolvePath(TargetStruct, Binding.GetTargetPath(), Copy.TargetIndirection, TargetLeafIndirection);
|
|
|
|
+ bSuccess = bSuccess && ResolveCopyType(SourceLeafIndirection, TargetLeafIndirection, Copy);
|
|
|
|
+ if (!bSuccess)
|
|
|
|
+ {
|
|
|
|
+ // Resolving or validating failed, make the copy a nop.
|
|
|
|
+ Copy.Type = EEzAbilityPropertyCopyType::None;
|
|
|
|
+ bResult = false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ PropertyAccesses.Reset();
|
|
|
|
+ PropertyAccesses.Reserve(PropertyReferencePaths.Num());
|
|
|
|
+
|
|
|
|
+ for (const FEzAbilityPropertyRefPath& ReferencePath : PropertyReferencePaths)
|
|
|
|
+ {
|
|
|
|
+ FEzAbilityPropertyAccess& PropertyAccess = PropertyAccesses.AddDefaulted_GetRef();
|
|
|
|
+
|
|
|
|
+ PropertyAccess.SourceDataHandle = ReferencePath.GetSourceDataHandle();
|
|
|
|
+ const FEzAbilityBindableStructDesc* SourceDesc = GetSourceDescByHandle(PropertyAccess.SourceDataHandle);
|
|
|
|
+ PropertyAccess.SourceStructType = SourceDesc->Struct;
|
|
|
|
+
|
|
|
|
+ FEzAbilityPropertyPathIndirection SourceLeafIndirection;
|
|
|
|
+ if (!ResolvePath(SourceDesc->Struct, ReferencePath.GetSourcePath(), PropertyAccess.SourceIndirection, SourceLeafIndirection))
|
|
|
|
+ {
|
|
|
|
+ bResult = false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ PropertyAccess.SourceLeafProperty = SourceLeafIndirection.GetProperty();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return bResult;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+bool FEzAbilityPropertyBindings::ResolvePath(const UStruct* Struct, const FEzAbilityPropertyPath& Path, TArray<FEzAbilityPropertyIndirection>& OutIndirections, FEzAbilityPropertyIndirection& OutFirstIndirection, FEzAbilityPropertyPathIndirection& OutLeafIndirection)
|
|
|
|
+{
|
|
|
|
+ if (!Struct)
|
|
|
|
+ {
|
|
|
|
+ UE_LOG(LogEzAbility, Error, TEXT("%hs: '%s' Invalid source struct."), __FUNCTION__, *Path.ToString());
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ FString Error;
|
|
|
|
+ TArray<FEzAbilityPropertyPathIndirection> PathIndirections;
|
|
|
|
+ if (!Path.ResolveIndirections(Struct, PathIndirections, &Error))
|
|
|
|
+ {
|
|
|
|
+ UE_LOG(LogEzAbility, Error, TEXT("%hs: %s"), __FUNCTION__, *Error);
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Casts array index to FEzAbilityIndex16 (clamping INDEX_NONE to 0) or returns false if out if index bounds.
|
|
|
|
+ auto CastArrayIndexToIndex16 = [](const int32 Index)
|
|
|
|
+ {
|
|
|
|
+ const int32 ClampedIndex = FMath::Max(0, Index);
|
|
|
|
+ if (!FEzAbilityIndex16::IsValidIndex(ClampedIndex))
|
|
|
|
+ {
|
|
|
|
+ return FEzAbilityIndex16();
|
|
|
|
+ }
|
|
|
|
+ return FEzAbilityIndex16(ClampedIndex);
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ TArray<FEzAbilityPropertyIndirection, TInlineAllocator<16>> TempIndirections;
|
|
|
|
+ for (FEzAbilityPropertyPathIndirection& PathIndirection : PathIndirections)
|
|
|
|
+ {
|
|
|
|
+ FEzAbilityPropertyIndirection& Indirection = TempIndirections.AddDefaulted_GetRef();
|
|
|
|
+
|
|
|
|
+ check(PathIndirection.GetPropertyOffset() >= MIN_uint16 && PathIndirection.GetPropertyOffset() <= MAX_uint16);
|
|
|
|
+
|
|
|
|
+ Indirection.Offset = static_cast<uint16>(PathIndirection.GetPropertyOffset());
|
|
|
|
+ Indirection.Type = PathIndirection.GetAccessType();
|
|
|
|
+
|
|
|
|
+ if (Indirection.Type == EEzAbilityPropertyAccessType::IndexArray)
|
|
|
|
+ {
|
|
|
|
+ if (const FArrayProperty* ArrayProperty = CastField<FArrayProperty>(PathIndirection.GetProperty()))
|
|
|
|
+ {
|
|
|
|
+ Indirection.ArrayProperty = ArrayProperty;
|
|
|
|
+ Indirection.ArrayIndex = CastArrayIndexToIndex16(PathIndirection.GetArrayIndex());
|
|
|
|
+ if (!Indirection.ArrayIndex.IsValid())
|
|
|
|
+ {
|
|
|
|
+ UE_LOG(LogEzAbility, Error, TEXT("%hs: Array index %d at %s, is too large."),
|
|
|
|
+ __FUNCTION__, PathIndirection.GetArrayIndex(), *Path.ToString(PathIndirection.GetPathSegmentIndex(), TEXT("<"), TEXT(">")));
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ UE_LOG(LogEzAbility, Error, TEXT("%hs: Expect property %s to be array property."),
|
|
|
|
+ __FUNCTION__, *Path.ToString(PathIndirection.GetPathSegmentIndex(), TEXT("<"), TEXT(">")));
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (Indirection.Type == EEzAbilityPropertyAccessType::StructInstance
|
|
|
|
+ || Indirection.Type == EEzAbilityPropertyAccessType::ObjectInstance)
|
|
|
|
+ {
|
|
|
|
+ if (PathIndirection.GetInstanceStruct())
|
|
|
|
+ {
|
|
|
|
+ Indirection.InstanceStruct = PathIndirection.GetInstanceStruct();
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ UE_LOG(LogEzAbility, Error, TEXT("%hs: Expect instanced property access %s to have instance type specified."),
|
|
|
|
+ __FUNCTION__, *Path.ToString(PathIndirection.GetPathSegmentIndex(), TEXT("<"), TEXT(">")));
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (TempIndirections.Num() > 0)
|
|
|
|
+ {
|
|
|
|
+ for (int32 Index = 0; Index < TempIndirections.Num(); Index++)
|
|
|
|
+ {
|
|
|
|
+ FEzAbilityPropertyIndirection& Indirection = TempIndirections[Index];
|
|
|
|
+ if ((Index + 1) < TempIndirections.Num())
|
|
|
|
+ {
|
|
|
|
+ const FEzAbilityPropertyIndirection& NextIndirection = TempIndirections[Index + 1];
|
|
|
|
+ if (Indirection.Type == EEzAbilityPropertyAccessType::Offset
|
|
|
|
+ && NextIndirection.Type == EEzAbilityPropertyAccessType::Offset)
|
|
|
|
+ {
|
|
|
|
+ // Collapse adjacent offset indirections
|
|
|
|
+ Indirection.Offset += NextIndirection.Offset;
|
|
|
|
+ TempIndirections.RemoveAt(Index + 1);
|
|
|
|
+ Index--;
|
|
|
|
+ }
|
|
|
|
+ else if (Indirection.Type == EEzAbilityPropertyAccessType::IndexArray
|
|
|
|
+ && NextIndirection.Type == EEzAbilityPropertyAccessType::Offset
|
|
|
|
+ && NextIndirection.Offset == 0)
|
|
|
|
+ {
|
|
|
|
+ // Remove empty offset after array indexing.
|
|
|
|
+ TempIndirections.RemoveAt(Index + 1);
|
|
|
|
+ Index--;
|
|
|
|
+ }
|
|
|
|
+ else if (Indirection.Type == EEzAbilityPropertyAccessType::StructInstance
|
|
|
|
+ && NextIndirection.Type == EEzAbilityPropertyAccessType::Offset
|
|
|
|
+ && NextIndirection.Offset == 0)
|
|
|
|
+ {
|
|
|
|
+ // Remove empty offset after struct indirection.
|
|
|
|
+ TempIndirections.RemoveAt(Index + 1);
|
|
|
|
+ Index--;
|
|
|
|
+ }
|
|
|
|
+ else if ((Indirection.Type == EEzAbilityPropertyAccessType::Object
|
|
|
|
+ || Indirection.Type == EEzAbilityPropertyAccessType::ObjectInstance)
|
|
|
|
+ && NextIndirection.Type == EEzAbilityPropertyAccessType::Offset
|
|
|
|
+ && NextIndirection.Offset == 0)
|
|
|
|
+ {
|
|
|
|
+ // Remove empty offset after object indirection.
|
|
|
|
+ TempIndirections.RemoveAt(Index + 1);
|
|
|
|
+ Index--;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ OutLeafIndirection = PathIndirections.Last();
|
|
|
|
+
|
|
|
|
+ // Store indirections
|
|
|
|
+ OutFirstIndirection = TempIndirections[0];
|
|
|
|
+ FEzAbilityPropertyIndirection* PrevIndirection = &OutFirstIndirection;
|
|
|
|
+ for (int32 Index = 1; Index < TempIndirections.Num(); Index++)
|
|
|
|
+ {
|
|
|
|
+ const int32 IndirectionIndex = OutIndirections.Num();
|
|
|
|
+ PrevIndirection->NextIndex = FEzAbilityIndex16(IndirectionIndex); // Set PrevIndirection before array add, as it can invalidate the pointer.
|
|
|
|
+ FEzAbilityPropertyIndirection& NewIndirection = OutIndirections.Add_GetRef(TempIndirections[Index]);
|
|
|
|
+ PrevIndirection = &NewIndirection;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ // Indirections can be empty in case we're directly binding to source structs.
|
|
|
|
+ // Zero offset will return the struct itself.
|
|
|
|
+ OutFirstIndirection.Offset = 0;
|
|
|
|
+ OutFirstIndirection.Type = EEzAbilityPropertyAccessType::Offset;
|
|
|
|
+
|
|
|
|
+ OutLeafIndirection = FEzAbilityPropertyPathIndirection(Struct);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+bool FEzAbilityPropertyBindings::ResolveCopyType(const FEzAbilityPropertyPathIndirection& SourceIndirection,const FEzAbilityPropertyPathIndirection& TargetIndirection, FEzAbilityPropertyCopy& OutCopy)
|
|
|
|
+{
|
|
|
|
+ // @todo: see if GetPropertyCompatibility() can be implemented as call to ResolveCopyType() instead so that we write this logic just once.
|
|
|
|
+
|
|
|
|
+ const FProperty* SourceProperty = SourceIndirection.GetProperty();
|
|
|
|
+ const UStruct* SourceStruct = SourceIndirection.GetContainerStruct();
|
|
|
|
+
|
|
|
|
+ const FProperty* TargetProperty = TargetIndirection.GetProperty();
|
|
|
|
+ const UStruct* TargetStruct = TargetIndirection.GetContainerStruct();
|
|
|
|
+
|
|
|
|
+ if (!SourceStruct || !TargetStruct)
|
|
|
|
+ {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ OutCopy.SourceLeafProperty = SourceProperty;
|
|
|
|
+ OutCopy.TargetLeafProperty = TargetProperty;
|
|
|
|
+ OutCopy.CopySize = 0;
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::None;
|
|
|
|
+
|
|
|
|
+ if (SourceProperty == nullptr)
|
|
|
|
+ {
|
|
|
|
+ // Copy directly from the source struct, target must be.
|
|
|
|
+ if (const FStructProperty* TargetStructProperty = CastField<FStructProperty>(TargetProperty))
|
|
|
|
+ {
|
|
|
|
+ if (TargetStructProperty->Struct == SourceStruct)
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::CopyStruct;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (const FObjectPropertyBase* TargetObjectProperty = CastField<FObjectPropertyBase>(TargetProperty))
|
|
|
|
+ {
|
|
|
|
+ if (SourceStruct->IsChildOf(TargetObjectProperty->PropertyClass))
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::CopyObject;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Handle FEzAbilityStructRef
|
|
|
|
+ if (const FStructProperty* TargetStructProperty = CastField<const FStructProperty>(TargetProperty))
|
|
|
|
+ {
|
|
|
|
+ if (TargetStructProperty->Struct == TBaseStructure<FEzAbilityStructRef>::Get())
|
|
|
|
+ {
|
|
|
|
+ if (const FStructProperty* SourceStructProperty = CastField<const FStructProperty>(SourceProperty))
|
|
|
|
+ {
|
|
|
|
+ // FEzAbilityStructRef to FEzAbilityStructRef is copied as usual.
|
|
|
|
+ if (SourceStructProperty->Struct != TBaseStructure<FEzAbilityStructRef>::Get())
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::StructReference;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const EEzAbilityPropertyAccessCompatibility Compatibility = FEzAbilityPropertyBindings::GetPropertyCompatibility(SourceProperty, TargetProperty);
|
|
|
|
+
|
|
|
|
+ // Extract underlying types for enums
|
|
|
|
+ if (const FEnumProperty* EnumPropertyA = CastField<const FEnumProperty>(SourceProperty))
|
|
|
|
+ {
|
|
|
|
+ SourceProperty = EnumPropertyA->GetUnderlyingProperty();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (const FEnumProperty* EnumPropertyB = CastField<const FEnumProperty>(TargetProperty))
|
|
|
|
+ {
|
|
|
|
+ TargetProperty = EnumPropertyB->GetUnderlyingProperty();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (Compatibility == EEzAbilityPropertyAccessCompatibility::Compatible)
|
|
|
|
+ {
|
|
|
|
+ if (CastField<FNameProperty>(TargetProperty))
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::CopyName;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ else if (CastField<FBoolProperty>(TargetProperty))
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::CopyBool;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ else if (CastField<FStructProperty>(TargetProperty))
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::CopyStruct;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ else if (CastField<FObjectPropertyBase>(TargetProperty))
|
|
|
|
+ {
|
|
|
|
+ if (SourceProperty->IsA<FSoftObjectProperty>()
|
|
|
|
+ && TargetProperty->IsA<FSoftObjectProperty>())
|
|
|
|
+ {
|
|
|
|
+ // Use CopyComplex when copying soft object to another soft object so that we do not try to dereference the object (just copies the path).
|
|
|
|
+ // This handles soft class too.
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::CopyComplex;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::CopyObject;
|
|
|
|
+ }
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ else if (CastField<FArrayProperty>(TargetProperty) && TargetProperty->HasAnyPropertyFlags(CPF_EditFixedSize))
|
|
|
|
+ {
|
|
|
|
+ // only apply array copying rules if the destination array is fixed size, otherwise it will be 'complex'
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::CopyFixedArray;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ else if (TargetProperty->PropertyFlags & CPF_IsPlainOldData)
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::CopyPlain;
|
|
|
|
+ OutCopy.CopySize = SourceProperty->ElementSize * SourceProperty->ArrayDim;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::CopyComplex;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (Compatibility == EEzAbilityPropertyAccessCompatibility::Promotable)
|
|
|
|
+ {
|
|
|
|
+ if (SourceProperty->IsA<FBoolProperty>())
|
|
|
|
+ {
|
|
|
|
+ if (TargetProperty->IsA<FByteProperty>())
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::PromoteBoolToByte;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ else if (TargetProperty->IsA<FIntProperty>())
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::PromoteBoolToInt32;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ else if (TargetProperty->IsA<FUInt32Property>())
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::PromoteBoolToUInt32;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ else if (TargetProperty->IsA<FInt64Property>())
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::PromoteBoolToInt64;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ else if (TargetProperty->IsA<FFloatProperty>())
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::PromoteBoolToFloat;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ else if (TargetProperty->IsA<FDoubleProperty>())
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::PromoteBoolToDouble;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (SourceProperty->IsA<FByteProperty>())
|
|
|
|
+ {
|
|
|
|
+ if (TargetProperty->IsA<FIntProperty>())
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::PromoteByteToInt32;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ else if (TargetProperty->IsA<FUInt32Property>())
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::PromoteByteToUInt32;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ else if (TargetProperty->IsA<FInt64Property>())
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::PromoteByteToInt64;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ else if (TargetProperty->IsA<FFloatProperty>())
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::PromoteByteToFloat;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ else if (TargetProperty->IsA<FDoubleProperty>())
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::PromoteByteToDouble;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (SourceProperty->IsA<FIntProperty>())
|
|
|
|
+ {
|
|
|
|
+ if (TargetProperty->IsA<FInt64Property>())
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::PromoteInt32ToInt64;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ else if (TargetProperty->IsA<FFloatProperty>())
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::PromoteInt32ToFloat;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ else if (TargetProperty->IsA<FDoubleProperty>())
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::PromoteInt32ToDouble;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (SourceProperty->IsA<FUInt32Property>())
|
|
|
|
+ {
|
|
|
|
+ if (TargetProperty->IsA<FInt64Property>())
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::PromoteUInt32ToInt64;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ else if (TargetProperty->IsA<FFloatProperty>())
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::PromoteUInt32ToFloat;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ else if (TargetProperty->IsA<FDoubleProperty>())
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::PromoteUInt32ToDouble;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (SourceProperty->IsA<FFloatProperty>())
|
|
|
|
+ {
|
|
|
|
+ if (TargetProperty->IsA<FIntProperty>())
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::PromoteFloatToInt32;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ else if (TargetProperty->IsA<FInt64Property>())
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::PromoteFloatToInt64;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ else if (TargetProperty->IsA<FDoubleProperty>())
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::PromoteFloatToDouble;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (SourceProperty->IsA<FDoubleProperty>())
|
|
|
|
+ {
|
|
|
|
+ if (TargetProperty->IsA<FIntProperty>())
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::DemoteDoubleToInt32;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ else if (TargetProperty->IsA<FInt64Property>())
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::DemoteDoubleToInt64;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ else if (TargetProperty->IsA<FFloatProperty>())
|
|
|
|
+ {
|
|
|
|
+ OutCopy.Type = EEzAbilityPropertyCopyType::DemoteDoubleToFloat;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return false;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+EEzAbilityPropertyAccessCompatibility FEzAbilityPropertyBindings::GetPropertyCompatibility(const FProperty* FromProperty, const FProperty* ToProperty)
|
|
|
|
+{
|
|
|
|
+ if (FromProperty == ToProperty)
|
|
|
|
+ {
|
|
|
|
+ return EEzAbilityPropertyAccessCompatibility::Compatible;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (FromProperty == nullptr || ToProperty == nullptr)
|
|
|
|
+ {
|
|
|
|
+ return EEzAbilityPropertyAccessCompatibility::Incompatible;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Special case for object properties since InPropertyA->SameType(InPropertyB) requires both properties to be of the exact same class.
|
|
|
|
+ // In our case we want to be able to bind a source property if its class is a child of the target property class.
|
|
|
|
+ if (FromProperty->IsA<FObjectPropertyBase>() && ToProperty->IsA<FObjectPropertyBase>())
|
|
|
|
+ {
|
|
|
|
+ const FObjectPropertyBase* SourceProperty = CastField<FObjectPropertyBase>(FromProperty);
|
|
|
|
+ const FObjectPropertyBase* TargetProperty = CastField<FObjectPropertyBase>(ToProperty);
|
|
|
|
+ return (SourceProperty->PropertyClass->IsChildOf(TargetProperty->PropertyClass)) ? EEzAbilityPropertyAccessCompatibility::Compatible : EEzAbilityPropertyAccessCompatibility::Incompatible;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // When copying to an enum property, expect FromProperty to be the same enum.
|
|
|
|
+ auto GetPropertyEnum = [](const FProperty* Property) -> const UEnum*
|
|
|
|
+ {
|
|
|
|
+ if (const FByteProperty* ByteProperty = CastField<FByteProperty>(Property))
|
|
|
|
+ {
|
|
|
|
+ return ByteProperty->GetIntPropertyEnum();
|
|
|
|
+ }
|
|
|
|
+ if (const FEnumProperty* EnumProperty = CastField<FEnumProperty>(Property))
|
|
|
|
+ {
|
|
|
|
+ return EnumProperty->GetEnum();
|
|
|
|
+ }
|
|
|
|
+ return nullptr;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ if (const UEnum* ToPropertyEnum = GetPropertyEnum(ToProperty))
|
|
|
|
+ {
|
|
|
|
+ const UEnum* FromPropertyEnum = GetPropertyEnum(FromProperty);
|
|
|
|
+ return (ToPropertyEnum == FromPropertyEnum) ? EEzAbilityPropertyAccessCompatibility::Compatible : EEzAbilityPropertyAccessCompatibility::Incompatible;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Allow source enums to be promoted to numbers.
|
|
|
|
+ if (const FEnumProperty* EnumPropertyA = CastField<const FEnumProperty>(FromProperty))
|
|
|
|
+ {
|
|
|
|
+ FromProperty = EnumPropertyA->GetUnderlyingProperty();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (FromProperty->SameType(ToProperty))
|
|
|
|
+ {
|
|
|
|
+ return EEzAbilityPropertyAccessCompatibility::Compatible;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ // Not directly compatible, check for promotions
|
|
|
|
+ if (FromProperty->IsA<FBoolProperty>())
|
|
|
|
+ {
|
|
|
|
+ if (ToProperty->IsA<FByteProperty>()
|
|
|
|
+ || ToProperty->IsA<FIntProperty>()
|
|
|
|
+ || ToProperty->IsA<FUInt32Property>()
|
|
|
|
+ || ToProperty->IsA<FInt64Property>()
|
|
|
|
+ || ToProperty->IsA<FFloatProperty>()
|
|
|
|
+ || ToProperty->IsA<FDoubleProperty>())
|
|
|
|
+ {
|
|
|
|
+ return EEzAbilityPropertyAccessCompatibility::Promotable;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (FromProperty->IsA<FByteProperty>())
|
|
|
|
+ {
|
|
|
|
+ if (ToProperty->IsA<FIntProperty>()
|
|
|
|
+ || ToProperty->IsA<FUInt32Property>()
|
|
|
|
+ || ToProperty->IsA<FInt64Property>()
|
|
|
|
+ || ToProperty->IsA<FFloatProperty>()
|
|
|
|
+ || ToProperty->IsA<FDoubleProperty>())
|
|
|
|
+ {
|
|
|
|
+ return EEzAbilityPropertyAccessCompatibility::Promotable;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (FromProperty->IsA<FIntProperty>())
|
|
|
|
+ {
|
|
|
|
+ if (ToProperty->IsA<FInt64Property>()
|
|
|
|
+ || ToProperty->IsA<FFloatProperty>()
|
|
|
|
+ || ToProperty->IsA<FDoubleProperty>())
|
|
|
|
+ {
|
|
|
|
+ return EEzAbilityPropertyAccessCompatibility::Promotable;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (FromProperty->IsA<FUInt32Property>())
|
|
|
|
+ {
|
|
|
|
+ if (ToProperty->IsA<FInt64Property>()
|
|
|
|
+ || ToProperty->IsA<FFloatProperty>()
|
|
|
|
+ || ToProperty->IsA<FDoubleProperty>())
|
|
|
|
+ {
|
|
|
|
+ return EEzAbilityPropertyAccessCompatibility::Promotable;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (FromProperty->IsA<FFloatProperty>())
|
|
|
|
+ {
|
|
|
|
+ if (ToProperty->IsA<FIntProperty>()
|
|
|
|
+ || ToProperty->IsA<FInt64Property>()
|
|
|
|
+ || ToProperty->IsA<FDoubleProperty>())
|
|
|
|
+ {
|
|
|
|
+ return EEzAbilityPropertyAccessCompatibility::Promotable;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (FromProperty->IsA<FDoubleProperty>())
|
|
|
|
+ {
|
|
|
|
+ if (ToProperty->IsA<FIntProperty>()
|
|
|
|
+ || ToProperty->IsA<FInt64Property>()
|
|
|
|
+ || ToProperty->IsA<FFloatProperty>())
|
|
|
|
+ {
|
|
|
|
+ return EEzAbilityPropertyAccessCompatibility::Promotable;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return EEzAbilityPropertyAccessCompatibility::Incompatible;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+uint8* FEzAbilityPropertyBindings::GetAddress(FEzAbilityDataView InStructView, TConstArrayView<FEzAbilityPropertyIndirection> Indirections, const FEzAbilityPropertyIndirection& FirstIndirection, const FProperty* LeafProperty)
|
|
|
|
+{
|
|
|
|
+ uint8* Address = InStructView.GetMutableMemory();
|
|
|
|
+ if (Address == nullptr)
|
|
|
|
+ {
|
|
|
|
+ // Failed indirection, will be reported by caller.
|
|
|
|
+ return nullptr;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const FEzAbilityPropertyIndirection* Indirection = &FirstIndirection;
|
|
|
|
+
|
|
|
|
+ while (Indirection != nullptr && Address != nullptr)
|
|
|
|
+ {
|
|
|
|
+ switch (Indirection->Type)
|
|
|
|
+ {
|
|
|
|
+ case EEzAbilityPropertyAccessType::Offset:
|
|
|
|
+ {
|
|
|
|
+ Address = Address + Indirection->Offset;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ case EEzAbilityPropertyAccessType::Object:
|
|
|
|
+ {
|
|
|
|
+ UObject* Object = *reinterpret_cast<UObject**>(Address + Indirection->Offset);
|
|
|
|
+ Address = reinterpret_cast<uint8*>(Object);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ case EEzAbilityPropertyAccessType::WeakObject:
|
|
|
|
+ {
|
|
|
|
+ TWeakObjectPtr<UObject>& WeakObjectPtr = *reinterpret_cast<TWeakObjectPtr<UObject>*>(Address + Indirection->Offset);
|
|
|
|
+ UObject* Object = WeakObjectPtr.Get();
|
|
|
|
+ Address = reinterpret_cast<uint8*>(Object);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ case EEzAbilityPropertyAccessType::SoftObject:
|
|
|
|
+ {
|
|
|
|
+ FSoftObjectPtr& SoftObjectPtr = *reinterpret_cast<FSoftObjectPtr*>(Address + Indirection->Offset);
|
|
|
|
+ UObject* Object = SoftObjectPtr.Get();
|
|
|
|
+ Address = reinterpret_cast<uint8*>(Object);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ case EEzAbilityPropertyAccessType::ObjectInstance:
|
|
|
|
+ {
|
|
|
|
+ check(Indirection->InstanceStruct);
|
|
|
|
+ UObject* Object = *reinterpret_cast<UObject**>(Address + Indirection->Offset);
|
|
|
|
+ if (Object
|
|
|
|
+ && Object->GetClass()->IsChildOf(Indirection->InstanceStruct))
|
|
|
|
+ {
|
|
|
|
+ Address = reinterpret_cast<uint8*>(Object);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ // Failed indirection, will be reported by caller.
|
|
|
|
+ return nullptr;
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ case EEzAbilityPropertyAccessType::StructInstance:
|
|
|
|
+ {
|
|
|
|
+ check(Indirection->InstanceStruct);
|
|
|
|
+ FInstancedStruct& InstancedStruct = *reinterpret_cast<FInstancedStruct*>(Address + Indirection->Offset);
|
|
|
|
+ const UScriptStruct* InstanceType = InstancedStruct.GetScriptStruct();
|
|
|
|
+ if (InstanceType != nullptr
|
|
|
|
+ && InstanceType->IsChildOf(Indirection->InstanceStruct))
|
|
|
|
+ {
|
|
|
|
+ Address = InstancedStruct.GetMutableMemory();
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ // Failed indirection, will be reported by caller.
|
|
|
|
+ return nullptr;
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ case EEzAbilityPropertyAccessType::IndexArray:
|
|
|
|
+ {
|
|
|
|
+ check(Indirection->ArrayProperty);
|
|
|
|
+ FScriptArrayHelper Helper(Indirection->ArrayProperty, Address + Indirection->Offset);
|
|
|
|
+ if (Helper.IsValidIndex(Indirection->ArrayIndex.Get()))
|
|
|
|
+ {
|
|
|
|
+ Address = Helper.GetRawPtr(Indirection->ArrayIndex.Get());
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ // Failed indirection, will be reported by caller.
|
|
|
|
+ return nullptr;
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ default:
|
|
|
|
+ ensureMsgf(false, TEXT("FEzAbilityPropertyBindings::GetAddress: Unhandled indirection type %s for '%s'"),
|
|
|
|
+ *StaticEnum<EEzAbilityPropertyAccessType>()->GetValueAsString(Indirection->Type), *LeafProperty->GetNameCPP());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Indirection = Indirection->NextIndex.IsValid() ? &Indirections[Indirection->NextIndex.Get()] : nullptr;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return Address;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void FEzAbilityPropertyBindings::PerformCopy(const FEzAbilityPropertyCopy& Copy, uint8* SourceAddress, uint8* TargetAddress) const
|
|
|
|
+{
|
|
|
|
+ // Source property can be null
|
|
|
|
+ check(SourceAddress);
|
|
|
|
+ check(Copy.TargetLeafProperty);
|
|
|
|
+ check(TargetAddress);
|
|
|
|
+
|
|
|
|
+ switch (Copy.Type)
|
|
|
|
+ {
|
|
|
|
+ case EEzAbilityPropertyCopyType::CopyPlain:
|
|
|
|
+ FMemory::Memcpy(TargetAddress, SourceAddress, Copy.CopySize);
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::CopyComplex:
|
|
|
|
+ Copy.TargetLeafProperty->CopyCompleteValue(TargetAddress, SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::CopyBool:
|
|
|
|
+ static_cast<const FBoolProperty*>(Copy.TargetLeafProperty)->SetPropertyValue(TargetAddress, static_cast<const FBoolProperty*>(Copy.SourceLeafProperty)->GetPropertyValue(SourceAddress));
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::CopyStruct:
|
|
|
|
+ // If SourceProperty == nullptr (pointing to the struct source directly), the GetAddress() did the right thing and is pointing the the beginning of the struct.
|
|
|
|
+ static_cast<const FStructProperty*>(Copy.TargetLeafProperty)->Struct->CopyScriptStruct(TargetAddress, SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::CopyObject:
|
|
|
|
+ if (Copy.SourceLeafProperty == nullptr)
|
|
|
|
+ {
|
|
|
|
+ // Source is pointing at object directly.
|
|
|
|
+ static_cast<const FObjectPropertyBase*>(Copy.TargetLeafProperty)->SetObjectPropertyValue(TargetAddress, (UObject*)SourceAddress);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ static_cast<const FObjectPropertyBase*>(Copy.TargetLeafProperty)->SetObjectPropertyValue(TargetAddress, static_cast<const FObjectPropertyBase*>(Copy.SourceLeafProperty)->GetObjectPropertyValue(SourceAddress));
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::CopyName:
|
|
|
|
+ static_cast<const FNameProperty*>(Copy.TargetLeafProperty)->SetPropertyValue(TargetAddress, static_cast<const FNameProperty*>(Copy.SourceLeafProperty)->GetPropertyValue(SourceAddress));
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::CopyFixedArray:
|
|
|
|
+ {
|
|
|
|
+ // Copy into fixed sized array (EditFixedSize). Resizable arrays are copied as Complex, and regular fixed sizes arrays via the regular copies (dim specifies array size).
|
|
|
|
+ const FArrayProperty* SourceArrayProperty = static_cast<const FArrayProperty*>(Copy.SourceLeafProperty);
|
|
|
|
+ const FArrayProperty* TargetArrayProperty = static_cast<const FArrayProperty*>(Copy.TargetLeafProperty);
|
|
|
|
+ FScriptArrayHelper SourceArrayHelper(SourceArrayProperty, SourceAddress);
|
|
|
|
+ FScriptArrayHelper TargetArrayHelper(TargetArrayProperty, TargetAddress);
|
|
|
|
+
|
|
|
|
+ const int32 MinSize = FMath::Min(SourceArrayHelper.Num(), TargetArrayHelper.Num());
|
|
|
|
+ for (int32 ElementIndex = 0; ElementIndex < MinSize; ++ElementIndex)
|
|
|
|
+ {
|
|
|
|
+ TargetArrayProperty->Inner->CopySingleValue(TargetArrayHelper.GetRawPtr(ElementIndex), SourceArrayHelper.GetRawPtr(ElementIndex));
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ case EEzAbilityPropertyCopyType::StructReference:
|
|
|
|
+ {
|
|
|
|
+ const FStructProperty* SourceStructProperty = static_cast<const FStructProperty*>(Copy.SourceLeafProperty);
|
|
|
|
+ FEzAbilityStructRef* Target = (FEzAbilityStructRef*)TargetAddress;
|
|
|
|
+ Target->Set(FStructView(SourceStructProperty->Struct, SourceAddress));
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ // Bool promotions
|
|
|
|
+ case EEzAbilityPropertyCopyType::PromoteBoolToByte:
|
|
|
|
+ *reinterpret_cast<uint8*>(TargetAddress) = (uint8)static_cast<const FBoolProperty*>(Copy.SourceLeafProperty)->GetPropertyValue(SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::PromoteBoolToInt32:
|
|
|
|
+ *reinterpret_cast<int32*>(TargetAddress) = (int32)static_cast<const FBoolProperty*>(Copy.SourceLeafProperty)->GetPropertyValue(SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::PromoteBoolToUInt32:
|
|
|
|
+ *reinterpret_cast<uint32*>(TargetAddress) = (uint32)static_cast<const FBoolProperty*>(Copy.SourceLeafProperty)->GetPropertyValue(SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::PromoteBoolToInt64:
|
|
|
|
+ *reinterpret_cast<int64*>(TargetAddress) = (int64)static_cast<const FBoolProperty*>(Copy.SourceLeafProperty)->GetPropertyValue(SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::PromoteBoolToFloat:
|
|
|
|
+ *reinterpret_cast<float*>(TargetAddress) = (float)static_cast<const FBoolProperty*>(Copy.SourceLeafProperty)->GetPropertyValue(SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::PromoteBoolToDouble:
|
|
|
|
+ *reinterpret_cast<double*>(TargetAddress) = (double)static_cast<const FBoolProperty*>(Copy.SourceLeafProperty)->GetPropertyValue(SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ // Byte promotions
|
|
|
|
+ case EEzAbilityPropertyCopyType::PromoteByteToInt32:
|
|
|
|
+ *reinterpret_cast<int32*>(TargetAddress) = (int32)*reinterpret_cast<const uint8*>(SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::PromoteByteToUInt32:
|
|
|
|
+ *reinterpret_cast<uint32*>(TargetAddress) = (uint32)*reinterpret_cast<const uint8*>(SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::PromoteByteToInt64:
|
|
|
|
+ *reinterpret_cast<int64*>(TargetAddress) = (int64)*reinterpret_cast<const uint8*>(SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::PromoteByteToFloat:
|
|
|
|
+ *reinterpret_cast<float*>(TargetAddress) = (float)*reinterpret_cast<const uint8*>(SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::PromoteByteToDouble:
|
|
|
|
+ *reinterpret_cast<double*>(TargetAddress) = (double)*reinterpret_cast<const uint8*>(SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ // Int32 promotions
|
|
|
|
+ case EEzAbilityPropertyCopyType::PromoteInt32ToInt64:
|
|
|
|
+ *reinterpret_cast<int64*>(TargetAddress) = (int64)*reinterpret_cast<const int32*>(SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::PromoteInt32ToFloat:
|
|
|
|
+ *reinterpret_cast<float*>(TargetAddress) = (float)*reinterpret_cast<const int32*>(SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::PromoteInt32ToDouble:
|
|
|
|
+ *reinterpret_cast<double*>(TargetAddress) = (double)*reinterpret_cast<const int32*>(SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ // Uint32 promotions
|
|
|
|
+ case EEzAbilityPropertyCopyType::PromoteUInt32ToInt64:
|
|
|
|
+ *reinterpret_cast<int64*>(TargetAddress) = (int64)*reinterpret_cast<const uint32*>(SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::PromoteUInt32ToFloat:
|
|
|
|
+ *reinterpret_cast<float*>(TargetAddress) = (float)*reinterpret_cast<const uint32*>(SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::PromoteUInt32ToDouble:
|
|
|
|
+ *reinterpret_cast<double*>(TargetAddress) = (double)*reinterpret_cast<const uint32*>(SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ // Float promotions
|
|
|
|
+ case EEzAbilityPropertyCopyType::PromoteFloatToInt32:
|
|
|
|
+ *reinterpret_cast<int32*>(TargetAddress) = (int32)*reinterpret_cast<const float*>(SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::PromoteFloatToInt64:
|
|
|
|
+ *reinterpret_cast<int64*>(TargetAddress) = (int64)*reinterpret_cast<const float*>(SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::PromoteFloatToDouble:
|
|
|
|
+ *reinterpret_cast<double*>(TargetAddress) = (double)*reinterpret_cast<const float*>(SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ // Double promotions
|
|
|
|
+ case EEzAbilityPropertyCopyType::DemoteDoubleToInt32:
|
|
|
|
+ *reinterpret_cast<int32*>(TargetAddress) = (int32)*reinterpret_cast<const double*>(SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::DemoteDoubleToInt64:
|
|
|
|
+ *reinterpret_cast<int64*>(TargetAddress) = (int64)*reinterpret_cast<const double*>(SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::DemoteDoubleToFloat:
|
|
|
|
+ *reinterpret_cast<float*>(TargetAddress) = (float)*reinterpret_cast<const double*>(SourceAddress);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ default:
|
|
|
|
+ ensureMsgf(false, TEXT("FEzAbilityPropertyBindings::PerformCopy: Unhandled copy type %s between '%s' and '%s'"),
|
|
|
|
+ *StaticEnum<EEzAbilityPropertyCopyType>()->GetValueAsString(Copy.Type), *Copy.SourceLeafProperty->GetNameCPP(), *Copy.TargetLeafProperty->GetNameCPP());
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+bool FEzAbilityPropertyBindings::CopyProperty(const FEzAbilityPropertyCopy& Copy, FEzAbilityDataView SourceStructView, FEzAbilityDataView TargetStructView) const
|
|
|
|
+{
|
|
|
|
+ // This is made ensure so that the programmers have the change to catch it (it's usually programming error not to call ResolvePaths(), and it wont spam log for others.
|
|
|
|
+ if (!ensureMsgf(bBindingsResolved, TEXT("Bindings must be resolved successfully before copying. See ResolvePaths()")))
|
|
|
|
+ {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Copies that fail to be resolved (i.e. property path does not resolve, types changed) will be marked as None, skip them.
|
|
|
|
+ if (Copy.Type == EEzAbilityPropertyCopyType::None)
|
|
|
|
+ {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ bool bResult = true;
|
|
|
|
+
|
|
|
|
+ if (SourceStructView.IsValid() && TargetStructView.IsValid())
|
|
|
|
+ {
|
|
|
|
+ check(SourceStructView.GetStruct() == Copy.SourceStructType
|
|
|
|
+ || (SourceStructView.GetStruct() && SourceStructView.GetStruct()->IsChildOf(Copy.SourceStructType)));
|
|
|
|
+
|
|
|
|
+ uint8* SourceAddress = GetAddress(SourceStructView, Copy.SourceIndirection, Copy.SourceLeafProperty);
|
|
|
|
+ uint8* TargetAddress = GetAddress(TargetStructView, Copy.TargetIndirection, Copy.TargetLeafProperty);
|
|
|
|
+
|
|
|
|
+ if (SourceAddress != nullptr && TargetAddress != nullptr)
|
|
|
|
+ {
|
|
|
|
+ PerformCopy(Copy, SourceAddress, TargetAddress);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ bResult = false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ bResult = false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return bResult;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void FEzAbilityPropertyBindings::PerformResetObjects(const FEzAbilityPropertyCopy& Copy, uint8* TargetAddress) const
|
|
|
|
+{
|
|
|
|
+ // Source property can be null
|
|
|
|
+ check(Copy.TargetLeafProperty);
|
|
|
|
+ check(TargetAddress);
|
|
|
|
+
|
|
|
|
+ switch (Copy.Type)
|
|
|
|
+ {
|
|
|
|
+ case EEzAbilityPropertyCopyType::CopyComplex:
|
|
|
|
+ Copy.TargetLeafProperty->InitializeValue(TargetAddress);
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::CopyStruct:
|
|
|
|
+ static_cast<const FStructProperty*>(Copy.TargetLeafProperty)->Struct->ClearScriptStruct(TargetAddress);
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::CopyObject:
|
|
|
|
+ static_cast<const FObjectPropertyBase*>(Copy.TargetLeafProperty)->SetObjectPropertyValue(TargetAddress, nullptr);
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::StructReference:
|
|
|
|
+ reinterpret_cast<FEzAbilityStructRef*>(TargetAddress)->Set(FStructView());
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::CopyName:
|
|
|
|
+ break;
|
|
|
|
+ case EEzAbilityPropertyCopyType::CopyFixedArray:
|
|
|
|
+ {
|
|
|
|
+ // Copy into fixed sized array (EditFixedSize). Resizable arrays are copied as Complex, and regular fixed sizes arrays via the regular copies (dim specifies array size).
|
|
|
|
+ const FArrayProperty* TargetArrayProperty = static_cast<const FArrayProperty*>(Copy.TargetLeafProperty);
|
|
|
|
+ FScriptArrayHelper TargetArrayHelper(TargetArrayProperty, TargetAddress);
|
|
|
|
+ for (int32 ElementIndex = 0; ElementIndex < TargetArrayHelper.Num(); ++ElementIndex)
|
|
|
|
+ {
|
|
|
|
+ TargetArrayProperty->Inner->InitializeValue(TargetArrayHelper.GetRawPtr(ElementIndex));
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ default:
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const FEzAbilityPropertyAccess* FEzAbilityPropertyBindings::GetPropertyAccess(const FEzAbilityPropertyRef& PropertyRef) const
|
|
|
|
+{
|
|
|
|
+ if (!PropertyRef.GetRefAccessIndex().IsValid())
|
|
|
|
+ {
|
|
|
|
+ return nullptr;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!ensure(PropertyAccesses.IsValidIndex(PropertyRef.GetRefAccessIndex().Get())))
|
|
|
|
+ {
|
|
|
|
+ return nullptr;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return &PropertyAccesses[PropertyRef.GetRefAccessIndex().Get()];
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+bool FEzAbilityPropertyBindings::ResetObjects(const FEzAbilityIndex16 TargetBatchIndex, FEzAbilityDataView TargetStructView) const
|
|
|
|
+{
|
|
|
|
+ // This is made ensure so that the programmers have the change to catch it (it's usually programming error not to call ResolvePaths(), and it wont spam log for others.
|
|
|
|
+ if (!ensureMsgf(bBindingsResolved, TEXT("Bindings must be resolved successfully before copying. See ResolvePaths()")))
|
|
|
|
+ {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (TargetBatchIndex.IsValid() == false)
|
|
|
|
+ {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ check(CopyBatches.IsValidIndex(TargetBatchIndex.Get()));
|
|
|
|
+ const FEzAbilityPropertyCopyBatch& Batch = CopyBatches[TargetBatchIndex.Get()];
|
|
|
|
+
|
|
|
|
+ check(TargetStructView.IsValid());
|
|
|
|
+ check(TargetStructView.GetStruct() == Batch.TargetStruct.Struct);
|
|
|
|
+
|
|
|
|
+ bool bResult = true;
|
|
|
|
+
|
|
|
|
+ for (int32 i = Batch.BindingsBegin; i != Batch.BindingsEnd; i++)
|
|
|
|
+ {
|
|
|
|
+ const FEzAbilityPropertyCopy& Copy = PropertyCopies[i];
|
|
|
|
+ // Copies that fail to be resolved (i.e. property path does not resolve, types changed) will be marked as None, skip them.
|
|
|
|
+ if (Copy.Type == EEzAbilityPropertyCopyType::None)
|
|
|
|
+ {
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ uint8* TargetAddress = GetAddress(TargetStructView, Copy.TargetIndirection, Copy.TargetLeafProperty);
|
|
|
|
+ check(TargetAddress != nullptr);
|
|
|
|
+ PerformResetObjects(Copy, TargetAddress);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return bResult;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+bool FEzAbilityPropertyBindings::ContainsAnyStruct(const TSet<const UStruct*>& Structs)
|
|
|
|
+{
|
|
|
|
+ for (FEzAbilityBindableStructDesc& SourceStruct : SourceStructs)
|
|
|
|
+ {
|
|
|
|
+ if (Structs.Contains(SourceStruct.Struct))
|
|
|
|
+ {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for (FEzAbilityPropertyCopyBatch& CopyBatch : CopyBatches)
|
|
|
|
+ {
|
|
|
|
+ if (Structs.Contains(CopyBatch.TargetStruct.Struct))
|
|
|
|
+ {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ auto PathContainsStruct = [&Structs](const FEzAbilityPropertyPath& PropertyPath)
|
|
|
|
+ {
|
|
|
|
+ for (const FEzAbilityPropertyPathSegment& Segment : PropertyPath.GetSegments())
|
|
|
|
+ {
|
|
|
|
+ if (Structs.Contains(Segment.GetInstanceStruct()))
|
|
|
|
+ {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return false;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ for (FEzAbilityPropertyPathBinding& PropertyPathBinding : PropertyPathBindings)
|
|
|
|
+ {
|
|
|
|
+ if (PathContainsStruct(PropertyPathBinding.GetSourcePath()))
|
|
|
|
+ {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ if (PathContainsStruct(PropertyPathBinding.GetTargetPath()))
|
|
|
|
+ {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return false;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void FEzAbilityPropertyBindings::DebugPrintInternalLayout(FString& OutString) const
|
|
|
|
+{
|
|
|
|
+ /** Array of expected source structs. */
|
|
|
|
+ OutString += FString::Printf(TEXT("\nBindableStructDesc (%d)\n [ %-40s | %-40s ]\n"), SourceStructs.Num(), TEXT("Type"), TEXT("Name"));
|
|
|
|
+ for (const FEzAbilityBindableStructDesc& BindableStructDesc : SourceStructs)
|
|
|
|
+ {
|
|
|
|
+ OutString += FString::Printf(TEXT(" | %-40s | %-40s |\n"),
|
|
|
|
+ BindableStructDesc.Struct ? *BindableStructDesc.Struct->GetName() : TEXT("null"),
|
|
|
|
+ *BindableStructDesc.Name.ToString());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /** Array of copy batches. */
|
|
|
|
+ OutString += FString::Printf(TEXT("\nCopyBatches (%d)\n [ %-40s | %-40s | %-8s [%-3s:%-3s[ ]\n"), CopyBatches.Num(),
|
|
|
|
+ TEXT("Target Type"), TEXT("Target Name"), TEXT("Bindings"), TEXT("Beg"), TEXT("End"));
|
|
|
|
+ for (const FEzAbilityPropertyCopyBatch& CopyBatch : CopyBatches)
|
|
|
|
+ {
|
|
|
|
+ OutString += FString::Printf(TEXT(" | %-40s | %-40s | %8s [%3d:%-3d[ |\n"),
|
|
|
|
+ CopyBatch.TargetStruct.Struct ? *CopyBatch.TargetStruct.Struct->GetName() : TEXT("null"),
|
|
|
|
+ *CopyBatch.TargetStruct.Name.ToString(),
|
|
|
|
+ TEXT(""), CopyBatch.BindingsBegin, CopyBatch.BindingsEnd);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /** Array of property bindings, resolved into arrays of copies before use. */
|
|
|
|
+ OutString += FString::Printf(TEXT("\nPropertyPathBindings (%d)\n"), PropertyPathBindings.Num());
|
|
|
|
+ for (const FEzAbilityPropertyPathBinding& PropertyBinding : PropertyPathBindings)
|
|
|
|
+ {
|
|
|
|
+ OutString += FString::Printf(TEXT("\n Source: %s | Target: %s"),
|
|
|
|
+ *PropertyBinding.GetSourcePath().ToString(), *PropertyBinding.GetSourcePath().ToString());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /** Array of property copies */
|
|
|
|
+ OutString += FString::Printf(TEXT("\nPropertyCopies (%d)\n [ %-7s | %-4s | %-4s | %-10s | %-7s | %-4s | %-4s | %-10s | %-10s | %-20s | %-4s ]\n"), PropertyCopies.Num(),
|
|
|
|
+ TEXT("Src Idx"), TEXT("Off."), TEXT("Next"), TEXT("Type"),
|
|
|
|
+ TEXT("Tgt Idx"), TEXT("Off."), TEXT("Next"), TEXT("Type"),
|
|
|
|
+ TEXT("Source"), TEXT("Copy Type"), TEXT("Size"));
|
|
|
|
+ for (const FEzAbilityPropertyCopy& PropertyCopy : PropertyCopies)
|
|
|
|
+ {
|
|
|
|
+ OutString += FString::Printf(TEXT(" | %7d | %4d | %4d | %-10s | %7d | %4d | %4d | %-10s | %10s | %-20s | %4d |\n"),
|
|
|
|
+ PropertyCopy.SourceIndirection.ArrayIndex.Get(),
|
|
|
|
+ PropertyCopy.SourceIndirection.Offset,
|
|
|
|
+ PropertyCopy.SourceIndirection.NextIndex.Get(),
|
|
|
|
+ *UEnum::GetDisplayValueAsText(PropertyCopy.SourceIndirection.Type).ToString(),
|
|
|
|
+ PropertyCopy.TargetIndirection.ArrayIndex.Get(),
|
|
|
|
+ PropertyCopy.TargetIndirection.Offset,
|
|
|
|
+ PropertyCopy.TargetIndirection.NextIndex.Get(),
|
|
|
|
+ *UEnum::GetDisplayValueAsText(PropertyCopy.TargetIndirection.Type).ToString(),
|
|
|
|
+ *PropertyCopy.SourceDataHandle.Describe(),
|
|
|
|
+ *UEnum::GetDisplayValueAsText(PropertyCopy.Type).ToString(),
|
|
|
|
+ PropertyCopy.CopySize);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /** Array of property indirections, indexed by accesses*/
|
|
|
|
+ OutString += FString::Printf(TEXT("\nPropertyIndirections (%d)\n [ %-4s | %-4s | %-4s | %-10s ] \n"), PropertyIndirections.Num(),
|
|
|
|
+ TEXT("Idx"), TEXT("Off."), TEXT("Next"), TEXT("Access Type"));
|
|
|
|
+ for (const FEzAbilityPropertyIndirection& PropertyIndirection : PropertyIndirections)
|
|
|
|
+ {
|
|
|
|
+ OutString += FString::Printf(TEXT(" | %4d | %4d | %4d | %-10s |\n"),
|
|
|
|
+ PropertyIndirection.ArrayIndex.Get(),
|
|
|
|
+ PropertyIndirection.Offset,
|
|
|
|
+ PropertyIndirection.NextIndex.Get(),
|
|
|
|
+ *UEnum::GetDisplayValueAsText(PropertyIndirection.Type).ToString());
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+//----------------------------------------------------------------//
|
|
|
|
+// FEzAbilityPropertyPath
|
|
|
|
+//----------------------------------------------------------------//
|
|
|
|
+
|
|
|
|
+bool FEzAbilityPropertyPath::FromString(const FString& InPath)
|
|
|
|
+{
|
|
|
|
+ Segments.Reset();
|
|
|
|
+
|
|
|
|
+ if (InPath.IsEmpty())
|
|
|
|
+ {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ bool bResult = true;
|
|
|
|
+ TArray<FString> PathSegments;
|
|
|
|
+ InPath.ParseIntoArray(PathSegments, TEXT("."), /*InCullEmpty=*/false);
|
|
|
|
+
|
|
|
|
+ for (const FString& Segment : PathSegments)
|
|
|
|
+ {
|
|
|
|
+ if (Segment.IsEmpty())
|
|
|
|
+ {
|
|
|
|
+ bResult = false;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ int32 FirstBracket = INDEX_NONE;
|
|
|
|
+ int32 LastBracket = INDEX_NONE;
|
|
|
|
+ if (Segment.FindChar(TEXT('['), FirstBracket)
|
|
|
|
+ && Segment.FindLastChar(TEXT(']'), LastBracket))
|
|
|
|
+ {
|
|
|
|
+ const int32 NameStringLength = FirstBracket;
|
|
|
|
+ const int32 IndexStringLength = LastBracket - FirstBracket - 1;
|
|
|
|
+ if (NameStringLength < 1
|
|
|
|
+ || IndexStringLength <= 0)
|
|
|
|
+ {
|
|
|
|
+ bResult = false;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const FString NameString = Segment.Left(FirstBracket);
|
|
|
|
+ const FString IndexString = Segment.Mid(FirstBracket + 1, IndexStringLength);
|
|
|
|
+ int32 ArrayIndex = INDEX_NONE;
|
|
|
|
+ LexFromString(ArrayIndex, *IndexString);
|
|
|
|
+ if (ArrayIndex < 0)
|
|
|
|
+ {
|
|
|
|
+ bResult = false;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ AddPathSegment(FName(NameString), ArrayIndex);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ AddPathSegment(FName(Segment));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!bResult)
|
|
|
|
+ {
|
|
|
|
+ Segments.Reset();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return bResult;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+bool FEzAbilityPropertyPath::UpdateSegments(const UStruct* BaseStruct, FString* OutError)
|
|
|
|
+{
|
|
|
|
+ return UpdateSegmentsFromValue(FEzAbilityDataView(BaseStruct, nullptr), OutError);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+bool FEzAbilityPropertyPath::UpdateSegmentsFromValue(const FEzAbilityDataView BaseValueView, FString* OutError)
|
|
|
|
+{
|
|
|
|
+ TArray<FEzAbilityPropertyPathIndirection> Indirections;
|
|
|
|
+ if (!ResolveIndirectionsWithValue(BaseValueView, Indirections, OutError, /*bHandleRedirects*/true))
|
|
|
|
+ {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for (FEzAbilityPropertyPathSegment& Segment : Segments)
|
|
|
|
+ {
|
|
|
|
+ Segment.SetInstanceStruct(nullptr);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for (const FEzAbilityPropertyPathIndirection& Indirection : Indirections)
|
|
|
|
+ {
|
|
|
|
+ if (Indirection.InstanceStruct != nullptr)
|
|
|
|
+ {
|
|
|
|
+ Segments[Indirection.PathSegmentIndex].SetInstanceStruct(Indirection.InstanceStruct);
|
|
|
|
+ }
|
|
|
|
+#if WITH_EDITORONLY_DATA
|
|
|
|
+ if (!Indirection.GetRedirectedName().IsNone())
|
|
|
|
+ {
|
|
|
|
+ Segments[Indirection.PathSegmentIndex].SetName(Indirection.GetRedirectedName());
|
|
|
|
+ }
|
|
|
|
+ Segments[Indirection.PathSegmentIndex].SetPropertyGuid(Indirection.GetPropertyGuid());
|
|
|
|
+#endif
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+FString FEzAbilityPropertyPath::ToString(const int32 HighlightedSegment, const TCHAR* HighlightPrefix, const TCHAR* HighlightPostfix, const bool bOutputInstances) const
|
|
|
|
+{
|
|
|
|
+ FString Result;
|
|
|
|
+ for (TEnumerateRef<const FEzAbilityPropertyPathSegment> Segment : EnumerateRange(Segments))
|
|
|
|
+ {
|
|
|
|
+ if (Segment.GetIndex() > 0)
|
|
|
|
+ {
|
|
|
|
+ Result += TEXT(".");
|
|
|
|
+ }
|
|
|
|
+ if (Segment.GetIndex() == HighlightedSegment && HighlightPrefix)
|
|
|
|
+ {
|
|
|
|
+ Result += HighlightPrefix;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (bOutputInstances && Segment->GetInstanceStruct())
|
|
|
|
+ {
|
|
|
|
+ Result += FString::Printf(TEXT("(%s)"), *GetNameSafe(Segment->GetInstanceStruct()));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (Segment->GetArrayIndex() >= 0)
|
|
|
|
+ {
|
|
|
|
+ Result += FString::Printf(TEXT("%s[%d]"), *Segment->GetName().ToString(), Segment->GetArrayIndex());
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ Result += Segment->GetName().ToString();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (Segment.GetIndex() == HighlightedSegment && HighlightPostfix)
|
|
|
|
+ {
|
|
|
|
+ Result += HighlightPostfix;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return Result;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+bool FEzAbilityPropertyPath::ResolveIndirections(const UStruct* BaseStruct, TArray<FEzAbilityPropertyPathIndirection>& OutIndirections, FString* OutError, bool bHandleRedirects) const
|
|
|
|
+{
|
|
|
|
+ return ResolveIndirectionsWithValue(FEzAbilityDataView(BaseStruct, nullptr), OutIndirections, OutError, bHandleRedirects);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+bool FEzAbilityPropertyPath::ResolveIndirectionsWithValue(const FEzAbilityDataView BaseValueView, TArray<FEzAbilityPropertyPathIndirection>& OutIndirections, FString* OutError, bool bHandleRedirects) const
|
|
|
|
+{
|
|
|
|
+ OutIndirections.Reset();
|
|
|
|
+ if (OutError)
|
|
|
|
+ {
|
|
|
|
+ OutError->Reset();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Nothing to do for an empty path.
|
|
|
|
+ if (IsPathEmpty())
|
|
|
|
+ {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const uint8* CurrentAddress = BaseValueView.GetMemory();
|
|
|
|
+ const UStruct* CurrentStruct = BaseValueView.GetStruct();
|
|
|
|
+
|
|
|
|
+ for (const TEnumerateRef<const FEzAbilityPropertyPathSegment> Segment : EnumerateRange(Segments))
|
|
|
|
+ {
|
|
|
|
+ if (CurrentStruct == nullptr)
|
|
|
|
+ {
|
|
|
|
+ if (OutError)
|
|
|
|
+ {
|
|
|
|
+ *OutError = FString::Printf(TEXT("Malformed path '%s'."),
|
|
|
|
+ *ToString(Segment.GetIndex(), TEXT("<"), TEXT(">")));
|
|
|
|
+ }
|
|
|
|
+ OutIndirections.Reset();
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const FProperty* Property = CurrentStruct->FindPropertyByName(Segment->GetName());
|
|
|
|
+ const bool bWithValue = CurrentAddress != nullptr;
|
|
|
|
+
|
|
|
|
+#if WITH_EDITORONLY_DATA
|
|
|
|
+ FName RedirectedName;
|
|
|
|
+ FGuid PropertyGuid = Segment->GetPropertyGuid();
|
|
|
|
+
|
|
|
|
+ // Try to fix the path in editor.
|
|
|
|
+ if (bHandleRedirects)
|
|
|
|
+ {
|
|
|
|
+
|
|
|
|
+ // Check if there's a core redirect for it.
|
|
|
|
+ if (!Property)
|
|
|
|
+ {
|
|
|
|
+ // Try to match by property ID (Blueprint or User Defined Struct).
|
|
|
|
+ if (Segment->GetPropertyGuid().IsValid())
|
|
|
|
+ {
|
|
|
|
+ if (const UBlueprintGeneratedClass* BlueprintClass = Cast<UBlueprintGeneratedClass>(CurrentStruct))
|
|
|
|
+ {
|
|
|
|
+ if (const FName* Name = BlueprintClass->PropertyGuids.FindKey(Segment->GetPropertyGuid()))
|
|
|
|
+ {
|
|
|
|
+ RedirectedName = *Name;
|
|
|
|
+ Property = CurrentStruct->FindPropertyByName(RedirectedName);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (const UUserDefinedStruct* UserDefinedStruct = Cast<UUserDefinedStruct>(CurrentStruct))
|
|
|
|
+ {
|
|
|
|
+ if (FProperty* FoundProperty = FStructureEditorUtils::GetPropertyByGuid(UserDefinedStruct, Segment->GetPropertyGuid()))
|
|
|
|
+ {
|
|
|
|
+ RedirectedName = FoundProperty->GetFName();
|
|
|
|
+ Property = FoundProperty;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (const UPropertyBag* PropertyBag = Cast<UPropertyBag>(CurrentStruct))
|
|
|
|
+ {
|
|
|
|
+ if (const FPropertyBagPropertyDesc* Desc = PropertyBag->FindPropertyDescByID(Segment->GetPropertyGuid()))
|
|
|
|
+ {
|
|
|
|
+ if (Desc->CachedProperty)
|
|
|
|
+ {
|
|
|
|
+ RedirectedName = Desc->CachedProperty->GetFName();
|
|
|
|
+ Property = Desc->CachedProperty;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ // Try core redirect
|
|
|
|
+ const FCoreRedirectObjectName OldPropertyName(Segment->GetName(), CurrentStruct->GetFName(), *CurrentStruct->GetOutermost()->GetPathName());
|
|
|
|
+ const FCoreRedirectObjectName NewPropertyName = FCoreRedirects::GetRedirectedName(ECoreRedirectFlags::Type_Property, OldPropertyName);
|
|
|
|
+ if (OldPropertyName != NewPropertyName)
|
|
|
|
+ {
|
|
|
|
+ // Cached the result for later use.
|
|
|
|
+ RedirectedName = NewPropertyName.ObjectName;
|
|
|
|
+
|
|
|
|
+ Property = CurrentStruct->FindPropertyByName(RedirectedName);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Update PropertyGuid
|
|
|
|
+ if (Property)
|
|
|
|
+ {
|
|
|
|
+ const FName PropertyName = !RedirectedName.IsNone() ? RedirectedName : Segment->GetName();
|
|
|
|
+ if (const UBlueprintGeneratedClass* BlueprintClass = Cast<UBlueprintGeneratedClass>(CurrentStruct))
|
|
|
|
+ {
|
|
|
|
+ if (const FGuid* VarGuid = BlueprintClass->PropertyGuids.Find(PropertyName))
|
|
|
|
+ {
|
|
|
|
+ PropertyGuid = *VarGuid;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (const UUserDefinedStruct* UserDefinedStruct = Cast<UUserDefinedStruct>(CurrentStruct))
|
|
|
|
+ {
|
|
|
|
+ // Parse Guid from UDS property name.
|
|
|
|
+ PropertyGuid = FStructureEditorUtils::GetGuidFromPropertyName(PropertyName);
|
|
|
|
+ }
|
|
|
|
+ else if (const UPropertyBag* PropertyBag = Cast<UPropertyBag>(CurrentStruct))
|
|
|
|
+ {
|
|
|
|
+ if (const FPropertyBagPropertyDesc* Desc = PropertyBag->FindPropertyDescByPropertyName(PropertyName))
|
|
|
|
+ {
|
|
|
|
+ PropertyGuid = Desc->ID;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+#endif // WITH_EDITORONLY_DATA
|
|
|
|
+
|
|
|
|
+ if (!Property)
|
|
|
|
+ {
|
|
|
|
+ if (OutError)
|
|
|
|
+ {
|
|
|
|
+ *OutError = FString::Printf(TEXT("Malformed path '%s', could not find property '%s%s::%s'."),
|
|
|
|
+ *ToString(Segment.GetIndex(), TEXT("<"), TEXT(">")),
|
|
|
|
+ CurrentStruct->GetPrefixCPP(), *CurrentStruct->GetName(), *Segment->GetName().ToString());
|
|
|
|
+ }
|
|
|
|
+ OutIndirections.Reset();
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Property);
|
|
|
|
+ int ArrayIndex = 0;
|
|
|
|
+ int32 Offset = 0;
|
|
|
|
+ if (ArrayProperty && Segment->GetArrayIndex() != INDEX_NONE)
|
|
|
|
+ {
|
|
|
|
+ FEzAbilityPropertyPathIndirection& Indirection = OutIndirections.AddDefaulted_GetRef();
|
|
|
|
+ Indirection.Property = Property;
|
|
|
|
+ Indirection.ContainerAddress = CurrentAddress;
|
|
|
|
+ Indirection.ContainerStruct = CurrentStruct;
|
|
|
|
+ Indirection.InstanceStruct = nullptr;
|
|
|
|
+ Indirection.ArrayIndex = Segment->GetArrayIndex();
|
|
|
|
+ Indirection.PropertyOffset = ArrayProperty->GetOffset_ForInternal();
|
|
|
|
+ Indirection.PathSegmentIndex = Segment.GetIndex();
|
|
|
|
+ Indirection.AccessType = EEzAbilityPropertyAccessType::IndexArray;
|
|
|
|
+#if WITH_EDITORONLY_DATA
|
|
|
|
+ Indirection.RedirectedName = RedirectedName;
|
|
|
|
+ Indirection.PropertyGuid = PropertyGuid;
|
|
|
|
+#endif
|
|
|
|
+
|
|
|
|
+ ArrayIndex = 0;
|
|
|
|
+ Offset = 0;
|
|
|
|
+ Property = ArrayProperty->Inner;
|
|
|
|
+
|
|
|
|
+ if (bWithValue)
|
|
|
|
+ {
|
|
|
|
+ FScriptArrayHelper Helper(ArrayProperty, CurrentAddress + ArrayProperty->GetOffset_ForInternal());
|
|
|
|
+ if (!Helper.IsValidIndex(Segment->GetArrayIndex()))
|
|
|
|
+ {
|
|
|
|
+ if (OutError)
|
|
|
|
+ {
|
|
|
|
+ *OutError = FString::Printf(TEXT("Index %d out of range (num elements %d) trying to access dynamic array '%s'."),
|
|
|
|
+ Segment->GetArrayIndex(), Helper.Num(), *ToString(Segment.GetIndex(), TEXT("<"), TEXT(">")));
|
|
|
|
+ }
|
|
|
|
+ OutIndirections.Reset();
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ CurrentAddress = Helper.GetRawPtr(Segment->GetArrayIndex());
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ if (Segment->GetArrayIndex() > Property->ArrayDim)
|
|
|
|
+ {
|
|
|
|
+ if (OutError)
|
|
|
|
+ {
|
|
|
|
+ *OutError = FString::Printf(TEXT("Index %d out of range %d trying to access static array '%s'."),
|
|
|
|
+ Segment->GetArrayIndex(), Property->ArrayDim, *ToString(Segment.GetIndex(), TEXT("<"), TEXT(">")));
|
|
|
|
+ }
|
|
|
|
+ OutIndirections.Reset();
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ ArrayIndex = FMath::Max(0, Segment->GetArrayIndex());
|
|
|
|
+ Offset = Property->GetOffset_ForInternal() + Property->ElementSize * ArrayIndex;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ FEzAbilityPropertyPathIndirection& Indirection = OutIndirections.AddDefaulted_GetRef();
|
|
|
|
+ Indirection.Property = Property;
|
|
|
|
+ Indirection.ContainerAddress = CurrentAddress;
|
|
|
|
+ Indirection.ContainerStruct = CurrentStruct;
|
|
|
|
+ Indirection.ArrayIndex = ArrayIndex;
|
|
|
|
+ Indirection.PropertyOffset = Offset;
|
|
|
|
+ Indirection.PathSegmentIndex = Segment.GetIndex();
|
|
|
|
+ Indirection.AccessType = EEzAbilityPropertyAccessType::Offset;
|
|
|
|
+#if WITH_EDITORONLY_DATA
|
|
|
|
+ Indirection.RedirectedName = RedirectedName;
|
|
|
|
+ Indirection.PropertyGuid = PropertyGuid;
|
|
|
|
+#endif
|
|
|
|
+ const bool bLastSegment = Segment.GetIndex() == (Segments.Num() - 1);
|
|
|
|
+
|
|
|
|
+ if (!bLastSegment)
|
|
|
|
+ {
|
|
|
|
+ if (const FStructProperty* StructProperty = CastField<FStructProperty>(Property))
|
|
|
|
+ {
|
|
|
|
+ if (bWithValue)
|
|
|
|
+ {
|
|
|
|
+ if (StructProperty->Struct == TBaseStructure<FInstancedStruct>::Get())
|
|
|
|
+ {
|
|
|
|
+ // The property path is pointing into the instanced struct, it must be present.
|
|
|
|
+ // @TODO: We could potentially check the BaseStruct metadata in editor (for similar behavior as objects)
|
|
|
|
+ // Omitting for now to have matching functionality in editor and runtime.
|
|
|
|
+ const FInstancedStruct& InstancedStruct = *reinterpret_cast<const FInstancedStruct*>(CurrentAddress + Offset);
|
|
|
|
+ if (!InstancedStruct.IsValid())
|
|
|
|
+ {
|
|
|
|
+ if (OutError)
|
|
|
|
+ {
|
|
|
|
+ *OutError = FString::Printf(TEXT("Expecting valid instanced struct value at path '%s'."),
|
|
|
|
+ *ToString(Segment.GetIndex(), TEXT("<"), TEXT(">")));
|
|
|
|
+ }
|
|
|
|
+ OutIndirections.Reset();
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ const UScriptStruct* ValueInstanceStructType = InstancedStruct.GetScriptStruct();
|
|
|
|
+
|
|
|
|
+ CurrentAddress = InstancedStruct.GetMemory();
|
|
|
|
+ CurrentStruct = ValueInstanceStructType;
|
|
|
|
+ Indirection.InstanceStruct = CurrentStruct;
|
|
|
|
+ Indirection.AccessType = EEzAbilityPropertyAccessType::StructInstance;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ CurrentAddress = CurrentAddress + Offset;
|
|
|
|
+ CurrentStruct = StructProperty->Struct;
|
|
|
|
+ Indirection.AccessType = EEzAbilityPropertyAccessType::Offset;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ if (Segment->GetInstanceStruct())
|
|
|
|
+ {
|
|
|
|
+ CurrentStruct = Segment->GetInstanceStruct();
|
|
|
|
+ Indirection.InstanceStruct = CurrentStruct;
|
|
|
|
+ Indirection.AccessType = EEzAbilityPropertyAccessType::StructInstance;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ CurrentStruct = StructProperty->Struct;
|
|
|
|
+ Indirection.AccessType = EEzAbilityPropertyAccessType::Offset;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (const FObjectProperty* ObjectProperty = CastField<FObjectProperty>(Property))
|
|
|
|
+ {
|
|
|
|
+ if (bWithValue)
|
|
|
|
+ {
|
|
|
|
+ const UObject* Object = *reinterpret_cast<UObject* const*>(CurrentAddress + Offset);
|
|
|
|
+ CurrentAddress = reinterpret_cast<const uint8*>(Object);
|
|
|
|
+
|
|
|
|
+ // The property path is pointing into the object, if the object is present use it's specific type, otherwise use the type of the pointer.
|
|
|
|
+ if (Object)
|
|
|
|
+ {
|
|
|
|
+ CurrentStruct = Object->GetClass();
|
|
|
|
+ Indirection.InstanceStruct = CurrentStruct;
|
|
|
|
+ Indirection.AccessType = EEzAbilityPropertyAccessType::ObjectInstance;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ CurrentStruct = ObjectProperty->PropertyClass;
|
|
|
|
+ Indirection.AccessType = EEzAbilityPropertyAccessType::Object;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ if (Segment->GetInstanceStruct())
|
|
|
|
+ {
|
|
|
|
+ CurrentStruct = Segment->GetInstanceStruct();
|
|
|
|
+ Indirection.InstanceStruct = CurrentStruct;
|
|
|
|
+ Indirection.AccessType = EEzAbilityPropertyAccessType::ObjectInstance;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ CurrentStruct = ObjectProperty->PropertyClass;
|
|
|
|
+ Indirection.AccessType = EEzAbilityPropertyAccessType::Object;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ // Check to see if this is a simple weak object property (eg. not an array of weak objects).
|
|
|
|
+ else if (const FWeakObjectProperty* WeakObjectProperty = CastField<FWeakObjectProperty>(Property))
|
|
|
|
+ {
|
|
|
|
+ if (bWithValue)
|
|
|
|
+ {
|
|
|
|
+ const TWeakObjectPtr<UObject>& WeakObjectPtr = *reinterpret_cast<const TWeakObjectPtr<UObject>*>(CurrentAddress + Offset);
|
|
|
|
+ const UObject* Object = WeakObjectPtr.Get();
|
|
|
|
+ CurrentAddress = reinterpret_cast<const uint8*>(Object);
|
|
|
|
+
|
|
|
|
+ if (Object)
|
|
|
|
+ {
|
|
|
|
+ CurrentStruct = Object->GetClass();
|
|
|
|
+ Indirection.InstanceStruct = CurrentStruct;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ CurrentStruct = WeakObjectProperty->PropertyClass;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Indirection.AccessType = EEzAbilityPropertyAccessType::WeakObject;
|
|
|
|
+ }
|
|
|
|
+ // Check to see if this is a simple soft object property (eg. not an array of soft objects).
|
|
|
|
+ else if (const FSoftObjectProperty* SoftObjectProperty = CastField<FSoftObjectProperty>(Property))
|
|
|
|
+ {
|
|
|
|
+ if (bWithValue)
|
|
|
|
+ {
|
|
|
|
+ const FSoftObjectPtr& SoftObjectPtr = *reinterpret_cast<const FSoftObjectPtr*>(CurrentAddress + Offset);
|
|
|
|
+ const UObject* Object = SoftObjectPtr.Get();
|
|
|
|
+ CurrentAddress = reinterpret_cast<const uint8*>(Object);
|
|
|
|
+
|
|
|
|
+ if (Object)
|
|
|
|
+ {
|
|
|
|
+ CurrentStruct = Object->GetClass();
|
|
|
|
+ Indirection.InstanceStruct = CurrentStruct;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ CurrentStruct = SoftObjectProperty->PropertyClass;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Indirection.AccessType = EEzAbilityPropertyAccessType::SoftObject;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ // We get here if we encounter a property type that is not supported for indirection (e.g. Map or Set).
|
|
|
|
+ if (OutError)
|
|
|
|
+ {
|
|
|
|
+ *OutError = FString::Printf(TEXT("Unsupported property indirection type %s in path '%s'."),
|
|
|
|
+ *Property->GetCPPType(), *ToString(Segment.GetIndex(), TEXT("<"), TEXT(">")));
|
|
|
|
+ }
|
|
|
|
+ OutIndirections.Reset();
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+bool FEzAbilityPropertyPath::operator==(const FEzAbilityPropertyPath& RHS) const
|
|
|
|
+{
|
|
|
|
+#if WITH_EDITORONLY_DATA
|
|
|
|
+ if (StructID != RHS.StructID)
|
|
|
|
+ {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+#endif // WITH_EDITORONLY_DATA
|
|
|
|
+ if (Segments.Num() != RHS.Segments.Num())
|
|
|
|
+ {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for (TEnumerateRef<const FEzAbilityPropertyPathSegment> Segment : EnumerateRange(Segments))
|
|
|
|
+ {
|
|
|
|
+ if (*Segment != RHS.Segments[Segment.GetIndex()])
|
|
|
|
+ {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+//----------------------------------------------------------------//
|
|
|
|
+// FEzAbilityEditorPropertyPath
|
|
|
|
+//----------------------------------------------------------------//
|
|
|
|
+
|
|
|
|
+FString FEzAbilityEditorPropertyPath::ToString(const int32 HighlightedSegment, const TCHAR* HighlightPrefix, const TCHAR* HighlightPostfix) const
|
|
|
|
+{
|
|
|
|
+ FString Result;
|
|
|
|
+ for (int32 i = 0; i < Path.Num(); i++)
|
|
|
|
+ {
|
|
|
|
+ if (i > 0)
|
|
|
|
+ {
|
|
|
|
+ Result += TEXT(".");
|
|
|
|
+ }
|
|
|
|
+ if (i == HighlightedSegment && HighlightPrefix)
|
|
|
|
+ {
|
|
|
|
+ Result += HighlightPrefix;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Result += Path[i];
|
|
|
|
+
|
|
|
|
+ if (i == HighlightedSegment && HighlightPostfix)
|
|
|
|
+ {
|
|
|
|
+ Result += HighlightPostfix;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return Result;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
|
|
+bool FEzAbilityEditorPropertyPath::operator==(const FEzAbilityEditorPropertyPath& RHS) const
|
|
|
|
+{
|
|
|
|
+ if (StructID != RHS.StructID)
|
|
|
|
+ {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (Path.Num() != RHS.Path.Num())
|
|
|
|
+ {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for (int32 i = 0; i < Path.Num(); i++)
|
|
|
|
+ {
|
|
|
|
+ if (Path[i] != RHS.Path[i])
|
|
|
|
+ {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+}
|
|
|
|
+PRAGMA_ENABLE_DEPRECATION_WARNINGS
|