diff --git a/Code/Framework/AzCore/AzCore/DOM/DomValue.cpp b/Code/Framework/AzCore/AzCore/DOM/DomValue.cpp index 675a6931f5..d9398da116 100644 --- a/Code/Framework/AzCore/AzCore/DOM/DomValue.cpp +++ b/Code/Framework/AzCore/AzCore/DOM/DomValue.cpp @@ -476,7 +476,7 @@ namespace AZ::Dom return FindMember(AZ::Name(name)); } - Object::Iterator Value::FindMember(KeyType name) + Object::Iterator Value::FindMutableMember(KeyType name) { Object::ContainerType& object = GetObjectInternal(); return AZStd::find_if( @@ -487,9 +487,9 @@ namespace AZ::Dom }); } - Object::Iterator Value::FindMember(AZStd::string_view name) + Object::Iterator Value::FindMutableMember(AZStd::string_view name) { - return FindMember(AZ::Name(name)); + return FindMutableMember(AZ::Name(name)); } Value& Value::MemberReserve(size_t newCapacity) @@ -511,7 +511,7 @@ namespace AZ::Dom Value& Value::AddMember(KeyType name, const Value& value) { Object::ContainerType& object = GetObjectInternal(); - if (auto memberIt = FindMember(name); memberIt != object.end()) + if (auto memberIt = FindMutableMember(name); memberIt != object.end()) { memberIt->second = value; } @@ -530,7 +530,7 @@ namespace AZ::Dom Value& Value::AddMember(AZ::Name name, Value&& value) { Object::ContainerType& object = GetObjectInternal(); - if (auto memberIt = FindMember(name); memberIt != object.end()) + if (auto memberIt = FindMutableMember(name); memberIt != object.end()) { memberIt->second = value; } @@ -601,7 +601,7 @@ namespace AZ::Dom return EraseMember(AZ::Name(name)); } - Object::ContainerType& Value::GetObject() + Object::ContainerType& Value::GetMutableObject() { return GetObjectInternal(); } @@ -647,6 +647,16 @@ namespace AZ::Dom return GetArrayInternal()[index]; } + Value& Value::MutableAt(size_t index) + { + return operator[](index); + } + + const Value& Value::At(size_t index) const + { + return operator[](index); + } + Array::ConstIterator Value::Begin() const { return GetArrayInternal().begin(); @@ -695,7 +705,7 @@ namespace AZ::Dom return GetArrayInternal().erase(first, last); } - Array::ContainerType& Value::GetArray() + Array::ContainerType& Value::GetMutableArray() { return GetArrayInternal(); } @@ -766,6 +776,16 @@ namespace AZ::Dom return Value(); } + Node& Value::GetMutableNode() + { + return GetNodeInternal(); + } + + const Node& Value::GetNode() const + { + return GetNodeInternal(); + } + int64_t Value::GetInt64() const { switch (m_value.index()) diff --git a/Code/Framework/AzCore/AzCore/DOM/DomValue.h b/Code/Framework/AzCore/AzCore/DOM/DomValue.h index 61c3207ea4..39ae5a5c37 100644 --- a/Code/Framework/AzCore/AzCore/DOM/DomValue.h +++ b/Code/Framework/AzCore/AzCore/DOM/DomValue.h @@ -162,10 +162,10 @@ namespace AZ::Dom 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; - Object::Iterator FindMember(KeyType name); - Object::Iterator FindMember(AZStd::string_view name); Value& MemberReserve(size_t newCapacity); bool HasMember(KeyType name) const; @@ -185,7 +185,7 @@ namespace AZ::Dom Object::Iterator EraseMember(KeyType name); Object::Iterator EraseMember(AZStd::string_view name); - Object::ContainerType& GetObject(); + Object::ContainerType& GetMutableObject(); const Object::ContainerType& GetObject() const; // Array API (also used by Node)... @@ -199,6 +199,9 @@ namespace AZ::Dom Value& operator[](size_t index); const Value& operator[](size_t index) const; + Value& MutableAt(size_t index); + const Value& At(size_t index) const; + Array::ConstIterator Begin() const; Array::ConstIterator End() const; Array::Iterator Begin(); @@ -211,7 +214,7 @@ namespace AZ::Dom Array::Iterator Erase(Array::ConstIterator pos); Array::Iterator Erase(Array::ConstIterator first, Array::ConstIterator last); - Array::ContainerType& GetArray(); + Array::ContainerType& GetMutableArray(); const Array::ContainerType& GetArray() const; // Node API (supports both object + array API, plus a dedicated NodeName)... @@ -230,6 +233,9 @@ namespace AZ::Dom //! 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); diff --git a/Code/Framework/AzCore/Tests/DOM/DomValueTests.cpp b/Code/Framework/AzCore/Tests/DOM/DomValueTests.cpp index 75cb71248f..84e2e16958 100644 --- a/Code/Framework/AzCore/Tests/DOM/DomValueTests.cpp +++ b/Code/Framework/AzCore/Tests/DOM/DomValueTests.cpp @@ -295,4 +295,90 @@ namespace AZ::Dom::Tests PerformValueChecks(); } + + TEST_F(DomValueTests, CopyOnWrite_Object) + { + Value v1(Type::ObjectType); + v1["foo"] = 5; + + Value nestedObject(Type::ObjectType); + 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::ArrayType); + v1.PushBack(1); + v1.PushBack(2); + + Value nestedArray(Type::ArrayType); + v1.PushBack(nestedArray); + Value v2 = v1; + + EXPECT_EQ(&v1.GetArray(), &v2.GetArray()); + EXPECT_EQ(&v1.At(2).GetArray(), &v2.At(2).GetArray()); + + v2[0] = 0; + + EXPECT_NE(&v1.GetArray(), &v2.GetArray()); + EXPECT_EQ(&v1.At(2).GetArray(), &v2.At(2).GetArray()); + + v2[2].PushBack(42); + + EXPECT_NE(&v1.GetArray(), &v2.GetArray()); + EXPECT_NE(&v1.At(2).GetArray(), &v2.At(2).GetArray()); + + v2 = v1; + + EXPECT_EQ(&v1.GetArray(), &v2.GetArray()); + EXPECT_EQ(&v1.At(2).GetArray(), &v2.At(2).GetArray()); + } + + TEST_F(DomValueTests, CopyOnWrite_Node) + { + Value v1; + v1.SetNode("TopLevel"); + + v1.PushBack(1); + v1.PushBack(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"].PushBack(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