mirror of
https://github.com/minetest/minetest.git
synced 2024-07-04 15:05:27 +02:00
Deletion
This commit is contained in:
parent
957d6e1874
commit
2701f63883
@ -1,21 +1,5 @@
|
|||||||
/*
|
// Copyright (C) 2024 Lars Müller
|
||||||
Minetest
|
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
Copyright (C) 2024 sfan5
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2.1 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public License along
|
|
||||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
|
||||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "noise.h"
|
#include "noise.h"
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
@ -32,8 +16,8 @@ class TestKdTree : public TestBase
|
|||||||
|
|
||||||
void runTests(IGameDef *gamedef);
|
void runTests(IGameDef *gamedef);
|
||||||
|
|
||||||
// TODO void cube();
|
// TODO basic small cube test
|
||||||
void randomInserts();
|
void randomOps();
|
||||||
};
|
};
|
||||||
|
|
||||||
template<uint8_t Dim, typename Component, typename Id>
|
template<uint8_t Dim, typename Component, typename Id>
|
||||||
@ -44,9 +28,11 @@ class ObjectVector {
|
|||||||
entries.push_back(Entry{p, id});
|
entries.push_back(Entry{p, id});
|
||||||
}
|
}
|
||||||
void remove(Id id) {
|
void remove(Id id) {
|
||||||
entries.erase(std::find_if(entries.begin(), entries.end(), [&](const auto &e) {
|
const auto it = std::find_if(entries.begin(), entries.end(), [&](const auto &e) {
|
||||||
return e.id == id;
|
return e.id == id;
|
||||||
}));
|
});
|
||||||
|
assert(it != entries.end());
|
||||||
|
entries.erase(it);
|
||||||
}
|
}
|
||||||
template<typename F>
|
template<typename F>
|
||||||
void rangeQuery(const Point &min, const Point &max, const F &cb) {
|
void rangeQuery(const Point &min, const Point &max, const F &cb) {
|
||||||
@ -71,10 +57,10 @@ static TestKdTree g_test_instance;
|
|||||||
void TestKdTree::runTests(IGameDef *gamedef)
|
void TestKdTree::runTests(IGameDef *gamedef)
|
||||||
{
|
{
|
||||||
rawstream << "-------- k-d-tree" << std::endl;
|
rawstream << "-------- k-d-tree" << std::endl;
|
||||||
TEST(randomInserts);
|
TEST(randomOps);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestKdTree::randomInserts() {
|
void TestKdTree::randomOps() {
|
||||||
PseudoRandom pr(814538);
|
PseudoRandom pr(814538);
|
||||||
|
|
||||||
ObjectVector<3, f32, u16> objvec;
|
ObjectVector<3, f32, u16> objvec;
|
||||||
@ -87,21 +73,32 @@ void TestKdTree::randomInserts() {
|
|||||||
kds.insert(point, id);
|
kds.insert(point, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 1000; ++i) {
|
const auto testRandomQueries = [&]() {
|
||||||
std::array<f32, 3> min, max;
|
for (int i = 0; i < 1000; ++i) {
|
||||||
for (uint8_t d = 0; d < 3; ++d) {
|
std::array<f32, 3> min, max;
|
||||||
min[d] = pr.range(-1500, 1500);
|
for (uint8_t d = 0; d < 3; ++d) {
|
||||||
max[d] = min[d] + pr.range(1, 2500);
|
min[d] = pr.range(-1500, 1500);
|
||||||
|
max[d] = min[d] + pr.range(1, 2500);
|
||||||
|
}
|
||||||
|
std::unordered_set<u16> expected_ids;
|
||||||
|
objvec.rangeQuery(min, max, [&](auto _, u16 id) {
|
||||||
|
UASSERT(expected_ids.count(id) == 0);
|
||||||
|
expected_ids.insert(id);
|
||||||
|
});
|
||||||
|
kds.rangeQuery(min, max, [&](auto point, u16 id) {
|
||||||
|
UASSERT(expected_ids.count(id) == 1);
|
||||||
|
expected_ids.erase(id);
|
||||||
|
});
|
||||||
|
UASSERT(expected_ids.empty());
|
||||||
}
|
}
|
||||||
std::unordered_set<u16> expected_ids;
|
};
|
||||||
objvec.rangeQuery(min, max, [&](auto _, u16 id) {
|
|
||||||
UASSERT(expected_ids.count(id) == 0);
|
testRandomQueries();
|
||||||
expected_ids.insert(id);
|
|
||||||
});
|
for (u16 id = 1; id < 800; ++id) {
|
||||||
kds.rangeQuery(min, max, [&](auto point, u16 id) {
|
objvec.remove(id);
|
||||||
UASSERT(expected_ids.count(id) == 1);
|
kds.remove(id);
|
||||||
expected_ids.erase(id);
|
|
||||||
});
|
|
||||||
UASSERT(expected_ids.empty());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testRandomQueries();
|
||||||
}
|
}
|
@ -1,13 +1,22 @@
|
|||||||
|
// Copyright (C) 2024 Lars Müller
|
||||||
|
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
using Idx = std::size_t;
|
using Idx = uint16_t;
|
||||||
|
|
||||||
|
// TODO docs and explanation
|
||||||
|
|
||||||
|
// TODO profile and tweak knobs
|
||||||
|
|
||||||
|
// TODO cleanup
|
||||||
|
|
||||||
template<uint8_t Dim, typename Component>
|
template<uint8_t Dim, typename Component>
|
||||||
class Points {
|
class Points {
|
||||||
@ -221,14 +230,15 @@ class KdTree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//! Build a tree
|
//! Build a tree
|
||||||
|
// FIXME something must be wrong here, otherwise deleting stuff would work
|
||||||
KdTree(Idx n, Id const *ids, std::array<Component const *, Dim> pts)
|
KdTree(Idx n, Id const *ids, std::array<Component const *, Dim> pts)
|
||||||
: items(n, pts)
|
: items(n, pts)
|
||||||
, tree(std::make_unique<Id[]>(n))
|
|
||||||
, ids(std::make_unique<Id[]>(n))
|
, ids(std::make_unique<Id[]>(n))
|
||||||
|
, tree(std::make_unique<Idx[]>(n))
|
||||||
, deleted(n)
|
, deleted(n)
|
||||||
{
|
{
|
||||||
std::copy(ids, ids + n, this->ids.get());
|
std::copy(ids, ids + n, this->ids.get());
|
||||||
init(0, items);
|
init(0, 0, items.indices);
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Merge two trees. Both trees are assumed to have a power of two size.
|
//! Merge two trees. Both trees are assumed to have a power of two size.
|
||||||
@ -245,29 +255,6 @@ class KdTree {
|
|||||||
init(0, 0, items.indices);
|
init(0, 0, items.indices);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO remove dead code
|
|
||||||
/* SortedPoints<Dim, Component> alivePoints() {
|
|
||||||
const auto newIndices = std::make_unique<Idx[]>(items.n);
|
|
||||||
Idx j = 0;
|
|
||||||
for (Idx i = 0; i < items.n; ++i)
|
|
||||||
newIndices[i] = deleted[i] ? j : j++;
|
|
||||||
Idx aliveN = std::count(deleted.begin(), deleted.end(), false);
|
|
||||||
SortedPoints<Dim, Component> res(aliveN);
|
|
||||||
for (uint8_t d = 0; d < Dim; ++d) {
|
|
||||||
const auto pts = &res.points[d * aliveN];
|
|
||||||
const auto indices = &res.indices[d * aliveN];
|
|
||||||
const auto items_pts = &items.points[d * items.n];
|
|
||||||
const auto items_indices = &items.indices[d * aliveN];
|
|
||||||
Idx j = 0;
|
|
||||||
for (Idx i = 0; i < items.n; ++i) {
|
|
||||||
if (deleted[i]) continue;
|
|
||||||
pts[j++] = items_pts[i];
|
|
||||||
indices[newIndices[i]] = items_indices[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
} */
|
|
||||||
|
|
||||||
template<typename F>
|
template<typename F>
|
||||||
void rangeQuery(const Point &min, const Point &max,
|
void rangeQuery(const Point &min, const Point &max,
|
||||||
const F &cb) const {
|
const F &cb) const {
|
||||||
@ -277,16 +264,15 @@ class KdTree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void remove(Idx internalIdx) {
|
void remove(Idx internalIdx) {
|
||||||
|
assert(!deleted[internalIdx]);
|
||||||
deleted[internalIdx] = true;
|
deleted[internalIdx] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class F>
|
template<class F>
|
||||||
void foreach(F cb) {
|
void foreach(F cb) const {
|
||||||
for (Idx i = 0; i < cap(); ++i) {
|
for (Idx i = 0; i < cap(); ++i)
|
||||||
if (!deleted[i]) {
|
if (!deleted[i])
|
||||||
cb(i, items.points.getPoint(i), ids[i]);
|
cb(i, items.points.getPoint(i), ids[i]);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Capacity, not size, since some items may be marked as deleted
|
//! Capacity, not size, since some items may be marked as deleted
|
||||||
@ -329,7 +315,7 @@ class KdTree {
|
|||||||
} else {
|
} else {
|
||||||
rangeQuery(rightChild, nextSplit, min, max, cb);
|
rangeQuery(rightChild, nextSplit, min, max, cb);
|
||||||
rangeQuery(leftChild, nextSplit, min, max, cb);
|
rangeQuery(leftChild, nextSplit, min, max, cb);
|
||||||
if (deleted[root])
|
if (deleted[ptid])
|
||||||
return;
|
return;
|
||||||
const auto point = items.points.getPoint(ptid);
|
const auto point = items.points.getPoint(ptid);
|
||||||
for (uint8_t d = 0; d < Dim; ++d)
|
for (uint8_t d = 0; d < Dim; ++d)
|
||||||
@ -378,7 +364,7 @@ class DynamicKdTrees {
|
|||||||
void remove(const Id id) {
|
void remove(const Id id) {
|
||||||
const auto del_entry = del_entries.at(id);
|
const auto del_entry = del_entries.at(id);
|
||||||
trees.at(del_entry.treeIdx).remove(del_entry.inTree);
|
trees.at(del_entry.treeIdx).remove(del_entry.inTree);
|
||||||
del_entries.erase(del_entry);
|
del_entries.erase(id); // TODO use iterator right away...
|
||||||
++deleted;
|
++deleted;
|
||||||
if (deleted > n_entries/2) // we want to shift out the one!
|
if (deleted > n_entries/2) // we want to shift out the one!
|
||||||
compactify();
|
compactify();
|
||||||
@ -391,57 +377,59 @@ class DynamicKdTrees {
|
|||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
void compactify() {
|
void compactify() {
|
||||||
n_entries -= deleted;
|
assert(n_entries >= deleted);
|
||||||
|
n_entries -= deleted; // note: this should be exactly n_entries/2
|
||||||
|
deleted = 0;
|
||||||
|
// reset map, freeing memory (instead of clearing)
|
||||||
del_entries = std::unordered_map<Id, DelEntry>();
|
del_entries = std::unordered_map<Id, DelEntry>();
|
||||||
|
|
||||||
|
|
||||||
// Collect all live points and corresponding IDs.
|
// Collect all live points and corresponding IDs.
|
||||||
const auto ids = std::make_unique<Id[]>(n_entries);
|
const auto live_ids = std::make_unique<Id[]>(n_entries);
|
||||||
Points<Dim, Component> pts;
|
Points<Dim, Component> live_points(n_entries);
|
||||||
Idx i = 0;
|
Idx i = 0;
|
||||||
for (const auto tree : trees) {
|
for (const auto &tree : trees) {
|
||||||
if (tree.empty())
|
tree.foreach([&](Idx _, auto point, Id id) {
|
||||||
continue;
|
assert(i < n_entries);
|
||||||
tree.foreach([&](Idx _, std::array<Component, Dim> point, Id id) {
|
live_points.setPoint(i, point);
|
||||||
for (uint8_t d = 0; d < Dim; ++d)
|
live_ids[i] = id;
|
||||||
pts.get(d)[i] = point[d];
|
|
||||||
ids[i] = id;
|
|
||||||
++i;
|
++i;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
assert(i == n_entries);
|
||||||
|
|
||||||
// Construct a new forest.
|
// Construct a new forest.
|
||||||
// The pattern will be the current pattern, shifted down by one.
|
// The "tree pattern" will effectively just be shifted down by one.
|
||||||
auto id_ptr = ids.get();
|
auto id_ptr = live_ids.get();
|
||||||
std::array<Component const *, Dim> point_ptrs;
|
std::array<Component const *, Dim> point_ptrs;
|
||||||
std::vector<Tree> newTrees(trees.size() - 1);
|
|
||||||
Idx n = 1;
|
Idx n = 1;
|
||||||
for (uint8_t d = 0; d < Dim; ++d)
|
for (uint8_t d = 0; d < Dim; ++d)
|
||||||
point_ptrs[d] = pts.begin(d);
|
point_ptrs[d] = live_points.begin(d);
|
||||||
for (uint8_t i = 0; i < newTrees.size(); ++i, n *= 2) {
|
for (uint8_t treeIdx = 0; treeIdx < trees.size() - 1; ++treeIdx, n *= 2) {
|
||||||
if (trees[i+1].empty())
|
Tree tree;
|
||||||
continue;
|
if (!trees[treeIdx+1].empty()) {
|
||||||
|
// TODO maybe optimize from log² -> log?
|
||||||
// TODO maybe optimize from log² -> log?
|
// This could be achieved by doing a sorted merge of live points, then doing a radix sort.
|
||||||
// This can be achieved by doing a sorted merge of live points,
|
tree = std::move(Tree(n, id_ptr, point_ptrs));
|
||||||
// then doing a radix sort.
|
id_ptr += n;
|
||||||
Tree tree(n, id_ptr, point_ptrs);
|
for (uint8_t d = 0; d < Dim; ++d)
|
||||||
id_ptr += n;
|
point_ptrs[d] += n;
|
||||||
for (uint8_t d = 0; d < Dim; ++d)
|
// TODO dedupe
|
||||||
point_ptrs[d] += n;
|
tree.foreach([&](Idx objIdx, auto _, Id id) {
|
||||||
tree.foreach([&](Idx i, auto _, Id id) {
|
del_entries[id] = {treeIdx, objIdx};
|
||||||
del_entries[id] = {n, i};
|
});
|
||||||
});
|
}
|
||||||
newTrees[i] = std::move(tree);
|
trees[treeIdx] = std::move(tree);
|
||||||
}
|
}
|
||||||
trees = std::move(newTrees);
|
trees.pop_back(); // "shift out" tree with the most elements
|
||||||
}
|
}
|
||||||
// could use an array here since we've got a good bound on the size ahead of time but meh
|
// could use an array (rather than a vector) here since we've got a good bound on the size ahead of time but meh
|
||||||
std::vector<Tree> trees;
|
std::vector<Tree> trees;
|
||||||
struct DelEntry {
|
struct DelEntry {
|
||||||
uint8_t treeIdx;
|
uint8_t treeIdx;
|
||||||
Idx inTree;
|
Idx inTree;
|
||||||
};
|
};
|
||||||
std::unordered_map<Id, DelEntry> del_entries;
|
std::unordered_map<Id, DelEntry> del_entries;
|
||||||
Idx n_entries;
|
Idx n_entries = 0;
|
||||||
Idx deleted;
|
Idx deleted = 0;
|
||||||
};
|
};
|
Loading…
Reference in New Issue
Block a user