Explorar el Código

Adding overloads for DOM Utils for ValueFromType and MarshalOpaqueValue which can accept a void* and a type trait structure (#16629)

* Adding overloads for DOM Utils for ValueFromType and MarshalOpaqueValue which can accept a void* and a type trait structure

This allows dynamic marshalling of data to a Dom::Value in a type erased manner.

Added registration of a function that can create an AZStd::any action handler which can be used to initial an AZStd::any in a type erased manner using a `void*` and `AZStd::any::type_info` object.

Updated the `PropertyEditorAPI_Internals.h` to use the `void*` overload ValueFromType overload to write the actual type stored in the PropertyHandler to a new Dom::Value.

Fixed nullptr deferenced in the AZ::Transform Row Widget.

resolves #16439

Signed-off-by: lumberyard-employee-dm <[email protected]>

* Added a native visualizer for Dom::Path that allows viewing the full
path as a single string.

It supports up to 48 path segments (/1/2/foo/bar/.../baz) which should
be more than enough to visualize any real Dom path in the debugger.

Signed-off-by: lumberyard-employee-dm <[email protected]>

* Fixed Document Property Editor removal of Assets using the UI

The Asset Json Serializer would not update the object being loaded if
the AssetId stored in JSON was the invalid Asset Id of
"id={00000000-0000-0000-0000-000000000000}:0"

The Asset Manager has been updated to not attempt to Create or Find
Assets using the Invalid asset Id of Uuid "0".

Finally the ReflectionAdapter VisitObjectBegin method has been updated
with `AZ_Error` messages to log when copying a new value into the
reflected instance address fails in JSON Serialization.

fixes #16231

Signed-off-by: lumberyard-employee-dm <[email protected]>

* Adding trace message suppression to the Slice Entity Ownership Test.

Signed-off-by: lumberyard-employee-dm <[email protected]>

* Fixed nullptr dereference in the Slice Entity ownership test

Signed-off-by: lumberyard-employee-dm <[email protected]>

---------

Signed-off-by: lumberyard-employee-dm <[email protected]>
lumberyard-employee-dm hace 1 año
padre
commit
3393f127db

+ 10 - 15
Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.cpp

@@ -130,25 +130,20 @@ namespace AZ::Data
         {
             ScopedContextPath subPath(context, "assetId");
             result.Combine(ContinueLoading(&id, azrtti_typeid<AssetId>(), it->value, context));
-            if (!id.m_guid.IsNull())
+
+            if (result.GetProcessing() == JSR::Processing::Completed)
             {
                 *instance = AssetManager::Instance().FindOrCreateAsset(id, instance->GetType(), instance->GetAutoLoadBehavior());
-                if (!instance->GetId().IsValid())
+
+                if (id.IsValid())
                 {
-                    // If the asset failed to be created, FindOrCreateAsset returns an asset instance with a null
-                    // id. To preserve the asset id in the source json, reset the asset to an empty one, but with
-                    // the right id.
-                    const auto loadBehavior = instance->GetAutoLoadBehavior();
-                    *instance = Asset<AssetData>(id, instance->GetType());
-                    instance->SetAutoLoadBehavior(loadBehavior);
+                    result.Combine(context.Report(result, "Successfully created Asset<T> with id."));
+                }
+                else
+                {
+                    result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed,
+                        "Null Asset<T> created."));
                 }
-
-                result.Combine(context.Report(result, "Successfully created Asset<T> with id."));
-            }
-            else if (result.GetProcessing() == JSR::Processing::Completed)
-            {
-                result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed,
-                    "Null Asset<T> created."));
             }
             else
             {

+ 17 - 0
Code/Framework/AzCore/AzCore/Asset/AssetManager.cpp

@@ -1206,6 +1206,14 @@ namespace AZ::Data
 
     Asset<AssetData> AssetManager::FindOrCreateAsset(const AssetId& assetId, const AssetType& assetType, AssetLoadBehavior assetReferenceLoadBehavior)
     {
+        if (!assetId.IsValid())
+        {
+            // The null Asset has an invalid asset ID, but uses the asset type and asset load behavior supplied to this method 
+            Asset<AssetData> nullAsset(assetId, assetType);
+            nullAsset.SetAutoLoadBehavior(assetReferenceLoadBehavior);
+            return nullAsset;
+        }
+
         // Look up the asset id in the catalog, and use the result of that instead.
         // If assetId is a legacy id, assetInfo.m_assetId will be the canonical id. Otherwise, assetInfo.m_assetID == assetId.
         // This is because only canonical ids are stored in m_assets.
@@ -1237,6 +1245,15 @@ namespace AZ::Data
     //=========================================================================
     Asset<AssetData> AssetManager::CreateAsset(const AssetId& assetId, const AssetType& assetType, AssetLoadBehavior assetReferenceLoadBehavior)
     {
+        if (!assetId.IsValid())
+        {
+            AZ_Error("AssetDatabase", false, R"(Cannot create Asset with the InvalidAssetId: %s)", assetId.ToFixedString().c_str());
+
+            // The null Asset has an invalid asset ID, but uses the asset type and asset load behavior supplied to this method 
+            Asset<AssetData> nullAsset(assetId, assetType);
+            nullAsset.SetAutoLoadBehavior(assetReferenceLoadBehavior);
+            return nullAsset;
+        }
         AZStd::scoped_lock<AZStd::recursive_mutex> asset_lock(m_assetMutex);
 
         // check if asset already exist

+ 35 - 2
Code/Framework/AzCore/AzCore/DOM/DomUtils.cpp

@@ -361,11 +361,11 @@ namespace AZ::Dom::Utils
     // remove dangerous implementation that could result in dangling references
     void* TryMarshalValueToPointer(AZ::Dom::Value&& value, const AZ::TypeId& expectedType) = delete;
 
-    Dom::Value MarshalTypedPointerToValue(void* value, const AZ::TypeId& typeId)
+    Dom::Value MarshalTypedPointerToValue(const void* value, const AZ::TypeId& typeId)
     {
         Dom::Value result(Dom::Type::Object);
         result[TypeFieldName] = Dom::Value(PointerTypeName.GetStringView(), false);
-        result[PointerValueFieldName] = Dom::Value(reinterpret_cast<uint64_t>(value));
+        result[PointerValueFieldName] = Dom::Value(static_cast<uint64_t>(reinterpret_cast<const uintptr_t>(value)));
         Dom::Value typeName = TypeIdToDomValue(typeId);
         if (!typeName.GetString().empty())
         {
@@ -399,4 +399,37 @@ namespace AZ::Dom::Utils
             return azrtti_typeid<void>();
         }
     }
+
+    Dom::Value MarshalOpaqueValue(const void* valueAddress, const MarshalTypeTraits& typeTraits,
+        AZStd::any::action_handler_for_t actionHandler)
+    {
+        if (typeTraits.m_isPointer)
+        {
+            return MarshalTypedPointerToValue(valueAddress, typeTraits.m_typeId);
+        }
+        else
+        {
+            AZStd::any::type_info typeInfo;
+            typeInfo.m_id = typeTraits.m_typeId;
+            typeInfo.m_handler = AZStd::move(actionHandler);
+            typeInfo.m_isPointer = false;
+            typeInfo.m_useHeap = typeTraits.m_typeSize > AZStd::Internal::ANY_SBO_BUF_SIZE;
+            return Dom::Value::FromOpaqueValue(AZStd::any(valueAddress, typeInfo));
+        }
+    }
+
+    Dom::Value ValueFromType(const void* valueAddress, const MarshalTypeTraits& typeTraits,
+        AZStd::any::action_handler_for_t actionHandler)
+    {
+        if (typeTraits.m_typeId == azrtti_typeid<Dom::Value>())
+        {
+            // Rely on the Dom::Value copy constructor to make a copy
+            return *reinterpret_cast<const Dom::Value*>(valueAddress);
+        }
+        else
+        {
+            return MarshalOpaqueValue(valueAddress, typeTraits, AZStd::move(actionHandler));
+        }
+    }
+
 } // namespace AZ::Dom::Utils

+ 87 - 35
Code/Framework/AzCore/AzCore/DOM/DomUtils.h

@@ -100,55 +100,110 @@ namespace AZ::Dom::Utils
     extern const AZ::Name PointerValueFieldName;
     extern const AZ::Name PointerTypeFieldName;
 
-    Dom::Value MarshalTypedPointerToValue(void* value, const AZ::TypeId& typeId);
+    Dom::Value MarshalTypedPointerToValue(const void* value, const AZ::TypeId& typeId);
     void* TryMarshalValueToPointer(const AZ::Dom::Value& value, const AZ::TypeId& expectedType = AZ::TypeId::CreateNull());
 
+    struct MarshalTypeTraits
+    {
+        AZ::TypeId m_typeId{};
+        bool m_isPointer{};
+        bool m_isReference{};
+        bool m_isCopyConstructible{};
+        size_t m_typeSize{};
+    };
+
+    Dom::Value MarshalOpaqueValue(const void* valueAddress, const MarshalTypeTraits& typeTraits,
+        AZStd::any::action_handler_for_t actionHandler);
+
     template <typename T>
     Dom::Value MarshalOpaqueValue(T value)
     {
-        if constexpr (AZStd::is_pointer_v<T>)
+        using WrapperType = DomValueWrapperType<T>;
+        MarshalTypeTraits typeTraits;
+        // Store if the WrapperType is a pointer
+        typeTraits.m_isPointer = AZStd::is_pointer_v<AZStd::remove_cvref_t<WrapperType>>;
+        // Store if the T type is a reference
+        typeTraits.m_isReference = AZStd::is_reference_v<T>;
+        // Store if the T type is a copy constructible
+        typeTraits.m_isCopyConstructible = AZStd::is_copy_constructible_v<T>;
+
+        if constexpr (AZStd::is_pointer_v<AZStd::remove_cvref_t<WrapperType>>)
         {
-            // C-style cast to break const
-            return MarshalTypedPointerToValue((void*)(value), azrtti_typeid<T>());
+            using ValueType = AZStd::remove_cvref_t<WrapperType>;
+            typeTraits.m_typeSize = sizeof(ValueType);
+            typeTraits.m_typeId = azrtti_typeid(value);
+            return MarshalOpaqueValue(AZStd::as_const(value),
+                typeTraits, AZStd::any::get_action_handler_for_t<ValueType>());
         }
         else
         {
-            return Dom::Value::FromOpaqueValue(AZStd::any(value));
+            using ValueType = AZStd::remove_cvref_t<WrapperType>;
+            typeTraits.m_typeSize = sizeof(ValueType);
+            typeTraits.m_typeId = azrtti_typeid(value);
+            return MarshalOpaqueValue(&value,
+                typeTraits, AZStd::any::get_action_handler_for_t<ValueType>());
         }
     }
 
+    Dom::Value ValueFromType(const void* valueAddress, const MarshalTypeTraits& typeTraits,
+        AZStd::any::action_handler_for_t actionHandler);
+
     template<typename T>
     Dom::Value ValueFromType(T value)
     {
         using WrapperType = DomValueWrapperType<T>;
-        if constexpr (AZStd::is_same_v<AZStd::decay_t<T>, Dom::Value>)
-        {
-            return value;
-        }
-        else if constexpr (AZStd::is_reference_v<T> || !AZStd::is_copy_constructible_v<T>)
-        {
-            WrapperType wrapper = value;
-            return MarshalOpaqueValue(wrapper);
-        }
-        else if constexpr (AZStd::is_same_v<WrapperType, Dom::Value>)
-        {
-            return value;
-        }
-        else if constexpr (AZStd::is_constructible_v<AZStd::string_view, WrapperType>)
-        {
-            return Dom::Value(value, true);
-        }
-        else if constexpr (AZStd::is_constructible_v<Dom::Value, const WrapperType&>)
-        {
-            return Dom::Value(value);
-        }
-        else if constexpr (AZStd::is_enum_v<WrapperType>)
+        MarshalTypeTraits typeTraits;
+        // Store if the WrapperType is a pointer
+        typeTraits.m_isPointer = AZStd::is_pointer_v<AZStd::remove_cvref_t<WrapperType>>;
+        // Store if the T type is a reference
+        typeTraits.m_isReference = AZStd::is_reference_v<T>;
+        // Store if the T type is a copy constructible
+        typeTraits.m_isCopyConstructible = AZStd::is_copy_constructible_v<T>;
+
+        AZStd::any::action_handler_for_t actionHandler;
+
+        if constexpr (AZStd::is_pointer_v<AZStd::remove_cvref_t<WrapperType>>)
         {
-            return ValueFromType(static_cast<AZStd::underlying_type_t<WrapperType>>(value));
+            using ValueType = AZStd::remove_cvref_t<WrapperType>;
+            // Store the size of the decayed wrapper type
+            typeTraits.m_typeSize = sizeof(ValueType);
+            typeTraits.m_typeId = azrtti_typeid(value);
+
+            actionHandler = AZStd::any::get_action_handler_for_t<ValueType>();
+
+            return ValueFromType(AZStd::as_const(value),
+                typeTraits, actionHandler);
         }
         else
         {
-            return MarshalOpaqueValue(value);
+            using ValueType = AZStd::remove_cvref_t<WrapperType>;
+            typeTraits.m_typeId = azrtti_typeid<ValueType>();
+            typeTraits.m_typeSize = sizeof(ValueType);
+
+            // Lifetime variables provides storage for Dom::Value variable
+            // long enough to complete the call to the ValueFromType overload which accepts a void pointer
+            Dom::Value domValueLifetime;
+            const void* valueAddress = &value;
+            if constexpr (AZStd::is_reference_wrapper<ValueType>())
+            {
+                return Dom::Value::FromOpaqueValue(AZStd::any(ValueType(value)));
+            }
+            else if constexpr (AZStd::is_constructible_v<AZStd::string_view, WrapperType>)
+            {
+                constexpr bool deepCopyString = true;
+                return Dom::Value(value, deepCopyString);
+            }
+            else if constexpr (AZStd::is_constructible_v<Dom::Value, const WrapperType&>)
+            {
+                return Dom::Value(value);
+            }
+            else
+            {
+                // The type is a non-referenced opaque value type that needs to be stored using
+                // an AZStd::any
+                actionHandler = AZStd::any::get_action_handler_for_t<ValueType>();
+                return ValueFromType(valueAddress, typeTraits, actionHandler);
+            }
         }
     }
 
@@ -166,7 +221,8 @@ namespace AZ::Dom::Utils
         {
             return value.IsBool();
         }
-        else if constexpr (AZStd::is_integral_v<WrapperType> || AZStd::is_floating_point_v<WrapperType>)
+        else if constexpr (AZStd::is_integral_v<WrapperType> || AZStd::is_floating_point_v<WrapperType>
+            || AZStd::is_enum_v<WrapperType>)
         {
             return value.IsNumber();
         }
@@ -174,10 +230,6 @@ namespace AZ::Dom::Utils
         {
             return value.IsString();
         }
-        else if constexpr (AZStd::is_enum_v<WrapperType>)
-        {
-            return CanConvertValueToType<AZStd::underlying_type_t<WrapperType>>(value);
-        }
         else
         {
             if constexpr (AZStd::is_pointer_v<WrapperType>)
@@ -294,7 +346,7 @@ namespace AZ::Dom::Utils
                     }
                     return {};
                 }
-                
+
             };
 
             return ExtractOpaqueValue();

