Merge pull request #6395 from aws-lumberyard-dev/nvsickle/GenericDomDocument
Add a generic Value type to the Generic DOM
This commit is contained in:
@@ -361,7 +361,7 @@ namespace AZ::Dom::Json
|
||||
|
||||
bool RapidJsonReadHandler::String(const char* str, rapidjson::SizeType length, bool copy)
|
||||
{
|
||||
const Lifetime lifetime = copy ? m_stringLifetime : Lifetime::Temporary;
|
||||
const Lifetime lifetime = !copy ? m_stringLifetime : Lifetime::Temporary;
|
||||
return CheckResult(m_visitor->String(AZStd::string_view(str, length), lifetime));
|
||||
}
|
||||
|
||||
@@ -373,11 +373,7 @@ namespace AZ::Dom::Json
|
||||
bool RapidJsonReadHandler::Key(const char* str, rapidjson::SizeType length, [[maybe_unused]] bool copy)
|
||||
{
|
||||
AZStd::string_view key = AZStd::string_view(str, length);
|
||||
if (!m_visitor->SupportsRawKeys())
|
||||
{
|
||||
m_visitor->Key(AZ::Name(key));
|
||||
}
|
||||
const Lifetime lifetime = copy ? m_stringLifetime : Lifetime::Temporary;
|
||||
const Lifetime lifetime = !copy ? m_stringLifetime : Lifetime::Temporary;
|
||||
return CheckResult(m_visitor->RawKey(key, lifetime));
|
||||
}
|
||||
|
||||
|
||||
@@ -21,4 +21,164 @@ namespace AZ::Dom::Utils
|
||||
{
|
||||
return backend.ReadFromBufferInPlace(string.data(), string.size(), visitor);
|
||||
}
|
||||
}
|
||||
|
||||
AZ::Outcome<Value, AZStd::string> WriteToValue(const Backend::WriteCallback& writeCallback)
|
||||
{
|
||||
Value value;
|
||||
AZStd::unique_ptr<Visitor> writer = value.GetWriteHandler();
|
||||
Visitor::Result result = writeCallback(*writer);
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(result.GetError().FormatVisitorErrorMessage());
|
||||
}
|
||||
return AZ::Success(AZStd::move(value));
|
||||
}
|
||||
|
||||
bool DeepCompareIsEqual(const Value& lhs, const Value& rhs)
|
||||
{
|
||||
const Value::ValueType& lhsValue = lhs.GetInternalValue();
|
||||
const Value::ValueType& rhsValue = rhs.GetInternalValue();
|
||||
|
||||
if (lhs.IsString() && rhs.IsString())
|
||||
{
|
||||
// If we both hold the same ref counted string we don't need to do a full comparison
|
||||
if (AZStd::holds_alternative<Value::SharedStringType>(lhsValue) && lhsValue == rhsValue)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return lhs.GetString() == rhs.GetString();
|
||||
}
|
||||
|
||||
return AZStd::visit(
|
||||
[&](auto&& ourValue) -> bool
|
||||
{
|
||||
using Alternative = AZStd::decay_t<decltype(ourValue)>;
|
||||
|
||||
if constexpr (AZStd::is_same_v<Alternative, ObjectPtr>)
|
||||
{
|
||||
if (!rhs.IsObject())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto&& theirValue = AZStd::get<AZStd::remove_cvref_t<decltype(ourValue)>>(rhsValue);
|
||||
if (ourValue == theirValue)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const Object::ContainerType& ourValues = ourValue->GetValues();
|
||||
const Object::ContainerType& theirValues = theirValue->GetValues();
|
||||
|
||||
if (ourValues.size() != theirValues.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ourValues.size(); ++i)
|
||||
{
|
||||
const Object::EntryType& lhsChild = ourValues[i];
|
||||
const Object::EntryType& rhsChild = theirValues[i];
|
||||
if (lhsChild.first != rhsChild.first || !DeepCompareIsEqual(lhsChild.second, rhsChild.second))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if constexpr (AZStd::is_same_v<Alternative, ArrayPtr>)
|
||||
{
|
||||
if (!rhs.IsArray())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto&& theirValue = AZStd::get<AZStd::remove_cvref_t<decltype(ourValue)>>(rhsValue);
|
||||
if (ourValue == theirValue)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const Array::ContainerType& ourValues = ourValue->GetValues();
|
||||
const Array::ContainerType& theirValues = theirValue->GetValues();
|
||||
|
||||
if (ourValues.size() != theirValues.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ourValues.size(); ++i)
|
||||
{
|
||||
const Value& lhsChild = ourValues[i];
|
||||
const Value& rhsChild = theirValues[i];
|
||||
if (!DeepCompareIsEqual(lhsChild, rhsChild))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if constexpr (AZStd::is_same_v<Alternative, NodePtr>)
|
||||
{
|
||||
if (!rhs.IsNode())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto&& theirValue = AZStd::get<AZStd::remove_cvref_t<decltype(ourValue)>>(rhsValue);
|
||||
if (ourValue == theirValue)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const Node& ourNode = *ourValue;
|
||||
const Node& theirNode = *theirValue;
|
||||
|
||||
const Object::ContainerType& ourProperties = ourNode.GetProperties();
|
||||
const Object::ContainerType& theirProperties = theirNode.GetProperties();
|
||||
|
||||
if (ourProperties.size() != theirProperties.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ourProperties.size(); ++i)
|
||||
{
|
||||
const Object::EntryType& lhsChild = ourProperties[i];
|
||||
const Object::EntryType& rhsChild = theirProperties[i];
|
||||
if (lhsChild.first != rhsChild.first || !DeepCompareIsEqual(lhsChild.second, rhsChild.second))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const Array::ContainerType& ourChildren = ourNode.GetChildren();
|
||||
const Array::ContainerType& theirChildren = theirNode.GetChildren();
|
||||
|
||||
for (size_t i = 0; i < ourChildren.size(); ++i)
|
||||
{
|
||||
const Value& lhsChild = ourChildren[i];
|
||||
const Value& rhsChild = theirChildren[i];
|
||||
if (!DeepCompareIsEqual(lhsChild, rhsChild))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return lhs == rhs;
|
||||
}
|
||||
},
|
||||
lhsValue);
|
||||
}
|
||||
|
||||
Value DeepCopy(const Value& value, bool copyStrings)
|
||||
{
|
||||
Value copiedValue;
|
||||
AZStd::unique_ptr<Visitor> writer = copiedValue.GetWriteHandler();
|
||||
value.Accept(*writer, copyStrings);
|
||||
return copiedValue;
|
||||
}
|
||||
} // namespace AZ::Dom::Utils
|
||||
|
||||
@@ -9,9 +9,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/DOM/DomBackend.h>
|
||||
#include <AzCore/DOM/DomValue.h>
|
||||
|
||||
namespace AZ::Dom::Utils
|
||||
{
|
||||
Visitor::Result ReadFromString(Backend& backend, AZStd::string_view string, AZ::Dom::Lifetime lifetime, Visitor& visitor);
|
||||
Visitor::Result ReadFromStringInPlace(Backend& backend, AZStd::string& string, Visitor& visitor);
|
||||
|
||||
AZ::Outcome<Value, AZStd::string> WriteToValue(const Backend::WriteCallback& writeCallback);
|
||||
|
||||
bool DeepCompareIsEqual(const Value& lhs, const Value& rhs);
|
||||
Value DeepCopy(const Value& value, bool copyStrings = true);
|
||||
} // namespace AZ::Dom::Utils
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,402 @@
|
||||
/*
|
||||
* 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/DOM/DomBackend.h>
|
||||
#include <AzCore/DOM/DomVisitor.h>
|
||||
#include <AzCore/Memory/HphaSchema.h>
|
||||
#include <AzCore/Memory/Memory.h>
|
||||
#include <AzCore/Memory/PoolAllocator.h>
|
||||
#include <AzCore/std/containers/array.h>
|
||||
#include <AzCore/std/containers/stack.h>
|
||||
#include <AzCore/std/containers/unordered_map.h>
|
||||
#include <AzCore/std/containers/variant.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/std/smart_ptr/shared_ptr.h>
|
||||
|
||||
namespace AZ::Dom
|
||||
{
|
||||
using KeyType = AZ::Name;
|
||||
|
||||
//! The type of underlying value stored in a value. \see Value
|
||||
enum class Type
|
||||
{
|
||||
Null,
|
||||
Bool,
|
||||
Object,
|
||||
Array,
|
||||
String,
|
||||
Int64,
|
||||
Uint64,
|
||||
Double,
|
||||
Node,
|
||||
Opaque,
|
||||
};
|
||||
|
||||
//! The allocator used by Value.
|
||||
//! Value heap allocates shared_ptrs for its container storage (Array / Object / Node) alongside
|
||||
class ValueAllocator final : public SimpleSchemaAllocator<AZ::HphaSchema, AZ::HphaSchema::Descriptor, false, false>
|
||||
{
|
||||
public:
|
||||
AZ_TYPE_INFO(ValueAllocator, "{5BC8B389-72C7-459E-B502-12E74D61869F}");
|
||||
|
||||
using Base = SimpleSchemaAllocator<AZ::HphaSchema, AZ::HphaSchema::Descriptor, false, false>;
|
||||
|
||||
ValueAllocator()
|
||||
: Base("DomValueAllocator", "Allocator for AZ::Dom::Value")
|
||||
{
|
||||
DisableOverriding();
|
||||
}
|
||||
};
|
||||
|
||||
using StdValueAllocator = AZStdAlloc<ValueAllocator>;
|
||||
|
||||
class Value;
|
||||
|
||||
//! Internal storage for a Value array: an ordered list of Values.
|
||||
class Array
|
||||
{
|
||||
public:
|
||||
using ContainerType = AZStd::vector<Value, StdValueAllocator>;
|
||||
using Iterator = ContainerType::iterator;
|
||||
using ConstIterator = ContainerType::const_iterator;
|
||||
static constexpr const size_t ReserveIncrement = 4;
|
||||
static_assert((ReserveIncrement & (ReserveIncrement - 1)) == 0, "ReserveIncremenet must be a power of 2");
|
||||
|
||||
const ContainerType& GetValues() const;
|
||||
|
||||
private:
|
||||
ContainerType m_values;
|
||||
|
||||
friend class Value;
|
||||
};
|
||||
|
||||
using ArrayPtr = AZStd::shared_ptr<Array>;
|
||||
using ConstArrayPtr = AZStd::shared_ptr<const Array>;
|
||||
|
||||
//! Internal storage for a Value object: an ordered list of Name / Value pairs.
|
||||
class Object
|
||||
{
|
||||
public:
|
||||
using EntryType = AZStd::pair<KeyType, Value>;
|
||||
using ContainerType = AZStd::vector<EntryType, StdValueAllocator>;
|
||||
using Iterator = ContainerType::iterator;
|
||||
using ConstIterator = ContainerType::const_iterator;
|
||||
static constexpr const size_t ReserveIncrement = 8;
|
||||
static_assert((ReserveIncrement & (ReserveIncrement - 1)) == 0, "ReserveIncremenet must be a power of 2");
|
||||
|
||||
const ContainerType& GetValues() const;
|
||||
|
||||
private:
|
||||
ContainerType m_values;
|
||||
|
||||
friend class Value;
|
||||
};
|
||||
|
||||
using ObjectPtr = AZStd::shared_ptr<Object>;
|
||||
using ConstObjectPtr = AZStd::shared_ptr<const Object>;
|
||||
|
||||
//! Storage for a Value node: a named Value with both properties and children.
|
||||
//! Properties are stored as an ordered list of Name / Value pairs.
|
||||
//! Children are stored as an oredered list of Values.
|
||||
class Node
|
||||
{
|
||||
public:
|
||||
Node() = default;
|
||||
Node(const Node&) = default;
|
||||
Node(Node&&) = default;
|
||||
explicit Node(AZ::Name name);
|
||||
|
||||
Node& operator=(const Node&) = default;
|
||||
Node& operator=(Node&&) = default;
|
||||
|
||||
AZ::Name GetName() const;
|
||||
void SetName(AZ::Name name);
|
||||
|
||||
Object::ContainerType& GetProperties();
|
||||
const Object::ContainerType& GetProperties() const;
|
||||
|
||||
Array::ContainerType& GetChildren();
|
||||
const Array::ContainerType& GetChildren() const;
|
||||
|
||||
private:
|
||||
AZ::Name m_name;
|
||||
Object::ContainerType m_properties;
|
||||
Array::ContainerType m_children;
|
||||
|
||||
friend class Value;
|
||||
};
|
||||
|
||||
using NodePtr = AZStd::shared_ptr<Node>;
|
||||
using ConstNodePtr = AZStd::shared_ptr<Node>;
|
||||
|
||||
//! Value is a typed union of Dom types that can represent the types provdied by AZ::Dom::Visitor.
|
||||
//! Value can be one of the following types:
|
||||
//! - Null: a type with no value, this is the default type for Value
|
||||
//! - Bool: a true or false boolean value
|
||||
//! - Object: a container with an ordered list of Name/Value pairs, analagous to a JSON object
|
||||
//! - Array: a container with an ordered list of Values, analagous to a JSON array
|
||||
//! - String: a UTF-8 string
|
||||
//! - Int64: a signed, 64-bit integer
|
||||
//! - Uint64: an unsigned, 64-bit integer
|
||||
//! - Double: a double precision floating point value
|
||||
//! - Node: a container with a Name, an ordered list of Name/Values pairs (attributes), and an ordered list of Values (children),
|
||||
//! analagous to an XML node
|
||||
//! - Opaque: an arbitrary value stored in an AZStd::any. This is a non-serializable representation of an entry used only for in-memory
|
||||
//! options. This is intended to be used as an intermediate value over the course of DOM transformation and as a proxy to pass through
|
||||
//! types of which the DOM has no knowledge to other systems.
|
||||
//! \note Value is a copy-on-write data structure and may be cheaply returned by value. Heap allocated data larger than the size of the
|
||||
//! value itself (objects, arrays, and nodes) are copied by new Values only when their contents change, so care should be taken in
|
||||
//! performance critical code to avoid mutation operations such as operator[] to avoid copies. It is recommended that an immutable Value
|
||||
//! be explicitly be stored as a `const Value` to avoid accidental detach and copy operations.
|
||||
class Value final
|
||||
{
|
||||
public:
|
||||
// Determine the short string buffer size based on the size of our largest internal type (string_view)
|
||||
// minus the size of the short string size field.
|
||||
static constexpr const size_t ShortStringSize = sizeof(AZStd::string_view) - 2;
|
||||
using ShortStringType = AZStd::fixed_string<ShortStringSize>;
|
||||
using SharedStringContainer = AZStd::vector<char>;
|
||||
using SharedStringType = AZStd::shared_ptr<const SharedStringContainer>;
|
||||
using OpaqueStorageType = AZStd::shared_ptr<AZStd::any>;
|
||||
|
||||
//! The internal storage type for Value.
|
||||
//! These types do not correspond one-to-one with the Value's external Type as there may be multiple storage classes
|
||||
//! for the same type in some instances, such as string storage.
|
||||
using ValueType = AZStd::variant<
|
||||
// Null
|
||||
AZStd::monostate,
|
||||
// Int64
|
||||
int64_t,
|
||||
// Uint64
|
||||
uint64_t,
|
||||
// Double
|
||||
double,
|
||||
// Bool
|
||||
bool,
|
||||
// String
|
||||
AZStd::string_view,
|
||||
SharedStringType,
|
||||
ShortStringType,
|
||||
// Object
|
||||
ObjectPtr,
|
||||
// Array
|
||||
ArrayPtr,
|
||||
// Node
|
||||
NodePtr,
|
||||
// Opaque
|
||||
OpaqueStorageType>;
|
||||
|
||||
// Constructors...
|
||||
Value() = default;
|
||||
Value(const Value&);
|
||||
Value(Value&&) noexcept;
|
||||
Value(AZStd::string_view stringView, bool copy);
|
||||
explicit Value(const ValueType&);
|
||||
explicit Value(ValueType&&);
|
||||
explicit Value(SharedStringType sharedString);
|
||||
|
||||
explicit Value(int8_t value);
|
||||
explicit Value(uint8_t value);
|
||||
explicit Value(int16_t value);
|
||||
explicit Value(uint16_t value);
|
||||
explicit Value(int32_t value);
|
||||
explicit Value(uint32_t value);
|
||||
explicit Value(int64_t value);
|
||||
explicit Value(uint64_t value);
|
||||
explicit Value(float value);
|
||||
explicit Value(double value);
|
||||
explicit Value(bool value);
|
||||
|
||||
explicit Value(Type type);
|
||||
|
||||
// Disable accidental calls to Value(bool) with pointer types
|
||||
template<class T>
|
||||
explicit Value(T*) = delete;
|
||||
|
||||
static Value FromOpaqueValue(const AZStd::any& value);
|
||||
|
||||
// Equality / comparison / swap...
|
||||
Value& operator=(const Value&);
|
||||
Value& operator=(Value&&) noexcept;
|
||||
|
||||
//! Assignment operator to allow forwarding types constructible via Value(T) to be assigned
|
||||
template<class T>
|
||||
auto operator=(T&& arg)
|
||||
-> AZStd::enable_if_t<!AZStd::is_same_v<AZStd::remove_cvref_t<T>, Value> && AZStd::is_constructible_v<Value, T>, Value&>
|
||||
{
|
||||
return operator=(Value(AZStd::forward<T>(arg)));
|
||||
}
|
||||
|
||||
bool operator==(const Value& rhs) const;
|
||||
bool operator!=(const Value& rhs) const;
|
||||
|
||||
void Swap(Value& other) noexcept;
|
||||
|
||||
// Type info...
|
||||
Type GetType() const;
|
||||
bool IsNull() const;
|
||||
bool IsFalse() const;
|
||||
bool IsTrue() const;
|
||||
bool IsBool() const;
|
||||
bool IsNode() const;
|
||||
bool IsObject() const;
|
||||
bool IsArray() const;
|
||||
bool IsOpaqueValue() const;
|
||||
bool IsNumber() const;
|
||||
bool IsInt() const;
|
||||
bool IsUint() const;
|
||||
bool IsDouble() const;
|
||||
bool IsString() const;
|
||||
|
||||
// Object API (also used by Node)...
|
||||
Value& SetObject();
|
||||
size_t MemberCount() const;
|
||||
size_t MemberCapacity() const;
|
||||
bool ObjectEmpty() const;
|
||||
|
||||
Value& operator[](KeyType name);
|
||||
const Value& operator[](KeyType name) const;
|
||||
Value& operator[](AZStd::string_view name);
|
||||
const Value& operator[](AZStd::string_view name) const;
|
||||
|
||||
Object::ConstIterator MemberBegin() const;
|
||||
Object::ConstIterator MemberEnd() const;
|
||||
Object::Iterator MemberBegin();
|
||||
Object::Iterator MemberEnd();
|
||||
|
||||
Object::Iterator FindMutableMember(KeyType name);
|
||||
Object::Iterator FindMutableMember(AZStd::string_view name);
|
||||
Object::ConstIterator FindMember(KeyType name) const;
|
||||
Object::ConstIterator FindMember(AZStd::string_view name) const;
|
||||
|
||||
Value& MemberReserve(size_t newCapacity);
|
||||
bool HasMember(KeyType name) const;
|
||||
bool HasMember(AZStd::string_view name) const;
|
||||
|
||||
Value& AddMember(KeyType name, const Value& value);
|
||||
Value& AddMember(AZStd::string_view name, const Value& value);
|
||||
Value& AddMember(KeyType name, Value&& value);
|
||||
Value& AddMember(AZStd::string_view name, Value&& value);
|
||||
|
||||
void RemoveAllMembers();
|
||||
void RemoveMember(KeyType name);
|
||||
void RemoveMember(AZStd::string_view name);
|
||||
Object::Iterator RemoveMember(Object::Iterator pos);
|
||||
Object::Iterator EraseMember(Object::ConstIterator pos);
|
||||
Object::Iterator EraseMember(Object::ConstIterator first, Object::ConstIterator last);
|
||||
Object::Iterator EraseMember(KeyType name);
|
||||
Object::Iterator EraseMember(AZStd::string_view name);
|
||||
|
||||
Object::ContainerType& GetMutableObject();
|
||||
const Object::ContainerType& GetObject() const;
|
||||
|
||||
// Array API (also used by Node)...
|
||||
Value& SetArray();
|
||||
|
||||
size_t ArraySize() const;
|
||||
size_t ArrayCapacity() const;
|
||||
bool IsArrayEmpty() const;
|
||||
void ClearArray();
|
||||
|
||||
Value& operator[](size_t index);
|
||||
const Value& operator[](size_t index) const;
|
||||
|
||||
Value& MutableArrayAt(size_t index);
|
||||
const Value& ArrayAt(size_t index) const;
|
||||
|
||||
Array::ConstIterator ArrayBegin() const;
|
||||
Array::ConstIterator ArrayEnd() const;
|
||||
Array::Iterator ArrayBegin();
|
||||
Array::Iterator ArrayEnd();
|
||||
|
||||
Value& ArrayReserve(size_t newCapacity);
|
||||
Value& ArrayPushBack(Value value);
|
||||
Value& ArrayPopBack();
|
||||
|
||||
Array::Iterator ArrayErase(Array::ConstIterator pos);
|
||||
Array::Iterator ArrayErase(Array::ConstIterator first, Array::ConstIterator last);
|
||||
|
||||
Array::ContainerType& GetMutableArray();
|
||||
const Array::ContainerType& GetArray() const;
|
||||
|
||||
// Node API (supports both object + array API, plus a dedicated NodeName)...
|
||||
void SetNode(AZ::Name name);
|
||||
void SetNode(AZStd::string_view name);
|
||||
|
||||
AZ::Name GetNodeName() const;
|
||||
void SetNodeName(AZ::Name name);
|
||||
void SetNodeName(AZStd::string_view name);
|
||||
//! Convenience method, sets the first non-node element of a Node.
|
||||
void SetNodeValue(Value value);
|
||||
//! Convenience method, gets the first non-node element of a Node.
|
||||
Value GetNodeValue() const;
|
||||
|
||||
Node& GetMutableNode();
|
||||
const Node& GetNode() const;
|
||||
|
||||
// int API...
|
||||
int64_t GetInt64() const;
|
||||
void SetInt64(int64_t);
|
||||
|
||||
// uint API...
|
||||
uint64_t GetUint64() const;
|
||||
void SetUint64(uint64_t);
|
||||
|
||||
// bool API...
|
||||
bool GetBool() const;
|
||||
void SetBool(bool);
|
||||
|
||||
// double API...
|
||||
double GetDouble() const;
|
||||
void SetDouble(double);
|
||||
|
||||
// String API...
|
||||
AZStd::string_view GetString() const;
|
||||
size_t GetStringLength() const;
|
||||
void SetString(AZStd::string_view);
|
||||
void SetString(SharedStringType sharedString);
|
||||
void CopyFromString(AZStd::string_view);
|
||||
|
||||
// Opaque type API...
|
||||
const AZStd::any& GetOpaqueValue() const;
|
||||
//! This sets this Value to represent a value of an type that the DOM has
|
||||
//! no formal knowledge of. Where possible, it should be preferred to
|
||||
//! serialize an opaque type into a DOM value instead, as serializers
|
||||
//! and other systems will have no means of dealing with fully arbitrary
|
||||
//! values.
|
||||
void SetOpaqueValue(AZStd::any);
|
||||
|
||||
// Null API...
|
||||
void SetNull();
|
||||
|
||||
// Visitor API...
|
||||
Visitor::Result Accept(Visitor& visitor, bool copyStrings) const;
|
||||
AZStd::unique_ptr<Visitor> GetWriteHandler();
|
||||
|
||||
//! Gets the internal value of this Value. Note that this value's types may not correspond one-to-one with the Type enumeration,
|
||||
//! as internally the same type might have different storage mechanisms. Where possible, prefer using the typed API.
|
||||
const ValueType& GetInternalValue() const;
|
||||
|
||||
private:
|
||||
const Node& GetNodeInternal() const;
|
||||
Node& GetNodeInternal();
|
||||
const Object::ContainerType& GetObjectInternal() const;
|
||||
Object::ContainerType& GetObjectInternal();
|
||||
const Array::ContainerType& GetArrayInternal() const;
|
||||
Array::ContainerType& GetArrayInternal();
|
||||
|
||||
explicit Value(AZStd::any opaqueValue);
|
||||
|
||||
static_assert(
|
||||
sizeof(ValueType) == sizeof(ShortStringType) + sizeof(size_t), "ValueType should have no members larger than ShortStringType");
|
||||
|
||||
ValueType m_value;
|
||||
};
|
||||
} // namespace AZ::Dom
|
||||
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* 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/DOM/DomValueWriter.h>
|
||||
|
||||
namespace AZ::Dom
|
||||
{
|
||||
ValueWriter::ValueWriter(Value& outputValue)
|
||||
: m_result(outputValue)
|
||||
{
|
||||
}
|
||||
|
||||
VisitorFlags ValueWriter::GetVisitorFlags() const
|
||||
{
|
||||
return VisitorFlags::SupportsRawKeys | VisitorFlags::SupportsArrays | VisitorFlags::SupportsObjects | VisitorFlags::SupportsNodes;
|
||||
}
|
||||
|
||||
ValueWriter::ValueInfo::ValueInfo(Value& container)
|
||||
: m_container(container)
|
||||
{
|
||||
}
|
||||
|
||||
Visitor::Result ValueWriter::Null()
|
||||
{
|
||||
CurrentValue().SetNull();
|
||||
return FinishWrite();
|
||||
}
|
||||
|
||||
Visitor::Result ValueWriter::Bool(bool value)
|
||||
{
|
||||
CurrentValue().SetBool(value);
|
||||
return FinishWrite();
|
||||
}
|
||||
|
||||
Visitor::Result ValueWriter::Int64(AZ::s64 value)
|
||||
{
|
||||
CurrentValue().SetInt64(value);
|
||||
return FinishWrite();
|
||||
}
|
||||
|
||||
Visitor::Result ValueWriter::Uint64(AZ::u64 value)
|
||||
{
|
||||
CurrentValue().SetUint64(value);
|
||||
return FinishWrite();
|
||||
}
|
||||
|
||||
Visitor::Result ValueWriter::Double(double value)
|
||||
{
|
||||
CurrentValue().SetDouble(value);
|
||||
return FinishWrite();
|
||||
}
|
||||
|
||||
Visitor::Result ValueWriter::String(AZStd::string_view value, Lifetime lifetime)
|
||||
{
|
||||
if (lifetime == Lifetime::Persistent)
|
||||
{
|
||||
CurrentValue().SetString(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentValue().CopyFromString(value);
|
||||
}
|
||||
return FinishWrite();
|
||||
}
|
||||
|
||||
Visitor::Result ValueWriter::RefCountedString(AZStd::shared_ptr<const AZStd::vector<char>> value, [[maybe_unused]] Lifetime lifetime)
|
||||
{
|
||||
CurrentValue().SetString(AZStd::move(value));
|
||||
return FinishWrite();
|
||||
}
|
||||
|
||||
Visitor::Result ValueWriter::StartObject()
|
||||
{
|
||||
CurrentValue().SetObject();
|
||||
|
||||
m_entryStack.emplace(CurrentValue());
|
||||
return VisitorSuccess();
|
||||
}
|
||||
|
||||
template <class T, class A>
|
||||
void MoveVectorMemory(AZStd::vector<T, A>& dest, AZStd::vector<T, A>& source)
|
||||
{
|
||||
dest.resize_no_construct(source.size());
|
||||
const size_t size = sizeof(T) * source.size();
|
||||
memcpy(dest.data(), source.data(), size);
|
||||
memset(source.data(), 0, size);
|
||||
source.resize(0);
|
||||
}
|
||||
|
||||
Visitor::Result ValueWriter::EndContainer(Type containerType, AZ::u64 attributeCount, AZ::u64 elementCount)
|
||||
{
|
||||
const char* endMethodName;
|
||||
switch (containerType)
|
||||
{
|
||||
case Type::Object:
|
||||
endMethodName = "EndObject";
|
||||
break;
|
||||
case Type::Array:
|
||||
endMethodName = "EndArray";
|
||||
break;
|
||||
case Type::Node:
|
||||
endMethodName = "EndNode";
|
||||
break;
|
||||
default:
|
||||
AZ_Assert(false, "Invalid container type specified");
|
||||
return VisitorFailure(VisitorErrorCode::InternalError, "AZ::Dom::ValueWriter: EndContainer called with invalid container type");
|
||||
}
|
||||
|
||||
if (m_entryStack.empty())
|
||||
{
|
||||
return VisitorFailure(
|
||||
VisitorErrorCode::InternalError,
|
||||
AZStd::string::format("AZ::Dom::ValueWriter: %s called without a matching call", endMethodName));
|
||||
}
|
||||
|
||||
const ValueInfo& topEntry = m_entryStack.top();
|
||||
Value& container = topEntry.m_container;
|
||||
ValueBuffer& buffer = GetValueBuffer();
|
||||
|
||||
if (container.GetType() != containerType)
|
||||
{
|
||||
return VisitorFailure(
|
||||
VisitorErrorCode::InternalError,
|
||||
AZStd::string::format("AZ::Dom::ValueWriter: %s called from within a different container type", endMethodName));
|
||||
}
|
||||
|
||||
if (aznumeric_cast<AZ::u64>(buffer.m_attributes.size()) != attributeCount)
|
||||
{
|
||||
return VisitorFailure(
|
||||
VisitorErrorCode::InternalError,
|
||||
AZStd::string::format(
|
||||
"AZ::Dom::ValueWriter: %s expected %llu attributes but received %zu attributes instead", endMethodName, attributeCount,
|
||||
buffer.m_attributes.size()));
|
||||
}
|
||||
|
||||
if (aznumeric_cast<AZ::u64>(buffer.m_elements.size()) != elementCount)
|
||||
{
|
||||
return VisitorFailure(
|
||||
VisitorErrorCode::InternalError,
|
||||
AZStd::string::format(
|
||||
"AZ::Dom::ValueWriter: %s expected %llu elements but received %zu elements instead", endMethodName, elementCount,
|
||||
buffer.m_elements.size()));
|
||||
}
|
||||
|
||||
if (buffer.m_attributes.size() > 0)
|
||||
{
|
||||
MoveVectorMemory(container.GetMutableObject(), buffer.m_attributes);
|
||||
}
|
||||
|
||||
if(buffer.m_elements.size() > 0)
|
||||
{
|
||||
MoveVectorMemory(container.GetMutableArray(), buffer.m_elements);
|
||||
}
|
||||
|
||||
m_entryStack.pop();
|
||||
return FinishWrite();
|
||||
}
|
||||
|
||||
ValueWriter::ValueBuffer& ValueWriter::GetValueBuffer()
|
||||
{
|
||||
if (m_entryStack.size() <= m_valueBuffers.size())
|
||||
{
|
||||
return m_valueBuffers[m_entryStack.size() - 1];
|
||||
}
|
||||
|
||||
m_valueBuffers.resize(m_entryStack.size());
|
||||
return m_valueBuffers[m_entryStack.size() - 1];
|
||||
}
|
||||
|
||||
Visitor::Result ValueWriter::EndObject(AZ::u64 attributeCount)
|
||||
{
|
||||
return EndContainer(Type::Object, attributeCount, 0);
|
||||
}
|
||||
|
||||
Visitor::Result ValueWriter::Key(AZ::Name key)
|
||||
{
|
||||
AZ_Assert(!m_entryStack.empty(), "Attempmted to push a key with no object");
|
||||
AZ_Assert(!m_entryStack.top().m_container.IsArray(), "Attempted to push a key to an array");
|
||||
m_entryStack.top().m_key = AZStd::move(key);
|
||||
return VisitorSuccess();
|
||||
}
|
||||
|
||||
Visitor::Result ValueWriter::RawKey(AZStd::string_view key, [[maybe_unused]] Lifetime lifetime)
|
||||
{
|
||||
return Key(AZ::Name(key));
|
||||
}
|
||||
|
||||
Visitor::Result ValueWriter::StartArray()
|
||||
{
|
||||
CurrentValue().SetArray();
|
||||
|
||||
m_entryStack.emplace(CurrentValue());
|
||||
return VisitorSuccess();
|
||||
}
|
||||
|
||||
Visitor::Result ValueWriter::EndArray(AZ::u64 elementCount)
|
||||
{
|
||||
return EndContainer(Type::Array, 0, elementCount);
|
||||
}
|
||||
|
||||
Visitor::Result ValueWriter::StartNode(AZ::Name name)
|
||||
{
|
||||
CurrentValue().SetNode(name);
|
||||
|
||||
m_entryStack.emplace(CurrentValue());
|
||||
return VisitorSuccess();
|
||||
}
|
||||
|
||||
Visitor::Result ValueWriter::RawStartNode(AZStd::string_view name, [[maybe_unused]] Lifetime lifetime)
|
||||
{
|
||||
return StartNode(AZ::Name(name));
|
||||
}
|
||||
|
||||
Visitor::Result ValueWriter::EndNode(AZ::u64 attributeCount, AZ::u64 elementCount)
|
||||
{
|
||||
return EndContainer(Type::Node, attributeCount, elementCount);
|
||||
}
|
||||
|
||||
Visitor::Result ValueWriter::OpaqueValue(OpaqueType& value)
|
||||
{
|
||||
CurrentValue().SetOpaqueValue(value);
|
||||
return FinishWrite();
|
||||
}
|
||||
|
||||
Visitor::Result ValueWriter::FinishWrite()
|
||||
{
|
||||
if (m_entryStack.empty())
|
||||
{
|
||||
return VisitorSuccess();
|
||||
}
|
||||
|
||||
Value value;
|
||||
m_entryStack.top().m_value.Swap(value);
|
||||
ValueInfo& newEntry = m_entryStack.top();
|
||||
|
||||
if (!newEntry.m_key.IsEmpty())
|
||||
{
|
||||
GetValueBuffer().m_attributes.emplace_back(AZStd::move(newEntry.m_key), AZStd::move(value));
|
||||
newEntry.m_key = AZ::Name();
|
||||
}
|
||||
else
|
||||
{
|
||||
GetValueBuffer().m_elements.emplace_back(AZStd::move(value));
|
||||
}
|
||||
|
||||
return VisitorSuccess();
|
||||
}
|
||||
|
||||
Value& ValueWriter::CurrentValue()
|
||||
{
|
||||
if (m_entryStack.empty())
|
||||
{
|
||||
return m_result;
|
||||
}
|
||||
return m_entryStack.top().m_value;
|
||||
}
|
||||
} // namespace AZ::Dom
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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/DOM/DomValue.h>
|
||||
#include <AzCore/std/containers/stack.h>
|
||||
|
||||
namespace AZ::Dom
|
||||
{
|
||||
//! Visitor that writes to a Value.
|
||||
//! Supports all Visitor operations.
|
||||
class ValueWriter : public Visitor
|
||||
{
|
||||
public:
|
||||
ValueWriter(Value& outputValue);
|
||||
|
||||
VisitorFlags GetVisitorFlags() const override;
|
||||
Result Null() override;
|
||||
Result Bool(bool value) override;
|
||||
Result Int64(AZ::s64 value) override;
|
||||
Result Uint64(AZ::u64 value) override;
|
||||
Result Double(double value) override;
|
||||
|
||||
Result String(AZStd::string_view value, Lifetime lifetime) override;
|
||||
Result RefCountedString(AZStd::shared_ptr<const AZStd::vector<char>> value, Lifetime lifetime) override;
|
||||
Result StartObject() override;
|
||||
Result EndObject(AZ::u64 attributeCount) override;
|
||||
Result Key(AZ::Name key) override;
|
||||
Result RawKey(AZStd::string_view key, Lifetime lifetime) override;
|
||||
Result StartArray() override;
|
||||
Result EndArray(AZ::u64 elementCount) override;
|
||||
Result StartNode(AZ::Name name) override;
|
||||
Result RawStartNode(AZStd::string_view name, Lifetime lifetime) override;
|
||||
Result EndNode(AZ::u64 attributeCount, AZ::u64 elementCount) override;
|
||||
Result OpaqueValue(OpaqueType& value) override;
|
||||
|
||||
private:
|
||||
Result FinishWrite();
|
||||
Value& CurrentValue();
|
||||
Visitor::Result EndContainer(Type containerType, AZ::u64 attributeCount, AZ::u64 elementCount);
|
||||
|
||||
struct ValueInfo
|
||||
{
|
||||
ValueInfo(Value& container);
|
||||
|
||||
KeyType m_key;
|
||||
Value m_value;
|
||||
Value& m_container;
|
||||
};
|
||||
|
||||
struct ValueBuffer
|
||||
{
|
||||
Array::ContainerType m_elements;
|
||||
Object::ContainerType m_attributes;
|
||||
};
|
||||
|
||||
ValueBuffer& GetValueBuffer();
|
||||
|
||||
Value& m_result;
|
||||
// Stores info about the current value being processed
|
||||
AZStd::stack<ValueInfo, AZStd::deque<ValueInfo, AZStdAlloc<ValueAllocator>>> m_entryStack;
|
||||
// Provides temporary storage for elements and attributes to prevent extra heap allocations
|
||||
// These buffers persist to be reused even as the entry stack changes
|
||||
AZStd::vector<ValueBuffer, AZStdAlloc<ValueAllocator>> m_valueBuffers;
|
||||
};
|
||||
} // namespace AZ::Dom
|
||||
@@ -105,7 +105,12 @@ namespace AZ::Dom
|
||||
return VisitorSuccess();
|
||||
}
|
||||
|
||||
Visitor::Result Visitor::OpaqueValue([[maybe_unused]] const OpaqueType& value, [[maybe_unused]] Lifetime lifetime)
|
||||
Visitor::Result Visitor::RefCountedString(AZStd::shared_ptr<const AZStd::vector<char>> value, Lifetime lifetime)
|
||||
{
|
||||
return String({ value->data(), value->size() }, lifetime);
|
||||
}
|
||||
|
||||
Visitor::Result Visitor::OpaqueValue([[maybe_unused]] OpaqueType& value)
|
||||
{
|
||||
if (!SupportsOpaqueValues())
|
||||
{
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include <AzCore/Name/Name.h>
|
||||
#include <AzCore/Outcome/Outcome.h>
|
||||
#include <AzCore/std/any.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/std/smart_ptr/shared_ptr.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
|
||||
namespace AZ::Dom
|
||||
@@ -167,15 +169,20 @@ namespace AZ::Dom
|
||||
virtual Result Uint64(AZ::u64 value);
|
||||
//! Operates on a double precision, 64 bit floating point value.
|
||||
virtual Result Double(double value);
|
||||
//! Operates on a string value. As strings are a reference type.
|
||||
//! Storage semantics are provided to indicate where the value may be stored persistently or requires a copy.
|
||||
//! Operates on a string value. As strings are a reference type,
|
||||
//! storage semantics are provided to indicate where the value may be stored persistently or requires a copy.
|
||||
//! \param lifetime Specifies the lifetime of this string - if the string has a temporary lifetime, it cannot
|
||||
//! safely be stored as a reference.
|
||||
virtual Result String(AZStd::string_view value, Lifetime lifetime);
|
||||
//! Operates on a ref-counted string value. S
|
||||
//! \param lifetime Specifies the lifetime of this string. If the string has a temporary lifetime, it may not
|
||||
//! be safely stored as a reference, but may still be safely stored as a ref-counted shared_ptr.
|
||||
virtual Result RefCountedString(AZStd::shared_ptr<const AZStd::vector<char>> value, Lifetime lifetime);
|
||||
//! Operates on an opaque value. As opaque values are a reference type, storage semantics are provided to
|
||||
//! indicate where the value may be stored persistently or requires a copy.
|
||||
//! The base implementation of OpaqueValue rejects the operation, as opaque values are meant for special
|
||||
//! cases with specific implementations, not generic usage.
|
||||
//! Storage semantics are provided to indicate where the value may be stored persistently or requires a copy.
|
||||
virtual Result OpaqueValue(const OpaqueType& value, Lifetime lifetime);
|
||||
virtual Result OpaqueValue(OpaqueType& value);
|
||||
//! Operates on a raw value encoded as a UTF-8 string that hasn't had its type deduced.
|
||||
//! Visitors that support raw values (\see VisitorFlags::SupportsRawValues) may parse the raw value and
|
||||
//! forward it to the corresponding value call or calls of their choice.
|
||||
|
||||
@@ -117,6 +117,10 @@ set(FILES
|
||||
DOM/DomBackend.h
|
||||
DOM/DomUtils.cpp
|
||||
DOM/DomUtils.h
|
||||
DOM/DomValue.cpp
|
||||
DOM/DomValue.h
|
||||
DOM/DomValueWriter.cpp
|
||||
DOM/DomValueWriter.h
|
||||
DOM/DomVisitor.cpp
|
||||
DOM/DomVisitor.h
|
||||
DOM/Backends/JSON/JsonBackend.h
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
|
||||
#if defined(HAVE_BENCHMARK)
|
||||
|
||||
#include <AzCore/DOM/DomUtils.h>
|
||||
#include <AzCore/DOM/Backends/JSON/JsonBackend.h>
|
||||
#include <AzCore/DOM/Backends/JSON/JsonSerializationUtils.h>
|
||||
#include <AzCore/DOM/DomUtils.h>
|
||||
#include <AzCore/DOM/DomValue.h>
|
||||
#include <AzCore/JSON/document.h>
|
||||
#include <AzCore/Name/NameDictionary.h>
|
||||
#include <AzCore/Serialization/Json/JsonUtils.h>
|
||||
@@ -25,27 +26,31 @@ namespace Benchmark
|
||||
{
|
||||
UnitTest::AllocatorsBenchmarkFixture::SetUp(st);
|
||||
AZ::NameDictionary::Create();
|
||||
AZ::AllocatorInstance<AZ::Dom::ValueAllocator>::Create();
|
||||
}
|
||||
|
||||
void SetUp(::benchmark::State& st) override
|
||||
{
|
||||
UnitTest::AllocatorsBenchmarkFixture::SetUp(st);
|
||||
AZ::NameDictionary::Create();
|
||||
AZ::AllocatorInstance<AZ::Dom::ValueAllocator>::Create();
|
||||
}
|
||||
|
||||
void TearDown(::benchmark::State& st) override
|
||||
{
|
||||
AZ::AllocatorInstance<AZ::Dom::ValueAllocator>::Destroy();
|
||||
AZ::NameDictionary::Destroy();
|
||||
UnitTest::AllocatorsBenchmarkFixture::TearDown(st);
|
||||
}
|
||||
|
||||
void TearDown(const ::benchmark::State& st) override
|
||||
{
|
||||
AZ::AllocatorInstance<AZ::Dom::ValueAllocator>::Destroy();
|
||||
AZ::NameDictionary::Destroy();
|
||||
UnitTest::AllocatorsBenchmarkFixture::TearDown(st);
|
||||
}
|
||||
|
||||
AZStd::string GenerateDomJsonBenchmarkPayload(int64_t entryCount, int64_t stringTemplateLength)
|
||||
rapidjson::Document GenerateDomJsonBenchmarkDocument(int64_t entryCount, int64_t stringTemplateLength)
|
||||
{
|
||||
rapidjson::Document document;
|
||||
document.SetObject();
|
||||
@@ -103,11 +108,28 @@ namespace Benchmark
|
||||
document.SetObject();
|
||||
document.AddMember("entries", createObject(), document.GetAllocator());
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
AZStd::string GenerateDomJsonBenchmarkPayload(int64_t entryCount, int64_t stringTemplateLength)
|
||||
{
|
||||
rapidjson::Document document = GenerateDomJsonBenchmarkDocument(entryCount, stringTemplateLength);
|
||||
|
||||
AZStd::string serializedJson;
|
||||
auto result = AZ::JsonSerializationUtils::WriteJsonString(document, serializedJson);
|
||||
AZ_Assert(result.IsSuccess(), "Failed to serialize generated JSON");
|
||||
return serializedJson;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void TakeAndDiscardWithoutTimingDtor(T&& value, benchmark::State& state)
|
||||
{
|
||||
{
|
||||
T instance = AZStd::move(value);
|
||||
state.PauseTiming();
|
||||
}
|
||||
state.ResumeTiming();
|
||||
}
|
||||
};
|
||||
|
||||
// Helper macro for registering JSON benchmarks
|
||||
@@ -119,7 +141,7 @@ namespace Benchmark
|
||||
->Args({ 100, 500 }) \
|
||||
->Unit(benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK_DEFINE_F(DomJsonBenchmark, DomDeserializeToDocumentInPlace)(benchmark::State& state)
|
||||
BENCHMARK_DEFINE_F(DomJsonBenchmark, AzDomDeserializeToRapidjsonInPlace)(benchmark::State& state)
|
||||
{
|
||||
AZ::Dom::JsonBackend backend;
|
||||
AZStd::string serializedPayload = GenerateDomJsonBenchmarkPayload(state.range(0), state.range(1));
|
||||
@@ -136,14 +158,38 @@ namespace Benchmark
|
||||
return AZ::Dom::Utils::ReadFromStringInPlace(backend, payloadCopy, visitor);
|
||||
});
|
||||
|
||||
benchmark::DoNotOptimize(result.GetValue());
|
||||
TakeAndDiscardWithoutTimingDtor(result.TakeValue(), state);
|
||||
}
|
||||
|
||||
state.SetBytesProcessed(serializedPayload.size() * state.iterations());
|
||||
}
|
||||
BENCHMARK_REGISTER_JSON(DomJsonBenchmark, DomDeserializeToDocumentInPlace)
|
||||
BENCHMARK_REGISTER_JSON(DomJsonBenchmark, AzDomDeserializeToRapidjsonInPlace)
|
||||
|
||||
BENCHMARK_DEFINE_F(DomJsonBenchmark, DomDeserializeToDocument)(benchmark::State& state)
|
||||
BENCHMARK_DEFINE_F(DomJsonBenchmark, AzDomDeserializeToAzDomValueInPlace)(benchmark::State& state)
|
||||
{
|
||||
AZ::Dom::JsonBackend backend;
|
||||
AZStd::string serializedPayload = GenerateDomJsonBenchmarkPayload(state.range(0), state.range(1));
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
state.PauseTiming();
|
||||
AZStd::string payloadCopy = serializedPayload;
|
||||
state.ResumeTiming();
|
||||
|
||||
auto result = AZ::Dom::Utils::WriteToValue(
|
||||
[&](AZ::Dom::Visitor& visitor)
|
||||
{
|
||||
return AZ::Dom::Utils::ReadFromStringInPlace(backend, payloadCopy, visitor);
|
||||
});
|
||||
|
||||
TakeAndDiscardWithoutTimingDtor(result.TakeValue(), state);
|
||||
}
|
||||
|
||||
state.SetBytesProcessed(serializedPayload.size() * state.iterations());
|
||||
}
|
||||
BENCHMARK_REGISTER_JSON(DomJsonBenchmark, AzDomDeserializeToAzDomValueInPlace)
|
||||
|
||||
BENCHMARK_DEFINE_F(DomJsonBenchmark, AzDomDeserializeToRapidjson)(benchmark::State& state)
|
||||
{
|
||||
AZ::Dom::JsonBackend backend;
|
||||
AZStd::string serializedPayload = GenerateDomJsonBenchmarkPayload(state.range(0), state.range(1));
|
||||
@@ -156,14 +202,34 @@ namespace Benchmark
|
||||
return AZ::Dom::Utils::ReadFromString(backend, serializedPayload, AZ::Dom::Lifetime::Temporary, visitor);
|
||||
});
|
||||
|
||||
benchmark::DoNotOptimize(result.GetValue());
|
||||
TakeAndDiscardWithoutTimingDtor(result.TakeValue(), state);
|
||||
}
|
||||
|
||||
state.SetBytesProcessed(serializedPayload.size() * state.iterations());
|
||||
}
|
||||
BENCHMARK_REGISTER_JSON(DomJsonBenchmark, DomDeserializeToDocument)
|
||||
BENCHMARK_REGISTER_JSON(DomJsonBenchmark, AzDomDeserializeToRapidjson)
|
||||
|
||||
BENCHMARK_DEFINE_F(DomJsonBenchmark, JsonUtilsDeserializeToDocument)(benchmark::State& state)
|
||||
BENCHMARK_DEFINE_F(DomJsonBenchmark, AzDomDeserializeToAzDomValue)(benchmark::State& state)
|
||||
{
|
||||
AZ::Dom::JsonBackend backend;
|
||||
AZStd::string serializedPayload = GenerateDomJsonBenchmarkPayload(state.range(0), state.range(1));
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
auto result = AZ::Dom::Utils::WriteToValue(
|
||||
[&](AZ::Dom::Visitor& visitor)
|
||||
{
|
||||
return AZ::Dom::Utils::ReadFromString(backend, serializedPayload, AZ::Dom::Lifetime::Temporary, visitor);
|
||||
});
|
||||
|
||||
TakeAndDiscardWithoutTimingDtor(result.TakeValue(), state);
|
||||
}
|
||||
|
||||
state.SetBytesProcessed(serializedPayload.size() * state.iterations());
|
||||
}
|
||||
BENCHMARK_REGISTER_JSON(DomJsonBenchmark, AzDomDeserializeToAzDomValue)
|
||||
|
||||
BENCHMARK_DEFINE_F(DomJsonBenchmark, RapidjsonDeserializeToRapidjson)(benchmark::State& state)
|
||||
{
|
||||
AZ::Dom::JsonBackend backend;
|
||||
AZStd::string serializedPayload = GenerateDomJsonBenchmarkPayload(state.range(0), state.range(1));
|
||||
@@ -172,12 +238,78 @@ namespace Benchmark
|
||||
{
|
||||
auto result = AZ::JsonSerializationUtils::ReadJsonString(serializedPayload);
|
||||
|
||||
benchmark::DoNotOptimize(result.GetValue());
|
||||
TakeAndDiscardWithoutTimingDtor(result.TakeValue(), state);
|
||||
}
|
||||
|
||||
state.SetBytesProcessed(serializedPayload.size() * state.iterations());
|
||||
}
|
||||
BENCHMARK_REGISTER_JSON(DomJsonBenchmark, JsonUtilsDeserializeToDocument)
|
||||
BENCHMARK_REGISTER_JSON(DomJsonBenchmark, RapidjsonDeserializeToRapidjson)
|
||||
|
||||
BENCHMARK_DEFINE_F(DomJsonBenchmark, RapidjsonMakeComplexObject)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
TakeAndDiscardWithoutTimingDtor(GenerateDomJsonBenchmarkPayload(state.range(0), state.range(1)), state);
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.range(0) * state.range(0) * state.iterations());
|
||||
}
|
||||
BENCHMARK_REGISTER_JSON(DomJsonBenchmark, RapidjsonMakeComplexObject)
|
||||
|
||||
BENCHMARK_DEFINE_F(DomJsonBenchmark, RapidjsonLookupMemberByString)(benchmark::State& state)
|
||||
{
|
||||
rapidjson::Document document(rapidjson::kObjectType);
|
||||
AZStd::vector<AZStd::string> keys;
|
||||
for (int64_t i = 0; i < state.range(0); ++i)
|
||||
{
|
||||
AZStd::string key(AZStd::string::format("key%" PRId64, i));
|
||||
keys.push_back(key);
|
||||
document.AddMember(rapidjson::Value(key.data(), static_cast<rapidjson::SizeType>(key.size()), document.GetAllocator()), rapidjson::Value(i), document.GetAllocator());
|
||||
}
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
for (const AZStd::string& key : keys)
|
||||
{
|
||||
benchmark::DoNotOptimize(document.FindMember(key.data()));
|
||||
}
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * state.range(0));
|
||||
}
|
||||
BENCHMARK_REGISTER_F(DomJsonBenchmark, RapidjsonLookupMemberByString)->Arg(100)->Arg(1000)->Arg(10000)->Unit(benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK_DEFINE_F(DomJsonBenchmark, RapidjsonDeepCopy)(benchmark::State& state)
|
||||
{
|
||||
rapidjson::Document original = GenerateDomJsonBenchmarkDocument(state.range(0), state.range(1));
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
rapidjson::Document copy;
|
||||
copy.CopyFrom(original, copy.GetAllocator(), true);
|
||||
TakeAndDiscardWithoutTimingDtor(AZStd::move(copy), state);
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations());
|
||||
}
|
||||
|
||||
BENCHMARK_REGISTER_JSON(DomJsonBenchmark, RapidjsonDeepCopy)
|
||||
|
||||
BENCHMARK_DEFINE_F(DomJsonBenchmark, RapidjsonCopyAndMutate)(benchmark::State& state)
|
||||
{
|
||||
rapidjson::Document original = GenerateDomJsonBenchmarkDocument(state.range(0), state.range(1));
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
rapidjson::Document copy;
|
||||
copy.CopyFrom(original, copy.GetAllocator(), true);
|
||||
copy["entries"]["Key0"].PushBack(42, copy.GetAllocator());
|
||||
TakeAndDiscardWithoutTimingDtor(AZStd::move(copy), state);
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations());
|
||||
}
|
||||
BENCHMARK_REGISTER_JSON(DomJsonBenchmark, RapidjsonCopyAndMutate)
|
||||
|
||||
#undef BENCHMARK_REGISTER_JSON
|
||||
} // namespace Benchmark
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* 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/DOM/DomValue.h>
|
||||
#include <AzCore/DOM/DomUtils.h>
|
||||
#include <AzCore/Name/NameDictionary.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <cinttypes>
|
||||
|
||||
namespace AZ::Dom::Benchmark
|
||||
{
|
||||
class DomValueBenchmark : public UnitTest::AllocatorsBenchmarkFixture
|
||||
{
|
||||
public:
|
||||
void SetUp(const ::benchmark::State& st) override
|
||||
{
|
||||
UnitTest::AllocatorsBenchmarkFixture::SetUp(st);
|
||||
AZ::NameDictionary::Create();
|
||||
AZ::AllocatorInstance<ValueAllocator>::Create();
|
||||
}
|
||||
|
||||
void SetUp(::benchmark::State& st) override
|
||||
{
|
||||
UnitTest::AllocatorsBenchmarkFixture::SetUp(st);
|
||||
AZ::NameDictionary::Create();
|
||||
AZ::AllocatorInstance<ValueAllocator>::Create();
|
||||
}
|
||||
|
||||
void TearDown(::benchmark::State& st) override
|
||||
{
|
||||
AZ::AllocatorInstance<ValueAllocator>::Destroy();
|
||||
AZ::NameDictionary::Destroy();
|
||||
UnitTest::AllocatorsBenchmarkFixture::TearDown(st);
|
||||
}
|
||||
|
||||
void TearDown(const ::benchmark::State& st) override
|
||||
{
|
||||
AZ::AllocatorInstance<ValueAllocator>::Destroy();
|
||||
AZ::NameDictionary::Destroy();
|
||||
UnitTest::AllocatorsBenchmarkFixture::TearDown(st);
|
||||
}
|
||||
|
||||
Value GenerateDomBenchmarkPayload(int64_t entryCount, int64_t stringTemplateLength)
|
||||
{
|
||||
Value root(Type::Object);
|
||||
|
||||
AZStd::string entryTemplate;
|
||||
while (entryTemplate.size() < static_cast<size_t>(stringTemplateLength))
|
||||
{
|
||||
entryTemplate += "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor ";
|
||||
}
|
||||
entryTemplate.resize(stringTemplateLength);
|
||||
AZStd::string buffer;
|
||||
|
||||
auto createString = [&](int n) -> Value
|
||||
{
|
||||
return Value(AZStd::string::format("#%i %s", n, entryTemplate.c_str()), true);
|
||||
};
|
||||
|
||||
auto createEntry = [&](int n) -> Value
|
||||
{
|
||||
Value entry(Type::Object);
|
||||
entry.AddMember("string", createString(n));
|
||||
entry.AddMember("int", Value(n));
|
||||
entry.AddMember("double", Value(static_cast<double>(n) * 0.5));
|
||||
entry.AddMember("bool", Value(n % 2 == 0));
|
||||
entry.AddMember("null", Value(Type::Null));
|
||||
return entry;
|
||||
};
|
||||
|
||||
auto createArray = [&]() -> Value
|
||||
{
|
||||
Value array(Type::Array);
|
||||
for (int i = 0; i < entryCount; ++i)
|
||||
{
|
||||
array.ArrayPushBack(createEntry(i));
|
||||
}
|
||||
return array;
|
||||
};
|
||||
|
||||
auto createObject = [&]() -> Value
|
||||
{
|
||||
Value object;
|
||||
object.SetObject();
|
||||
for (int i = 0; i < entryCount; ++i)
|
||||
{
|
||||
buffer = AZStd::string::format("Key%i", i);
|
||||
object.AddMember(AZ::Name(buffer), createArray());
|
||||
}
|
||||
return object;
|
||||
};
|
||||
|
||||
root["entries"] = createObject();
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void TakeAndDiscardWithoutTimingDtor(T&& value, benchmark::State& state)
|
||||
{
|
||||
{
|
||||
T instance = AZStd::move(value);
|
||||
state.PauseTiming();
|
||||
}
|
||||
state.ResumeTiming();
|
||||
}
|
||||
};
|
||||
|
||||
BENCHMARK_DEFINE_F(DomValueBenchmark, AzDomValueMakeComplexObject)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
TakeAndDiscardWithoutTimingDtor(GenerateDomBenchmarkPayload(state.range(0), state.range(1)), state);
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.range(0) * state.range(0) * state.iterations());
|
||||
}
|
||||
BENCHMARK_REGISTER_F(DomValueBenchmark, AzDomValueMakeComplexObject)
|
||||
->Args({ 10, 5 })
|
||||
->Args({ 10, 500 })
|
||||
->Args({ 100, 5 })
|
||||
->Args({ 100, 500 })
|
||||
->Unit(benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK_DEFINE_F(DomValueBenchmark, AzDomValueShallowCopy)(benchmark::State& state)
|
||||
{
|
||||
Value original = GenerateDomBenchmarkPayload(state.range(0), state.range(1));
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
Value copy = original;
|
||||
benchmark::DoNotOptimize(copy);
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations());
|
||||
}
|
||||
BENCHMARK_REGISTER_F(DomValueBenchmark, AzDomValueShallowCopy)
|
||||
->Args({ 10, 5 })
|
||||
->Args({ 10, 500 })
|
||||
->Args({ 100, 5 })
|
||||
->Args({ 100, 500 })
|
||||
->Unit(benchmark::kNanosecond);
|
||||
|
||||
BENCHMARK_DEFINE_F(DomValueBenchmark, AzDomValueCopyAndMutate)(benchmark::State& state)
|
||||
{
|
||||
Value original = GenerateDomBenchmarkPayload(state.range(0), state.range(1));
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
Value copy = original;
|
||||
copy["entries"]["Key0"].ArrayPushBack(Value(42));
|
||||
TakeAndDiscardWithoutTimingDtor(AZStd::move(copy), state);
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations());
|
||||
}
|
||||
BENCHMARK_REGISTER_F(DomValueBenchmark, AzDomValueCopyAndMutate)
|
||||
->Args({ 10, 5 })
|
||||
->Args({ 10, 500 })
|
||||
->Args({ 100, 5 })
|
||||
->Args({ 100, 500 })
|
||||
->Unit(benchmark::kNanosecond);
|
||||
|
||||
BENCHMARK_DEFINE_F(DomValueBenchmark, AzDomValueDeepCopy)(benchmark::State& state)
|
||||
{
|
||||
Value original = GenerateDomBenchmarkPayload(state.range(0), state.range(1));
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
Value copy = Utils::DeepCopy(original);
|
||||
TakeAndDiscardWithoutTimingDtor(AZStd::move(copy), state);
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations());
|
||||
}
|
||||
BENCHMARK_REGISTER_F(DomValueBenchmark, AzDomValueDeepCopy)
|
||||
->Args({ 10, 5 })
|
||||
->Args({ 10, 500 })
|
||||
->Args({ 100, 5 })
|
||||
->Args({ 100, 500 })
|
||||
->Unit(benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK_DEFINE_F(DomValueBenchmark, LookupMemberByName)(benchmark::State& state)
|
||||
{
|
||||
Value value(Type::Object);
|
||||
AZStd::vector<AZ::Name> keys;
|
||||
for (int64_t i = 0; i < state.range(0); ++i)
|
||||
{
|
||||
AZ::Name key(AZStd::string::format("key%" PRId64, i));
|
||||
keys.push_back(key);
|
||||
value[key] = i;
|
||||
}
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
for (const AZ::Name& key : keys)
|
||||
{
|
||||
benchmark::DoNotOptimize(value[key]);
|
||||
}
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * state.range(0));
|
||||
}
|
||||
BENCHMARK_REGISTER_F(DomValueBenchmark, LookupMemberByName)->Arg(100)->Arg(1000)->Arg(10000)->Unit(benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK_DEFINE_F(DomValueBenchmark, LookupMemberByString)(benchmark::State& state)
|
||||
{
|
||||
Value value(Type::Object);
|
||||
AZStd::vector<AZStd::string> keys;
|
||||
for (int64_t i = 0; i < state.range(0); ++i)
|
||||
{
|
||||
AZStd::string key(AZStd::string::format("key%" PRId64, i));
|
||||
keys.push_back(key);
|
||||
value[key] = i;
|
||||
}
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
for (const AZStd::string& key : keys)
|
||||
{
|
||||
benchmark::DoNotOptimize(value[key]);
|
||||
}
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * state.range(0));
|
||||
}
|
||||
BENCHMARK_REGISTER_F(DomValueBenchmark, LookupMemberByString)->Arg(100)->Arg(1000)->Arg(10000)->Unit(benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK_DEFINE_F(DomValueBenchmark, LookupMemberByStringComparison)(benchmark::State& state)
|
||||
{
|
||||
Value value(Type::Object);
|
||||
AZStd::vector<AZStd::string> keys;
|
||||
for (int64_t i = 0; i < state.range(0); ++i)
|
||||
{
|
||||
AZStd::string key(AZStd::string::format("key%" PRId64, i));
|
||||
keys.push_back(key);
|
||||
value[key] = i;
|
||||
}
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
for (const AZStd::string& key : keys)
|
||||
{
|
||||
const Object::ContainerType& object = value.GetObject();
|
||||
benchmark::DoNotOptimize(AZStd::find_if(
|
||||
object.cbegin(), object.cend(),
|
||||
[&key](const Object::EntryType& entry)
|
||||
{
|
||||
return key == entry.first.GetStringView();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * state.range(0));
|
||||
}
|
||||
BENCHMARK_REGISTER_F(DomValueBenchmark, LookupMemberByStringComparison)->Arg(100)->Arg(1000)->Arg(10000)->Unit(benchmark::kMillisecond);
|
||||
|
||||
} // namespace AZ::Dom::Benchmark
|
||||
@@ -0,0 +1,392 @@
|
||||
/*
|
||||
* 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/DOM/Backends/JSON/JsonBackend.h>
|
||||
#include <AzCore/DOM/Backends/JSON/JsonSerializationUtils.h>
|
||||
#include <AzCore/DOM/DomUtils.h>
|
||||
#include <AzCore/DOM/DomValue.h>
|
||||
#include <AzCore/Name/NameDictionary.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerialization.h>
|
||||
#include <AzCore/Serialization/Json/JsonUtils.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzCore/std/numeric.h>
|
||||
|
||||
namespace AZ::Dom::Tests
|
||||
{
|
||||
class DomValueTests : public UnitTest::AllocatorsFixture
|
||||
{
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
UnitTest::AllocatorsFixture::SetUp();
|
||||
NameDictionary::Create();
|
||||
AZ::AllocatorInstance<ValueAllocator>::Create();
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
m_value = Value();
|
||||
|
||||
AZ::AllocatorInstance<ValueAllocator>::Destroy();
|
||||
NameDictionary::Destroy();
|
||||
UnitTest::AllocatorsFixture::TearDown();
|
||||
}
|
||||
|
||||
void PerformValueChecks()
|
||||
{
|
||||
Value shallowCopy = m_value;
|
||||
EXPECT_EQ(m_value, shallowCopy);
|
||||
EXPECT_TRUE(Utils::DeepCompareIsEqual(m_value, shallowCopy));
|
||||
|
||||
Value deepCopy = Utils::DeepCopy(m_value);
|
||||
EXPECT_TRUE(Utils::DeepCompareIsEqual(m_value, deepCopy));
|
||||
}
|
||||
|
||||
Value m_value;
|
||||
};
|
||||
|
||||
TEST_F(DomValueTests, EmptyArray)
|
||||
{
|
||||
m_value.SetArray();
|
||||
|
||||
EXPECT_TRUE(m_value.IsArray());
|
||||
EXPECT_EQ(m_value.ArraySize(), 0);
|
||||
|
||||
PerformValueChecks();
|
||||
}
|
||||
|
||||
TEST_F(DomValueTests, SimpleArray)
|
||||
{
|
||||
m_value.SetArray();
|
||||
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
m_value.ArrayPushBack(Value(i));
|
||||
EXPECT_EQ(m_value.ArraySize(), i + 1);
|
||||
EXPECT_EQ(m_value[i].GetInt64(), i);
|
||||
}
|
||||
|
||||
PerformValueChecks();
|
||||
}
|
||||
|
||||
TEST_F(DomValueTests, NestedArrays)
|
||||
{
|
||||
Value x(5);
|
||||
m_value.SetArray();
|
||||
for (int j = 0; j < 5; ++j)
|
||||
{
|
||||
Value nestedArray(Type::Array);
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
nestedArray.ArrayPushBack(Value(i));
|
||||
}
|
||||
m_value.ArrayPushBack(AZStd::move(nestedArray));
|
||||
}
|
||||
|
||||
EXPECT_EQ(m_value.ArraySize(), 5);
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
EXPECT_EQ(m_value[i].ArraySize(), 5);
|
||||
for (int j = 0; j < 5; ++j)
|
||||
{
|
||||
EXPECT_EQ(m_value[i][j].GetInt64(), j);
|
||||
}
|
||||
}
|
||||
|
||||
PerformValueChecks();
|
||||
}
|
||||
|
||||
TEST_F(DomValueTests, EmptyObject)
|
||||
{
|
||||
m_value.SetObject();
|
||||
EXPECT_EQ(m_value.MemberCount(), 0);
|
||||
|
||||
PerformValueChecks();
|
||||
}
|
||||
|
||||
TEST_F(DomValueTests, SimpleObject)
|
||||
{
|
||||
m_value.SetObject();
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
AZStd::string key = AZStd::string::format("Key%i", i);
|
||||
m_value.AddMember(key, Value(i));
|
||||
EXPECT_EQ(m_value.MemberCount(), i + 1);
|
||||
EXPECT_EQ(m_value[key].GetInt64(), i);
|
||||
}
|
||||
|
||||
PerformValueChecks();
|
||||
}
|
||||
|
||||
TEST_F(DomValueTests, NestedObjects)
|
||||
{
|
||||
m_value.SetObject();
|
||||
for (int j = 0; j < 3; ++j)
|
||||
{
|
||||
Value nestedObject(Type::Object);
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
nestedObject.AddMember(AZStd::string::format("Key%i", i), Value(i));
|
||||
}
|
||||
m_value.AddMember(AZStd::string::format("Obj%i", j), AZStd::move(nestedObject));
|
||||
}
|
||||
|
||||
EXPECT_EQ(m_value.MemberCount(), 3);
|
||||
for (int j = 0; j < 3; ++j)
|
||||
{
|
||||
const Value& nestedObject = m_value[AZStd::string::format("Obj%i", j)];
|
||||
EXPECT_EQ(nestedObject.MemberCount(), 5);
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
EXPECT_EQ(nestedObject[AZStd::string::format("Key%i", i)].GetInt64(), i);
|
||||
}
|
||||
}
|
||||
|
||||
PerformValueChecks();
|
||||
}
|
||||
|
||||
TEST_F(DomValueTests, EmptyNode)
|
||||
{
|
||||
m_value.SetNode("Test");
|
||||
EXPECT_EQ(m_value.GetNodeName(), AZ::Name("Test"));
|
||||
EXPECT_EQ(m_value.MemberCount(), 0);
|
||||
EXPECT_EQ(m_value.ArraySize(), 0);
|
||||
|
||||
PerformValueChecks();
|
||||
}
|
||||
|
||||
TEST_F(DomValueTests, SimpleNode)
|
||||
{
|
||||
m_value.SetNode("Test");
|
||||
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
m_value.ArrayPushBack(Value(i));
|
||||
EXPECT_EQ(m_value.ArraySize(), i + 1);
|
||||
EXPECT_EQ(m_value[i].GetInt64(), i);
|
||||
|
||||
if (i < 5)
|
||||
{
|
||||
AZ::Name key = AZ::Name(AZStd::string::format("TwoTimes%i", i));
|
||||
m_value.AddMember(key, Value(i * 2));
|
||||
EXPECT_EQ(m_value.MemberCount(), i + 1);
|
||||
EXPECT_EQ(m_value[key].GetInt64(), i * 2);
|
||||
}
|
||||
}
|
||||
|
||||
PerformValueChecks();
|
||||
}
|
||||
|
||||
TEST_F(DomValueTests, NestedNodes)
|
||||
{
|
||||
m_value.SetNode("TopLevel");
|
||||
|
||||
const AZ::Name childNodeName("ChildNode");
|
||||
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
Value childNode(Type::Node);
|
||||
childNode.SetNodeName(childNodeName);
|
||||
childNode.SetNodeValue(Value(i));
|
||||
|
||||
childNode.AddMember("foo", Value(i));
|
||||
childNode.AddMember("bar", Value("test", false));
|
||||
|
||||
m_value.ArrayPushBack(childNode);
|
||||
}
|
||||
|
||||
EXPECT_EQ(m_value.ArraySize(), 5);
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
const Value& childNode = m_value[i];
|
||||
EXPECT_EQ(childNode.GetNodeName(), childNodeName);
|
||||
EXPECT_EQ(childNode.GetNodeValue().GetInt64(), i);
|
||||
EXPECT_EQ(childNode["foo"].GetInt64(), i);
|
||||
EXPECT_EQ(childNode["bar"].GetString(), "test");
|
||||
}
|
||||
|
||||
PerformValueChecks();
|
||||
}
|
||||
|
||||
TEST_F(DomValueTests, Int64)
|
||||
{
|
||||
m_value.SetObject();
|
||||
m_value["int64_min"] = AZStd::numeric_limits<int64_t>::min();
|
||||
m_value["int64_max"] = AZStd::numeric_limits<int64_t>::max();
|
||||
|
||||
EXPECT_EQ(m_value["int64_min"].GetType(), Type::Int64);
|
||||
EXPECT_EQ(m_value["int64_min"].GetInt64(), AZStd::numeric_limits<int64_t>::min());
|
||||
EXPECT_EQ(m_value["int64_max"].GetType(), Type::Int64);
|
||||
EXPECT_EQ(m_value["int64_max"].GetInt64(), AZStd::numeric_limits<int64_t>::max());
|
||||
|
||||
PerformValueChecks();
|
||||
}
|
||||
|
||||
TEST_F(DomValueTests, Uint64)
|
||||
{
|
||||
m_value.SetObject();
|
||||
m_value["uint64_min"] = AZStd::numeric_limits<uint64_t>::min();
|
||||
m_value["uint64_max"] = AZStd::numeric_limits<uint64_t>::max();
|
||||
|
||||
EXPECT_EQ(m_value["uint64_min"].GetType(), Type::Uint64);
|
||||
EXPECT_EQ(m_value["uint64_min"].GetInt64(), AZStd::numeric_limits<uint64_t>::min());
|
||||
EXPECT_EQ(m_value["uint64_max"].GetType(), Type::Uint64);
|
||||
EXPECT_EQ(m_value["uint64_max"].GetInt64(), AZStd::numeric_limits<uint64_t>::max());
|
||||
|
||||
PerformValueChecks();
|
||||
}
|
||||
|
||||
TEST_F(DomValueTests, Double)
|
||||
{
|
||||
m_value.SetObject();
|
||||
m_value["double_min"] = AZStd::numeric_limits<double>::min();
|
||||
m_value["double_max"] = AZStd::numeric_limits<double>::max();
|
||||
|
||||
EXPECT_EQ(m_value["double_min"].GetType(), Type::Double);
|
||||
EXPECT_EQ(m_value["double_min"].GetDouble(), AZStd::numeric_limits<double>::min());
|
||||
EXPECT_EQ(m_value["double_max"].GetType(), Type::Double);
|
||||
EXPECT_EQ(m_value["double_max"].GetDouble(), AZStd::numeric_limits<double>::max());
|
||||
|
||||
PerformValueChecks();
|
||||
}
|
||||
|
||||
TEST_F(DomValueTests, Null)
|
||||
{
|
||||
m_value.SetObject();
|
||||
m_value["null_value"] = Value(Type::Null);
|
||||
|
||||
EXPECT_EQ(m_value["null_value"].GetType(), Type::Null);
|
||||
EXPECT_EQ(m_value["null_type"], Value());
|
||||
|
||||
PerformValueChecks();
|
||||
}
|
||||
|
||||
TEST_F(DomValueTests, Bool)
|
||||
{
|
||||
m_value.SetObject();
|
||||
m_value["true_value"] = true;
|
||||
m_value["false_value"] = false;
|
||||
|
||||
EXPECT_EQ(m_value["true_value"].GetType(), Type::Bool);
|
||||
EXPECT_EQ(m_value["true_value"].GetBool(), true);
|
||||
EXPECT_EQ(m_value["false_value"].GetType(), Type::Bool);
|
||||
EXPECT_EQ(m_value["false_value"].GetBool(), false);
|
||||
|
||||
PerformValueChecks();
|
||||
}
|
||||
|
||||
TEST_F(DomValueTests, String)
|
||||
{
|
||||
const char* s1 = "reference string long enough to avoid SSO";
|
||||
const char* s2 = "copy string long enough to avoid SSO";
|
||||
|
||||
m_value.SetObject();
|
||||
AZStd::string stringToReference = s1;
|
||||
m_value["no_copy"] = Value(stringToReference, false);
|
||||
AZStd::string stringToCopy = s2;
|
||||
m_value["copy"] = Value(stringToCopy, true);
|
||||
|
||||
EXPECT_EQ(m_value["no_copy"].GetType(), Type::String);
|
||||
EXPECT_EQ(m_value["no_copy"].GetString(), s1);
|
||||
stringToReference.at(0) = 'F';
|
||||
EXPECT_NE(m_value["no_copy"].GetString(), s1);
|
||||
|
||||
EXPECT_EQ(m_value["copy"].GetType(), Type::String);
|
||||
EXPECT_EQ(m_value["copy"].GetString(), s2);
|
||||
stringToCopy.at(0) = 'F';
|
||||
EXPECT_EQ(m_value["copy"].GetString(), s2);
|
||||
|
||||
PerformValueChecks();
|
||||
}
|
||||
|
||||
TEST_F(DomValueTests, CopyOnWrite_Object)
|
||||
{
|
||||
Value v1(Type::Object);
|
||||
v1["foo"] = 5;
|
||||
|
||||
Value nestedObject(Type::Object);
|
||||
v1["obj"] = nestedObject;
|
||||
|
||||
Value v2 = v1;
|
||||
EXPECT_EQ(&v1.GetObject(), &v2.GetObject());
|
||||
EXPECT_EQ(&v1.FindMember("obj")->second.GetObject(), &v2.FindMember("obj")->second.GetObject());
|
||||
|
||||
v2["foo"] = 0;
|
||||
|
||||
EXPECT_NE(&v1.GetObject(), &v2.GetObject());
|
||||
EXPECT_EQ(&v1.FindMember("obj")->second.GetObject(), &v2.FindMember("obj")->second.GetObject());
|
||||
|
||||
v2["obj"]["key"] = true;
|
||||
|
||||
EXPECT_NE(&v1.GetObject(), &v2.GetObject());
|
||||
EXPECT_NE(&v1.FindMember("obj")->second.GetObject(), &v2.FindMember("obj")->second.GetObject());
|
||||
|
||||
v2 = v1;
|
||||
|
||||
EXPECT_EQ(&v1.GetObject(), &v2.GetObject());
|
||||
EXPECT_EQ(&v1.FindMember("obj")->second.GetObject(), &v2.FindMember("obj")->second.GetObject());
|
||||
}
|
||||
|
||||
TEST_F(DomValueTests, CopyOnWrite_Array)
|
||||
{
|
||||
Value v1(Type::Array);
|
||||
v1.ArrayPushBack(Value(1));
|
||||
v1.ArrayPushBack(Value(2));
|
||||
|
||||
Value nestedArray(Type::Array);
|
||||
v1.ArrayPushBack(nestedArray);
|
||||
Value v2 = v1;
|
||||
|
||||
EXPECT_EQ(&v1.GetArray(), &v2.GetArray());
|
||||
EXPECT_EQ(&v1.ArrayAt(2).GetArray(), &v2.ArrayAt(2).GetArray());
|
||||
|
||||
v2[0] = 0;
|
||||
|
||||
EXPECT_NE(&v1.GetArray(), &v2.GetArray());
|
||||
EXPECT_EQ(&v1.ArrayAt(2).GetArray(), &v2.ArrayAt(2).GetArray());
|
||||
|
||||
v2[2].ArrayPushBack(Value(42));
|
||||
|
||||
EXPECT_NE(&v1.GetArray(), &v2.GetArray());
|
||||
EXPECT_NE(&v1.ArrayAt(2).GetArray(), &v2.ArrayAt(2).GetArray());
|
||||
|
||||
v2 = v1;
|
||||
|
||||
EXPECT_EQ(&v1.GetArray(), &v2.GetArray());
|
||||
EXPECT_EQ(&v1.ArrayAt(2).GetArray(), &v2.ArrayAt(2).GetArray());
|
||||
}
|
||||
|
||||
TEST_F(DomValueTests, CopyOnWrite_Node)
|
||||
{
|
||||
Value v1;
|
||||
v1.SetNode("TopLevel");
|
||||
|
||||
v1.ArrayPushBack(Value(1));
|
||||
v1.ArrayPushBack(Value(2));
|
||||
v1["obj"].SetNode("Nested");
|
||||
Value v2 = v1;
|
||||
|
||||
EXPECT_EQ(&v1.GetNode(), &v2.GetNode());
|
||||
EXPECT_EQ(&v1["obj"].GetNode(), &v2["obj"].GetNode());
|
||||
|
||||
v2[0] = 0;
|
||||
|
||||
EXPECT_NE(&v1.GetNode(), &v2.GetNode());
|
||||
EXPECT_EQ(&v1["obj"].GetNode(), &v2["obj"].GetNode());
|
||||
|
||||
v2["obj"].ArrayPushBack(Value(42));
|
||||
|
||||
EXPECT_NE(&v1.GetNode(), &v2.GetNode());
|
||||
EXPECT_NE(&v1["obj"].GetNode(), &v2["obj"].GetNode());
|
||||
|
||||
v2 = v1;
|
||||
|
||||
EXPECT_EQ(&v1.GetNode(), &v2.GetNode());
|
||||
EXPECT_EQ(&v1["obj"].GetNode(), &v2["obj"].GetNode());
|
||||
}
|
||||
} // namespace AZ::Dom::Tests
|
||||
@@ -217,6 +217,8 @@ set(FILES
|
||||
AZStd/VectorAndArray.cpp
|
||||
DOM/DomJsonTests.cpp
|
||||
DOM/DomJsonBenchmarks.cpp
|
||||
DOM/DomValueTests.cpp
|
||||
DOM/DomValueBenchmarks.cpp
|
||||
)
|
||||
|
||||
# Prevent the following files from being grouped in UNITY builds
|
||||
|
||||
Reference in New Issue
Block a user