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>
This commit is contained in:
@@ -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 <AzCore/JSON/document.h>
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
#include <AzCore/Settings/ConfigurableStack.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerialization.h>
|
||||
#include <AzCore/Serialization/Json/RegistrationContext.h>
|
||||
#include <AzCore/Serialization/Json/JsonSystemComponent.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
|
||||
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<AZ::SerializeContext*>(context))
|
||||
{
|
||||
sc->Class<ConfigInt>()->Field("Value", &ConfigInt::m_value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct ConfigurableStackTests : public AllocatorsFixture
|
||||
{
|
||||
void Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto sc = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
AZ::JsonSystemComponent::Reflect(sc);
|
||||
ConfigInt::Reflect(sc);
|
||||
sc->RegisterGenericType<AZ::ConfigurableStack<ConfigInt>>();
|
||||
}
|
||||
else if (auto jrc = azrtti_cast<AZ::JsonRegistrationContext*>(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<ConfigInt> 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<ConfigInt> 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<ConfigInt> 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<ConfigInt> 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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user