+ 11 - 0
Code/Framework/AzCore/AzCore/DOM/DomValue.h

@@ -20,6 +20,7 @@
 #include <AzCore/std/containers/variant.h>
 #include <AzCore/std/containers/vector.h>
 #include <AzCore/std/smart_ptr/shared_ptr.h>
+#include <AzCore/std/utility/to_underlying.h>
 
 namespace AZ::Dom
 {
@@ -213,6 +214,10 @@ namespace AZ::Dom
 
         explicit Value(Type type);
 
+        // Stores the enum type as it's underlying type
+        template<class EnumType, class = AZStd::enable_if_t<AZStd::is_enum_v<EnumType>>>
+        explicit Value(EnumType enumType);
+
         // Disable accidental calls to Value(bool) with pointer types
         template<class T>
         explicit Value(T*) = delete;
@@ -409,4 +414,10 @@ namespace AZ::Dom
 
         ValueType m_value;
     };
+
+    template<class EnumType, class>
+    Value::Value(EnumType enumType)
+        : Value(AZStd::to_underlying(enumType))
+    {
+    }
 } // namespace AZ::Dom

+ 8 - 4
Code/Framework/AzCore/AzCore/Serialization/SerializeContext.cpp

@@ -881,7 +881,8 @@ namespace AZ
     // Create a class builder that that can reflect fields and attributes to SerializeContext ClassData
     auto SerializeContext::ReflectClassInternal(const char* className, const AZ::TypeId& classTypeId,
         IObjectFactory* factory, const DeprecatedNameVisitWrapper& callback,
-        IRttiHelper* rttiHelper, CreateAnyFunc createAnyFunc) -> ClassBuilder
+        IRttiHelper* rttiHelper, CreateAnyFunc createAnyFunc,
+        CreateAnyActionHandler createAnyActionHandlerFunc) -> ClassBuilder
     {
         // Add any the deprecated type names to the deprecated type name to type id map
         auto AddDeprecatedNames = [this, &typeUuid = classTypeId](AZStd::string_view deprecatedName)
@@ -891,8 +892,9 @@ namespace AZ
         callback(AddDeprecatedNames);
 
         m_classNameToUuid.emplace(AZ::Crc32(className), classTypeId);
-        auto result = m_uuidMap.emplace(classTypeId,
-            ClassData::CreateImpl(className, classTypeId, factory, nullptr, nullptr, rttiHelper));
+        auto result = m_uuidMap.emplace(
+            classTypeId,
+            ClassData::CreateImpl(className, classTypeId, factory, nullptr, nullptr, rttiHelper, AZStd::move(createAnyActionHandlerFunc)));
         AZ_Assert(result.second, "This class type %s could not be registered with duplicated Uuid: %s.",
             className, classTypeId.ToString<AZStd::string>().c_str());
         m_uuidAnyCreationMap.emplace(classTypeId, createAnyFunc);
@@ -3381,7 +3383,7 @@ namespace AZ::Serialize
 
     auto ClassData::CreateImpl(const char* name, const Uuid& typeUuid,
         IObjectFactory* factory, IDataSerializer* serializer, IDataContainer* container,
-        IRttiHelper* rttiHelper) -> ClassData
+        IRttiHelper* rttiHelper, SerializeContext::CreateAnyActionHandler createAzStdAnyActionHandler) -> ClassData
     {
         ClassData cd;
         cd.m_name = name;
@@ -3397,6 +3399,8 @@ namespace AZ::Serialize
         cd.m_container = container;
         cd.m_azRtti = rttiHelper;
         cd.m_editData = nullptr;
+        // Store the action handler
+        cd.m_createAzStdAnyActionHandler = AZStd::move(createAzStdAnyActionHandler);
         return cd;
     }
 

