Skip to content

Commit

Permalink
Got `range_tree::remove* working
Browse files Browse the repository at this point in the history
  • Loading branch information
kgorking committed Mar 17, 2024
1 parent d094236 commit 3191195
Show file tree
Hide file tree
Showing 2 changed files with 212 additions and 49 deletions.
211 changes: 167 additions & 44 deletions include/ecs/detail/range_tree.h
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#ifndef ECS_DETAIL_RANGE_TREE_H
#define ECS_DETAIL_RANGE_TREE_H

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

namespace ecs::detail {

Expand All @@ -14,7 +14,7 @@ namespace ecs::detail {
// 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 {
struct node_t {
entity_range range;
entity_id max = 0;
int children[2] = {-1, -1};
Expand All @@ -28,10 +28,18 @@ namespace ecs::detail {
struct promise_type { // required
entity_range value{0, 0};

iterator get_return_object() { return iterator(handle_type::from_promise(*this)); }
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { std::terminate(); }
iterator get_return_object() {
return iterator(handle_type::from_promise(*this));
}
std::suspend_never initial_suspend() {
return {};
}
std::suspend_always final_suspend() noexcept {
return {};
}
void unhandled_exception() {
std::terminate();
}
void return_void() {}

std::suspend_always yield_value(entity_range r) {
Expand All @@ -58,16 +66,26 @@ namespace ecs::detail {
return !handle.done();
}

void operator++() { handle.resume(); }
void operator++(int) { handle.resume(); }
void operator++() {
handle.resume();
}
void operator++(int) {
handle.resume();
}

private:
};
friend iterator;

public:
int size() const {
return static_cast<int>(nodes.size());
int s = static_cast<int>(nodes.size());
int f = free;
while (f != -1) {
s -= 1;
f = nodes[f].level;
}
return s;
}

int height() const {
Expand All @@ -88,14 +106,24 @@ namespace ecs::detail {
return {};
}

void insert(entity_range r) {
PreAudit(!overlaps(r), "can not add range that overlaps with existing range");
void insert(entity_range range) {
PreAudit(!overlaps(range), "can not add range that overlaps with existing range");

if (nodes.empty()) {
nodes.emplace_back(r, r.last());
nodes.emplace_back(range, range.last());
root = 0;
} else {
root = insert(root, r);
root = insert(root, range);
}
}

void remove(entity_range range) {
PreAudit(overlaps(range), "range must overlap existing range");

if (nodes.empty()) {
return;
} else {
root = remove(root, range);
}
}

Expand All @@ -109,29 +137,80 @@ namespace ecs::detail {
private:
iterator iterate(int index) {
if (index != -1) {
for(auto x = iterate(nodes[index].children[0]); x; ++x)
for (auto x = iterate(nodes[index].children[0]); x; ++x)
co_yield *x;
co_yield nodes[index].range;
for (auto x = iterate(nodes[index].children[1]); x; x++)
co_yield *x;
}
}

int insert(int index, entity_range r) {
if (index == -1) {
int const size = static_cast<int>(std::ssize(nodes));
nodes.emplace_back(r, r.last());
return size;
int insert(int i, entity_range range) {
if (i == -1) {
return create_node(range);
} else {
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;
bool const is_right_node = (range.first() >= nodes[i].max);
nodes[i].children[is_right_node] = insert(nodes[i].children[is_right_node], range);
nodes[i].max = std::max(nodes[i].max, range.last());
i = skew(i);
i = split(i);
return i;
}
}

int remove(int i, entity_range range) {
if (i == -1)
return -1;

if (left(i) != -1 && range.overlaps(nodes[left(i)].range)) {
left(i) = remove(left(i), range);
}
if (right(i) != -1 && range.overlaps(nodes[right(i)].range)) {
right(i) = remove(right(i), range);
}

if (range.contains(node(i).range)) {
// The range removes this node whole
if (leaf(i)) {
free_node(i);
return -1;
} else if (-1 == left(i)) {
int const s = successor(i);
right(i) = remove(right(i), node(s).range);
node(i).range = node(s).range;
} else {
int const s = predecessor(i);
left(i) = remove(left(i), node(s).range);
node(i).range = node(s).range;
}
} else if(range.overlaps(node(i).range)) {
// it's a partial remove, update the current node
auto [rng_l, rng_r] = entity_range::remove(node(i).range, range);
node(i).range = rng_l;
if (rng_r) {
// Range was split in two, so add the new range
right(i) = insert(right(i), *rng_r);
}
}

int const level_l = (-1 == left(i)) ? 1 : level(left(i));
int const level_r = (-1 == right(i)) ? 1 : level(right(i));
int const new_level = 1 + std::min(level_l, level_r);
if (new_level < level(i)) {
level(i) = new_level;
if (-1 != right(i) && new_level < level(right(i)))
level(right(i)) = new_level;
}

i = skew(i);
right(i) = skew(right(i));
if (-1 != right(i))
right(right(i)) = skew(right(right(i)));
i = split(i);
right(i) = split(right(i));
return i;
}

bool overlaps(int index, entity_range r) const {
if (index == -1)
return false;
Expand All @@ -154,18 +233,61 @@ namespace ecs::detail {
calc_height(right, depth + 1, h);
}

int& left(int node) {
Pre(node != -1, "index must be valid");
return nodes[node].children[0];
int create_node(entity_range range) {
if (free == -1) {
int const size = static_cast<int>(std::ssize(nodes));
nodes.emplace_back(range, range.last());
return size;
} else {
int const i = free;
free = nodes[free].level;
nodes[i] = {range, range.last()};
return i;
}
}

void free_node(int i) {
if (i == -1)
return;
nodes[i].level = free;
free = i;
}

int& right(int node) {
Pre(node != -1, "index must be valid");
return nodes[node].children[1];
node_t& node(int i) {
return nodes[i];
}

int& level(int node) {
return nodes[node].level;
bool leaf(int i) const {
return nodes[i].children[0] == -1 && nodes[i].children[1] == -1;
}

int successor(int i) {
i = right(i);
while (left(i) != -1)
i = left(i);
return i;
}

int predecessor(int i) {
i = left(i);
while (right(i) != -1)
i = right(i);
return i;
}

int& left(int i) {
Pre(i != -1, "index must be valid");
return nodes[i].children[0];
}

int& right(int i) {
Pre(i != -1, "index must be valid");
return nodes[i].children[1];
}

int& level(int i) {
Pre(i != -1, "index must be valid");
return nodes[i].level;
}

// Removes left-horizontal links
Expand All @@ -187,27 +309,28 @@ namespace ecs::detail {
}

// Removes consecutive horizontal links
int split(int node) {
if (node == -1)
int split(int i) {
if (i == -1)
return -1;

if (right(node) == -1 || right(right(node)) == -1)
return node;
if (right(i) == -1 || right(right(i)) == -1)
return i;

if (level(node) == level(right(right(node)))) {
int r = right(node);
right(node) = left(r);
left(r) = node;
if (level(i) == level(right(right(i)))) {
int r = right(i);
right(i) = left(r);
left(r) = i;
level(r) += 1;
return r;
}

return node;
return i;
}

private:
int root = -1;
std::vector<node> nodes;
int free = -1; // free list.
std::vector<node_t> nodes;
};
} // namespace ecs::detail

Expand Down
50 changes: 45 additions & 5 deletions unittest/range_tree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ TEST_CASE("range_tree specification") {
REQUIRE(tree.size() == 0);
}

SECTION("inserting ranges works") {
SECTION("insert") {
SECTION("overlap testing works") {
ecs::detail::range_tree tree;
tree.insert({1, 3});
Expand All @@ -49,11 +49,51 @@ TEST_CASE("range_tree specification") {
}
}

SECTION("balancing") {
SECTION("remove") {
ecs::detail::range_tree tree;
for (auto r : random_ranges)
tree.insert(r);

CHECK(tree.height() < (int)random_ranges.size());
SECTION("full interval removal") {
tree.insert({0, 10});
tree.remove({0, 10});
REQUIRE(tree.size() == 0);
}

SECTION("partial interval removal") {
tree.insert({0, 10});
tree.remove({1, 9});
REQUIRE(tree.size() == 2);
}

SECTION("multiple interval removals") {
tree.insert({0, 2});
tree.insert({5, 7});
tree.insert({9, 14});
tree.remove({-10, 20});
REQUIRE(tree.size() == 0);
}

SECTION("multiple+partial interval removals") {
tree.insert({-2, 2});
tree.insert({4, 7});
tree.insert({19, 24});

tree.remove({0, 6});
REQUIRE(tree.size() == 3);

std::vector<ecs::entity_range> ranges;
for (auto const range : tree)
ranges.push_back(range);
REQUIRE(ranges.size() == (std::size_t)3);
std::vector<ecs::entity_range> expected{{-2, -1}, {7, 7}, {19, 24}};
REQUIRE(expected == ranges);

tree.remove({6, 20});
REQUIRE(tree.size() == 2);
ranges.clear();
for (auto const range : tree)
ranges.push_back(range);
expected = {{-2, -1}, {21, 24}};
REQUIRE(expected == ranges);
}
}
}

0 comments on commit 3191195

Please sign in to comment.