Skip to content

Commit

Permalink
Interval/range tree is working
Browse files Browse the repository at this point in the history
  • Loading branch information
kgorking committed Mar 17, 2024
1 parent ff427b5 commit d094236
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 24 deletions.
99 changes: 92 additions & 7 deletions include/ecs/detail/range_tree.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@
#define ECS_DETAIL_RANGE_TREE_H

#include "entity_range.h"
#include "contract.h"
#include <coroutine>
#include <vector>
#include <iterator>

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 {
Expand Down Expand Up @@ -59,31 +66,44 @@ namespace ecs::detail {
friend iterator;

public:
std::size_t size() const {
return nodes.size();
int size() const {
return static_cast<int>(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);
}
}

bool overlaps(entity_range r) const {
if (nodes.empty())
return false;
else
return overlaps(0, r);
return overlaps(root, r);
}

private:
Expand All @@ -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;
}
}
Expand All @@ -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<node> nodes;
};
} // namespace ecs::detail
Expand Down
49 changes: 32 additions & 17 deletions unittest/range_tree.cpp
Original file line number Diff line number Diff line change
@@ -1,44 +1,59 @@
#include <ecs/detail/range_tree.h>
#include <exception>
#include <numeric>
#include <catch2/catch_test_macros.hpp>
#include <ranges>

// 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<ecs::entity_range> make_ranges(int n) {
std::vector<ecs::entity_range> 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<ecs::entity_range> 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<ecs::entity_range> 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());
}
}

0 comments on commit d094236

Please sign in to comment.