+ 47 - 10
Code/Framework/AzCore/AzCore/Serialization/SerializeContext.h

@@ -202,6 +202,10 @@ namespace AZ
 
         //! Function Pointer which is used to construct an AZStd::any for a registered type using the Serialize Context
         using CreateAnyFunc = AZStd::any(*)(SerializeContext*);
+        //! Function object which is used to create an instance of an ActionHandler function which can be supplied
+        //! to the AZStd::any::type_info handler object to allow type erased operations such as construct/destruct, copy/move
+        using CreateAnyActionHandler = AZStd::function<AZStd::any::action_handler_for_t(SerializeContext* serializeContext)>;
+
         //! Allows registration of a TypeId without the need to supply a C++ type
         //! If the type is not already registered, then the ClassData is moved into the SerializeContext
         //! internal structure
@@ -457,7 +461,8 @@ namespace AZ
         using DeprecatedNameVisitWrapper = void(*)(const DeprecatedTypeNameCallback&);
         ClassBuilder ReflectClassInternal(const char* className, const AZ::TypeId& classId,
             IObjectFactory* factory, const DeprecatedNameVisitWrapper& callback,
-            IRttiHelper* rttiHelper, CreateAnyFunc createAnyFunc);
+            IRttiHelper* rttiHelper, CreateAnyFunc createAnyFunc,
+            CreateAnyActionHandler createAnyActionHandlerFunc);
 
         ClassBuilder UnreflectClassInternal(const char* className, const AZ::TypeId& classId,
             const DeprecatedNameVisitWrapper& callback
@@ -976,10 +981,18 @@ namespace AZ::Serialize
         /// which while it inherits from IAllocator, does not work as function pointers do not support covariant return types
         AZStd::vector<AttributeSharedPair, AZStdFunctorAllocator> m_attributes{ AZStdFunctorAllocator(&GetSystemAllocator) };
 
+        //! Functor object which can be used to initialize the AZStd::any::type_info in order
+        //! to allow it to construct, destruct, copy/move instances of the stored type
+        //! in a type-erased manner
+        //! This can be used with the `any(const void* pointer, const type_info& typeInfo)` extension constructor
+        //! to construct an AZStd::any without access to the C++ type at that point
+        //! @param pointer to SerializeContext which is used for type-erased operations for non-copyable types
+        SerializeContext::CreateAnyActionHandler m_createAzStdAnyActionHandler;
+
     private:
         static ClassData CreateImpl(const char* name, const Uuid& typeUuid, IObjectFactory* factory,
             IDataSerializer* serializer, IDataContainer* container,
-            IRttiHelper* rttiHelper);
+            IRttiHelper* rttiHelper, SerializeContext::CreateAnyActionHandler createAzStdAnyActionHandler);
         static IAllocator& GetSystemAllocator()
         {
             return AZ::AllocatorInstance<AZ::SystemAllocator>::Get();
@@ -1497,6 +1510,11 @@ namespace AZ
         {
             return AZStd::any(ValueType());
         }
+
+        static AZStd::any::action_handler_for_t GetAnyActionHandler(SerializeContext*)
+        {
+            return AZStd::any::get_action_handler_for_t<ValueType>();
+        }
     };
 
     template<typename ValueType>
