Merge pull request #7565 from aws-lumberyard-dev/nvsickle/DomPrefixTree

Add DomPrefixTree, a DOM path => value lookup structure
monroegm-disable-blank-issue-2
Nicholas Van Sickle 4 years ago committed by GitHub
commit 26566a06d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -7,9 +7,9 @@
*/
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/Console/ConsoleTypeHelpers.h>
#include <AzCore/DOM/DomPath.h>
#include <AzCore/std/string/fixed_string.h>
#include <AzCore/Console/ConsoleTypeHelpers.h>
namespace AZ::Dom
{
@ -117,6 +117,36 @@ namespace AZ::Dom
return AZStd::get<size_t>(m_value);
}
size_t PathEntry::GetHash() const
{
return AZStd::visit(
[&](auto&& value) -> size_t
{
using CurrentType = AZStd::decay_t<decltype(value)>;
if constexpr (AZStd::is_same_v<CurrentType, size_t>)
{
AZStd::hash<size_t> hasher;
return hasher(value);
}
else if constexpr (AZStd::is_same_v<CurrentType, AZ::Name>)
{
return value.GetHash();
}
},
m_value);
}
} // namespace AZ::Dom
namespace AZStd
{
size_t AZStd::hash<AZ::Dom::PathEntry>::operator()(const AZ::Dom::PathEntry& entry) const
{
return entry.GetHash();
}
} // namespace AZStd
namespace AZ::Dom
{
const AZ::Name& PathEntry::GetKey() const
{
AZ_Assert(IsKey(), "Key called on PathEntry that is not a key");

@ -55,11 +55,24 @@ namespace AZ::Dom
size_t GetIndex() const;
const AZ::Name& GetKey() const;
size_t GetHash() const;
private:
AZStd::variant<size_t, AZ::Name> m_value;
};
} // namespace AZ::Dom
namespace AZStd
{
template<>
struct hash<AZ::Dom::PathEntry>
{
size_t operator()(const AZ::Dom::PathEntry& entry) const;
};
} // namespace AZStd
namespace AZ::Dom
{
//! Represents a path, represented as a series of PathEntry values, to a position in a Value.
class Path final
{
@ -135,7 +148,7 @@ namespace AZ::Dom
AZStd::string ToString() const;
void AppendToString(AZStd::string& output) const;
template <class T>
template<class T>
void AppendToString(T& output) const
{
const size_t startIndex = output.length();

@ -0,0 +1,100 @@
/*
* 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/DomPath.h>
#include <AzCore/std/containers/stack.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/optional.h>
#include <AzCore/std/ranges/ranges.h>
#include <AzCore/std/tuple.h>
namespace AZ::Dom
{
//! Specifies how a path matches against a DomPrefixTree
enum class PrefixTreeMatch
{
//! Only an exact path will match.
//! For the path "/foo/bar" only "/foo/bar" will match while
//! "/foo" and "/foo/bar/baz" will not.
ExactPath,
//! The path, and any of its subpaths, will match.
//! For the path "/foo/bar" both "/foo/bar" and any subpaths like
//! "/foo/bar/0" will match, while "/foo" and orthogonal paths like
//! "/bar" will not
PathAndSubpaths,
//! Any of the path's subpaths will match, excepting the path itself.
//! For the path "/foo/bar", "/foo/bar" will not match but "/foo/bar/0"
//! will.
SubpathsOnly,
};
template<class Range, class T, class = void>
constexpr bool RangeConvertibleToPrefixTree = false;
template<class Range, class T>
constexpr bool RangeConvertibleToPrefixTree<
Range,
T,
AZStd::enable_if_t<
AZStd::ranges::input_range<Range> && AZStd::tuple_size<AZStd::ranges::range_value_t<Range>>::value == 2 &&
AZStd::convertible_to<AZStd::tuple_element_t<0, AZStd::ranges::range_value_t<Range>>, Path> &&
AZStd::convertible_to<AZStd::tuple_element_t<1, AZStd::ranges::range_value_t<Range>>, T>>> = true;
//! A prefix tree that maps DOM paths to some arbitrary value.
template<class T>
class DomPrefixTree
{
public:
DomPrefixTree() = default;
DomPrefixTree(const DomPrefixTree&) = default;
DomPrefixTree(DomPrefixTree&&) = default;
explicit DomPrefixTree(AZStd::initializer_list<AZStd::pair<Path, T>> init);
template<class Range, class = AZStd::enable_if_t<RangeConvertibleToPrefixTree<Range, T>>>
explicit DomPrefixTree(Range&& range);
DomPrefixTree& operator=(const DomPrefixTree&) = default;
DomPrefixTree& operator=(DomPrefixTree&&) = default;
using VisitorFunction = AZStd::function<void(const Path&, const T&)>;
//! Visits a path and calls a visitor for each matching path and value.
void VisitPath(const Path& path, PrefixTreeMatch match, const VisitorFunction& visitor) const;
//! Visits a path and returns the most specific matching value, or null if none was found.
T* ValueAtPath(const Path& path, PrefixTreeMatch match);
//! \see ValueAtPath
const T* ValueAtPath(const Path& path, PrefixTreeMatch match) const;
//! Visits a path and returns the most specific matching value or some default value.
template<class Deduced>
T ValueAtPathOrDefault(const Path& path, Deduced&& defaultValue, PrefixTreeMatch match) const;
//! Sets the value stored at path.
template<class Deduced>
void SetValue(const Path& path, Deduced&& value);
//! Removes the value stored at path. If removeChildren is true, also removes any values stored at subpaths.
void EraseValue(const Path& path, bool removedChildren = false);
//! Removes all entries from this tree.
void Clear();
private:
struct Node
{
AZStd::unordered_map<PathEntry, Node> m_values;
AZStd::optional<T> m_data;
};
Node* GetNodeForPath(const Path& path);
const Node* GetNodeForPath(const Path& path) const;
Node m_rootNode;
};
} // namespace AZ::Dom
#include <AzCore/DOM/DomPrefixTree.inl>

@ -0,0 +1,234 @@
/*
* 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::Dom
{
template<class T>
DomPrefixTree<T>::DomPrefixTree(AZStd::initializer_list<AZStd::pair<Path, T>> init)
{
for (const auto& [path, value] : init)
{
SetValue(path, value);
}
}
template <class T>
template<class Range, class>
DomPrefixTree<T>::DomPrefixTree(Range&& range)
{
for (auto&& [path, value] : AZStd::forward<Range>(range))
{
SetValue(path, value);
}
}
template<class T>
auto DomPrefixTree<T>::GetNodeForPath(const Path& path) -> Node*
{
Node* node = &m_rootNode;
for (const auto& entry : path)
{
auto entryIt = node->m_values.find(entry);
if (entryIt == node->m_values.end())
{
return nullptr;
}
node = &entryIt->second;
}
return node;
}
template<class T>
auto DomPrefixTree<T>::GetNodeForPath(const Path& path) const -> const Node*
{
const Node* node = &m_rootNode;
for (const auto& entry : path)
{
auto entryIt = node->m_values.find(entry);
if (entryIt == node->m_values.end())
{
return nullptr;
}
node = &entryIt->second;
}
return node;
}
template<class T>
void DomPrefixTree<T>::VisitPath(const Path& path, PrefixTreeMatch match, const VisitorFunction& visitor) const
{
const Node* rootNode = GetNodeForPath(path);
if (rootNode == nullptr)
{
return;
}
if ((match == PrefixTreeMatch::ExactPath || match == PrefixTreeMatch::PathAndSubpaths) && rootNode->m_data.has_value())
{
visitor(path, rootNode->m_data.value());
}
if (match == PrefixTreeMatch::ExactPath)
{
return;
}
Path currentPath = path;
struct PopPathEntry
{
};
using StackEntry = AZStd::variant<const Node*, PathEntry, PopPathEntry>;
AZStd::stack<StackEntry> stack({ rootNode });
while (!stack.empty())
{
StackEntry entry = AZStd::move(stack.top());
stack.pop();
AZStd::visit(
[&](auto&& value)
{
using CurrentType = AZStd::decay_t<decltype(value)>;
if constexpr (AZStd::is_same_v<CurrentType, const Node*>)
{
if (value != rootNode && value->m_data.has_value())
{
visitor(currentPath, value->m_data.value());
}
for (const auto& entry : value->m_values)
{
// The stack runs this in reverse order, so we'll:
// 1) Push the current path entry to currentPath
// 2a) Process the value at the path (if any)
// 2b) Process the value's descendants at the path (if any)
// 3) Pop the path entry from the stack
stack.push(PopPathEntry{});
stack.push(&entry.second);
stack.push(entry.first);
}
}
else if constexpr (AZStd::is_same_v<CurrentType, PathEntry>)
{
currentPath.Push(value);
}
else if constexpr (AZStd::is_same_v<CurrentType, PopPathEntry>)
{
currentPath.Pop();
}
},
entry);
}
}
template<class T>
T* DomPrefixTree<T>::ValueAtPath(const Path& path, PrefixTreeMatch match)
{
// Just look up the node if we're looking for an exact path
if (match == PrefixTreeMatch::ExactPath)
{
if (Node* node = GetNodeForPath(path); node != nullptr && node->m_data.has_value())
{
return &node->m_data.value();
}
return {};
}
// Otherwise, walk to find the closest anscestor with a value
Node* node = &m_rootNode;
T* result = nullptr;
const size_t lengthToIterate = match == PrefixTreeMatch::SubpathsOnly ? path.Size() - 1 : path.Size();
for (size_t i = 0; i < lengthToIterate; ++i)
{
if (node->m_data.has_value())
{
result = &node->m_data.value();
}
const PathEntry& entry = path[i];
auto entryIt = node->m_values.find(entry);
if (entryIt == node->m_values.end())
{
break;
}
node = &entryIt->second;
}
if (node->m_data.has_value())
{
result = &node->m_data.value();
}
return result;
}
template<class T>
const T* DomPrefixTree<T>::ValueAtPath(const Path& path, PrefixTreeMatch match) const
{
// Const coerce the ValueAtPath result, which doesn't mutate but returns a mutable pointer
return const_cast<DomPrefixTree<T>*>(this)->ValueAtPath(path, match);
}
template<class T>
template<class Deduced>
T DomPrefixTree<T>::ValueAtPathOrDefault(const Path& path, Deduced&& defaultValue, PrefixTreeMatch match) const
{
const T* value = ValueAtPath(path, match);
return value == nullptr ? AZStd::forward<Deduced>(defaultValue) : *value;
}
template<class T>
template<class Deduced>
void DomPrefixTree<T>::SetValue(const Path& path, Deduced&& value)
{
Node* node = &m_rootNode;
for (const PathEntry& entry : path)
{
// Get or create an entry in this node
node = &node->m_values[entry];
}
node->m_data = AZStd::forward<Deduced>(value);
}
template<class T>
void DomPrefixTree<T>::EraseValue(const Path& path, bool removeChildren)
{
Node* node = &m_rootNode;
const size_t entriesToIterate = path.Size() - 1;
for (size_t i = 0; i < entriesToIterate; ++i)
{
const PathEntry& entry = path[i];
auto nodeIt = node->m_values.find(entry);
if (nodeIt == node->m_values.end())
{
return;
}
node = &nodeIt->second;
}
auto nodeIt = node->m_values.find(path[path.Size() - 1]);
if (nodeIt != node->m_values.end())
{
if (removeChildren)
{
node->m_values.erase(nodeIt);
}
else
{
nodeIt->second.m_data = {};
}
}
}
template<class T>
void DomPrefixTree<T>::Clear()
{
m_rootNode = Node();
}
} // namespace AZ::Dom

@ -151,6 +151,18 @@ namespace AZ::Dom
return Value(value);
}
Value Value::CreateNode(AZ::Name nodeName)
{
Value result(Type::Node);
result.SetNodeName(AZStd::move(nodeName));
return result;
}
Value Value::CreateNode(AZStd::string_view nodeName)
{
return CreateNode(AZ::Name(nodeName));
}
Value::Value(int8_t value)
: m_value(aznumeric_cast<int64_t>(value))
{

@ -222,6 +222,8 @@ namespace AZ::Dom
explicit Value(T*) = delete;
static Value FromOpaqueValue(const AZStd::any& value);
static Value CreateNode(AZ::Name nodeName);
static Value CreateNode(AZStd::string_view nodeName);
// Equality / comparison / swap...
Value& operator=(const Value&);

@ -130,6 +130,8 @@ set(FILES
DOM/DomVisitor.h
DOM/DomComparison.cpp
DOM/DomComparison.h
DOM/DomPrefixTree.h
DOM/DomPrefixTree.inl
DOM/Backends/JSON/JsonBackend.h
DOM/Backends/JSON/JsonSerializationUtils.cpp
DOM/Backends/JSON/JsonSerializationUtils.h

@ -0,0 +1,104 @@
/*
* 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 <Tests/DOM/DomFixtures.h>
#include <AzCore/DOM/DomPrefixTree.h>
#define REGISTER_TREE_BENCHMARK(BaseClass, Method) \
BENCHMARK_REGISTER_F(BaseClass, Method)->Args({10, 1})->Args({1000, 1})->Args({10, 5})->Args({1000, 5})
namespace AZ::Dom::Benchmark
{
class DomPrefixTreeBenchmark : public Tests::DomBenchmarkFixture
{
public:
void SetUpHarness() override
{
Tests::DomBenchmarkFixture::SetUpHarness();
m_registeredPaths = AZStd::make_unique<AZStd::vector<Path>>();
}
void TearDownHarness() override
{
m_registeredPaths.reset();
m_tree.Clear();
Tests::DomBenchmarkFixture::TearDownHarness();
}
void SetupTree(benchmark::State& state)
{
const size_t numPaths = aznumeric_cast<size_t>(state.range(0));
const size_t depth = aznumeric_cast<size_t>(state.range(1));
Path path("/root");
for (size_t i = 0; i < numPaths; ++i)
{
for (size_t c = 0; c < depth; ++c)
{
path.Push(i % 4);
m_tree.SetValue(path, AZStd::string::format("entry%zu", i));
m_registeredPaths->push_back(path);
}
for (size_t c = 0; c < depth; ++c)
{
path.Pop();
}
}
}
DomPrefixTree<AZStd::string> m_tree;
AZStd::unique_ptr<AZStd::vector<Path>> m_registeredPaths;
};
BENCHMARK_DEFINE_F(DomPrefixTreeBenchmark, FindValue_ExactPath)(benchmark::State& state)
{
SetupTree(state);
for (auto _ : state)
{
for (const auto& pathToCheck : *m_registeredPaths)
{
benchmark::DoNotOptimize(m_tree.ValueAtPath(pathToCheck, PrefixTreeMatch::ExactPath));
}
}
state.SetItemsProcessed(m_registeredPaths->size() * state.iterations());
}
REGISTER_TREE_BENCHMARK(DomPrefixTreeBenchmark, FindValue_ExactPath);
BENCHMARK_DEFINE_F(DomPrefixTreeBenchmark, FindValue_InexactPath)(benchmark::State& state)
{
SetupTree(state);
for (auto _ : state)
{
for (const auto& pathToCheck : *m_registeredPaths)
{
benchmark::DoNotOptimize(m_tree.ValueAtPath(pathToCheck, PrefixTreeMatch::PathAndSubpaths));
}
}
state.SetItemsProcessed(m_registeredPaths->size() * state.iterations());
}
REGISTER_TREE_BENCHMARK(DomPrefixTreeBenchmark, FindValue_InexactPath);
BENCHMARK_DEFINE_F(DomPrefixTreeBenchmark, FindValue_VisitEntries)(benchmark::State& state)
{
SetupTree(state);
for (auto _ : state)
{
m_tree.VisitPath(Path(), PrefixTreeMatch::PathAndSubpaths, [](const Path& path, const AZStd::string& value)
{
benchmark::DoNotOptimize(path);
benchmark::DoNotOptimize(value);
});
}
state.SetItemsProcessed(m_registeredPaths->size() * state.iterations());
}
REGISTER_TREE_BENCHMARK(DomPrefixTreeBenchmark, FindValue_VisitEntries);
}
#undef REGISTER_TREE_BENCHMARK

@ -0,0 +1,204 @@
/*
* 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/DomPrefixTree.h>
#include <Tests/DOM/DomFixtures.h>
namespace AZ::Dom::Tests
{
using DomPrefixTreeTests = DomTestFixture;
static_assert(!RangeConvertibleToPrefixTree<AZStd::vector<int>, int>, "Non-pair range should not convert to tree");
static_assert(
!RangeConvertibleToPrefixTree<AZStd::vector<AZStd::pair<Path, AZStd::string>>, int>,
"Mismatched value type should not convert to tree");
static_assert(
!RangeConvertibleToPrefixTree<AZStd::vector<AZStd::pair<AZStd::string, int>>, int>,
"Mismatched value type should not convert to tree");
static_assert(
RangeConvertibleToPrefixTree<AZStd::vector<AZStd::pair<Path, int>>, int>,
"Vector with path / key type pairs should convert to tree");
TEST_F(DomPrefixTreeTests, InitializeFromInitializerList)
{
DomPrefixTree<int> tree({
{ Path(), 0 },
{ Path("/foo/bar"), 1 },
});
EXPECT_EQ(0, *tree.ValueAtPath(Path(), PrefixTreeMatch::ExactPath));
EXPECT_EQ(1, *tree.ValueAtPath(Path("/foo/bar"), PrefixTreeMatch::ExactPath));
}
TEST_F(DomPrefixTreeTests, InitializeFromRange)
{
AZStd::vector<AZStd::pair<Path, int>> container({
{ Path(), 21 },
{ Path("/foo/bar"), 42 },
});
DomPrefixTree<int> tree(container);
EXPECT_EQ(21, *tree.ValueAtPath(Path(), PrefixTreeMatch::ExactPath));
EXPECT_EQ(42, *tree.ValueAtPath(Path("/foo/bar"), PrefixTreeMatch::ExactPath));
}
TEST_F(DomPrefixTreeTests, GetAndSetRoot)
{
DomPrefixTree<AZStd::string> tree;
tree.SetValue(Path(), "root");
EXPECT_EQ("root", *tree.ValueAtPath(Path(), PrefixTreeMatch::ExactPath));
}
TEST_F(DomPrefixTreeTests, GetExactPath)
{
DomPrefixTree<int> tree;
tree.SetValue(Path("/foo/0"), 0);
tree.SetValue(Path("/foo/1"), 42);
tree.SetValue(Path("/foo/foo"), 1);
tree.SetValue(Path("/foo/bar"), 2);
EXPECT_EQ(0, *tree.ValueAtPath(Path("/foo/0"), PrefixTreeMatch::ExactPath));
EXPECT_EQ(42, *tree.ValueAtPath(Path("/foo/1"), PrefixTreeMatch::ExactPath));
EXPECT_EQ(1, *tree.ValueAtPath(Path("/foo/foo"), PrefixTreeMatch::ExactPath));
EXPECT_EQ(2, *tree.ValueAtPath(Path("/foo/bar"), PrefixTreeMatch::ExactPath));
EXPECT_EQ(nullptr, tree.ValueAtPath(Path(), PrefixTreeMatch::ExactPath));
EXPECT_EQ(nullptr, tree.ValueAtPath(Path("/foo"), PrefixTreeMatch::ExactPath));
EXPECT_EQ(nullptr, tree.ValueAtPath(Path("/foo/0/subpath"), PrefixTreeMatch::ExactPath));
}
TEST_F(DomPrefixTreeTests, GetSubpath)
{
DomPrefixTree<int> tree;
tree.SetValue(Path("/foo/0"), 0);
tree.SetValue(Path("/foo/1"), 42);
EXPECT_EQ(0, *tree.ValueAtPath(Path("/foo/0/bar"), PrefixTreeMatch::SubpathsOnly));
EXPECT_EQ(0, *tree.ValueAtPath(Path("/foo/0/bar/baz"), PrefixTreeMatch::SubpathsOnly));
EXPECT_EQ(42, *tree.ValueAtPath(Path("/foo/1/0"), PrefixTreeMatch::SubpathsOnly));
EXPECT_EQ(nullptr, tree.ValueAtPath(Path("/foo/0"), PrefixTreeMatch::SubpathsOnly));
EXPECT_EQ(nullptr, tree.ValueAtPath(Path("/foo/1"), PrefixTreeMatch::SubpathsOnly));
}
TEST_F(DomPrefixTreeTests, GetPathOrSubpath)
{
DomPrefixTree<int> tree;
tree.SetValue(Path("/foo/0"), 0);
tree.SetValue(Path("/foo/1"), 42);
EXPECT_EQ(0, *tree.ValueAtPath(Path("/foo/0"), PrefixTreeMatch::PathAndSubpaths));
EXPECT_EQ(0, *tree.ValueAtPath(Path("/foo/0/bar"), PrefixTreeMatch::PathAndSubpaths));
EXPECT_EQ(0, *tree.ValueAtPath(Path("/foo/0/bar/baz"), PrefixTreeMatch::PathAndSubpaths));
EXPECT_EQ(42, *tree.ValueAtPath(Path("/foo/1"), PrefixTreeMatch::PathAndSubpaths));
EXPECT_EQ(42, *tree.ValueAtPath(Path("/foo/1/0"), PrefixTreeMatch::PathAndSubpaths));
EXPECT_EQ(nullptr, tree.ValueAtPath(Path(), PrefixTreeMatch::PathAndSubpaths));
EXPECT_EQ(nullptr, tree.ValueAtPath(Path("/foo"), PrefixTreeMatch::PathAndSubpaths));
EXPECT_EQ(nullptr, tree.ValueAtPath(Path("/path/0"), PrefixTreeMatch::PathAndSubpaths));
}
TEST_F(DomPrefixTreeTests, RemovePath)
{
DomPrefixTree<int> tree;
tree.SetValue(Path(), 20);
tree.SetValue(Path("/foo"), 40);
tree.SetValue(Path("/foo/0"), 80);
tree.EraseValue(Path("/foo"));
EXPECT_EQ(20, *tree.ValueAtPath(Path("/foo"), PrefixTreeMatch::PathAndSubpaths));
EXPECT_EQ(80, *tree.ValueAtPath(Path("/foo/0"), PrefixTreeMatch::PathAndSubpaths));
}
TEST_F(DomPrefixTreeTests, RemovePathAndChildren)
{
DomPrefixTree<int> tree;
tree.SetValue(Path(), 20);
tree.SetValue(Path("/foo"), 40);
tree.SetValue(Path("/foo/0"), 80);
tree.EraseValue(Path("/foo"), true);
EXPECT_EQ(20, *tree.ValueAtPath(Path("/foo"), PrefixTreeMatch::PathAndSubpaths));
EXPECT_EQ(20, *tree.ValueAtPath(Path("/foo/0"), PrefixTreeMatch::PathAndSubpaths));
}
TEST_F(DomPrefixTreeTests, ClearTree)
{
DomPrefixTree<int> tree;
tree.SetValue(Path(), 20);
tree.SetValue(Path("/foo"), 40);
tree.Clear();
EXPECT_EQ(-10, tree.ValueAtPathOrDefault(Path("/foo"), -10, PrefixTreeMatch::PathAndSubpaths));
}
TEST_F(DomPrefixTreeTests, Visit)
{
DomPrefixTree<int> tree;
AZStd::vector<AZStd::pair<Path, int>> results;
auto visitorFn = [&results](const Path& path, int n)
{
results.emplace_back(path, n);
};
auto validateResult = [&results](const Path& path, int n)
{
for (const auto& pair : results)
{
if (pair.first == path)
{
return pair.second == n;
}
}
return false;
};
tree.SetValue(Path("/foo"), 99);
tree.SetValue(Path("/foo/0"), 0);
tree.SetValue(Path("/foo/1"), 42);
tree.SetValue(Path("/bar/bat"), 1);
tree.SetValue(Path("/bar/baz"), 2);
tree.VisitPath(Path("/bar"), PrefixTreeMatch::ExactPath, visitorFn);
EXPECT_EQ(0, results.size());
results.clear();
tree.VisitPath(Path("/foo/0"), PrefixTreeMatch::ExactPath, visitorFn);
EXPECT_EQ(1, results.size());
EXPECT_TRUE(validateResult(Path("/foo/0"), 0));
results.clear();
tree.VisitPath(Path("/foo/1"), PrefixTreeMatch::ExactPath, visitorFn);
EXPECT_EQ(1, results.size());
EXPECT_TRUE(validateResult(Path("/foo/1"), 42));
results.clear();
tree.VisitPath(Path("/foo"), PrefixTreeMatch::SubpathsOnly, visitorFn);
EXPECT_EQ(2, results.size());
EXPECT_TRUE(validateResult(Path("/foo/0"), 0));
EXPECT_TRUE(validateResult(Path("/foo/1"), 42));
results.clear();
tree.VisitPath(Path("/foo"), PrefixTreeMatch::PathAndSubpaths, visitorFn);
EXPECT_EQ(3, results.size());
EXPECT_TRUE(validateResult(Path("/foo"), 99));
EXPECT_TRUE(validateResult(Path("/foo/0"), 0));
EXPECT_TRUE(validateResult(Path("/foo/1"), 42));
results.clear();
}
} // namespace AZ::Dom::Tests

@ -231,6 +231,8 @@ set(FILES
DOM/DomPatchBenchmarks.cpp
DOM/DomValueTests.cpp
DOM/DomValueBenchmarks.cpp
DOM/DomPrefixTreeTests.cpp
DOM/DomPrefixTreeBenchmarks.cpp
)
# Prevent the following files from being grouped in UNITY builds

Loading…
Cancel
Save