From d094236f9c5dba579728c12d77ebaaebe25f0b9e Mon Sep 17 00:00:00 2001 From: Kenneth Gorking Date: Sun, 17 Mar 2024 13:43:54 +0100 Subject: [PATCH] Interval/range tree is working --- include/ecs/detail/range_tree.h | 99 ++++++++++++++++++++++++++++++--- unittest/range_tree.cpp | 49 ++++++++++------ 2 files changed, 124 insertions(+), 24 deletions(-) diff --git a/include/ecs/detail/range_tree.h b/include/ecs/detail/range_tree.h index 03a5a53c..4f318edb 100644 --- a/include/ecs/detail/range_tree.h +++ b/include/ecs/detail/range_tree.h @@ -2,16 +2,23 @@ #define ECS_DETAIL_RANGE_TREE_H #include "entity_range.h" +#include "contract.h" #include #include #include namespace ecs::detail { + + // A tree of ranges (technically an interval tree) to be implemented as an AA tree (https://en.wikipedia.org/wiki/AA_tree) + // Ranges can not overlap in the tree + // Ranges may be merged iof adjacent, ie. [0,2] and [3,5] may become [0,5] + // Currently uses a compact layout, can hopefully be optimized to a linear layout (van Emde Boas or Eytzinger) class range_tree { struct node { entity_range range; entity_id max = 0; int children[2] = {-1, -1}; + int level = 1; }; struct iterator { @@ -59,23 +66,36 @@ namespace ecs::detail { friend iterator; public: - std::size_t size() const { - return nodes.size(); + int size() const { + return static_cast(nodes.size()); + } + + int height() const { + if (nodes.empty()) + return 0; + else { + int h = 1; + calc_height(root, 1, h); + return h; + } } iterator begin() { - return iterate(0); + return iterate(root); } - auto end() const { - return std::default_sentinel; + std::default_sentinel_t end() const { + return {}; } void insert(entity_range r) { + PreAudit(!overlaps(r), "can not add range that overlaps with existing range"); + if (nodes.empty()) { nodes.emplace_back(r, r.last()); + root = 0; } else { - insert(0, r); + root = insert(root, r); } } @@ -83,7 +103,7 @@ namespace ecs::detail { if (nodes.empty()) return false; else - return overlaps(0, r); + return overlaps(root, r); } private: @@ -106,6 +126,8 @@ namespace ecs::detail { bool const left_right = (r.first() >= nodes[index].max); nodes[index].children[left_right] = insert(nodes[index].children[left_right], r); nodes[index].max = std::max(nodes[index].max, r.last()); + index = skew(index); + index = split(index); return index; } } @@ -121,7 +143,70 @@ namespace ecs::detail { } } + void calc_height(int node, int depth, int& h) const { + if (depth > h) + h = depth; + + auto const [left, right] = nodes[node].children; + if (left != -1) + calc_height(left, depth + 1, h); + if (right != -1) + calc_height(right, depth + 1, h); + } + + int& left(int node) { + Pre(node != -1, "index must be valid"); + return nodes[node].children[0]; + } + + int& right(int node) { + Pre(node != -1, "index must be valid"); + return nodes[node].children[1]; + } + + int& level(int node) { + return nodes[node].level; + } + + // Removes left-horizontal links + int skew(int node) { + if (node == -1) + return -1; + + int l = left(node); + if (l == -1) + return node; + + if (level(l) == level(node)) { + left(node) = right(l); + right(l) = node; + return l; + } + + return node; + } + + // Removes consecutive horizontal links + int split(int node) { + if (node == -1) + return -1; + + if (right(node) == -1 || right(right(node)) == -1) + return node; + + if (level(node) == level(right(right(node)))) { + int r = right(node); + right(node) = left(r); + left(r) = node; + level(r) += 1; + return r; + } + + return node; + } + private: + int root = -1; std::vector nodes; }; } // namespace ecs::detail diff --git a/unittest/range_tree.cpp b/unittest/range_tree.cpp index eea48409..5f5be586 100644 --- a/unittest/range_tree.cpp +++ b/unittest/range_tree.cpp @@ -1,44 +1,59 @@ #include -#include -#include #include +#include -// Override the default handler for contract violations. -#include "override_contract_handler_to_throw.h" +// Make a vector of n ranges, with some gaps in between +std::vector make_ranges(int n) { + std::vector ranges; + ranges.reserve(n); + for (int i = 0, offset=0; i < n; i++) { + ranges.emplace_back(offset, offset + 3 + (i&1)); + offset = ranges.back().last() + 1; + } + return ranges; +} // A bunch of tests to ensure that the range_tree behaves as expected TEST_CASE("range_tree specification") { + auto const random_ranges = make_ranges(40); + SECTION("A new range_tree is empty") { ecs::detail::range_tree tree; - REQUIRE(tree.size() == 0ull); + REQUIRE(tree.size() == 0); } SECTION("inserting ranges works") { - ecs::detail::range_tree tree; - tree.insert({1, 3}); - tree.insert({6, 7}); - tree.insert({-10, -6}); - REQUIRE(tree.size() == 3ull); - SECTION("overlap testing works") { + ecs::detail::range_tree tree; + tree.insert({1, 3}); + tree.insert({6, 7}); + tree.insert({-10, -6}); + REQUIRE(tree.size() == 3); + REQUIRE(tree.overlaps({-14, -5}) == true); REQUIRE(tree.overlaps({4, 5}) == false); REQUIRE(tree.overlaps({7, 9}) == true); } SECTION("tree can be iterated") { - const std::vector expected{{-10, -6} ,{1, 3}, {6, 7}}; + auto const expected = make_ranges(25); + ecs::detail::range_tree tree; + for (auto const range : expected | std::views::reverse) + tree.insert(range); + std::vector ranges; - for(auto range : tree) { + for(auto const range : tree) ranges.push_back(range); - } REQUIRE(expected == ranges); } } - SECTION("An empty pool") { - SECTION("does not throw on bad component access") { - } + SECTION("balancing") { + ecs::detail::range_tree tree; + for (auto r : random_ranges) + tree.insert(r); + + CHECK(tree.height() < (int)random_ranges.size()); } }