Add DomPrefixTree, a DOM path => value lookup structure
This is Document Property Editor work I've pulled out as I needed it in a few places, and the API may be generally useful. Real-world performance measurements will need to be done, if we have path lookup based bottlenecks we can consider adopting a contiguous memory approach with a sorted vector. Signed-off-by: Nicholas Van Sickle <nvsickle@amazon.com>monroegm-disable-blank-issue-2
parent
3f32669883
commit
27c6388c3c
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* 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/map.h>
|
||||||
|
#include <AzCore/std/containers/stack.h>
|
||||||
|
#include <AzCore/std/optional.h>
|
||||||
|
|
||||||
|
namespace AZ::Dom
|
||||||
|
{
|
||||||
|
//! Specifies how a patch 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,
|
||||||
|
};
|
||||||
|
|
||||||
|
//! 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);
|
||||||
|
|
||||||
|
//! Visits a path and calls a visitor for each matching path and value.
|
||||||
|
void VisitPath(const Path& path, PrefixTreeMatch match, AZStd::function<void(const Path&, const T&)> 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.
|
||||||
|
//! \note This returns a copy of a value. If T is expensive to copy, consider using ValueAtPath instead.
|
||||||
|
T ValueAtPathOrDefault(const Path& path, const T& defaultValue, PrefixTreeMatch match) const;
|
||||||
|
|
||||||
|
//! Sets the value stored at path.
|
||||||
|
void SetValue(const Path& path, T 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::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,222 @@
|
|||||||
|
/*
|
||||||
|
* 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& entry : init)
|
||||||
|
{
|
||||||
|
SetValue(entry.first, entry.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, AZStd::function<void(const Path&, const T&)> 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>
|
||||||
|
T DomPrefixTree<T>::ValueAtPathOrDefault(const Path& path, const T& defaultValue, PrefixTreeMatch match) const
|
||||||
|
{
|
||||||
|
const T* value = ValueAtPath(path, match);
|
||||||
|
return value == nullptr ? defaultValue : *value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void DomPrefixTree<T>::SetValue(const Path& path, T 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 = 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
|
||||||
@ -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,170 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
TEST_F(DomPrefixTreeTests, GetAndSetRoot)
|
||||||
|
{
|
||||||
|
DomPrefixTree<AZStd::string> tree;
|
||||||
|
tree.SetValue(Path(), "root");
|
||||||
|
EXPECT_EQ(*tree.ValueAtPath(Path(), PrefixTreeMatch::ExactPath), "root");
|
||||||
|
}
|
||||||
|
|
||||||
|
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(*tree.ValueAtPath(Path("/foo/0"), PrefixTreeMatch::ExactPath), 0);
|
||||||
|
EXPECT_EQ(*tree.ValueAtPath(Path("/foo/1"), PrefixTreeMatch::ExactPath), 42);
|
||||||
|
EXPECT_EQ(*tree.ValueAtPath(Path("/foo/foo"), PrefixTreeMatch::ExactPath), 1);
|
||||||
|
EXPECT_EQ(*tree.ValueAtPath(Path("/foo/bar"), PrefixTreeMatch::ExactPath), 2);
|
||||||
|
|
||||||
|
EXPECT_EQ(tree.ValueAtPath(Path(), PrefixTreeMatch::ExactPath), nullptr);
|
||||||
|
EXPECT_EQ(tree.ValueAtPath(Path("/foo"), PrefixTreeMatch::ExactPath), nullptr);
|
||||||
|
EXPECT_EQ(tree.ValueAtPath(Path("/foo/0/subpath"), PrefixTreeMatch::ExactPath), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DomPrefixTreeTests, GetSubpath)
|
||||||
|
{
|
||||||
|
DomPrefixTree<int> tree;
|
||||||
|
|
||||||
|
tree.SetValue(Path("/foo/0"), 0);
|
||||||
|
tree.SetValue(Path("/foo/1"), 42);
|
||||||
|
|
||||||
|
EXPECT_EQ(*tree.ValueAtPath(Path("/foo/0/bar"), PrefixTreeMatch::SubpathsOnly), 0);
|
||||||
|
EXPECT_EQ(*tree.ValueAtPath(Path("/foo/0/bar/baz"), PrefixTreeMatch::SubpathsOnly), 0);
|
||||||
|
EXPECT_EQ(*tree.ValueAtPath(Path("/foo/1/0"), PrefixTreeMatch::SubpathsOnly), 42);
|
||||||
|
|
||||||
|
EXPECT_EQ(tree.ValueAtPath(Path("/foo/0"), PrefixTreeMatch::SubpathsOnly), nullptr);
|
||||||
|
EXPECT_EQ(tree.ValueAtPath(Path("/foo/1"), PrefixTreeMatch::SubpathsOnly), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DomPrefixTreeTests, GetPathOrSubpath)
|
||||||
|
{
|
||||||
|
DomPrefixTree<int> tree;
|
||||||
|
|
||||||
|
tree.SetValue(Path("/foo/0"), 0);
|
||||||
|
tree.SetValue(Path("/foo/1"), 42);
|
||||||
|
|
||||||
|
EXPECT_EQ(*tree.ValueAtPath(Path("/foo/0"), PrefixTreeMatch::PathAndSubpaths), 0);
|
||||||
|
EXPECT_EQ(*tree.ValueAtPath(Path("/foo/0/bar"), PrefixTreeMatch::PathAndSubpaths), 0);
|
||||||
|
EXPECT_EQ(*tree.ValueAtPath(Path("/foo/0/bar/baz"), PrefixTreeMatch::PathAndSubpaths), 0);
|
||||||
|
EXPECT_EQ(*tree.ValueAtPath(Path("/foo/1"), PrefixTreeMatch::PathAndSubpaths), 42);
|
||||||
|
EXPECT_EQ(*tree.ValueAtPath(Path("/foo/1/0"), PrefixTreeMatch::PathAndSubpaths), 42);
|
||||||
|
|
||||||
|
EXPECT_EQ(tree.ValueAtPath(Path(), PrefixTreeMatch::PathAndSubpaths), nullptr);
|
||||||
|
EXPECT_EQ(tree.ValueAtPath(Path("/foo"), PrefixTreeMatch::PathAndSubpaths), nullptr);
|
||||||
|
EXPECT_EQ(tree.ValueAtPath(Path("/path/0"), PrefixTreeMatch::PathAndSubpaths), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(*tree.ValueAtPath(Path("/foo"), PrefixTreeMatch::PathAndSubpaths), 20);
|
||||||
|
EXPECT_EQ(*tree.ValueAtPath(Path("/foo/0"), PrefixTreeMatch::PathAndSubpaths), 80);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(*tree.ValueAtPath(Path("/foo"), PrefixTreeMatch::PathAndSubpaths), 20);
|
||||||
|
EXPECT_EQ(*tree.ValueAtPath(Path("/foo/0"), PrefixTreeMatch::PathAndSubpaths), 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DomPrefixTreeTests, ClearTree)
|
||||||
|
{
|
||||||
|
DomPrefixTree<int> tree;
|
||||||
|
|
||||||
|
tree.SetValue(Path(), 20);
|
||||||
|
tree.SetValue(Path("/foo"), 40);
|
||||||
|
|
||||||
|
tree.Clear();
|
||||||
|
|
||||||
|
EXPECT_EQ(tree.ValueAtPathOrDefault(Path("/foo"), -10, PrefixTreeMatch::PathAndSubpaths), -10);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(results.size(), 0);
|
||||||
|
results.clear();
|
||||||
|
|
||||||
|
tree.VisitPath(Path("/foo/0"), PrefixTreeMatch::ExactPath, visitorFn);
|
||||||
|
EXPECT_EQ(results.size(), 1);
|
||||||
|
EXPECT_TRUE(validateResult(Path("/foo/0"), 0));
|
||||||
|
results.clear();
|
||||||
|
|
||||||
|
tree.VisitPath(Path("/foo/1"), PrefixTreeMatch::ExactPath, visitorFn);
|
||||||
|
EXPECT_EQ(results.size(), 1);
|
||||||
|
EXPECT_TRUE(validateResult(Path("/foo/1"), 42));
|
||||||
|
results.clear();
|
||||||
|
|
||||||
|
tree.VisitPath(Path("/foo"), PrefixTreeMatch::SubpathsOnly, visitorFn);
|
||||||
|
EXPECT_EQ(results.size(), 2);
|
||||||
|
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(results.size(), 3);
|
||||||
|
EXPECT_TRUE(validateResult(Path("/foo"), 99));
|
||||||
|
EXPECT_TRUE(validateResult(Path("/foo/0"), 0));
|
||||||
|
EXPECT_TRUE(validateResult(Path("/foo/1"), 42));
|
||||||
|
results.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue