Merge pull request #7561 from aws-lumberyard-dev/ConfigurableStack
Improved the way the Settings Registry can handle stacks/arrays.monroegm-disable-blank-issue-2
commit
f2378fc8d8
@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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/Memory/SystemAllocator.h>
|
||||
#include <AzCore/Settings/ConfigurableStack.h>
|
||||
#include <AzCore/Serialization/Json/RegistrationContext.h>
|
||||
#include <AzCore/std/containers/queue.h>
|
||||
#include <AzCore/std/tuple.h>
|
||||
|
||||
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<JsonRegistrationContext*>(context))
|
||||
{
|
||||
jsonContext->Serializer<JsonConfigurableStackSerializer>()->HandlesType<ConfigurableStack>();
|
||||
}
|
||||
}
|
||||
|
||||
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<ConfigurableStackInterface*>(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<ConfigurableStackInterface*>(outputValue);
|
||||
const Uuid& nodeValueType = stack->GetNodeType();
|
||||
|
||||
AZStd::queue<AZStd::tuple<ConfigurableStackInterface::InsertPosition, AZStd::string_view, rapidjson::Value::ConstMemberIterator>>
|
||||
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
|
||||
@ -0,0 +1,187 @@
|
||||
/*
|
||||
* 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 <AzCore/base.h>
|
||||
#include <AzCore/Serialization/Json/BaseJsonSerializer.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/std/smart_ptr/shared_ptr.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzCore/std/utils.h>
|
||||
|
||||
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<typename StackBaseType>
|
||||
class ConfigurableStack final : public ConfigurableStackInterface
|
||||
{
|
||||
public:
|
||||
using NodeValue = AZStd::shared_ptr<StackBaseType>;
|
||||
using Node = AZStd::pair<AZStd::string, NodeValue>;
|
||||
using NodeContainer = AZStd::vector<Node>;
|
||||
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;
|
||||
|
||||
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<typename StackBaseType>
|
||||
struct SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>
|
||||
{
|
||||
using ConfigurableStackType = ConfigurableStack<StackBaseType>;
|
||||
|
||||
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 <AzCore/Settings/ConfigurableStack.inl>
|
||||
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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<typename StackBaseType>
|
||||
const TypeId& ConfigurableStack<StackBaseType>::GetNodeType() const
|
||||
{
|
||||
return azrtti_typeid<NodeValue>();
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
auto ConfigurableStack<StackBaseType>::begin() -> Iterator
|
||||
{
|
||||
return m_nodes.begin();
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
auto ConfigurableStack<StackBaseType>::end() -> Iterator
|
||||
{
|
||||
return m_nodes.end();
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
auto ConfigurableStack<StackBaseType>::begin() const -> ConstIterator
|
||||
{
|
||||
return m_nodes.begin();
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
auto ConfigurableStack<StackBaseType>::end() const -> ConstIterator
|
||||
{
|
||||
return m_nodes.end();
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
size_t ConfigurableStack<StackBaseType>::size() const
|
||||
{
|
||||
return m_nodes.size();
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
void* ConfigurableStack<StackBaseType>::AddNode(AZStd::string name)
|
||||
{
|
||||
Node& result = m_nodes.emplace_back(AZStd::move(name));
|
||||
return &result.second;
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
void* ConfigurableStack<StackBaseType>::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<typename StackBaseType>
|
||||
SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GenericConfigurableStackInfo::GenericConfigurableStackInfo()
|
||||
: m_classData{ SerializeContext::ClassData::Create<ConfigurableStackType>(
|
||||
"AZ::ConfigurableStack", GetSpecializedTypeId(), Internal::NullFactory::GetInstance(), nullptr, nullptr) }
|
||||
{
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
SerializeContext::ClassData* SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GenericConfigurableStackInfo::GetClassData()
|
||||
{
|
||||
return &m_classData;
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
size_t SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GenericConfigurableStackInfo::GetNumTemplatedArguments()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
const Uuid& SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GenericConfigurableStackInfo::GetTemplatedTypeId(
|
||||
[[maybe_unused]] size_t element)
|
||||
{
|
||||
return SerializeGenericTypeInfo<StackBaseType>::GetClassTypeId();
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
const Uuid& SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GenericConfigurableStackInfo::GetSpecializedTypeId() const
|
||||
{
|
||||
return azrtti_typeid<ConfigurableStackType>();
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
const Uuid& SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GenericConfigurableStackInfo::GetGenericTypeId() const
|
||||
{
|
||||
return TYPEINFO_Uuid();
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
void SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GenericConfigurableStackInfo::Reflect(SerializeContext* serializeContext)
|
||||
{
|
||||
if (serializeContext)
|
||||
{
|
||||
serializeContext->RegisterGenericClassInfo(GetSpecializedTypeId(), this, &AnyTypeInfoConcept<ConfigurableStackType>::CreateAny);
|
||||
if (serializeContext->FindClassData(azrtti_typeid<AZStd::shared_ptr<StackBaseType>>()) == nullptr)
|
||||
{
|
||||
serializeContext->RegisterGenericType<AZStd::shared_ptr<StackBaseType>>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
auto SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GetGenericInfo() -> ClassInfoType*
|
||||
{
|
||||
return GetCurrentSerializeContextModule().CreateGenericClassInfo<ConfigurableStackType>();
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
const Uuid& SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GetClassTypeId()
|
||||
{
|
||||
return GetGenericInfo()->GetClassData()->m_typeId;
|
||||
}
|
||||
} // namespace AZ
|
||||
|
||||
@ -0,0 +1,281 @@
|
||||
/*
|
||||
* 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/std/smart_ptr/unique_ptr.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 ScopedAllocatorSetupFixture
|
||||
{
|
||||
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
|
||||
{
|
||||
ScopedAllocatorSetupFixture::SetUp();
|
||||
|
||||
m_serializeContext = AZStd::make_unique<AZ::SerializeContext>();
|
||||
m_jsonRegistrationContext = AZStd::make_unique<AZ::JsonRegistrationContext>();
|
||||
|
||||
Reflect(m_serializeContext.get());
|
||||
Reflect(m_jsonRegistrationContext.get());
|
||||
|
||||
m_deserializationSettings.m_registrationContext = m_jsonRegistrationContext.get();
|
||||
m_deserializationSettings.m_serializeContext = m_serializeContext.get();
|
||||
}
|
||||
|
||||
void TearDown()
|
||||
{
|
||||
m_jsonRegistrationContext->EnableRemoveReflection();
|
||||
Reflect(m_jsonRegistrationContext.get());
|
||||
m_jsonRegistrationContext->DisableRemoveReflection();
|
||||
|
||||
m_serializeContext->EnableRemoveReflection();
|
||||
Reflect(m_serializeContext.get());
|
||||
m_serializeContext->DisableRemoveReflection();
|
||||
|
||||
ScopedAllocatorSetupFixture::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++;
|
||||
}
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<AZ::SerializeContext> m_serializeContext;
|
||||
AZStd::unique_ptr<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
|
||||
Loading…
Reference in New Issue