// Copyright Epic Games, Inc. All Rights Reserved. #include "UObject/PropertyAccessUtil.h" #include "UObject/EnumProperty.h" #include "UObject/Object.h" #include "UObject/Class.h" namespace PropertyAccessUtil { const UEnum* GetPropertyEnumType(const FProperty* InProp) { if (const FByteProperty* ByteProp = CastField(InProp)) { return ByteProp->Enum; } if (const FEnumProperty* EnumProp = CastField(InProp)) { return EnumProp->GetEnum(); } return nullptr; } int64 GetPropertyEnumValue(const FProperty* InProp, const void* InPropValue) { if (const FByteProperty* ByteProp = CastField(InProp)) { return ByteProp->GetSignedIntPropertyValue(InPropValue); } if (const FEnumProperty* EnumProp = CastField(InProp)) { EnumProp->GetUnderlyingProperty()->GetSignedIntPropertyValue(InPropValue); } return INDEX_NONE; } bool SetPropertyEnumValue(const FProperty* InProp, void* InPropValue, const int64 InEnumValue) { if (const FByteProperty* ByteProp = CastField(InProp)) { ByteProp->SetPropertyValue(InPropValue, (uint8)InEnumValue); return true; } if (const FEnumProperty* EnumProp = CastField(InProp)) { EnumProp->GetUnderlyingProperty()->SetIntPropertyValue(InPropValue, InEnumValue); return true; } return false; } bool ArePropertiesCompatible(const FProperty* InSrcProp, const FProperty* InDestProp) { // Enum properties can either be a ByteProperty with an enum set, or an EnumProperty // We allow coercion between these two types if they're using the same enum type if (const UEnum* DestEnumType = GetPropertyEnumType(InDestProp)) { const UEnum* SrcEnumType = GetPropertyEnumType(InSrcProp); if (SrcEnumType == DestEnumType) { return true; } // Blueprints don't always set the Enum field on the ByteProperty when setting properties, so we also // allow assigning from a raw ByteProperty (for type safety there we rely on the compiler frontend) const FByteProperty* SrcByteProp = CastField(InSrcProp); if (SrcByteProp && !SrcByteProp->Enum && InDestProp->IsA()) { return true; } } // Compare the classes as these must be an *exact* match as the access is low-level and without property coercion if (InSrcProp->GetClass() != InDestProp->GetClass()) { return false; } // Containers also need to check their inner types if (const FArrayProperty* SrcArrayProp = CastField(InSrcProp)) { const FArrayProperty* DestArrayProp = CastFieldChecked(InDestProp); return ArePropertiesCompatible(SrcArrayProp->Inner, DestArrayProp->Inner); } if (const FSetProperty* SrcSetProp = CastField(InSrcProp)) { const FSetProperty* DestSetProp = CastFieldChecked(InDestProp); return ArePropertiesCompatible(SrcSetProp->ElementProp, DestSetProp->ElementProp); } if (const FMapProperty* SrcMapProp = CastField(InSrcProp)) { const FMapProperty* DestMapProp = CastFieldChecked(InDestProp); return ArePropertiesCompatible(SrcMapProp->KeyProp, DestMapProp->KeyProp) && ArePropertiesCompatible(SrcMapProp->ValueProp, DestMapProp->ValueProp); } return true; } bool IsSinglePropertyIdentical(const FProperty* InSrcProp, const void* InSrcValue, const FProperty* InDestProp, const void* InDestValue) { if (!ArePropertiesCompatible(InSrcProp, InDestProp)) { return false; } if (const FBoolProperty* SrcBoolProp = CastField(InSrcProp)) { const FBoolProperty* DestBoolProp = CastFieldChecked(InDestProp); // Bools can be represented as bitfields, we we have to handle the compare a little differently to only check the bool we want const bool bSrcBoolValue = SrcBoolProp->GetPropertyValue(InSrcValue); const bool bDestBoolValue = DestBoolProp->GetPropertyValue(InDestValue); return bSrcBoolValue == bDestBoolValue; } return InSrcProp->Identical(InSrcValue, InDestValue); } bool IsCompletePropertyIdentical(const FProperty* InSrcProp, const void* InSrcValue, const FProperty* InDestProp, const void* InDestValue) { bool bIsIdentical = InSrcProp->ArrayDim == InDestProp->ArrayDim; for (int32 Idx = 0; Idx < InSrcProp->ArrayDim && bIsIdentical; ++Idx) { const void* SrcElemValue = static_cast(InSrcValue) + (InSrcProp->ElementSize * Idx); const void* DestElemValue = static_cast(InDestValue) + (InDestProp->ElementSize * Idx); bIsIdentical &= IsSinglePropertyIdentical(InSrcProp, SrcElemValue, InDestProp, DestElemValue); } return bIsIdentical; } bool CopySinglePropertyValue(const FProperty* InSrcProp, const void* InSrcValue, const FProperty* InDestProp, void* InDestValue) { if (!ArePropertiesCompatible(InSrcProp, InDestProp)) { return false; } // Enum properties can either be a ByteProperty with an enum set, or an EnumProperty // We allow coercion between these two types as long as they're using the same enum type (as validated by ArePropertiesCompatible) if (const UEnum* EnumType = GetPropertyEnumType(InDestProp)) { const int64 SrcEnumValue = GetPropertyEnumValue(InSrcProp, InSrcValue); return SetPropertyEnumValue(InDestProp, InDestValue, SrcEnumValue); } if (const FBoolProperty* SrcBoolProp = CastField(InSrcProp)) { const FBoolProperty* DestBoolProp = CastFieldChecked(InDestProp); // Bools can be represented as bitfields, we we have to handle the copy a little differently to only extract the bool we want const bool bBoolValue = SrcBoolProp->GetPropertyValue(InSrcValue); DestBoolProp->SetPropertyValue(InDestValue, bBoolValue); return true; } InSrcProp->CopySingleValue(InDestValue, InSrcValue); return true; } bool CopyCompletePropertyValue(const FProperty* InSrcProp, const void* InSrcValue, const FProperty* InDestProp, void* InDestValue) { if (!ArePropertiesCompatible(InSrcProp, InDestProp) || InSrcProp->ArrayDim != InDestProp->ArrayDim) { return false; } // Enum properties can either be a ByteProperty with an enum set, or an EnumProperty // We allow coercion between these two types as long as they're using the same enum type (as validated by ArePropertiesCompatible) if (const UEnum* EnumType = GetPropertyEnumType(InDestProp)) { bool bSuccess = true; for (int32 Idx = 0; Idx < InSrcProp->ArrayDim; ++Idx) { const void* SrcElemValue = static_cast(InSrcValue) + (InSrcProp->ElementSize * Idx); void* DestElemValue = static_cast(InDestValue) + (InDestProp->ElementSize * Idx); const int64 SrcEnumValue = GetPropertyEnumValue(InSrcProp, SrcElemValue); bSuccess &= SetPropertyEnumValue(InDestProp, DestElemValue, SrcEnumValue); } return bSuccess; } if (const FBoolProperty* SrcBoolProp = CastField(InSrcProp)) { const FBoolProperty* DestBoolProp = CastFieldChecked(InDestProp); for (int32 Idx = 0; Idx < InSrcProp->ArrayDim; ++Idx) { const void* SrcElemValue = static_cast(InSrcValue) + (InSrcProp->ElementSize * Idx); void* DestElemValue = static_cast(InDestValue) + (InDestProp->ElementSize * Idx); // Bools can be represented as bitfields, we we have to handle the copy a little differently to only extract the bool we want const bool bBoolValue = SrcBoolProp->GetPropertyValue(SrcElemValue); DestBoolProp->SetPropertyValue(DestElemValue, bBoolValue); } return true; } InSrcProp->CopyCompleteValue(InDestValue, InSrcValue); return true; } EPropertyAccessResultFlags GetPropertyValue_Object(const FProperty* InObjectProp, const UObject* InObject, const FProperty* InDestProp, void* InDestValue, const int32 InArrayIndex) { check(InObject->IsA(InObjectProp->GetOwnerClass())); return GetPropertyValue_InContainer(InObjectProp, InObject, InDestProp, InDestValue, InArrayIndex); } EPropertyAccessResultFlags GetPropertyValue_InContainer(const FProperty* InContainerProp, const void* InContainerData, const FProperty* InDestProp, void* InDestValue, const int32 InArrayIndex) { if (InArrayIndex == INDEX_NONE || InContainerProp->ArrayDim == 1) { const void* SrcValue = InContainerProp->ContainerPtrToValuePtr(InContainerData); return GetPropertyValue_DirectComplete(InContainerProp, SrcValue, InDestProp, InDestValue); } else { check(InArrayIndex < InContainerProp->ArrayDim); const void* SrcValue = InContainerProp->ContainerPtrToValuePtr(InContainerData, InArrayIndex); return GetPropertyValue_DirectSingle(InContainerProp, SrcValue, InDestProp, InDestValue); } } EPropertyAccessResultFlags GetPropertyValue_DirectSingle(const FProperty* InSrcProp, const void* InSrcValue, const FProperty* InDestProp, void* InDestValue) { EPropertyAccessResultFlags Result = CanGetPropertyValue(InSrcProp); if (Result != EPropertyAccessResultFlags::Success) { return Result; } return GetPropertyValue([InSrcProp, InSrcValue, InDestProp, InDestValue]() { return CopySinglePropertyValue(InSrcProp, InSrcValue, InDestProp, InDestValue); }); } EPropertyAccessResultFlags GetPropertyValue_DirectComplete(const FProperty* InSrcProp, const void* InSrcValue, const FProperty* InDestProp, void* InDestValue) { EPropertyAccessResultFlags Result = CanGetPropertyValue(InSrcProp); if (Result != EPropertyAccessResultFlags::Success) { return Result; } return GetPropertyValue([InSrcProp, InSrcValue, InDestProp, InDestValue]() { return CopyCompletePropertyValue(InSrcProp, InSrcValue, InDestProp, InDestValue); }); } EPropertyAccessResultFlags GetPropertyValue(const FPropertyAccessGetFunc& InGetFunc) { const bool bGetResult = InGetFunc(); return bGetResult ? EPropertyAccessResultFlags::Success : EPropertyAccessResultFlags::ConversionFailed; } EPropertyAccessResultFlags CanGetPropertyValue(const FProperty* InProp) { if (!InProp->HasAnyPropertyFlags(CPF_Edit | CPF_BlueprintVisible | CPF_BlueprintAssignable)) { return EPropertyAccessResultFlags::PermissionDenied | EPropertyAccessResultFlags::AccessProtected; } return EPropertyAccessResultFlags::Success; } EPropertyAccessResultFlags SetPropertyValue_Object(const FProperty* InObjectProp, UObject* InObject, const FProperty* InSrcProp, const void* InSrcValue, const int32 InArrayIndex, const uint64 InReadOnlyFlags, const EPropertyAccessChangeNotifyMode InNotifyMode) { check(InObject->IsA(InObjectProp->GetOwnerClass())); return SetPropertyValue_InContainer(InObjectProp, InObject, InSrcProp, InSrcValue, InArrayIndex, InReadOnlyFlags, IsObjectTemplate(InObject), [InObjectProp, InObject, InNotifyMode]() { return BuildBasicChangeNotify(InObjectProp, InObject, InNotifyMode); }); } EPropertyAccessResultFlags SetPropertyValue_InContainer(const FProperty* InContainerProp, void* InContainerData, const FProperty* InSrcProp, const void* InSrcValue, const int32 InArrayIndex, const uint64 InReadOnlyFlags, const bool InOwnerIsTemplate, const FPropertyAccessBuildChangeNotifyFunc& InBuildChangeNotifyFunc) { if (InArrayIndex == INDEX_NONE || InContainerProp->ArrayDim == 1) { void* DestValue = InContainerProp->ContainerPtrToValuePtr(InContainerData); return SetPropertyValue_DirectComplete(InSrcProp, InSrcValue, InContainerProp, DestValue, InReadOnlyFlags, InOwnerIsTemplate, InBuildChangeNotifyFunc); } else { check(InArrayIndex < InContainerProp->ArrayDim); void* DestValue = InContainerProp->ContainerPtrToValuePtr(InContainerData, InArrayIndex); return SetPropertyValue_DirectSingle(InSrcProp, InSrcValue, InContainerProp, DestValue, InReadOnlyFlags, InOwnerIsTemplate, InBuildChangeNotifyFunc); } } EPropertyAccessResultFlags SetPropertyValue_DirectSingle(const FProperty* InSrcProp, const void* InSrcValue, const FProperty* InDestProp, void* InDestValue, const uint64 InReadOnlyFlags, const bool InOwnerIsTemplate, const FPropertyAccessBuildChangeNotifyFunc& InBuildChangeNotifyFunc) { EPropertyAccessResultFlags Result = CanSetPropertyValue(InDestProp, InReadOnlyFlags, InOwnerIsTemplate); if (Result != EPropertyAccessResultFlags::Success) { return Result; } return SetPropertyValue([InSrcProp, InSrcValue, InDestProp, InDestValue](const FPropertyAccessChangeNotify* InChangeNotify) { bool bResult = true; const bool bIdenticalValue = IsSinglePropertyIdentical(InSrcProp, InSrcValue, InDestProp, InDestValue); EmitPreChangeNotify(InChangeNotify, bIdenticalValue); if (!bIdenticalValue) { bResult = CopySinglePropertyValue(InSrcProp, InSrcValue, InDestProp, InDestValue); } EmitPostChangeNotify(InChangeNotify, bIdenticalValue); return bResult; }, InBuildChangeNotifyFunc); } EPropertyAccessResultFlags SetPropertyValue_DirectComplete(const FProperty* InSrcProp, const void* InSrcValue, const FProperty* InDestProp, void* InDestValue, const uint64 InReadOnlyFlags, const bool InOwnerIsTemplate, const FPropertyAccessBuildChangeNotifyFunc& InBuildChangeNotifyFunc) { EPropertyAccessResultFlags Result = CanSetPropertyValue(InDestProp, InReadOnlyFlags, InOwnerIsTemplate); if (Result != EPropertyAccessResultFlags::Success) { return Result; } return SetPropertyValue([InSrcProp, InSrcValue, InDestProp, InDestValue](const FPropertyAccessChangeNotify* InChangeNotify) { bool bResult = true; const bool bIdenticalValue = IsCompletePropertyIdentical(InSrcProp, InSrcValue, InDestProp, InDestValue); EmitPreChangeNotify(InChangeNotify, bIdenticalValue); if (!bIdenticalValue) { bResult = CopyCompletePropertyValue(InSrcProp, InSrcValue, InDestProp, InDestValue); } EmitPostChangeNotify(InChangeNotify, bIdenticalValue); return bResult; }, InBuildChangeNotifyFunc); } EPropertyAccessResultFlags SetPropertyValue(const FPropertyAccessSetFunc& InSetFunc, const FPropertyAccessBuildChangeNotifyFunc& InBuildChangeNotifyFunc) { TUniquePtr ChangeNotify = InBuildChangeNotifyFunc(); const bool bSetResult = InSetFunc(ChangeNotify.Get()); return bSetResult ? EPropertyAccessResultFlags::Success : EPropertyAccessResultFlags::ConversionFailed; } EPropertyAccessResultFlags CanSetPropertyValue(const FProperty* InProp, const uint64 InReadOnlyFlags, const bool InOwnerIsTemplate) { if (!InProp->HasAnyPropertyFlags(CPF_Edit | CPF_BlueprintVisible | CPF_BlueprintAssignable)) { return EPropertyAccessResultFlags::PermissionDenied | EPropertyAccessResultFlags::AccessProtected; } if (InOwnerIsTemplate) { if (InProp->HasAnyPropertyFlags(CPF_DisableEditOnTemplate)) { return EPropertyAccessResultFlags::PermissionDenied | EPropertyAccessResultFlags::CannotEditTemplate; } } else { if (InProp->HasAnyPropertyFlags(CPF_DisableEditOnInstance)) { return EPropertyAccessResultFlags::PermissionDenied | EPropertyAccessResultFlags::CannotEditInstance; } } if (InProp->HasAnyPropertyFlags(InReadOnlyFlags)) { return EPropertyAccessResultFlags::PermissionDenied | EPropertyAccessResultFlags::ReadOnly; } return EPropertyAccessResultFlags::Success; } void EmitPreChangeNotify(const FPropertyAccessChangeNotify* InChangeNotify, const bool InIdenticalValue) { #if WITH_EDITOR if (InChangeNotify && InChangeNotify->NotifyMode != EPropertyAccessChangeNotifyMode::Never) { check(InChangeNotify->ChangedObject); if (!InIdenticalValue || InChangeNotify->NotifyMode == EPropertyAccessChangeNotifyMode::Always) { // Notify that a change is about to occur InChangeNotify->ChangedObject->PreEditChange(const_cast(InChangeNotify->ChangedPropertyChain)); } } #endif } void EmitPostChangeNotify(const FPropertyAccessChangeNotify* InChangeNotify, const bool InIdenticalValue) { #if WITH_EDITOR if (InChangeNotify && InChangeNotify->NotifyMode != EPropertyAccessChangeNotifyMode::Never) { check(InChangeNotify->ChangedObject); if (!InIdenticalValue || InChangeNotify->NotifyMode == EPropertyAccessChangeNotifyMode::Always) { // Notify that the change has occurred FPropertyChangedEvent PropertyEvent(InChangeNotify->ChangedPropertyChain.GetActiveNode()->GetValue(), InChangeNotify->ChangeType, MakeArrayView(&InChangeNotify->ChangedObject, 1)); PropertyEvent.SetActiveMemberProperty(InChangeNotify->ChangedPropertyChain.GetActiveMemberNode()->GetValue()); FPropertyChangedChainEvent PropertyChainEvent(const_cast(InChangeNotify->ChangedPropertyChain), PropertyEvent); InChangeNotify->ChangedObject->PostEditChangeChainProperty(PropertyChainEvent); } } #endif } TUniquePtr BuildBasicChangeNotify(const FProperty* InProp, const UObject* InObject, const EPropertyAccessChangeNotifyMode InNotifyMode) { check(InObject->IsA(InProp->GetOwnerClass())); #if WITH_EDITOR if (InNotifyMode != EPropertyAccessChangeNotifyMode::Never) { TUniquePtr ChangeNotify = MakeUnique(); ChangeNotify->ChangedObject = const_cast(InObject); ChangeNotify->ChangedPropertyChain.AddHead(const_cast(InProp)); ChangeNotify->ChangedPropertyChain.SetActivePropertyNode(const_cast(InProp)); ChangeNotify->ChangedPropertyChain.SetActiveMemberPropertyNode(const_cast(InProp)); ChangeNotify->NotifyMode = InNotifyMode; return ChangeNotify; } #endif return nullptr; } bool IsObjectTemplate(const UObject* InObject) { // Templates can edit default properties if (InObject->IsTemplate()) { return true; } // Assets can edit default properties if (InObject->IsAsset()) { return true; } // Objects within an asset that are edit-inline can edit default properties, as this mimics the inlining that the details panel shows if (InObject->GetClass()->HasAnyClassFlags(CLASS_EditInlineNew)) { for (const UObject* Outer = InObject->GetOuter(); Outer; Outer = Outer->GetOuter()) { if (Outer->IsAsset()) { return true; } } } return false; } FProperty* FindPropertyByName(const FName InPropName, const UStruct* InStruct) { FProperty* Prop = InStruct->FindPropertyByName(InPropName); if (!Prop) { const FName NewPropName = FProperty::FindRedirectedPropertyName(const_cast(InStruct), InPropName); if (!NewPropName.IsNone()) { Prop = InStruct->FindPropertyByName(NewPropName); } } if (!Prop) { Prop = InStruct->CustomFindProperty(InPropName); } return Prop; } } // namespace PropertyAccessUtil