Add randomized spatial index unit testing
This commit is contained in:
parent
0167f39778
commit
1a163024f4
@ -4,8 +4,12 @@
|
||||
|
||||
#include "activeobjectmgr.h"
|
||||
#include "catch.h"
|
||||
#include "irrTypes.h"
|
||||
#include "irr_aabb3d.h"
|
||||
#include "mock_serveractiveobject.h"
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <random>
|
||||
#include <utility>
|
||||
|
||||
#include "server/activeobjectmgr.h"
|
||||
@ -13,6 +17,7 @@
|
||||
|
||||
class TestServerActiveObjectMgr {
|
||||
server::ActiveObjectMgr saomgr;
|
||||
std::vector<u16> ids;
|
||||
|
||||
public:
|
||||
|
||||
@ -21,15 +26,30 @@ public:
|
||||
}
|
||||
|
||||
bool registerObject(std::unique_ptr<ServerActiveObject> obj) {
|
||||
return saomgr.registerObject(std::move(obj));
|
||||
auto *ptr = obj.get();
|
||||
if (!saomgr.registerObject(std::move(obj)))
|
||||
return false;
|
||||
ids.push_back(ptr->getId());
|
||||
return true;
|
||||
}
|
||||
|
||||
void removeObject(u16 id) {
|
||||
const auto it = std::find(ids.begin(), ids.end(), id);
|
||||
REQUIRE(it != ids.end());
|
||||
ids.erase(it);
|
||||
saomgr.removeObject(id);
|
||||
}
|
||||
|
||||
void updatePos(u16 id, const v3f &pos) {
|
||||
auto *obj = saomgr.getActiveObject(id);
|
||||
REQUIRE(obj != nullptr);
|
||||
obj->setPos(pos);
|
||||
saomgr.updatePos(pos, id); // HACK work around m_env == nullptr
|
||||
}
|
||||
|
||||
void clear() {
|
||||
saomgr.clear();
|
||||
ids.clear();
|
||||
}
|
||||
|
||||
ServerActiveObject *getActiveObject(u16 id) {
|
||||
@ -45,6 +65,82 @@ public:
|
||||
void getAddedActiveObjectsAroundPos(T&& arg) {
|
||||
saomgr.getAddedActiveObjectsAroundPos(std::forward(arg));
|
||||
}
|
||||
|
||||
// Testing
|
||||
|
||||
bool empty() {
|
||||
return ids.empty();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
u16 randomId(T &random) {
|
||||
REQUIRE(!ids.empty());
|
||||
std::uniform_int_distribution<u16> index(0, ids.size() - 1);
|
||||
return ids[index(random)];
|
||||
}
|
||||
|
||||
void getObjectsInsideRadiusNaive(const v3f &pos, float radius,
|
||||
std::vector<ServerActiveObject *> &result)
|
||||
{
|
||||
for (const auto &[id, obj] : saomgr.m_active_objects.iter()) {
|
||||
if (obj->getBasePosition().getDistanceFromSQ(pos) <= radius * radius) {
|
||||
result.push_back(obj.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void getObjectsInAreaNaive(const aabb3f &box,
|
||||
std::vector<ServerActiveObject *> &result)
|
||||
{
|
||||
for (const auto &[id, obj] : saomgr.m_active_objects.iter()) {
|
||||
if (box.isPointInside(obj->getBasePosition())) {
|
||||
result.push_back(obj.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constexpr static auto compare_by_id = [](auto *sao1, auto *sao2) -> bool {
|
||||
return sao1->getId() < sao2->getId();
|
||||
};
|
||||
|
||||
static void sortById(std::vector<ServerActiveObject *> &saos) {
|
||||
std::sort(saos.begin(), saos.end(), compare_by_id);
|
||||
}
|
||||
|
||||
void compareObjects(std::vector<ServerActiveObject *> &actual,
|
||||
std::vector<ServerActiveObject *> &expected)
|
||||
{
|
||||
std::vector<ServerActiveObject *> unexpected, missing;
|
||||
sortById(actual);
|
||||
sortById(expected);
|
||||
|
||||
std::set_difference(actual.begin(), actual.end(),
|
||||
expected.begin(), expected.end(),
|
||||
std::back_inserter(unexpected), compare_by_id);
|
||||
|
||||
assert(unexpected.empty());
|
||||
|
||||
std::set_difference(expected.begin(), expected.end(),
|
||||
actual.begin(), actual.end(),
|
||||
std::back_inserter(missing), compare_by_id);
|
||||
assert(missing.empty());
|
||||
}
|
||||
|
||||
void compareObjectsInsideRadius(const v3f &pos, float radius)
|
||||
{
|
||||
std::vector<ServerActiveObject *> actual, expected;
|
||||
saomgr.getObjectsInsideRadius(pos, radius, actual, nullptr);
|
||||
getObjectsInsideRadiusNaive(pos, radius, expected);
|
||||
compareObjects(actual, expected);
|
||||
}
|
||||
|
||||
void compareObjectsInArea(const aabb3f &box)
|
||||
{
|
||||
std::vector<ServerActiveObject *> actual, expected;
|
||||
saomgr.getObjectsInArea(box, actual, nullptr);
|
||||
getObjectsInAreaNaive(box, expected);
|
||||
compareObjects(actual, expected);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -175,4 +271,54 @@ TEST_CASE("server active object manager")
|
||||
|
||||
saomgr.clear();
|
||||
}
|
||||
|
||||
SECTION("spatial index") {
|
||||
TestServerActiveObjectMgr saomgr;
|
||||
std::mt19937 gen(0xABCDEF);
|
||||
std::uniform_int_distribution<s32> coordinate(-1000, 1000);
|
||||
const auto random_pos = [&]() {
|
||||
return v3f(coordinate(gen), coordinate(gen), coordinate(gen));
|
||||
};
|
||||
|
||||
std::uniform_int_distribution<u32> percent(0, 99);
|
||||
const auto modify = [&](u32 p_insert, u32 p_delete, u32 p_update) {
|
||||
const auto p = percent(gen);
|
||||
if (p < p_insert) {
|
||||
saomgr.registerObject(std::make_unique<MockServerActiveObject>(nullptr, random_pos()));
|
||||
} else if (p < p_insert + p_delete) {
|
||||
if (!saomgr.empty())
|
||||
saomgr.removeObject(saomgr.randomId(gen));
|
||||
} else if (p < p_insert + p_delete + p_update) {
|
||||
if (!saomgr.empty())
|
||||
saomgr.updatePos(saomgr.randomId(gen), random_pos());
|
||||
}
|
||||
};
|
||||
|
||||
const auto test_queries = [&]() {
|
||||
std::uniform_real_distribution<f32> radius(0, 100);
|
||||
saomgr.compareObjectsInsideRadius(random_pos(), radius(gen));
|
||||
|
||||
aabb3f box(random_pos(), random_pos());
|
||||
box.repair();
|
||||
saomgr.compareObjectsInArea(box);
|
||||
};
|
||||
|
||||
// Grow: Insertion twice as likely as deletion
|
||||
for (u32 i = 0; i < 3000; ++i) {
|
||||
modify(50, 25, 25);
|
||||
test_queries();
|
||||
}
|
||||
|
||||
// Stagnate: Insertion and deletion equally likely
|
||||
for (u32 i = 0; i < 3000; ++i) {
|
||||
modify(25, 25, 50);
|
||||
test_queries();
|
||||
}
|
||||
|
||||
// Shrink: Deletion twice as likely as insertion
|
||||
while (!saomgr.empty()) {
|
||||
modify(25, 50, 25);
|
||||
test_queries();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user