@@ -1532,7 +1550,7 @@ namespace AZ
             }
         }
         // The SerializeContext CloneObject function is used to copy data between Any
-        static AZStd::any::type_info::HandleFnT NonCopyableAnyHandler(SerializeContext* serializeContext)
+        static AZStd::any::action_handler_for_t NonCopyableAnyHandler(SerializeContext* serializeContext)
         {
             return [serializeContext](AZStd::any::Action action, AZStd::any* dest, const AZStd::any* source)
             {
@@ -1597,6 +1615,11 @@ namespace AZ
             };
         }
 
+        static AZStd::any::action_handler_for_t GetAnyActionHandler(SerializeContext* serializeContext)
+        {
+            return NonCopyableAnyHandler(serializeContext);
+        }
+
         static AZStd::any CreateAny(SerializeContext* serializeContext)
         {
             AZStd::any::type_info typeinfo;
@@ -1622,6 +1645,13 @@ namespace AZ
         {
             return {};
         }
+
+        static AZStd::any::action_handler_for_t GetAnyActionHandler(SerializeContext*)
+        {
+            return [](AZStd::any::Action, AZStd::any*, const AZStd::any*)
+            {
+            };
+        }
     };
 
     /*
@@ -1927,8 +1957,7 @@ namespace AZ
     // [10/31/2012]
     //=========================================================================
     template<class T, class... TBaseClasses>
-    SerializeContext::ClassBuilder
-    SerializeContext::Class()
+    SerializeContext::ClassBuilder SerializeContext::Class()
     {
         return Class<T, TBaseClasses...>(&Serialize::StaticInstance<Serialize::InstanceFactory<T> >::s_instance);
     }
@@ -1938,8 +1967,7 @@ namespace AZ
     // [10/31/2012]
     //=========================================================================
     template<class T, class... TBaseClasses>
-    SerializeContext::ClassBuilder
-    SerializeContext::Class(IObjectFactory* factory)
+    SerializeContext::ClassBuilder SerializeContext::Class(IObjectFactory* factory)
     {
         static_assert((AZStd::negation_v<AZStd::disjunction<AZStd::is_same<T, TBaseClasses>...> >), "You cannot reflect a type as its own base");
         static_assert(sizeof...(TBaseClasses) <= c_serializeMaxNumBaseClasses, "Only " AZ_STRINGIZE(c_serializeMaxNumBaseClasses) " base classes are supported. You can add more in c_serializeBaseClassStrings.");
@@ -1958,9 +1986,13 @@ namespace AZ
         }
         else
         {
+            auto createAnyActionHandler = [](SerializeContext* serializeContext) -> AZStd::any::action_handler_for_t
+            {
+                return AnyTypeInfoConcept<T>::GetAnyActionHandler(serializeContext);
+            };
             ClassBuilder builder = ReflectClassInternal(name, typeUuid,
                 factory, deprecatedNameVisitor,
-                GetRttiHelper<T>(), &AnyTypeInfoConcept<T>::CreateAny);
+                GetRttiHelper<T>(), &AnyTypeInfoConcept<T>::CreateAny, AZStd::move(createAnyActionHandler));
             AddClassData<T, TBaseClasses...>(&builder.m_classData->second);
 
             return builder;
@@ -2061,7 +2093,7 @@ namespace AZ
                 AZ_Assert(element.m_typeId != AzTypeInfo<BaseType>::Uuid(),
                     "You can not reflect %s as base class of %s with Class<%s,%s> and then reflect the some of it's fields with FieldFromBase! Either use FieldFromBase or just reflect the entire base class!",
                     AzTypeInfo<BaseType>::Name(), AzTypeInfo<ClassType>::Name(), AzTypeInfo<ClassType>::Name(), AzTypeInfo<BaseType>::Name()
-                    );
+                );
             }
             else
             {
@@ -2523,7 +2555,12 @@ namespace AZ::Serialize
     template<class T>
     ClassData ClassData::Create(const char* name, const Uuid& typeUuid, IObjectFactory* factory, IDataSerializer* serializer, IDataContainer* container)
     {
-        return CreateImpl(name, typeUuid, factory, serializer, container, GetRttiHelper<T>());
+        ClassData createdClassData = CreateImpl(name, typeUuid, factory, serializer, container, GetRttiHelper<T>(),
+            [](SerializeContext* serializeContext) -> AZStd::any::action_handler_for_t
+        {
+            return AnyTypeInfoConcept<T>::GetAnyActionHandler(serializeContext);
+        });
+        return createdClassData;
     }
 }
 

+ 14 - 0
Code/Framework/AzCore/AzCore/std/any.h

@@ -85,6 +85,14 @@ namespace AZStd
             bool m_useHeap = false;
         };
 
+        using action_handler_for_t = type_info::HandleFnT;
+        // Returns a default action handler for the type.
+        // The type must be constructible and destructible
+        // To support copy construction it must be constructible with a `const T&`
+        // To support move construction it must be constructible with a `T&&`
+        template <typename T>
+        static constexpr auto get_action_handler_for_t();
+
         /// Constructs an empty object.
         any(allocator alloc = allocator("AZStd::any"))
             : m_allocator(alloc)
@@ -424,6 +432,12 @@ namespace AZStd
         friend add_pointer_t<ValueType> any_cast(any*);
     };
 
+    template <typename ValueType>
+    constexpr auto any::get_action_handler_for_t()
+    {
+        return &action_handler<ValueType>;
+    }
+
     namespace Internal
     {
         /**

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 44 - 1
Code/Framework/AzCore/Platform/Common/VisualStudio/AzCore/Natvis/azcore.natvis


+ 63 - 20
Code/Framework/AzFramework/AzFramework/DocumentPropertyEditor/Reflection/LegacyReflectionBridge.cpp

@@ -148,6 +148,13 @@ namespace AZ::Reflection
                 // Commonly, it is the parent instance for property nodes, and the instance for UI element nodes.
                 void* m_instanceToInvoke = nullptr;
                 AZ::TypeId m_typeId;
+
+                //! Keeps tracks of how many pointers are attached to the instance type
+                //! For example if the actual instance being referenced is an AZ::Component, then m_pointerLevel is 0
+                //! But if the instance being referenced is an AZ::Component*, then the m_pointerLevel is 1
+                //! Finally if the instance being referenced is an AZ::Component**, then the m_pointerLevel is 2
+                AZ::u32 m_pointerLevel = 0;
+
                 const SerializeContext::ClassData* m_classData = nullptr;
                 const SerializeContext::ClassElement* m_classElement = nullptr;
                 AZStd::vector<AttributeData> m_cachedAttributes;
@@ -185,6 +192,24 @@ namespace AZ::Reflection
                     return nullptr;
                 }
 
+                //! Returns an address that can be casted to the Type of the instance via a "reinterpret_cast<Type*>" cast
+                //! If the instance is storing a pointer to a Type*, that is actually a Type**
+                //! So this code make sure that dereferences occurs before returning the address
+                const void* GetRawInstance() const
+                {
+                    const void* directInstance = m_instance;
+                    for (AZ::u32 pointersToDereference = m_pointerLevel; pointersToDereference > 0; --pointersToDereference)
+                    {
+                        directInstance = *reinterpret_cast<const void* const*>(directInstance);
+                    }
+                    return directInstance;
+                }
+
+                void* GetRawInstance()
+                {
+                    return const_cast<void*>(AZStd::as_const(*this).GetRawInstance());
+                }
+
                 AZStd::vector<AZStd::pair<AZStd::string, AZStd::optional<StackEntry>>> m_groups;
                 AZStd::map<AZStd::string, AZStd::vector<StackEntry>> m_groupEntries;
                 AZStd::map<AZStd::string, AZStd::string> m_propertyToGroupMap;
@@ -212,7 +237,7 @@ namespace AZ::Reflection
                 , m_serializeContext(serializeContext)
             {
                 // Push a dummy node into stack, which serves as the parent node for the first node.
-                m_stack.push_back({ instance, nullptr, typeId });
+                m_stack.emplace_back(StackEntry{ instance, nullptr, typeId });
 
                 m_visitFromRoot = visitFromRoot;
 
@@ -257,7 +282,7 @@ namespace AZ::Reflection
             {
                 m_handlers[azrtti_typeid<T>()] = [this, handler = AZStd::move(handler)]() -> bool
                 {
-                    return handler(*reinterpret_cast<T*>(m_stack.back().m_instance));
+                    return handler(*reinterpret_cast<T*>(Get()));
                 };
             }
 
@@ -283,7 +308,7 @@ namespace AZ::Reflection
 
                 // Note that this is the dummy parent node for the root node. It contains null classData and classElement.
                 const StackEntry& nodeData = m_stack.back();
-                m_serializeContext->EnumerateInstance(m_enumerateContext, nodeData.m_instance, nodeData.m_typeId, nullptr, nullptr);
+                m_serializeContext->EnumerateInstanceConst(m_enumerateContext, nodeData.GetRawInstance(), nodeData.m_typeId, nullptr, nullptr);
             }
 
             void GenerateNodePath(const StackEntry& parentData, StackEntry& nodeData)
@@ -354,12 +379,17 @@ namespace AZ::Reflection
                                 if (currElement.m_serializeClassElement)
                                 {
                                     // groups with a serialize class element are toggle groups, make an entry for their bool value
+                                    const AZ::Serialize::ClassElement* serializeClassElement = currElement.m_serializeClassElement;
                                     void* boolAddress = reinterpret_cast<void*>(
-                                        reinterpret_cast<size_t>(nodeData.m_instance) + currElement.m_serializeClassElement->m_offset);
+                                        reinterpret_cast<size_t>(nodeData.GetRawInstance()) + serializeClassElement->m_offset);
+                                    boolAddress = AZ::Utils::ResolvePointer(boolAddress, *serializeClassElement, *m_serializeContext);
+
+                                    constexpr AZ::u32 pointerLevel = 0;
 
                                     StackEntry entry = { boolAddress,
                                                          nodeData.m_instance,
-                                                         currElement.m_serializeClassElement->m_typeId,
+                                                         serializeClassElement->m_typeId,
+                                                         pointerLevel,
                                                          nullptr,
                                                          currElement.m_serializeClassElement };
                                     nodeData.m_groups.emplace_back(AZStd::make_pair(groupName, AZStd::move(entry)));
@@ -374,9 +404,12 @@ namespace AZ::Reflection
                                     AZ::SerializeContext::ClassElement* UIElement = new AZ::SerializeContext::ClassElement();
                                     UIElement->m_editData = &currElement;
                                     UIElement->m_flags = SerializeContext::ClassElement::Flags::FLG_UI_ELEMENT;
+                                    // A UI node isn't backed with a real C++ type, so its pointer level is 0
+                                    constexpr AZ::u32 pointerLevel =  0;
                                     StackEntry entry = { nodeData.m_instance,
                                                          nodeData.m_instance,
                                                          nodeData.m_classData->m_typeId,
+                                                         pointerLevel,
                                                          nodeData.m_classData,
                                                          UIElement };
 
@@ -433,8 +466,10 @@ namespace AZ::Reflection
                             UIElement->m_editData = &*iter;
                             UIElement->m_flags = SerializeContext::ClassElement::Flags::FLG_UI_ELEMENT;
                             // Use the instance itself to retrieve parameter values that invoke functions on the UI Element.
+                            constexpr AZ::u32 pointerLevel = 0;
                             StackEntry entry = {
-                                nodeData.m_instance, nodeData.m_instance, nodeData.m_classData->m_typeId, nodeData.m_classData, UIElement
+                                nodeData.m_instance, nodeData.m_instance, nodeData.m_classData->m_typeId, pointerLevel,
+                                nodeData.m_classData, UIElement
                             };
 
                             AZStd::string pathString = nodeData.m_path;
@@ -633,11 +668,15 @@ namespace AZ::Reflection
                     }
                 }
 
+                // Set the pointerLevel to 0 as the pointer is resolved by the AZ::Utils::ResolvePointer
+                constexpr AZ::u32 pointerLevel = 0;
+
+
                 // search up the stack for the "true parent", which is the last entry created by the serialize enumerate itself
                 void* instanceToInvoke = instance;
                 for (auto rIter = m_stack.rbegin(), rEnd = m_stack.rend(); rIter != rEnd; ++rIter)
                 {
-                    auto* currInstance = rIter->m_instance;
+                    auto* currInstance = rIter->GetRawInstance();
                     if (rIter->m_createdByEnumerate && currInstance)
                     {
                         instanceToInvoke = currInstance;
@@ -646,13 +685,12 @@ namespace AZ::Reflection
                 }
 
                 StackEntry newEntry = {
-                    instance, instanceToInvoke, classData ? classData->m_typeId : Uuid::CreateNull(), classData, classElement
+                    instance, instanceToInvoke, classData ? classData->m_typeId : Uuid::CreateNull(), pointerLevel,
+                    classData, classElement
                 };
                 newEntry.m_createdByEnumerate = true;
 
-                m_stack.push_back(newEntry);
-
-                StackEntry& nodeData = m_stack.back();
+                StackEntry& nodeData = m_stack.emplace_back(newEntry);
 
                 // Generate this node's path (will be stored in nodeData.m_path)
                 GenerateNodePath(parentData, nodeData);
@@ -738,14 +776,15 @@ namespace AZ::Reflection
                                     continue;
                                 }
                                 auto& parentEntry = m_stack.back();
-                                m_stack.push_back({ groupEntry.m_instance, nullptr, AZ::TypeId() });
-                                InheritChangeNotify(parentEntry, m_stack.back());
+                                StackEntry& newStackEntry = m_stack.emplace_back(StackEntry{ groupEntry.m_instance, nullptr, AZ::TypeId(), groupEntry.m_pointerLevel,
+                                    groupEntry.m_classData, groupEntry.m_classElement });
+                                InheritChangeNotify(parentEntry, newStackEntry);
                                 m_serializeContext->EnumerateInstance(
                                     m_enumerateContext,
-                                    groupEntry.m_instance,
-                                    groupEntry.m_typeId,
-                                    groupEntry.m_classData,
-                                    groupEntry.m_classElement);
+                                    newStackEntry.m_instance,
+                                    newStackEntry.m_typeId,
+                                    newStackEntry.m_classData,
+                                    newStackEntry.m_classElement);
                                 m_stack.pop_back();
                             }
 
@@ -797,14 +836,18 @@ namespace AZ::Reflection
                 return classData ? classData->m_name : "<unregistered type>";
             }
 
+            // This returns a pointer to an instance that can be cast to a <TypeId>* for that instance
+            // Even if the StackEntry::m_instance is referencing a <TypeId>**, a pointer is returned that can be casted to <TypeId>*
             void* Get() override
             {
-                return m_stack.back().m_instance;
+                StackEntry& topEntry = m_stack.back();
+                return topEntry.GetRawInstance();
             }
 
             const void* Get() const override
             {
-                return m_stack.back().m_instance;
+                const StackEntry& topEntry = m_stack.back();
+                return topEntry.GetRawInstance();
             }
 
             AZStd::string_view GetNodeDisplayLabel(StackEntry& nodeData, AZStd::fixed_string<128>& labelAttributeBuffer)
@@ -930,7 +973,7 @@ namespace AZ::Reflection
                         }
                         visitedAttributes.insert(name);
 
-                        // Handle visibility calculations internally, as we calculate and emit an aggregate visiblity value.
+                        // Handle visibility calculations internally, as we calculate and emit an aggregate visibility value.
                         // We also need to handle special cases here, because the Visibility attribute supports 3 different value types:
                         //      1. AZ::Crc32 - This is the default
                         //      2. AZ::u32 - This allows the user to specify a value of 1/0 for Show/Hide, respectively

+ 54 - 33
Code/Framework/AzFramework/AzFramework/DocumentPropertyEditor/ReflectionAdapter.cpp

@@ -330,7 +330,7 @@ namespace AZ::DocumentPropertyEditor
             attributes.ListAttributes(
                 [this](AZ::Name group, AZ::Name name, const Dom::Value& value)
                 {
-                    AZ_Warning("dpe", !name.IsEmpty(), "DPE: Received empty name in ListAttributes");
+                    AZ_Warning("ReflectionAdapter", !name.IsEmpty(), "Received empty name in ListAttributes");
                     // Skip non-default groups, we don't presently source any attributes from outside of the default group.
                     if (!group.IsEmpty())
                     {
@@ -477,7 +477,7 @@ namespace AZ::DocumentPropertyEditor
                 [&value](const Dom::Value& newValue)
                 {
                     AZStd::optional<T> extractedValue = Dom::Utils::ValueToType<T>(newValue);
-                    AZ_Warning("DPE", extractedValue.has_value(), "OnChanged failed, unable to extract value from DOM");
+                    AZ_Warning("ReflectionAdapter", extractedValue.has_value(), "OnChanged failed, unable to extract value from DOM");
                     if (extractedValue.has_value())
                     {
                         value = AZStd::move(extractedValue.value());
@@ -780,43 +780,64 @@ namespace AZ::DocumentPropertyEditor
                             typeSize = rttiHelper->GetTypeSize();
                         }
                     }
+
+                    // this needs to write the value back into the reflected object via Json serialization
+                    auto StoreValueIntoPointer = [valuePointer = access.Get(), valueType = access.GetType(), this](const Dom::Value& newValue)
+                    {
+                        // marshal this new value into a pointer for use by the Json serializer
+                        auto marshalledPointer = AZ::Dom::Utils::TryMarshalValueToPointer(newValue, valueType);
+
+                        rapidjson::Document buffer;
+                        JsonSerializerSettings serializeSettings;
+                        JsonDeserializerSettings deserializeSettings;
+                        serializeSettings.m_serializeContext = m_serializeContext;
+                        deserializeSettings.m_serializeContext = m_serializeContext;
+
+                        // serialize the new value to Json, using the original valuePointer as a reference object to generate a minimal
+                        // diff
+                        AZ::JsonSerializationResult::ResultCode resultCode = JsonSerialization::Store(
+                            buffer, buffer.GetAllocator(), marshalledPointer, valuePointer, valueType, serializeSettings);
+
+                        if (resultCode.GetProcessing() == AZ::JsonSerializationResult::Processing::Halted)
+                        {
+                            AZ_Error(
+                                "ReflectionAdapter",
+                                false,
+                                "Storing new property editor value to JSON for copying to instance has failed with error %s",
+                                resultCode.ToString("").c_str());
+                            return newValue;
+                        }
+
+                        // now deserialize that value into the original location
+                        resultCode = JsonSerialization::Load(valuePointer, valueType, buffer, deserializeSettings);
+                        if (resultCode.GetProcessing() == AZ::JsonSerializationResult::Processing::Halted)
+                        {
+                            AZ_Error(
+                                "ReflectionAdapter",
+                                false,
+                                "Loading JSON value containing new property editor into instance has failed with error %s",
+                                resultCode.ToString("").c_str());
+                            return newValue;
+                        }
+
+                        // NB: the returned value for serialized pointer values is instancePointerValue, but since this is passed by
+                        // pointer, it will not actually detect a changed dom value. Since we are already writing directly to the DOM
+                        // before this step, it won't affect the calling DPE, however, other DPEs pointed at the same adapter would be
+                        // unaware of the change, and wouldn't update their UI. In future, to properly support multiple DPEs on one
+                        // adapter, we will need to solve this. One way would be to store the json serialized value (which is mostly
+                        // human-readable text) as an attribute, so any change to the Json would trigger an update. This would have the
+                        // advantage of allowing opaque and pointer types to be searchable by the string-based Filter adapter. Without
+                        // this, things like Vector3 will not have searchable values by text. These advantages would have to be measured
+                        // against the size changes in the DOM and the time taken to populate and parse them.
+                        return newValue;
+                    };
                     void* instance = access.Get();
                     VisitValue(
                         instancePointerValue,
                         instance,
                         typeSize,
                         attributes,
-                        // this needs to write the value back into the reflected object via Json serialization
-                        [valuePointer = access.Get(), valueType = access.GetType(), this](const Dom::Value& newValue)
-                        {
-                            // marshal this new value into a pointer for use by the Json serializer
-                            auto marshalledPointer = AZ::Dom::Utils::TryMarshalValueToPointer(newValue, valueType);
-
-                            rapidjson::Document buffer;
-                            JsonSerializerSettings serializeSettings;
-                            JsonDeserializerSettings deserializeSettings;
-                            serializeSettings.m_serializeContext = m_serializeContext;
-                            deserializeSettings.m_serializeContext = m_serializeContext;
-
-                            // serialize the new value to Json, using the original valuePointer as a reference object to generate a minimal
-                            // diff
-                            JsonSerialization::Store(
-                                buffer, buffer.GetAllocator(), marshalledPointer, valuePointer, valueType, serializeSettings);
-
-                            // now deserialize that value into the original location
-                            JsonSerialization::Load(valuePointer, valueType, buffer, deserializeSettings);
-
-                            // NB: the returned value for serialized pointer values is instancePointerValue, but since this is passed by
-                            // pointer, it will not actually detect a changed dom value. Since we are already writing directly to the DOM
-                            // before this step, it won't affect the calling DPE, however, other DPEs pointed at the same adapter would be
-                            // unaware of the change, and wouldn't update their UI. In future, to properly support multiple DPEs on one
-                            // adapter, we will need to solve this. One way would be to store the json serialized value (which is mostly
-                            // human-readable text) as an attribute, so any change to the Json would trigger an update. This would have the
-                            // advantage of allowing opaque and pointer types to be searchable by the string-based Filter adapter. Without
-                            // this, things like Vector3 will not have searchable values by text. These advantages would have to be measured
-                            // against the size changes in the DOM and the time taken to populate and parse them.
-                            return newValue;
-                        },
+                        AZStd::move(StoreValueIntoPointer),
                         false,
                         hashValue);
                 }

+ 1 - 1
Code/Framework/AzToolsFramework/AzToolsFramework/Fingerprinting/TypeFingerprinter.cpp

@@ -76,7 +76,7 @@ namespace AzToolsFramework
             for (const AZ::SerializeContext::ClassElement& element : classData.m_elements)
             {
                 AZStd::hash_combine(fingerprint, element.m_typeId);
-                AZStd::hash_range(fingerprint, element.m_name, element.m_name + strlen(element.m_name));
+                AZStd::hash_combine(fingerprint, AZStd::string_view(element.m_name));
                 AZStd::hash_combine(fingerprint, element.m_flags);
             }
 

+ 65 - 10
Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI_Internals.h

@@ -253,7 +253,12 @@ namespace AzToolsFramework
                         auto parentValuePtr = AZ::Dom::Utils::ValueToTypeUnsafe<void*>(parentValue.value());
                         AZ_Assert(parentValuePtr, "Parent instance was nullptr when attempting to add to instance list.");
 
-                        m_proxyParentNode.m_instances.push_back(parentValuePtr);
+                        // Only add parent instance if it has not been added previously
+                        if (auto foundParentIt = AZStd::find(m_proxyParentNode.m_instances.begin(), m_proxyParentNode.m_instances.end(), parentValuePtr);
+                            foundParentIt == m_proxyParentNode.m_instances.end())
+                        {
+                            m_proxyParentNode.m_instances.push_back(parentValuePtr);
+                        }
 
                         // Set up the reference to parent node only if a parent value is available.
                         m_proxyNode.m_parent = &m_proxyParentNode;
@@ -266,11 +271,6 @@ namespace AzToolsFramework
                 // to tell which property is currently processing the attributes.
                 else if (name == AZ::Reflection::DescriptorAttributes::SerializedPath)
                 {
-                    auto elementName = AZ::Dom::Utils::ValueToType<AZStd::string_view>(attributeIt->second);
-                    if (elementName.has_value())
-                    {
-                        m_proxyClassElement.m_name = elementName.value().data();
-                    }
                     continue;
                 }
 
@@ -520,18 +520,73 @@ namespace AzToolsFramework
             // NOTE: The exception to this is for enum types, which might have a specialized type vs. an underlying type (e.g. short, int,
             // etc...) in which case, the proxy value still comes as a primitive instead of a pointer, so we still need to use ValueFromType
             // for that case. For everything else, we can just use ValueFromType which can imply the type from the value itself.
+
+            // Use the serialize context to lookup the AZStd any action handler
+            AZ::SerializeContext* serializeContext{};
+            if (auto componentApplicationRequests = AZ::Interface<AZ::ComponentApplicationRequests>::Get();
+                componentApplicationRequests != nullptr)
+            {
+                serializeContext = componentApplicationRequests->GetSerializeContext();
+            }
+
             AZ::Dom::Value newValue;
-            if (!typeId.IsNull() && (typeId != m_proxyClassData.m_typeId) && !m_domNode.HasMember(PropertyEditor::EnumType.GetName()))
+            bool newValueSet{};
+            if (const AZ::SerializeContext::ClassData* classData = serializeContext != nullptr ? serializeContext->FindClassData(typeId) : nullptr;
+                classData != nullptr && !typeId.IsNull() && (typeId != m_proxyClassData.m_typeId) && !m_domNode.HasMember(PropertyEditor::EnumType.GetName()))
             {
-                newValue = AZ::Dom::Utils::MarshalTypedPointerToValue(&m_proxyValue, typeId);
+                // Cast the proxy value to typeId type if is a pointer
+                void* proxyAddress{};
+                if constexpr(AZStd::is_pointer_v<WrappedType>)
+                {
+                    if (auto proxyRtti = m_proxyClassData.m_azRtti; proxyRtti != nullptr)
+                    {
+                        proxyAddress = proxyRtti->Cast(m_proxyValue, typeId);
+                    }
+                }
+                else if (classData->CanConvertFromType(m_proxyClassData.m_typeId, *serializeContext))
+                {
+                    // Otherwise attempt to check if the proxy value is convertible to the TypeId type
+                    // such as for the case of an AZ::Data::Asset<T> from an AZ::Data::Asset<AZ::Data::AssetData>
+                    void* sourceAddress{};
+                    if constexpr (AZStd::is_pointer_v<WrappedType>)
+                    {
+                        sourceAddress = m_proxyValue;
+                    }
+                    else
+                    {
+                        sourceAddress = &m_proxyValue;
+                    }
+                    classData->ConvertFromType(proxyAddress, m_proxyClassData.m_typeId, sourceAddress, *serializeContext);
+                }
+
+                if (proxyAddress != nullptr)
+                {
+                    AZ::Dom::Utils::MarshalTypeTraits marshalTypeTraits;
+                    marshalTypeTraits.m_typeId = typeId;
+                    marshalTypeTraits.m_isPointer = AZStd::is_pointer_v<WrappedType>;
+                    marshalTypeTraits.m_isReference = AZStd::is_reference_v<WrappedType>;
+                    marshalTypeTraits.m_isCopyConstructible = AZStd::is_copy_constructible_v<WrappedType>;
+                    marshalTypeTraits.m_typeSize = classData->m_azRtti!= nullptr ? classData->m_azRtti->GetTypeSize() : sizeof(WrappedType);
+
+                    if (classData->m_createAzStdAnyActionHandler)
+                    {
+                        auto anyActionHandler = classData->m_createAzStdAnyActionHandler(serializeContext);
+                        newValue = AZ::Dom::Utils::ValueFromType(proxyAddress, marshalTypeTraits, AZStd::move(anyActionHandler));
+                        newValueSet = true;
+                    }
+                }
             }
             else
             {
                 newValue = AZ::Dom::Utils::ValueFromType(m_proxyValue);
+                newValueSet = true;
             }
 
-            PropertyEditor::OnChanged.InvokeOnDomNode(m_domNode, newValue, changeType);
-            OnRequestPropertyNotify();
+            if (newValueSet)
+            {
+                PropertyEditor::OnChanged.InvokeOnDomNode(m_domNode, newValue, changeType);
+                OnRequestPropertyNotify();
+            }
         }
 
         void OnRequestPropertyNotify() override

+ 4 - 7
Code/Framework/AzToolsFramework/Tests/EntityOwnershipService/SliceEntityOwnershipTests.cpp

@@ -287,15 +287,12 @@ namespace UnitTest
 
     TEST_F(SliceEntityOwnershipTests, InstantiateSlice_InvalidAssetId_ReturnBlankInstantiationTicket)
     {
-        AZ::Entity* sliceEntity = aznew AZ::Entity();
-        AZ::SliceComponent* sliceComponent = sliceEntity->CreateComponent<AZ::SliceComponent>();
-        sliceComponent->SetSerializeContext(m_app->GetSerializeContext());
-        sliceComponent->AddEntity(aznew AZ::Entity());
-
         // Set the asset id to null to invalidate it.
+        AZ_TEST_START_TRACE_SUPPRESSION;
         AZ::Data::Asset<AZ::SliceAsset> sliceAssetHolder = AZ::Data::AssetManager::Instance().
-            CreateAsset<AZ::SliceAsset>(AZ::Data::AssetId(AZ::Uuid::CreateNull()));
-        sliceAssetHolder.Get()->SetData(sliceEntity, sliceComponent);
+            CreateAsset<AZ::SliceAsset>(AZ::Data::AssetId{});
+        AZ_TEST_STOP_TRACE_SUPPRESSION(1);
+        EXPECT_EQ(nullptr, sliceAssetHolder.Get());
 
         AzFramework::SliceInstantiationTicket sliceInstantiationTicket;
         AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(sliceInstantiationTicket,

+ 37 - 29
Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowWidget.cpp

@@ -96,8 +96,8 @@ namespace AZ
             {
                 m_scale = scale;
             }
-            
-            
+
+
             AZ_CLASS_ALLOCATOR_IMPL(TransformRowWidget, SystemAllocator);
 
             TransformRowWidget::TransformRowWidget(QWidget* parent)
@@ -109,10 +109,15 @@ namespace AZ
                 QGridLayout* layout2 = new QGridLayout();
                 setLayout(layout);
                 AzToolsFramework::PropertyRowWidget* parentWidget = static_cast<AzToolsFramework::PropertyRowWidget*>(parent);
-                QToolButton* toolButton = parentWidget->GetIndicatorButton();
-                QVBoxLayout* layoutOriginal = parentWidget->GetLeftHandSideLayoutParent();
-                parentWidget->SetAsCustom(true);
-                parentWidget->GetNameLabel()->setContentsMargins(0, 0, 0, 0);
+                QToolButton* toolButton{};
+                QVBoxLayout* layoutOriginal{};
+                if (parentWidget != nullptr)
+                {
+                    toolButton = parentWidget->GetIndicatorButton();
+                    layoutOriginal = parentWidget->GetLeftHandSideLayoutParent();
+                    parentWidget->SetAsCustom(true);
+                    parentWidget->GetNameLabel()->setContentsMargins(0, 0, 0, 0);
+                }
 
                 m_translationWidget = new AzQtComponents::VectorInput(this, 3);
                 m_translationWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
@@ -132,7 +137,7 @@ namespace AZ
                 m_scaleWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
                 m_scaleWidget->setMinimum(0);
                 m_scaleWidget->setMaximum(10000);
-                
+
                 layout2->addWidget(new  AzQtComponents::ElidingLabel("Position"), 0, 1);
                 layout->addWidget(m_translationWidget, 1, 1);
                 layout2->addWidget(new  AzQtComponents::ElidingLabel("Rotation"), 1, 1);
@@ -142,29 +147,32 @@ namespace AZ
                 layout->setRowMinimumHeight(0,16);
                 layout2->setColumnMinimumWidth(0, 30);
 
-                toolButton->setArrowType(Qt::DownArrow);
-                parentWidget->SetIndentSize(1);
-                toolButton->setVisible(true);
+                if (parentWidget != nullptr)
+                {
+                    toolButton->setArrowType(Qt::DownArrow);
+                    parentWidget->SetIndentSize(1);
+                    toolButton->setVisible(true);
 
-                m_containerWidget->setLayout(layout2);
-                layoutOriginal->addWidget(m_containerWidget);
+                    m_containerWidget->setLayout(layout2);
+                    layoutOriginal->addWidget(m_containerWidget);
 
-                connect(toolButton, &QToolButton::clicked, this, [&, toolButton]
-                {
-                    m_expanded = !m_expanded;
-                    if (m_expanded)
-                    {
-                        show();
-                        m_containerWidget->show();
-                        toolButton->setArrowType(Qt::DownArrow);
-                    }
-                    else
+                    connect(toolButton, &QToolButton::clicked, this, [&, toolButton]
                     {
-                        hide();
-                        m_containerWidget->hide();
-                        toolButton->setArrowType(Qt::RightArrow);
-                    }
-                });
+                        m_expanded = !m_expanded;
+                        if (m_expanded)
+                        {
+                            show();
+                            m_containerWidget->show();
+                            toolButton->setArrowType(Qt::DownArrow);
+                        }
+                        else
+                        {
+                            hide();
+                            m_containerWidget->hide();
+                            toolButton->setArrowType(Qt::RightArrow);
+                        }
+                    });
+                }
 
                 QObject::connect(m_translationWidget, &AzQtComponents::VectorInput::valueChanged, this, [this]
                 {
@@ -215,9 +223,9 @@ namespace AZ
             void TransformRowWidget::SetTransform(const AZ::Transform& transform)
             {
                 blockSignals(true);
-                
+
                 m_transform.SetTransform(transform);
-                
+
                 m_translationWidget->setValuebyIndex(m_transform.GetTranslation().GetX(), 0);
                 m_translationWidget->setValuebyIndex(m_transform.GetTranslation().GetY(), 1);
                 m_translationWidget->setValuebyIndex(m_transform.GetTranslation().GetZ(), 2);

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio