// Copyright Epic Games, Inc. All Rights Reserved. #include "PropertyPathHelpers.h" #include "UObject/UnrealType.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(PropertyPathHelpers) /** Internal helper functions */ namespace PropertyPathHelpersInternal { template static bool IteratePropertyPathRecursive(UStruct* InStruct, ContainerType* InContainer, int32 SegmentIndex, const FCachedPropertyPath& InPropertyPath, FPropertyPathResolver& InResolver) { const FPropertyPathSegment& Segment = InPropertyPath.GetSegment(SegmentIndex); const int32 ArrayIndex = Segment.GetArrayIndex() == INDEX_NONE ? 0 : Segment.GetArrayIndex(); // Reset cached address usage flag at the path root. This will be reset later in the recursion if conditions are not met in the path. if(SegmentIndex == 0) { InPropertyPath.SetCachedContainer(InContainer); InPropertyPath.SetCanSafelyUsedCachedAddress(true); } // Obtain the property info from the given structure definition FFieldVariant Field = Segment.Resolve(InStruct); if (Field.IsValid()) { const bool bFinalSegment = SegmentIndex == (InPropertyPath.GetNumSegments() - 1); if ( FProperty* Property = Field.Get() ) { if (bFinalSegment) { return InResolver.Resolve(static_cast(InContainer), InPropertyPath); } else { // Check first to see if this is a simple object (eg. not an array of objects) if ( FObjectProperty* ObjectProperty = CastField(Property) ) { // Object boundary that can change, so we cant use the cached address safely InPropertyPath.SetCanSafelyUsedCachedAddress(false); // If it's an object we need to get the value of the property in the container first before we // can continue, if the object is null we safely stop processing the chain of properties. if ( UObject* CurrentObject = ObjectProperty->GetPropertyValue_InContainer(InContainer, ArrayIndex) ) { InPropertyPath.SetCachedLastContainer(CurrentObject, SegmentIndex); return IteratePropertyPathRecursive(CurrentObject->GetClass(), CurrentObject, SegmentIndex + 1, InPropertyPath, InResolver); } } // Check to see if this is a simple weak object property (eg. not an array of weak objects). if ( FWeakObjectProperty* WeakObjectProperty = CastField(Property) ) { // Object boundary that can change, so we cant use the cached address safely InPropertyPath.SetCanSafelyUsedCachedAddress(false); FWeakObjectPtr WeakObject = WeakObjectProperty->GetPropertyValue_InContainer(InContainer, ArrayIndex); // If it's an object we need to get the value of the property in the container first before we // can continue, if the object is null we safely stop processing the chain of properties. if ( UObject* CurrentObject = WeakObject.Get() ) { InPropertyPath.SetCachedLastContainer(CurrentObject, SegmentIndex); return IteratePropertyPathRecursive(CurrentObject->GetClass(), CurrentObject, SegmentIndex + 1, InPropertyPath, InResolver); } } // Check to see if this is a simple soft object property (eg. not an array of soft objects). else if ( FSoftObjectProperty* SoftObjectProperty = CastField(Property) ) { // Object boundary that can change, so we cant use the cached address safely InPropertyPath.SetCanSafelyUsedCachedAddress(false); FSoftObjectPtr SoftObject = SoftObjectProperty->GetPropertyValue_InContainer(InContainer, ArrayIndex); // If it's an object we need to get the value of the property in the container first before we // can continue, if the object is null we safely stop processing the chain of properties. if ( UObject* CurrentObject = SoftObject.Get() ) { InPropertyPath.SetCachedLastContainer(CurrentObject, SegmentIndex); return IteratePropertyPathRecursive(CurrentObject->GetClass(), CurrentObject, SegmentIndex + 1, InPropertyPath, InResolver); } } // Check to see if this is a simple structure (eg. not an array of structures) else if ( FStructProperty* StructProp = CastField(Property) ) { // Recursively call back into this function with the structure property and container value return IteratePropertyPathRecursive(StructProp->Struct, StructProp->ContainerPtrToValuePtr(InContainer, ArrayIndex), SegmentIndex + 1, InPropertyPath, InResolver); } else if ( FArrayProperty* ArrayProp = CastField(Property) ) { // Dynamic array boundary that can change, so we cant use the cached address safely InPropertyPath.SetCanSafelyUsedCachedAddress(false); // It is an array, now check to see if this is an array of structures if ( FStructProperty* ArrayOfStructsProp = CastField(ArrayProp->Inner) ) { FScriptArrayHelper_InContainer ArrayHelper(ArrayProp, InContainer); if ( ArrayHelper.IsValidIndex(ArrayIndex) ) { // Recursively call back into this function with the array element and container value return IteratePropertyPathRecursive(ArrayOfStructsProp->Struct, static_cast(ArrayHelper.GetRawPtr(ArrayIndex)), SegmentIndex + 1, InPropertyPath, InResolver); } } // if it's not an array of structs, maybe it's an array of classes //else if ( FObjectProperty* ObjectProperty = CastField(ArrayProp->Inner) ) { //TODO Add support for arrays of objects. } } else if( FSetProperty* SetProperty = CastField(Property) ) { // TODO: we dont support set properties yet } else if( FMapProperty* MapProperty = CastField(Property) ) { // TODO: we dont support map properties yet } } } else { // If it's the final segment, use the resolver to get the value. if (bFinalSegment) { return InResolver.Resolve(static_cast(InContainer), InPropertyPath); } else { // If it's not the final segment, but still a function, we're going to treat it as an Object* getter. // in the hopes that it leads to another object that we can resolve the next segment on. These // getter functions must be very simple. UObject* CurrentObject = nullptr; FProperty* GetterProperty = nullptr; FInternalGetterResolver GetterResolver(CurrentObject, GetterProperty); FCachedPropertyPath TempPath(Segment); if (GetterResolver.Resolve(InContainer, TempPath)) { if (CurrentObject) { InPropertyPath.SetCanSafelyUsedCachedAddress(false); InPropertyPath.SetCachedLastContainer(CurrentObject, SegmentIndex); return IteratePropertyPathRecursive(CurrentObject->GetClass(), CurrentObject, SegmentIndex + 1, InPropertyPath, InResolver); } } } } } return false; } /** Non-UObject helper struct for GetPropertyValueAsString function calls */ template struct FCallGetterFunctionAsStringHelper { static bool CallGetterFunction(ContainerType* InContainer, UFunction* InFunction, FString& OutValue) { // Cant call UFunctions on non-UObject containers return false; } }; /** UObject partial specialization of FCallGetterFunctionHelper */ template<> struct FCallGetterFunctionAsStringHelper { static bool CallGetterFunction(UObject* InContainer, UFunction* InFunction, FString& OutValue) { // We only support calling functions that return a single value and take no parameters. if ( InFunction->NumParms == 1 ) { // Verify there's a return property. if ( FProperty* ReturnProperty = InFunction->GetReturnProperty() ) { if ( !InContainer->IsUnreachable() ) { // Create and init a buffer for the function to write to TArray TempBuffer; TempBuffer.AddUninitialized(ReturnProperty->GetElementSize()); ReturnProperty->InitializeValue(TempBuffer.GetData()); InContainer->ProcessEvent(InFunction, TempBuffer.GetData()); ReturnProperty->ExportTextItem_Direct(OutValue, TempBuffer.GetData(), nullptr, nullptr, 0); return true; } } } return false; } }; template static bool GetPropertyValueAsString(ContainerType* InContainer, const FCachedPropertyPath& InPropertyPath, FProperty*& OutProperty, FString& OutValue) { const FPropertyPathSegment& LastSegment = InPropertyPath.GetLastSegment(); int32 ArrayIndex = LastSegment.GetArrayIndex(); FFieldVariant Field = LastSegment.GetField(); // We're on the final property in the path, it may be an array property, so check that first. if ( FArrayProperty* ArrayProp = Field.Get() ) { // if it's an array property, we need to see if we parsed an index as part of the segment // as a user may have baked the index directly into the property path. if(ArrayIndex != INDEX_NONE) { // Property is an array property, so make sure we have a valid index specified FScriptArrayHelper_InContainer ArrayHelper(ArrayProp, InContainer); if ( ArrayHelper.IsValidIndex(ArrayIndex) ) { OutProperty = ArrayProp->Inner; OutProperty->ExportTextItem_Direct(OutValue, static_cast(ArrayHelper.GetRawPtr(ArrayIndex)), nullptr, nullptr, 0); return true; } } else { // No index, so assume we want the array property itself if ( !!ArrayProp->ContainerPtrToValuePtr(InContainer) ) { OutProperty = ArrayProp; OutProperty->ExportTextItem_InContainer(OutValue, InContainer, nullptr, nullptr, 0); return true; } } } else if(UFunction* Function = Field.Get()) { return FCallGetterFunctionAsStringHelper::CallGetterFunction(InContainer, Function, OutValue); } else if(FProperty* Property = Field.Get()) { ArrayIndex = ArrayIndex == INDEX_NONE ? 0 : ArrayIndex; if( ArrayIndex < Property->ArrayDim ) { if ( void* ValuePtr = Property->ContainerPtrToValuePtr(InContainer, ArrayIndex) ) { OutProperty = Property; OutProperty->ExportTextItem_Direct(OutValue, ValuePtr, nullptr, nullptr, 0); return true; } } } return false; } /** Non-UObject helper struct for SetPropertyValueFromString function calls */ template struct FCallSetterFunctionFromStringHelper { static bool CallSetterFunction(ContainerType* InContainer, UFunction* InFunction, const FString& InValue) { // Cant call UFunctions on non-UObject containers return false; } }; /** UObject partial specialization of FCallSetterFunctionFromStringHelper */ template<> struct FCallSetterFunctionFromStringHelper { static bool CallSetterFunction(UObject* InContainer, UFunction* InFunction, const FString& InValue) { // We only support calling functions that take a single value and return no parameters if ( InFunction->NumParms == 1 && InFunction->GetReturnProperty() == nullptr ) { // Verify there's a single param if ( FProperty* ParamProperty = PropertyPathHelpersInternal::GetFirstParamProperty(InFunction) ) { if ( !InContainer->IsUnreachable() ) { // Create and init a buffer for the function to read from TArray TempBuffer; TempBuffer.AddUninitialized(ParamProperty->GetElementSize()); ParamProperty->InitializeValue(TempBuffer.GetData()); ParamProperty->ImportText_Direct(*InValue, TempBuffer.GetData(), nullptr, 0); InContainer->ProcessEvent(InFunction, TempBuffer.GetData()); return true; } } } return false; } }; template static bool SetPropertyValueFromString(ContainerType* InContainer, const FCachedPropertyPath& InPropertyPath, const FString& InValue) { const FPropertyPathSegment& LastSegment = InPropertyPath.GetLastSegment(); int32 ArrayIndex = LastSegment.GetArrayIndex(); FFieldVariant Field = LastSegment.GetField(); // We're on the final property in the path, it may be an array property, so check that first. if ( FArrayProperty* ArrayProp = Field.Get() ) { // if it's an array property, we need to see if we parsed an index as part of the segment // as a user may have baked the index directly into the property path. if(ArrayIndex != INDEX_NONE) { // Property is an array property, so make sure we have a valid index specified FScriptArrayHelper_InContainer ArrayHelper(ArrayProp, InContainer); if ( ArrayHelper.IsValidIndex(ArrayIndex) ) { ArrayProp->Inner->ImportText_Direct(*InValue, static_cast(ArrayHelper.GetRawPtr(ArrayIndex)), nullptr, 0); return true; } } else { // No index, so assume we want the array property itself if ( !!ArrayProp->ContainerPtrToValuePtr(InContainer) ) { ArrayProp->ImportText_InContainer(*InValue, InContainer, nullptr, 0); return true; } } } else if(UFunction* Function = Field.Get()) { return FCallSetterFunctionFromStringHelper::CallSetterFunction(InContainer, Function, InValue); } else if(FProperty* Property = Field.Get()) { ArrayIndex = ArrayIndex == INDEX_NONE ? 0 : ArrayIndex; if(ArrayIndex < Property->ArrayDim) { if ( void* ValuePtr = Property->ContainerPtrToValuePtr(InContainer, ArrayIndex) ) { Property->ImportText_Direct(*InValue, ValuePtr, nullptr, 0); return true; } } } return false; } template static bool PerformArrayOperation(ContainerType* InContainer, const FCachedPropertyPath& InPropertyPath, TFunctionRef InOperation) { const FPropertyPathSegment& LastSegment = InPropertyPath.GetLastSegment(); int32 ArrayIndex = LastSegment.GetArrayIndex(); // We only support array properties right now if ( FArrayProperty* ArrayProp = LastSegment.GetField().Get() ) { FScriptArrayHelper_InContainer ArrayHelper(ArrayProp, InContainer); return InOperation(ArrayHelper, ArrayIndex); } return false; } /** Caches resolve addresses in property paths for later use */ template static bool CacheResolveAddress(ContainerType* InContainer, const FCachedPropertyPath& InPropertyPath) { const FPropertyPathSegment& LastSegment = InPropertyPath.GetLastSegment(); int32 ArrayIndex = LastSegment.GetArrayIndex(); FFieldVariant Field = LastSegment.GetField(); // We're on the final property in the path, it may be an array property, so check that first. if ( FArrayProperty* ArrayProp = Field.Get() ) { // if it's an array property, we need to see if we parsed an index as part of the segment // as a user may have baked the index directly into the property path. if(ArrayIndex != INDEX_NONE) { // Property is an array property, so make sure we have a valid index specified FScriptArrayHelper_InContainer ArrayHelper(ArrayProp, InContainer); if ( ArrayHelper.IsValidIndex(ArrayIndex) ) { if(void* Address = static_cast(ArrayHelper.GetRawPtr(ArrayIndex))) { InPropertyPath.ResolveLeaf(Address); return true; } } } else { // No index, so assume we want the array property itself if(void* Address = ArrayProp->ContainerPtrToValuePtr(InContainer)) { InPropertyPath.ResolveLeaf(Address); return true; } } } else if(UFunction* Function = Field.Get()) { InPropertyPath.ResolveLeaf(Function); return true; } else if(FProperty* Property = Field.Get()) { ArrayIndex = ArrayIndex == INDEX_NONE ? 0 : ArrayIndex; if ( ArrayIndex < Property->ArrayDim ) { if(void* Address = Property->ContainerPtrToValuePtr(InContainer, ArrayIndex)) { InPropertyPath.ResolveLeaf(Address); return true; } } } return false; } /** helper function. Copy the values between two resolved paths. It is assumed that CanCopyProperties has been previously called and returned true. */ static bool CopyResolvedPaths(const FCachedPropertyPath& InDestPropertyPath, const FCachedPropertyPath& InSrcPropertyPath) { // check we have valid addresses/functions that match if(InDestPropertyPath.GetCachedFunction() != nullptr && InSrcPropertyPath.GetCachedFunction() != nullptr) { // copying via functions is not supported yet return false; } else if(InDestPropertyPath.GetCachedAddress() != nullptr && InSrcPropertyPath.GetCachedAddress() != nullptr) { const FPropertyPathSegment& DestLastSegment = InDestPropertyPath.GetLastSegment(); FProperty* DestProperty = CastFieldChecked(DestLastSegment.GetField().ToField()); FArrayProperty* DestArrayProp = CastField(DestProperty); if ( DestArrayProp && DestLastSegment.GetArrayIndex() != INDEX_NONE ) { DestArrayProp->Inner->CopySingleValue(InDestPropertyPath.GetCachedAddress(), InSrcPropertyPath.GetCachedAddress()); } else if(DestProperty->ArrayDim > 1) { DestProperty->CopyCompleteValue(InDestPropertyPath.GetCachedAddress(), InSrcPropertyPath.GetCachedAddress()); } else if(FBoolProperty* DestBoolProperty = CastField(DestProperty)) { FBoolProperty* SrcBoolProperty = InSrcPropertyPath.GetLastSegment().GetField().Get(); const bool bValue = SrcBoolProperty->GetPropertyValue(InSrcPropertyPath.GetCachedAddress()); DestBoolProperty->SetPropertyValue(InDestPropertyPath.GetCachedAddress(), bValue); } else { DestProperty->CopySingleValue(InDestPropertyPath.GetCachedAddress(), InSrcPropertyPath.GetCachedAddress()); } return true; } return false; } /** Checks if two fully resolved paths can have their values copied between them. Checks the leaf property class to see if they match */ static bool CanCopyProperties(const FCachedPropertyPath& InDestPropertyPath, const FCachedPropertyPath& InSrcPropertyPath) { const FPropertyPathSegment& DestLastSegment = InDestPropertyPath.GetLastSegment(); const FPropertyPathSegment& SrcLastSegment = InSrcPropertyPath.GetLastSegment(); FProperty* DestProperty = DestLastSegment.GetField().Get(); FProperty* SrcProperty = SrcLastSegment.GetField().Get(); if(SrcProperty && DestProperty) { FArrayProperty* DestArrayProperty = CastField(DestProperty); FArrayProperty* SrcArrayProperty = CastField(SrcProperty); // If we have a valid index and an array property then we should use the inner property FFieldClass* DestClass = (DestArrayProperty != nullptr && DestLastSegment.GetArrayIndex() != INDEX_NONE) ? DestArrayProperty->Inner->GetClass() : DestProperty->GetClass(); FFieldClass* SrcClass = (SrcArrayProperty != nullptr && SrcLastSegment.GetArrayIndex() != INDEX_NONE) ? SrcArrayProperty->Inner->GetClass() : SrcProperty->GetClass(); return DestClass == SrcClass && SrcProperty->ArrayDim == DestProperty->ArrayDim; } return false; } bool ResolvePropertyPath(UObject* InContainer, const FString& InPropertyPath, FPropertyPathResolver& InResolver) { if (InContainer) { FCachedPropertyPath InternalPropertyPath(InPropertyPath); return IteratePropertyPathRecursive(InContainer->GetClass(), InContainer, 0, InternalPropertyPath, InResolver); } return false; } bool ResolvePropertyPath(UObject* InContainer, const FCachedPropertyPath& InPropertyPath, FPropertyPathResolver& InResolver) { if (InContainer) { return IteratePropertyPathRecursive(InContainer->GetClass(), InContainer, 0, InPropertyPath, InResolver); } return false; } bool ResolvePropertyPath(void* InContainer, UStruct* InStruct, const FString& InPropertyPath, FPropertyPathResolver& InResolver) { FCachedPropertyPath InternalPropertyPath(InPropertyPath); return IteratePropertyPathRecursive(InStruct, InContainer, 0, InternalPropertyPath, InResolver); } bool ResolvePropertyPath(void* InContainer, UStruct* InStruct, const FCachedPropertyPath& InPropertyPath, FPropertyPathResolver& InResolver) { return IteratePropertyPathRecursive(InStruct, InContainer, 0, InPropertyPath, InResolver); } FProperty* GetFirstParamProperty(UFunction* InFunction) { for( TFieldIterator It(InFunction); It && (It->PropertyFlags & CPF_Parm); ++It ) { if( (It->PropertyFlags & CPF_ReturnParm) == 0 ) { return *It; } } return nullptr; } void CallParentSetters(const FCachedPropertyPath& InPropertyPath) { int32 LastContainerInPathIndex = InPropertyPath.GetCachedLastContainerInPathIndex(); int32 IndexAfterCachedLastContainer = InPropertyPath.GetCachedLastContainerInPathIndex() + 1; void* ObjectContainerPtr = LastContainerInPathIndex == INDEX_NONE ? InPropertyPath.GetCachedContainer() : InPropertyPath.GetCachedLastContainerInPath(); // Compilers will warn if Alloca is used inside a loop, so break Alloca logic out of the loop int32 ParentSegmentIndex = INDEX_NONE; // Call the topmost setter on the last UObject in path const int32 NumSegments = InPropertyPath.GetNumSegments(); for (int32 Index = IndexAfterCachedLastContainer; Index < NumSegments; Index++) { const FPropertyPathSegment& ParentSegment = InPropertyPath.GetSegment(Index); int32 ParentArrayIndex = ParentSegment.GetArrayIndex(); FProperty* ParentProperty = CastFieldChecked(ParentSegment.GetField().ToField()); ParentArrayIndex = ParentArrayIndex == INDEX_NONE ? 0 : ParentArrayIndex; if (ParentProperty->HasSetter() && ParentArrayIndex < ParentProperty->ArrayDim) { ParentSegmentIndex = Index; break; } } // Note: We check for HasSetter & ParentArrayIndex valid above if (ParentSegmentIndex != INDEX_NONE) { const FPropertyPathSegment& ParentSegment = InPropertyPath.GetSegment(ParentSegmentIndex); int32 ParentArrayIndex = ParentSegment.GetArrayIndex(); FProperty* ParentProperty = CastFieldChecked(ParentSegment.GetField().ToField()); ParentArrayIndex = ParentArrayIndex == INDEX_NONE ? 0 : ParentArrayIndex; // We want to call the setter with the current value, so just get the pointer to current value via container if (void* ParentAddress = ParentProperty->ContainerPtrToValuePtr(ObjectContainerPtr, ParentArrayIndex)) { if (ParentProperty->HasGetter()) { // Call getter if it has one, getter SHOULD be pure and thus won't cause behavioral changes. // But this is a read on ParentAddress so we call getter for now int32 Size = ParentSegment.GetStruct()->GetPropertiesSize(); int32 Alignment = ParentSegment.GetStruct()->GetMinAlignment(); uint8* Temp = (uint8*)FMemory_Alloca_Aligned(Size, Alignment); FMemory::Memzero(Temp, Size); ParentProperty->InitializeValue(Temp); ParentProperty->CallGetter(ObjectContainerPtr, Temp); ParentProperty->CallSetter(ObjectContainerPtr, Temp); ParentProperty->DestroyValue(Temp); } else { ParentProperty->CallSetter(ObjectContainerPtr, ParentAddress); } return; } } } void CallParentGetters(void* OutValue, const FCachedPropertyPath& InPropertyPath, const void* InPropertyAddress) { int32 LastContainerInPathIndex = InPropertyPath.GetCachedLastContainerInPathIndex(); int32 IndexAfterCachedLastContainer = InPropertyPath.GetCachedLastContainerInPathIndex() + 1; void* ObjectContainerPtr = LastContainerInPathIndex == INDEX_NONE ? InPropertyPath.GetCachedContainer() : InPropertyPath.GetCachedLastContainerInPath(); // Helper to get value regardless of property address / if parent getter is called auto GetValueFromProperty = [](void* OutValue, const FCachedPropertyPath& InPropertyPath, const void* PropertyAddress) { FProperty* Property = CastFieldChecked(InPropertyPath.GetLastSegment().GetField().ToField()); if (FBoolProperty* BoolProperty = CastField(Property)) { *static_cast(OutValue) = BoolProperty->GetPropertyValue(PropertyAddress); } else if (ensure(Property)) { Property->CopySingleValue(OutValue, PropertyAddress); } }; // Compilers will warn if Alloca is used inside a loop, so break Alloca logic out of the loop int32 ParentSegmentIndex = INDEX_NONE; // Call the topmost getter on the last UObject in path const int32 NumSegments = InPropertyPath.GetNumSegments(); for (int32 Index = IndexAfterCachedLastContainer; Index < NumSegments; Index++) { const FPropertyPathSegment& ParentSegment = InPropertyPath.GetSegment(Index); int32 ParentArrayIndex = ParentSegment.GetArrayIndex(); FProperty* ParentProperty = CastFieldChecked(ParentSegment.GetField().ToField()); ParentArrayIndex = ParentArrayIndex == INDEX_NONE ? 0 : ParentArrayIndex; if (ParentProperty->HasGetter() && ParentArrayIndex < ParentProperty->ArrayDim) { ParentSegmentIndex = Index; break; } } // Note: We check for HasGetter & ParentArrayIndex valid above if (ParentSegmentIndex != INDEX_NONE) { const FPropertyPathSegment& ParentSegment = InPropertyPath.GetSegment(ParentSegmentIndex); int32 ParentArrayIndex = ParentSegment.GetArrayIndex(); FProperty* ParentProperty = CastFieldChecked(ParentSegment.GetField().ToField()); ParentArrayIndex = ParentArrayIndex == INDEX_NONE ? 0 : ParentArrayIndex; // We want to call the Getter with the current value, so just get the pointer to current value via container void* ParentAddress = ParentProperty->ContainerPtrToValuePtr(ObjectContainerPtr, ParentArrayIndex); if (ensure(ParentAddress)) { int32 Size = ParentSegment.GetStruct()->GetPropertiesSize(); int32 Alignment = ParentSegment.GetStruct()->GetMinAlignment(); uint8* Temp = (uint8*)FMemory_Alloca_Aligned(Size, Alignment); FMemory::Memzero(Temp, Size); ParentProperty->InitializeValue(Temp); ParentProperty->CallGetter(ObjectContainerPtr, Temp); // We resolved the property address earlier & it's containing parent, use this for relative-offset for Temp uint8* TempPropertyAddress = Temp + ((uint8*)InPropertyAddress - (uint8*)ParentAddress); GetValueFromProperty(OutValue, InPropertyPath, TempPropertyAddress); ParentProperty->DestroyValue(Temp); } return; } GetValueFromProperty(OutValue, InPropertyPath, InPropertyAddress); } } FPropertyPathSegment::FPropertyPathSegment() : Name(NAME_None) , ArrayIndex(INDEX_NONE) , Struct(nullptr) , Field() { } FPropertyPathSegment::FPropertyPathSegment(int32 InCount, const TCHAR* InString) : ArrayIndex(INDEX_NONE) , Struct(nullptr) , Field() { const TCHAR* PropertyName = nullptr; int32 PropertyNameLength = 0; PropertyPathHelpers::FindFieldNameAndArrayIndex(InCount, InString, PropertyNameLength, &PropertyName, ArrayIndex); ensure(PropertyName != nullptr); FString PropertyNameString = FString::ConstructFromPtrSize(PropertyName, PropertyNameLength); Name = FName(*PropertyNameString, FNAME_Find); } void FPropertyPathSegment::PostSerialize(const FArchive& Ar) { if (Struct) { // if the struct has been serialized then we're loading a CDO and we need to re-cache the field pointer UStruct* ResolveStruct = Struct; Struct = nullptr; Resolve(ResolveStruct); } } FPropertyPathSegment FPropertyPathSegment::MakeUnresolvedCopy(const FPropertyPathSegment& ToCopy) { FPropertyPathSegment Segment; Segment.Name = ToCopy.Name; Segment.ArrayIndex = ToCopy.ArrayIndex; return Segment; } FFieldVariant FPropertyPathSegment::Resolve(UStruct* InStruct) const { if ( InStruct ) { // only perform the find field work if the structure where this property would resolve to // has changed. If it hasn't changed, the just return the FProperty we found last time. if ( InStruct != Struct ) { Struct = InStruct; Field = FindUFieldOrFProperty(InStruct, Name); } return Field; } return FFieldVariant(); } FName FPropertyPathSegment::GetName() const { return Name; } int32 FPropertyPathSegment::GetArrayIndex() const { return ArrayIndex; } FFieldVariant FPropertyPathSegment::GetField() const { return Field; } UStruct* FPropertyPathSegment::GetStruct() const { return Struct; } FCachedPropertyPath::FCachedPropertyPath() : CachedAddress(nullptr) , CachedFunction(nullptr) , CachedContainer(nullptr) , CachedLastContainerInPath(nullptr) , CachedLastContainerInPathIndex(INDEX_NONE) , bCanSafelyUsedCachedAddress(false) { } FCachedPropertyPath::FCachedPropertyPath(const FString& Path) : CachedAddress(nullptr) , CachedFunction(nullptr) , CachedContainer(nullptr) , CachedLastContainerInPath(nullptr) , CachedLastContainerInPathIndex(INDEX_NONE) , bCanSafelyUsedCachedAddress(false) { MakeFromString(Path); } FCachedPropertyPath::FCachedPropertyPath(const TArray& PathSegments) : CachedAddress(nullptr) , CachedFunction(nullptr) , CachedContainer(nullptr) , CachedLastContainerInPath(nullptr) , CachedLastContainerInPathIndex(INDEX_NONE) , bCanSafelyUsedCachedAddress(false) { for (const FString& Segment : PathSegments) { Segments.Add(FPropertyPathSegment(Segment.Len(), *Segment)); } } FCachedPropertyPath::FCachedPropertyPath(const FPropertyPathSegment& Segment) : CachedAddress(nullptr) , CachedFunction(nullptr) , CachedContainer(nullptr) , CachedLastContainerInPath(nullptr) , CachedLastContainerInPathIndex(INDEX_NONE) , bCanSafelyUsedCachedAddress(false) { Segments.Add(Segment); } FCachedPropertyPath::~FCachedPropertyPath() = default; void FCachedPropertyPath::MakeFromString(const FString& InPropertyPath) { const TCHAR Delim = TEXT('.'); const TCHAR* Path = *InPropertyPath; int32 Length = InPropertyPath.Len(); int32 Offset = 0; int32 Start = 0; while(Offset < Length) { if (Path[Offset] == Delim) { Segments.Add(FPropertyPathSegment(Offset - Start, &Path[Start])); Start = ++Offset; } Offset++; } Segments.Add(FPropertyPathSegment(Length - Start, &Path[Start])); } FCachedPropertyPath FCachedPropertyPath::MakeUnresolvedCopy(const FCachedPropertyPath& ToCopy) { FCachedPropertyPath Path; for (const FPropertyPathSegment& Segment : ToCopy.Segments) { Path.Segments.Add(FPropertyPathSegment::MakeUnresolvedCopy(Segment)); } return Path; } int32 FCachedPropertyPath::GetNumSegments() const { return Segments.Num(); } const FPropertyPathSegment& FCachedPropertyPath::GetSegment(int32 InSegmentIndex) const { return Segments[InSegmentIndex]; } const FPropertyPathSegment& FCachedPropertyPath::GetLastSegment() const { return Segments.Last(); } /** Helper for cache/copy resolver */ struct FInternalCacheResolver : public PropertyPathHelpersInternal::TPropertyPathResolver { template bool Resolve_Impl(ContainerType* InContainer, const FCachedPropertyPath& InPropertyPath) { return PropertyPathHelpersInternal::CacheResolveAddress(InContainer, InPropertyPath); } }; bool FCachedPropertyPath::Resolve(UObject* InContainer) const { FInternalCacheResolver Resolver; return PropertyPathHelpersInternal::ResolvePropertyPath(InContainer, *this, Resolver); } void FCachedPropertyPath::ResolveLeaf(void* InAddress) const { check(CachedFunction == nullptr); CachedAddress = InAddress; } void FCachedPropertyPath::ResolveLeaf(UFunction* InFunction) const { check(CachedAddress == nullptr); CachedFunction = InFunction; } void FCachedPropertyPath::SetCanSafelyUsedCachedAddress(bool bInCanSafelyUsedCachedAddress) const { bCanSafelyUsedCachedAddress = bInCanSafelyUsedCachedAddress; } void FCachedPropertyPath::SetCachedLastContainer(void* InContainer, int32 InIndex) const { SetCanSafelyUsedCachedAddress(false); CachedLastContainerInPath = InContainer; CachedLastContainerInPathIndex = InIndex; } void* FCachedPropertyPath::GetCachedLastContainerInPath() const { return CachedLastContainerInPath; } int32 FCachedPropertyPath::GetCachedLastContainerInPathIndex() const { return CachedLastContainerInPathIndex; } bool FCachedPropertyPath::IsResolved() const { return (CachedFunction != nullptr || CachedAddress != nullptr); } bool FCachedPropertyPath::IsFullyResolved() const { bool bCachedContainer = CachedContainer != nullptr; return bCanSafelyUsedCachedAddress && bCachedContainer && IsResolved(); } void* FCachedPropertyPath::GetCachedAddress() const { // @TODO: DarenC - Should we? Maybe add a GetCachedAddressUnsafe method. // check(bCanSafelyUsedCachedAddress); return CachedAddress; } UFunction* FCachedPropertyPath::GetCachedFunction() const { return CachedFunction; } FPropertyChangedEvent FCachedPropertyPath::ToPropertyChangedEvent(EPropertyChangeType::Type InChangeType) const { // Path must be resolved check(IsResolved()); // Note: path must not be a to a UFunction FPropertyChangedEvent PropertyChangedEvent(CastFieldChecked(GetLastSegment().GetField().ToField()), InChangeType); // Set a containing 'struct' if we need to if(Segments.Num() > 1) { PropertyChangedEvent.SetActiveMemberProperty(CastFieldChecked(Segments[Segments.Num() - 2].GetField().ToField())); } return PropertyChangedEvent; } void FCachedPropertyPath::ToEditPropertyChain(FEditPropertyChain& OutPropertyChain) const { // Path must be resolved check(IsResolved()); for (const FPropertyPathSegment& Segment : Segments) { // Note: path must not be a to a UFunction OutPropertyChain.AddTail(CastFieldChecked(Segment.GetField().ToField())); } OutPropertyChain.SetActivePropertyNode(CastFieldChecked(GetLastSegment().GetField().ToField())); if (Segments.Num() > 1) { OutPropertyChain.SetActiveMemberPropertyNode(CastFieldChecked(Segments[0].GetField().ToField())); } } FString FCachedPropertyPath::ToString() const { FString OutString; for (int32 SegmentIndex = 0; SegmentIndex < Segments.Num(); ++SegmentIndex) { const FPropertyPathSegment& Segment = Segments[SegmentIndex]; // Add property name OutString += Segment.GetName().ToString(); // Add array index if(Segment.GetArrayIndex() != INDEX_NONE) { OutString += FString::Printf(TEXT("[%d]"), Segment.GetArrayIndex()); } // Add separator if(SegmentIndex < Segments.Num() - 1) { OutString += TEXT("."); } } return OutString; } bool FCachedPropertyPath::operator==(const FString& Other) const { return Equals(Other); } bool FCachedPropertyPath::Equals(const FString& Other) const { return ToString() == Other; } void* FCachedPropertyPath::GetCachedContainer() const { return CachedContainer; } void FCachedPropertyPath::SetCachedContainer(void* InContainer) const { CachedContainer = InContainer; } void FCachedPropertyPath::RemoveFromEnd(int32 InNumSegments) { if(InNumSegments <= Segments.Num()) { Segments.RemoveAt(Segments.Num() - 1, InNumSegments); // Clear cached data - the path is not the same as the previous Resolve() call for (const FPropertyPathSegment& Segment : Segments) { Segment.Struct = nullptr; Segment.Field = FFieldVariant(); } CachedAddress = nullptr; CachedFunction = nullptr; CachedContainer = nullptr; CachedLastContainerInPath = nullptr; CachedLastContainerInPathIndex = INDEX_NONE; bCanSafelyUsedCachedAddress = false; } } void FCachedPropertyPath::RemoveFromStart(int32 InNumSegments) { if(InNumSegments <= Segments.Num()) { Segments.RemoveAt(0, InNumSegments); // Clear cached data - the path is not the same as the previous Resolve() call for (const FPropertyPathSegment& Segment : Segments) { Segment.Struct = nullptr; Segment.Field = FFieldVariant(); } CachedAddress = nullptr; CachedFunction = nullptr; CachedContainer = nullptr; CachedLastContainerInPath = nullptr; CachedLastContainerInPathIndex = INDEX_NONE; bCanSafelyUsedCachedAddress = false; } } FProperty* FCachedPropertyPath::GetFProperty() const { return CastField(GetLastSegment().GetField().ToField()); } namespace PropertyPathHelpers { void FindFieldNameAndArrayIndex(int32 InCount, const TCHAR* InString, int32& OutCount, const TCHAR** OutPropertyName, int32& OutArrayIndex) { *OutPropertyName = InString; // Parse the property name and (optional) array index OutArrayIndex = INDEX_NONE; OutCount = InCount; int32 Offset = 1; const TCHAR Bracket = '['; while ( Offset < InCount ) { if (InString[Offset] == Bracket) { OutCount = Offset; // here we need to copy - since we need a section of the string only FString ArrayIndexString = FString::ConstructFromPtrSize(&InString[Offset + 1], InCount - Offset - 2); OutArrayIndex = FCString::Atoi(*ArrayIndexString); break; } Offset++; } } bool GetPropertyValueAsString(UObject* InContainer, const FString& InPropertyPath, FString& OutValue) { FProperty* Property; return GetPropertyValueAsString(InContainer, InPropertyPath, OutValue, Property); } /** Helper for string-based getters */ struct FInternalStringGetterResolver : public PropertyPathHelpersInternal::TPropertyPathResolver { FInternalStringGetterResolver(FString& InOutValue, FProperty*& InOutProperty) : Value(InOutValue) , Property(InOutProperty) { } template bool Resolve_Impl(ContainerType* InContainer, const FCachedPropertyPath& InPropertyPath) { return PropertyPathHelpersInternal::GetPropertyValueAsString(InContainer, InPropertyPath, Property, Value); } FString& Value; FProperty*& Property; }; bool GetPropertyValueAsString(UObject* InContainer, const FString& InPropertyPath, FString& OutValue, FProperty*& OutProperty) { check(InContainer); FInternalStringGetterResolver Resolver(OutValue, OutProperty); return ResolvePropertyPath(InContainer, InPropertyPath, Resolver); } bool GetPropertyValueAsString(void* InContainer, UStruct* InStruct, const FString& InPropertyPath, FString& OutValue) { FProperty* Property; return GetPropertyValueAsString(InContainer, InStruct, InPropertyPath, OutValue, Property); } bool GetPropertyValueAsString(void* InContainer, UStruct* InStruct, const FString& InPropertyPath, FString& OutValue, FProperty*& OutProperty) { check(InContainer); check(InStruct); FInternalStringGetterResolver Resolver(OutValue, OutProperty); return ResolvePropertyPath(InContainer, InStruct, InPropertyPath, Resolver); } bool GetPropertyValueAsString(UObject* InContainer, const FCachedPropertyPath& InPropertyPath, FString& OutValue) { check(InContainer); FProperty* Property; FInternalStringGetterResolver Resolver(OutValue, Property); return ResolvePropertyPath(InContainer, InPropertyPath, Resolver); } bool GetPropertyValueAsString(void* InContainer, UStruct* InStruct, const FCachedPropertyPath& InPropertyPath, FString& OutValue) { check(InContainer); check(InStruct); FProperty* Property; FInternalStringGetterResolver Resolver(OutValue, Property); return ResolvePropertyPath(InContainer, InStruct, InPropertyPath, Resolver); } /** Helper for string-based setters */ struct FInternalStringSetterResolver : public PropertyPathHelpersInternal::TPropertyPathResolver { FInternalStringSetterResolver(const FString& InValueAsString) : Value(InValueAsString) { } template bool Resolve_Impl(ContainerType* InContainer, const FCachedPropertyPath& InPropertyPath) { return PropertyPathHelpersInternal::SetPropertyValueFromString(InContainer, InPropertyPath, Value); } const FString& Value; }; bool SetPropertyValueFromString(UObject* InContainer, const FString& InPropertyPath, const FString& InValue) { check(InContainer); FInternalStringSetterResolver Resolver(InValue); return ResolvePropertyPath(InContainer, InPropertyPath, Resolver); } bool SetPropertyValueFromString(UObject* InContainer, const FCachedPropertyPath& InPropertyPath, const FString& InValue) { check(InContainer); FInternalStringSetterResolver Resolver(InValue); return ResolvePropertyPath(InContainer, InPropertyPath, Resolver); } bool SetPropertyValueFromString(void* InContainer, UStruct* InStruct, const FString& InPropertyPath, const FString& InValue) { check(InContainer); check(InStruct); FInternalStringSetterResolver Resolver(InValue); return ResolvePropertyPath(InContainer, InStruct, InPropertyPath, Resolver); } bool SetPropertyValueFromString(void* InContainer, UStruct* InStruct, const FCachedPropertyPath& InPropertyPath, const FString& InValue) { check(InContainer); check(InStruct); FInternalStringSetterResolver Resolver(InValue); return ResolvePropertyPath(InContainer, InStruct, InPropertyPath, Resolver); } bool SetPropertyValue(UObject* InContainer, const FCachedPropertyPath& InPropertyPath, const UScriptStruct* InScriptStruct, const uint8* InValue) { PropertyPathHelpersInternal::FPropertyStructView StructView(InScriptStruct, InValue); return SetPropertyValue(InContainer, InPropertyPath, StructView); } bool SetPropertyValue(UObject* InContainer, const FString& InPropertyPath, const UScriptStruct* InScriptStruct, const uint8* InValue) { PropertyPathHelpersInternal::FPropertyStructView StructView(InScriptStruct, InValue); return SetPropertyValue(InContainer, InPropertyPath, StructView); } bool CopyPropertyValue(UObject* InContainer, const FCachedPropertyPath& InDestPropertyPath, const FCachedPropertyPath& InSrcPropertyPath) { if(InDestPropertyPath.IsFullyResolved() && InSrcPropertyPath.IsFullyResolved()) { return PropertyPathHelpersInternal::CopyResolvedPaths(InDestPropertyPath, InSrcPropertyPath); } else { FInternalCacheResolver DestResolver; FInternalCacheResolver SrcResolver; if(ResolvePropertyPath(InContainer, InDestPropertyPath, DestResolver) && ResolvePropertyPath(InContainer, InSrcPropertyPath, SrcResolver)) { if(InDestPropertyPath.IsResolved() && InSrcPropertyPath.IsResolved()) { if(PropertyPathHelpersInternal::CanCopyProperties(InDestPropertyPath, InSrcPropertyPath)) { return PropertyPathHelpersInternal::CopyResolvedPaths(InDestPropertyPath, InSrcPropertyPath); } } } } return false; } bool CopyPropertyValueFast(UObject* InContainer, const FCachedPropertyPath& InDestPropertyPath, const FCachedPropertyPath& InSrcPropertyPath) { check(InContainer == InDestPropertyPath.GetCachedContainer()); check(InContainer == InSrcPropertyPath.GetCachedContainer()); checkSlow(InDestPropertyPath.IsResolved()); checkSlow(InSrcPropertyPath.IsResolved()); checkSlow(PropertyPathHelpersInternal::CanCopyProperties(InDestPropertyPath, InSrcPropertyPath)); return PropertyPathHelpersInternal::CopyResolvedPaths(InDestPropertyPath, InSrcPropertyPath); } /** Helper for array operations*/ struct FInternalArrayOperationResolver : public PropertyPathHelpersInternal::TPropertyPathResolver { FInternalArrayOperationResolver(TFunctionRef InOperation) : Operation(InOperation) { } template bool Resolve_Impl(ContainerType* InContainer, const FCachedPropertyPath& InPropertyPath) { return PropertyPathHelpersInternal::PerformArrayOperation(InContainer, InPropertyPath, Operation); } TFunctionRef Operation; }; bool PerformArrayOperation(UObject* InContainer, const FString& InPropertyPath, TFunctionRef InOperation) { FInternalArrayOperationResolver Resolver(InOperation); return ResolvePropertyPath(InContainer, InPropertyPath, Resolver); } bool PerformArrayOperation(UObject* InContainer, const FCachedPropertyPath& InPropertyPath, TFunctionRef InOperation) { FInternalArrayOperationResolver Resolver(InOperation); return ResolvePropertyPath(InContainer, InPropertyPath, Resolver); } }