From d00436e26a19085ec3ff2a4b73b67f9536fb609c Mon Sep 17 00:00:00 2001 From: AMZN-koppersr <82230785+AMZN-koppersr@users.noreply.github.com> Date: Thu, 10 Feb 2022 10:57:16 -0800 Subject: [PATCH 1/3] Improved the way the Settings Registry can handle stacks/arrays. The ConfigurableStack makes configuring stacks and arrays through the Settings Registry easier than using direct serialization to a container like AZStd::vector. It does this by using JSON Objects rather than JSON arrays, although arrays are supported for backwards compatibility. Two key words were added: - $stack_before : Insert the new entry before the referenced entry. Referencing is done by name. - $stack_after : Insert the new entry after the referenced entry. Referencing is done by name. to allow inserting new entries at specific locations. An example of a .setreg file at updates existing settings would be: // Original settings { "Settings in a stack": { "AnOriginalEntry": { "MyValue": "hello", "ExampleValue": 84 }, "TheSecondEntry": { "MyValue": "world" } } } // Customized settings. { "Settings in a stack": { // Add a new entry before "AnOriginalEntry" in the original document. "NewEntry": { "$stack_before": "AnOriginalEntry", "MyValue": 42 }, // Add a second entry after "AnOriginalEntry" in the original document. "SecondNewEntry": { "$stack_after": "AnOriginalEntry", "MyValue": "FortyTwo". }, // Update a value in "AnOriginalEntry". "AnOriginalEntry": { "ExampleValue": 42 }, // Delete the "TheSecondEntry" from the settings. "TheSecondEntry" : null, } } The ConfigurableStack uses an AZStd::shared_ptr to store the values. This supports settings up a base class and specifying derived classes in the settings, but requires that the base and derived classes all have a memory allocator associated with them (i.e. by using the "AZ_CLASS_ALLOCATOR" macro) and that the relation of the classes is reflected. Loading a ConfigurableStack can be done using the GetObject call on the SettingsRegistryInterface. Signed-off-by: AMZN-koppersr <82230785+AMZN-koppersr@users.noreply.github.com> --- .../Json/JsonSystemComponent.cpp | 3 + .../Serialization/Json/RegistrationContext.h | 4 +- .../AzCore/Settings/ConfigurableStack.cpp | 174 +++++++++++ .../AzCore/Settings/ConfigurableStack.h | 189 ++++++++++++ .../AzCore/Settings/ConfigurableStack.inl | 161 ++++++++++ .../AzCore/AzCore/azcore_files.cmake | 3 + .../Tests/Settings/ConfigurableStackTests.cpp | 283 ++++++++++++++++++ .../AzCore/Tests/azcoretests_files.cmake | 1 + 8 files changed, 817 insertions(+), 1 deletion(-) create mode 100644 Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.cpp create mode 100644 Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.h create mode 100644 Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.inl create mode 100644 Code/Framework/AzCore/Tests/Settings/ConfigurableStackTests.cpp diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSystemComponent.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSystemComponent.cpp index 0ba44e4e61..c08f038c8b 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSystemComponent.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSystemComponent.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +51,8 @@ namespace AZ void JsonSystemComponent::Reflect(ReflectContext* reflectContext) { + JsonConfigurableStackSerializer::Reflect(reflectContext); + if (JsonRegistrationContext* jsonContext = azrtti_cast(reflectContext)) { jsonContext->Serializer()->HandlesType(); diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/RegistrationContext.h b/Code/Framework/AzCore/AzCore/Serialization/Json/RegistrationContext.h index 25a3bfedba..f62dd54e71 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/RegistrationContext.h +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/RegistrationContext.h @@ -8,11 +8,12 @@ #pragma once +#include +#include #include #include #include #include -#include #include namespace AZ @@ -22,6 +23,7 @@ namespace AZ { public: AZ_RTTI(JsonRegistrationContext, "{5A763774-CA8B-4245-A897-A03C503DCD60}", ReflectContext); + AZ_CLASS_ALLOCATOR(JsonRegistrationContext, SystemAllocator, 0); class SerializerBuilder; using SerializerMap = AZStd::unordered_map, AZStd::hash>; diff --git a/Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.cpp b/Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.cpp new file mode 100644 index 0000000000..75748e0f11 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace AZ +{ + AZ_CLASS_ALLOCATOR_IMPL(JsonConfigurableStackSerializer, AZ::SystemAllocator, 0); + + JsonSerializationResult::Result JsonConfigurableStackSerializer::Load( + void* outputValue, + [[maybe_unused]] const Uuid& outputValueTypeId, + const rapidjson::Value& inputValue, + JsonDeserializerContext& context) + { + namespace JSR = JsonSerializationResult; // Used remove name conflicts in AzCore in uber builds. + + switch (inputValue.GetType()) + { + case rapidjson::kArrayType: + return LoadFromArray(outputValue, inputValue, context); + case rapidjson::kObjectType: + return LoadFromObject(outputValue, inputValue, context); + + case rapidjson::kNullType: + [[fallthrough]]; + case rapidjson::kFalseType: + [[fallthrough]]; + case rapidjson::kTrueType: + [[fallthrough]]; + case rapidjson::kStringType: + [[fallthrough]]; + case rapidjson::kNumberType: + return context.Report( + JSR::Tasks::ReadField, JSR::Outcomes::Unsupported, + "Unsupported type. Configurable stack values can only be read from arrays or objects."); + default: + return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unknown, "Unknown json type encountered for string value."); + } + } + + JsonSerializationResult::Result JsonConfigurableStackSerializer::Store( + [[maybe_unused]] rapidjson::Value& outputValue, + [[maybe_unused]] const void* inputValue, + [[maybe_unused]] const void* defaultValue, + [[maybe_unused]] const Uuid& valueTypeId, + JsonSerializerContext& context) + { + return context.Report( + JsonSerializationResult::Tasks::WriteValue, JsonSerializationResult::Outcomes::Unsupported, + "Configuration stacks can not be written out."); + } + + void JsonConfigurableStackSerializer::Reflect(ReflectContext* context) + { + if (JsonRegistrationContext* jsonContext = azrtti_cast(context)) + { + jsonContext->Serializer()->HandlesType(); + } + } + + JsonSerializationResult::Result JsonConfigurableStackSerializer::LoadFromArray( + void* outputValue, const rapidjson::Value& inputValue, JsonDeserializerContext& context) + { + namespace JSR = JsonSerializationResult; // Used remove name conflicts in AzCore in uber builds. + + auto stack = reinterpret_cast(outputValue); + const Uuid& nodeValueType = stack->GetNodeType(); + + JSR::ResultCode result(JSR::Tasks::ReadField); + uint32_t counter = 0; + for (auto& it : inputValue.GetArray()) + { + ScopedContextPath subPath(context, counter); + void* value = stack->AddNode(AZStd::to_string(counter)); + result.Combine(ContinueLoading(value, nodeValueType, it, context)); + counter++; + } + + return context.Report( + result, + result.GetProcessing() != JSR::Processing::Halted ? "Loaded configurable stack from array." + : "Failed to load configurable stack from array."); + } + + JsonSerializationResult::Result JsonConfigurableStackSerializer::LoadFromObject( + void* outputValue, const rapidjson::Value& inputValue, JsonDeserializerContext& context) + { + namespace JSR = JsonSerializationResult; // Used remove name conflicts in AzCore in uber builds. + + auto stack = reinterpret_cast(outputValue); + const Uuid& nodeValueType = stack->GetNodeType(); + + AZStd::queue> + delayedEntries; + JSR::ResultCode result(JSR::Tasks::ReadField); + + auto add = [&](ConfigurableStackInterface::InsertPosition position, rapidjson::Value::ConstMemberIterator target, + rapidjson::Value::ConstMemberIterator it) + { + if (target->value.IsString()) + { + delayedEntries.emplace(position, AZStd::string_view(target->value.GetString(), target->value.GetStringLength()), it); + } + else + { + result.Combine(context.Report( + JSR::Tasks::ReadField, JSR::Outcomes::Skipped, + "Skipped value for the Configurable Stack because the target wasn't a string.")); + } + }; + + // Load all the regular entries into the stack and store any with a before or after binding for + // later inserting. + for (auto it = inputValue.MemberBegin(); it != inputValue.MemberEnd(); ++it) + { + AZStd::string_view name(it->name.GetString(), it->name.GetStringLength()); + ScopedContextPath subPath(context, name); + if (it->value.IsObject()) + { + if (auto target = it->value.FindMember(StackBefore); target != it->value.MemberEnd()) + { + add(ConfigurableStackInterface::InsertPosition::Before, target, it); + continue; + } + if (auto target = it->value.FindMember(StackAfter); target != it->value.MemberEnd()) + { + add(ConfigurableStackInterface::InsertPosition::After, target, it); + continue; + } + } + + void* value = stack->AddNode(name); + result.Combine(ContinueLoading(value, nodeValueType, it->value, context)); + } + + // Insert the entries that have been delayed. + while (!delayedEntries.empty()) + { + auto&& [insertPosition, target, valueStore] = delayedEntries.front(); + AZStd::string_view name(valueStore->name.GetString(), valueStore->name.GetStringLength()); + ScopedContextPath subPath(context, name); + void* value = stack->AddNode(name, target, insertPosition); + if (value != nullptr) + { + result.Combine(ContinueLoading(value, nodeValueType, valueStore->value, context)); + } + else + { + result.Combine(context.Report( + JSR::Tasks::ReadField, JSR::Outcomes::Skipped, + AZStd::string::format( + "Skipped value for the Configurable Stack because the target '%.*s' couldn't be found.", AZ_STRING_ARG(name)))); + } + delayedEntries.pop(); + } + + return context.Report( + result, + result.GetProcessing() != JSR::Processing::Halted ? "Loaded configurable stack from array." + : "Failed to load configurable stack from array."); + } +} // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.h b/Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.h new file mode 100644 index 0000000000..bafb6f47a3 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.h @@ -0,0 +1,189 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace AZ +{ + //! The ConfigurableStack makes configuring stacks and arrays through the Settings Registry easier than using direct serialization + //! to a container like AZStd::vector. It does this by using JSON Objects rather than JSON arrays, although arrays are supported + //! for backwards compatibility. Two key words were added: + //! - $stack_before : Insert the new entry before the referenced entry. Referencing is done by name. + //! - $stack_after : Insert the new entry after the referenced entry. Referencing is done by name. + //! to allow inserting new entries at specific locations. An example of a .setreg file at updates existing settings would be: + //! // Original settings + //! { + //! "Settings in a stack": + //! { + //! "AnOriginalEntry": + //! { + //! "MyValue": "hello", + //! "ExampleValue": 84 + //! }, + //! "TheSecondEntry": + //! { + //! "MyValue": "world" + //! } + //! } + //! } + //! + //! // Customized settings. + //! { + //! "Settings in a stack": + //! { + //! // Add a new entry before "AnOriginalEntry" in the original document. + //! "NewEntry": + //! { + //! "$stack_before": "AnOriginalEntry", + //! "MyValue": 42 + //! }, + //! // Add a second entry after "AnOriginalEntry" in the original document. + //! "SecondNewEntry": + //! { + //! "$stack_after": "AnOriginalEntry", + //! "MyValue": "FortyTwo". + //! }, + //! // Update a value in "AnOriginalEntry". + //! "AnOriginalEntry": + //! { + //! "ExampleValue": 42 + //! }, + //! // Delete the "TheSecondEntry" from the settings. + //! "TheSecondEntry" : null, + //! } + //! } + //! + //! The ConfigurableStack uses an AZStd::shared_ptr to store the values. This supports settings up a base class and specifying + //! derived classes in the settings, but requires that the base and derived classes all have a memory allocator associated with + //! them (i.e. by using the "AZ_CLASS_ALLOCATOR" macro) and that the relation of the classes is reflected. Loading a + //! ConfigurableStack can be done using the GetObject call on the SettingsRegistryInterface. + + class ReflectContext; + + class ConfigurableStackInterface + { + public: + friend class JsonConfigurableStackSerializer; + + virtual ~ConfigurableStackInterface() = default; + + virtual const TypeId& GetNodeType() const = 0; + + protected: + enum class InsertPosition + { + Before, + After + }; + virtual void* AddNode(AZStd::string name) = 0; + virtual void* AddNode(AZStd::string name, AZStd::string_view target, InsertPosition position) = 0; + }; + + template + class ConfigurableStack final : public ConfigurableStackInterface + { + public: + using NodeValue = AZStd::shared_ptr; + using Node = AZStd::pair; + using NodeContainer = AZStd::vector; + using Iterator = typename NodeContainer::iterator; + using ConstIterator = typename NodeContainer::const_iterator; + + ~ConfigurableStack() override = default; + + const TypeId& GetNodeType() const override; + + Iterator begin(); + Iterator end(); + ConstIterator begin() const; + ConstIterator end() const; + ConstIterator cbegin() const; + ConstIterator cend() const; + + size_t size() const; + + protected: + void* AddNode(AZStd::string name) override; + void* AddNode(AZStd::string name, AZStd::string_view target, InsertPosition position) override; + + private: + NodeContainer m_nodes; + }; + + AZ_TYPE_INFO_TEMPLATE(ConfigurableStack, "{0A3D2038-6E6A-4EFD-A1B4-F30D947E21B1}", AZ_TYPE_INFO_TYPENAME); + + template + struct SerializeGenericTypeInfo> + { + using ConfigurableStackType = ConfigurableStack; + + class GenericConfigurableStackInfo : public GenericClassInfo + { + public: + AZ_TYPE_INFO(GenericConfigurableStackInfo, "{FC5A9353-D0DE-48F6-81B5-1CB2985C5F65}"); + + GenericConfigurableStackInfo(); + + SerializeContext::ClassData* GetClassData() override; + size_t GetNumTemplatedArguments() override; + const Uuid& GetTemplatedTypeId([[maybe_unused]] size_t element) override; + const Uuid& GetSpecializedTypeId() const override; + const Uuid& GetGenericTypeId() const override; + + void Reflect(SerializeContext* serializeContext) override; + + SerializeContext::ClassData m_classData; + }; + + using ClassInfoType = GenericConfigurableStackInfo; + + static ClassInfoType* GetGenericInfo(); + static const Uuid& GetClassTypeId(); + }; + + class JsonConfigurableStackSerializer : public BaseJsonSerializer + { + public: + AZ_RTTI(JsonConfigurableStackSerializer, "{45A31805-9058-41A9-B1A3-71E2CB4D9237}", BaseJsonSerializer); + AZ_CLASS_ALLOCATOR_DECL; + + JsonSerializationResult::Result Load( + void* outputValue, + const Uuid& outputValueTypeId, + const rapidjson::Value& inputValue, + JsonDeserializerContext& context) override; + + JsonSerializationResult::Result Store( + rapidjson::Value& outputValue, + const void* inputValue, + const void* defaultValue, + const Uuid& valueTypeId, + JsonSerializerContext& context) override; + + static void Reflect(ReflectContext* context); + + private: + JsonSerializationResult::Result LoadFromArray( + void* outputValue, const rapidjson::Value& inputValue, JsonDeserializerContext& context); + JsonSerializationResult::Result LoadFromObject( + void* outputValue, const rapidjson::Value& inputValue, JsonDeserializerContext& context); + + static constexpr const char* StackBefore = "$stack_before"; + static constexpr const char* StackAfter = "$stack_after"; + }; +} // namespace AZ + +#include diff --git a/Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.inl b/Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.inl new file mode 100644 index 0000000000..ba34ac1d03 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.inl @@ -0,0 +1,161 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +namespace AZ +{ + // + // ConfigurableStack + // + + template + const TypeId& ConfigurableStack::GetNodeType() const + { + return azrtti_typeid(); + } + + template + auto ConfigurableStack::begin() -> Iterator + { + return m_nodes.begin(); + } + + template + auto ConfigurableStack::end() -> Iterator + { + return m_nodes.end(); + } + + template + auto ConfigurableStack::begin() const -> ConstIterator + { + return m_nodes.begin(); + } + + template + auto ConfigurableStack::end() const -> ConstIterator + { + return m_nodes.end(); + } + + template + auto ConfigurableStack::cbegin() const -> ConstIterator + { + return m_nodes.cbegin(); + } + + template + auto ConfigurableStack::cend() const -> ConstIterator + { + return m_nodes.cend(); + } + + template + size_t ConfigurableStack::size() const + { + return m_nodes.size(); + } + + template + void* ConfigurableStack::AddNode(AZStd::string name) + { + Node& result = m_nodes.emplace_back(AZStd::move(name)); + return &result.second; + } + + template + void* ConfigurableStack::AddNode(AZStd::string name, AZStd::string_view target, InsertPosition position) + { + auto end = m_nodes.end(); + for (auto it = m_nodes.begin(); it != end; ++it) + { + if (it->first == target) + { + if (position == InsertPosition::After) + { + ++it; + } + auto result = m_nodes.insert(it, Node(AZStd::move(name), {})); + return &result->second; + } + } + return nullptr; + } + + + + // + // SerializeGenericTypeInfo + // + + + template + SerializeGenericTypeInfo>::GenericConfigurableStackInfo::GenericConfigurableStackInfo() + : m_classData{ SerializeContext::ClassData::Create( + "AZ::ConfigurableStack", GetSpecializedTypeId(), Internal::NullFactory::GetInstance(), nullptr, nullptr) } + { + } + + template + SerializeContext::ClassData* SerializeGenericTypeInfo>::GenericConfigurableStackInfo::GetClassData() + { + return &m_classData; + } + + template + size_t SerializeGenericTypeInfo>::GenericConfigurableStackInfo::GetNumTemplatedArguments() + { + return 1; + } + + template + const Uuid& SerializeGenericTypeInfo>::GenericConfigurableStackInfo::GetTemplatedTypeId( + [[maybe_unused]] size_t element) + { + return SerializeGenericTypeInfo::GetClassTypeId(); + } + + template + const Uuid& SerializeGenericTypeInfo>::GenericConfigurableStackInfo::GetSpecializedTypeId() const + { + return azrtti_typeid(); + } + + template + const Uuid& SerializeGenericTypeInfo>::GenericConfigurableStackInfo::GetGenericTypeId() const + { + return TYPEINFO_Uuid(); + } + + template + void SerializeGenericTypeInfo>::GenericConfigurableStackInfo::Reflect(SerializeContext* serializeContext) + { + if (serializeContext) + { + serializeContext->RegisterGenericClassInfo(GetSpecializedTypeId(), this, &AnyTypeInfoConcept::CreateAny); + if (serializeContext->FindClassData(azrtti_typeid>()) == nullptr) + { + serializeContext->RegisterGenericType>(); + } + } + } + + template + auto SerializeGenericTypeInfo>::GetGenericInfo() -> ClassInfoType* + { + return GetCurrentSerializeContextModule().CreateGenericClassInfo(); + } + + template + const Uuid& SerializeGenericTypeInfo>::GetClassTypeId() + { + return GetGenericInfo()->GetClassData()->m_typeId; + } +} // namespace AZ + diff --git a/Code/Framework/AzCore/AzCore/azcore_files.cmake b/Code/Framework/AzCore/AzCore/azcore_files.cmake index b7821f1d65..dc2481b4c5 100644 --- a/Code/Framework/AzCore/AzCore/azcore_files.cmake +++ b/Code/Framework/AzCore/AzCore/azcore_files.cmake @@ -560,6 +560,9 @@ set(FILES Serialization/std/VariantReflection.inl Settings/CommandLine.cpp Settings/CommandLine.h + Settings/ConfigurableStack.cpp + Settings/ConfigurableStack.inl + Settings/ConfigurableStack.h Settings/SettingsRegistry.cpp Settings/SettingsRegistry.h Settings/SettingsRegistryConsoleUtils.cpp diff --git a/Code/Framework/AzCore/Tests/Settings/ConfigurableStackTests.cpp b/Code/Framework/AzCore/Tests/Settings/ConfigurableStackTests.cpp new file mode 100644 index 0000000000..df535976bf --- /dev/null +++ b/Code/Framework/AzCore/Tests/Settings/ConfigurableStackTests.cpp @@ -0,0 +1,283 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace UnitTest +{ + struct ConfigInt + { + AZ_TYPE_INFO(UnitTest::ConfigInt, "{1FAF6E55-7FA4-4FFA-8C41-34F422B8E8AB}"); + AZ_CLASS_ALLOCATOR(ConfigInt, AZ::SystemAllocator, 0); + + int m_value; + + static void Reflect(AZ::ReflectContext* context) + { + if (auto sc = azrtti_cast(context)) + { + sc->Class()->Field("Value", &ConfigInt::m_value); + } + } + }; + + struct ConfigurableStackTests : public AllocatorsFixture + { + void Reflect(AZ::ReflectContext* context) + { + if (auto sc = azrtti_cast(context)) + { + AZ::JsonSystemComponent::Reflect(sc); + ConfigInt::Reflect(sc); + sc->RegisterGenericType>(); + } + else if (auto jrc = azrtti_cast(context)) + { + AZ::JsonSystemComponent::Reflect(jrc); + } + } + + void SetUp() override + { + AllocatorsFixture::SetUp(); + + m_serializeContext = aznew AZ::SerializeContext(); + m_jsonRegistrationContext = aznew AZ::JsonRegistrationContext(); + + Reflect(m_serializeContext); + Reflect(m_jsonRegistrationContext); + + m_deserializationSettings.m_registrationContext = m_jsonRegistrationContext; + m_deserializationSettings.m_serializeContext = m_serializeContext; + } + + void TearDown() + { + m_jsonRegistrationContext->EnableRemoveReflection(); + Reflect(m_jsonRegistrationContext); + m_jsonRegistrationContext->DisableRemoveReflection(); + + m_serializeContext->EnableRemoveReflection(); + Reflect(m_serializeContext); + m_serializeContext->DisableRemoveReflection(); + + delete m_jsonRegistrationContext; + delete m_serializeContext; + + AllocatorsFixture::TearDown(); + } + + void ObjectTest(AZStd::string_view jsonText) + { + AZ::ConfigurableStack stack; + + rapidjson::Document document; + document.Parse(jsonText.data(), jsonText.length()); + ASSERT_FALSE(document.HasParseError()); + AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::Load(stack, document, m_deserializationSettings); + ASSERT_EQ(AZ::JsonSerializationResult::Processing::Completed, result.GetProcessing()); + ASSERT_EQ(4, stack.size()); + + int numberCounter = 0; + int valueCounter = 42; + for (auto& [name, value] : stack) + { + EXPECT_STREQ(AZStd::string::format("Value%i", numberCounter).c_str(), name.c_str()); + EXPECT_EQ(valueCounter, value->m_value); + numberCounter++; + valueCounter++; + } + } + + AZ::SerializeContext* m_serializeContext; + AZ::JsonRegistrationContext* m_jsonRegistrationContext; + AZ::JsonDeserializerSettings m_deserializationSettings; + }; + + TEST_F(ConfigurableStackTests, DeserializeArray) + { + AZ::ConfigurableStack stack; + + rapidjson::Document document; + document.Parse( + R"([ + { "Value": 42 }, + { "Value": 43 }, + { "Value": 44 }, + { "Value": 45 } + ])"); + ASSERT_FALSE(document.HasParseError()); + AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::Load(stack, document, m_deserializationSettings); + ASSERT_EQ(AZ::JsonSerializationResult::Processing::Completed, result.GetProcessing()); + ASSERT_EQ(4, stack.size()); + + int numberCounter = 0; + int valueCounter = 42; + for (auto& [name, value] : stack) + { + EXPECT_STREQ(AZStd::to_string(numberCounter).c_str(), name.c_str()); + EXPECT_EQ(valueCounter, value->m_value); + numberCounter++; + valueCounter++; + } + } + + TEST_F(ConfigurableStackTests, DeserializeObject) + { + ObjectTest( + R"({ + "Value0": { "Value": 42 }, + "Value1": { "Value": 43 }, + "Value2": { "Value": 44 }, + "Value3": { "Value": 45 } + })"); + } + + TEST_F(ConfigurableStackTests, DeserializeObjectWithLateBefore) + { + ObjectTest( + R"({ + "Value0": { "Value": 42 }, + "Value2": { "Value": 44 }, + "Value3": { "Value": 45 }, + "Value1": + { + "$stack_before": "Value2", + "Value": 43 + } + })"); + } + + TEST_F(ConfigurableStackTests, DeserializeObjectWithEarlyBefore) + { + ObjectTest( + R"({ + "Value1": + { + "$stack_before": "Value2", + "Value": 43 + }, + "Value0": { "Value": 42 }, + "Value2": { "Value": 44 }, + "Value3": { "Value": 45 } + })"); + } + + TEST_F(ConfigurableStackTests, DeserializeObjectWithLateAfter) + { + ObjectTest( + R"({ + "Value0": { "Value": 42 }, + "Value2": { "Value": 44 }, + "Value3": { "Value": 45 }, + "Value1": + { + "$stack_after": "Value0", + "Value": 43 + } + })"); + } + + TEST_F(ConfigurableStackTests, DeserializeObjectWithEarlyAfter) + { + ObjectTest( + R"({ + "Value1": + { + "$stack_after": "Value0", + "Value": 43 + }, + "Value0": { "Value": 42 }, + "Value2": { "Value": 44 }, + "Value3": { "Value": 45 } + })"); + } + + TEST_F(ConfigurableStackTests, DeserializeObjectWithBeforeFirst) + { + ObjectTest( + R"({ + "Value1": { "Value": 43 }, + "Value2": { "Value": 44 }, + "Value3": { "Value": 45 }, + "Value0": + { + "$stack_before": "Value1", + "Value": 42 + } + + })"); + } + + TEST_F(ConfigurableStackTests, DeserializeObjectWithInsertAfterLast) + { + ObjectTest( + R"({ + "Value3": + { + "$stack_after": "Value2", + "Value": 45 + }, + "Value0": { "Value": 42 }, + "Value1": { "Value": 43 }, + "Value2": { "Value": 44 } + })"); + } + + TEST_F(ConfigurableStackTests, DeserializeObjectWithInvalidTarget) + { + AZ::ConfigurableStack stack; + + rapidjson::Document document; + document.Parse( + R"({ + "Value1": + { + "$stack_after": "airplane", + "Value": 43 + }, + "Value0": { "Value": 42 }, + "Value2": { "Value": 44 }, + "Value3": { "Value": 45 } + })"); + ASSERT_FALSE(document.HasParseError()); + AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::Load(stack, document, m_deserializationSettings); + EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, result.GetProcessing()); + EXPECT_EQ(AZ::JsonSerializationResult::Outcomes::PartialSkip, result.GetOutcome()); + EXPECT_EQ(3, stack.size()); + } + + TEST_F(ConfigurableStackTests, DeserializeObjectWithInvalidTargetType) + { + AZ::ConfigurableStack stack; + + rapidjson::Document document; + document.Parse( + R"({ + "Value1": + { + "$stack_after": 42, + "Value": 43 + }, + "Value0": { "Value": 42 }, + "Value2": { "Value": 44 }, + "Value3": { "Value": 45 } + })"); + ASSERT_FALSE(document.HasParseError()); + AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::Load(stack, document, m_deserializationSettings); + EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, result.GetProcessing()); + EXPECT_EQ(AZ::JsonSerializationResult::Outcomes::PartialSkip, result.GetOutcome()); + EXPECT_EQ(3, stack.size()); + } +} // namespace UnitTest diff --git a/Code/Framework/AzCore/Tests/azcoretests_files.cmake b/Code/Framework/AzCore/Tests/azcoretests_files.cmake index b07cd32ec1..1f67bd6db8 100644 --- a/Code/Framework/AzCore/Tests/azcoretests_files.cmake +++ b/Code/Framework/AzCore/Tests/azcoretests_files.cmake @@ -76,6 +76,7 @@ set(FILES Name/NameTests.cpp RTTI/TypeSafeIntegralTests.cpp Settings/CommandLineTests.cpp + Settings/ConfigurableStackTests.cpp Settings/SettingsRegistryTests.cpp Settings/SettingsRegistryConsoleUtilsTests.cpp Settings/SettingsRegistryMergeUtilsTests.cpp From 9bd487e3bda9463ea9ad75bb9eba54eb44b4036f Mon Sep 17 00:00:00 2001 From: AMZN-koppersr <82230785+AMZN-koppersr@users.noreply.github.com> Date: Tue, 15 Feb 2022 10:11:41 -0800 Subject: [PATCH 2/3] Minor fixes to the configurable stack based on provided feedback. Signed-off-by: AMZN-koppersr <82230785+AMZN-koppersr@users.noreply.github.com> --- .../AzCore/Settings/ConfigurableStack.h | 4 +-- .../AzCore/Settings/ConfigurableStack.inl | 12 -------- .../Tests/Settings/ConfigurableStackTests.cpp | 30 +++++++++---------- 3 files changed, 15 insertions(+), 31 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.h b/Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.h index bafb6f47a3..afb0eca6e0 100644 --- a/Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.h +++ b/Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.h @@ -110,9 +110,7 @@ namespace AZ Iterator end(); ConstIterator begin() const; ConstIterator end() const; - ConstIterator cbegin() const; - ConstIterator cend() const; - + size_t size() const; protected: diff --git a/Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.inl b/Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.inl index ba34ac1d03..7cfba21497 100644 --- a/Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.inl +++ b/Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.inl @@ -44,18 +44,6 @@ namespace AZ return m_nodes.end(); } - template - auto ConfigurableStack::cbegin() const -> ConstIterator - { - return m_nodes.cbegin(); - } - - template - auto ConfigurableStack::cend() const -> ConstIterator - { - return m_nodes.cend(); - } - template size_t ConfigurableStack::size() const { diff --git a/Code/Framework/AzCore/Tests/Settings/ConfigurableStackTests.cpp b/Code/Framework/AzCore/Tests/Settings/ConfigurableStackTests.cpp index df535976bf..0a69ac0053 100644 --- a/Code/Framework/AzCore/Tests/Settings/ConfigurableStackTests.cpp +++ b/Code/Framework/AzCore/Tests/Settings/ConfigurableStackTests.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace UnitTest @@ -32,7 +33,7 @@ namespace UnitTest } }; - struct ConfigurableStackTests : public AllocatorsFixture + struct ConfigurableStackTests : public ScopedAllocatorSetupFixture { void Reflect(AZ::ReflectContext* context) { @@ -50,32 +51,29 @@ namespace UnitTest void SetUp() override { - AllocatorsFixture::SetUp(); + ScopedAllocatorSetupFixture::SetUp(); - m_serializeContext = aznew AZ::SerializeContext(); - m_jsonRegistrationContext = aznew AZ::JsonRegistrationContext(); + m_serializeContext = AZStd::make_unique(); + m_jsonRegistrationContext = AZStd::make_unique(); - Reflect(m_serializeContext); - Reflect(m_jsonRegistrationContext); + Reflect(m_serializeContext.get()); + Reflect(m_jsonRegistrationContext.get()); - m_deserializationSettings.m_registrationContext = m_jsonRegistrationContext; - m_deserializationSettings.m_serializeContext = m_serializeContext; + m_deserializationSettings.m_registrationContext = m_jsonRegistrationContext.get(); + m_deserializationSettings.m_serializeContext = m_serializeContext.get(); } void TearDown() { m_jsonRegistrationContext->EnableRemoveReflection(); - Reflect(m_jsonRegistrationContext); + Reflect(m_jsonRegistrationContext.get()); m_jsonRegistrationContext->DisableRemoveReflection(); m_serializeContext->EnableRemoveReflection(); - Reflect(m_serializeContext); + Reflect(m_serializeContext.get()); m_serializeContext->DisableRemoveReflection(); - delete m_jsonRegistrationContext; - delete m_serializeContext; - - AllocatorsFixture::TearDown(); + ScopedAllocatorSetupFixture::TearDown(); } void ObjectTest(AZStd::string_view jsonText) @@ -100,8 +98,8 @@ namespace UnitTest } } - AZ::SerializeContext* m_serializeContext; - AZ::JsonRegistrationContext* m_jsonRegistrationContext; + AZStd::unique_ptr m_serializeContext; + AZStd::unique_ptr m_jsonRegistrationContext; AZ::JsonDeserializerSettings m_deserializationSettings; }; From f91ea8de134fab0dfd5e5d258b7e28ff32577688 Mon Sep 17 00:00:00 2001 From: AMZN-koppersr <82230785+AMZN-koppersr@users.noreply.github.com> Date: Tue, 15 Feb 2022 13:10:10 -0800 Subject: [PATCH 3/3] Removed #pragma once in cpp file. Signed-off-by: AMZN-koppersr <82230785+AMZN-koppersr@users.noreply.github.com> --- Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.cpp b/Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.cpp index 75748e0f11..320219c7e5 100644 --- a/Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.cpp +++ b/Code/Framework/AzCore/AzCore/Settings/ConfigurableStack.cpp @@ -6,8 +6,6 @@ * */ -#pragma once - #include #include #include