From 7e504cbd378ae6e74ec9b5f17143ecbab85aba94 Mon Sep 17 00:00:00 2001 From: ExeVirus Date: Sun, 5 May 2024 23:51:10 -0400 Subject: [PATCH] Add Spatial Map, add 10,000 benchmark. --- src/benchmark/benchmark_activeobjectmgr.cpp | 2 + src/server/CMakeLists.txt | 1 + src/server/activeobjectmgr.cpp | 91 +++++++---- src/server/activeobjectmgr.h | 5 + src/server/luaentity_sao.cpp | 28 ++-- src/server/luaentity_sao.h | 2 +- src/server/player_sao.cpp | 28 ++-- src/server/player_sao.h | 6 +- src/server/serveractiveobject.cpp | 7 + src/server/serveractiveobject.h | 9 +- src/server/spatial_map.cpp | 167 ++++++++++++++++++++ src/server/spatial_map.h | 83 ++++++++++ src/serverenvironment.h | 5 + 13 files changed, 368 insertions(+), 66 deletions(-) create mode 100644 src/server/spatial_map.cpp create mode 100644 src/server/spatial_map.h diff --git a/src/benchmark/benchmark_activeobjectmgr.cpp b/src/benchmark/benchmark_activeobjectmgr.cpp index d9036c632..d80675e8d 100644 --- a/src/benchmark/benchmark_activeobjectmgr.cpp +++ b/src/benchmark/benchmark_activeobjectmgr.cpp @@ -105,7 +105,9 @@ void benchGetObjectsInArea(Catch::Benchmark::Chronometer &meter) TEST_CASE("ActiveObjectMgr") { BENCH_INSIDE_RADIUS(200) BENCH_INSIDE_RADIUS(1450) + BENCH_INSIDE_RADIUS(10000) BENCH_IN_AREA(200) BENCH_IN_AREA(1450) + BENCH_IN_AREA(10000) } diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index d4afbc55b..6c5476020 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -8,6 +8,7 @@ set(server_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/serveractiveobject.cpp ${CMAKE_CURRENT_SOURCE_DIR}/serverinventorymgr.cpp ${CMAKE_CURRENT_SOURCE_DIR}/serverlist.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/spatial_map.cpp ${CMAKE_CURRENT_SOURCE_DIR}/unit_sao.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rollback.cpp PARENT_SCOPE) diff --git a/src/server/activeobjectmgr.cpp b/src/server/activeobjectmgr.cpp index 983fc0d95..8ca61a61b 100644 --- a/src/server/activeobjectmgr.cpp +++ b/src/server/activeobjectmgr.cpp @@ -34,6 +34,12 @@ ActiveObjectMgr::~ActiveObjectMgr() } } +void ActiveObjectMgr::clear() +{ + ::ActiveObjectMgr::clear(); + m_spatial_map.removeAll(); +} + void ActiveObjectMgr::clearIf(const std::function &cb) { for (auto &it : m_active_objects.iter()) { @@ -41,6 +47,7 @@ void ActiveObjectMgr::clearIf(const std::functiongetBasePosition()); m_active_objects.remove(it.first); } } @@ -61,6 +68,11 @@ void ActiveObjectMgr::step( g_profiler->avg("ActiveObjectMgr: SAO count [#]", count); } +void ActiveObjectMgr::updateObjectPosition(u16 id, const v3f &last_position, const v3f &new_position) +{ + m_spatial_map.updatePosition(id, last_position, new_position); +} + bool ActiveObjectMgr::registerObject(std::unique_ptr obj) { assert(obj); // Pre-condition @@ -91,7 +103,8 @@ bool ActiveObjectMgr::registerObject(std::unique_ptr obj) return false; } - auto obj_id = obj->getId(); + auto obj_id = obj->getId(); + m_spatial_map.insert(obj_id, obj->getBasePosition()); m_active_objects.put(obj_id, std::move(obj)); auto new_size = m_active_objects.size(); @@ -110,6 +123,13 @@ void ActiveObjectMgr::removeObject(u16 id) verbosestream << "Server::ActiveObjectMgr::removeObject(): " << "id=" << id << std::endl; + auto &obj = m_active_objects.get(id); + if(obj) { + m_spatial_map.remove(id, obj->getBasePosition()); + } else { + m_spatial_map.remove(id); + } + // this will take the object out of the map and then destruct it bool ok = m_active_objects.remove(id); if (!ok) { @@ -123,34 +143,42 @@ void ActiveObjectMgr::getObjectsInsideRadius(const v3f &pos, float radius, std::function include_obj_cb) { float r2 = radius * radius; - for (auto &activeObject : m_active_objects.iter()) { - ServerActiveObject *obj = activeObject.second.get(); - if (!obj) - continue; + aabb3f bounds(pos.X-radius, pos.Y-radius, pos.Z-radius, + pos.X+radius, pos.Y+radius, pos.Z+radius); + + m_spatial_map.getRelevantObjectIds(bounds, [&](u16 id) { + auto obj = m_active_objects.get(id).get(); + if (!obj) { // should never be hit + m_spatial_map.remove(id); + return; + } const v3f &objectpos = obj->getBasePosition(); if (objectpos.getDistanceFromSQ(pos) > r2) - continue; + return; if (!include_obj_cb || include_obj_cb(obj)) result.push_back(obj); - } + }); } void ActiveObjectMgr::getObjectsInArea(const aabb3f &box, std::vector &result, std::function include_obj_cb) { - for (auto &activeObject : m_active_objects.iter()) { - ServerActiveObject *obj = activeObject.second.get(); - if (!obj) - continue; + m_spatial_map.getRelevantObjectIds(box,[&](u16 id) { + auto obj = m_active_objects.get(id).get(); + if (!obj) { // should never be hit + m_spatial_map.remove(id); + return; + } + const v3f &objectpos = obj->getBasePosition(); if (!box.isPointInside(objectpos)) - continue; + return; if (!include_obj_cb || include_obj_cb(obj)) result.push_back(obj); - } + }); } void ActiveObjectMgr::getAddedActiveObjectsAroundPos(v3f player_pos, f32 radius, @@ -164,32 +192,35 @@ void ActiveObjectMgr::getAddedActiveObjectsAroundPos(v3f player_pos, f32 radius, - discard objects that are found in current_objects. - add remaining objects to added_objects */ - for (auto &ao_it : m_active_objects.iter()) { - u16 id = ao_it.first; - - // Get object - ServerActiveObject *object = ao_it.second.get(); - if (!object) - continue; - - if (object->isGone()) - continue; - - f32 distance_f = object->getBasePosition().getDistanceFrom(player_pos); - if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) { + f32 offset = radius > player_radius ? radius : player_radius; + aabb3f bounds(player_pos.X-offset, player_pos.Y-offset, player_pos.Z-offset, + player_pos.X+offset, player_pos.Y+offset, player_pos.Z+offset); + m_spatial_map.getRelevantObjectIds(bounds, [&](u16 id) { + auto obj = m_active_objects.get(id).get(); + if (!obj) { // should never be hit + m_spatial_map.remove(id); + return; + } + if (obj->isGone()) { + return; + } + + f32 distance_f = obj->getBasePosition().getDistanceFrom(player_pos); + if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER) { // Discard if too far if (distance_f > player_radius && player_radius != 0) - continue; + return; } else if (distance_f > radius) - continue; + return; // Discard if already on current_objects auto n = current_objects.find(id); if (n != current_objects.end()) - continue; + return; + // Add to added_objects added_objects.push_back(id); - } + }); } } // namespace server diff --git a/src/server/activeobjectmgr.h b/src/server/activeobjectmgr.h index dab795e8c..8d64e0962 100644 --- a/src/server/activeobjectmgr.h +++ b/src/server/activeobjectmgr.h @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "../activeobjectmgr.h" #include "serveractiveobject.h" +#include "spatial_map.h" namespace server { @@ -35,8 +36,10 @@ class ActiveObjectMgr final : public ::ActiveObjectMgr void clearIf(const std::function &cb); void step(float dtime, const std::function &f) override; + void clear(); bool registerObject(std::unique_ptr obj) override; void removeObject(u16 id) override; + void updateObjectPosition(u16 id, const v3f &last_position, const v3f &new_position); void getObjectsInsideRadius(const v3f &pos, float radius, std::vector &result, @@ -48,5 +51,7 @@ class ActiveObjectMgr final : public ::ActiveObjectMgr void getAddedActiveObjectsAroundPos(v3f player_pos, f32 radius, f32 player_radius, const std::set ¤t_objects, std::vector &added_objects); +protected: + SpatialMap m_spatial_map; }; } // namespace server diff --git a/src/server/luaentity_sao.cpp b/src/server/luaentity_sao.cpp index 020f13fa5..643c1cf5f 100644 --- a/src/server/luaentity_sao.cpp +++ b/src/server/luaentity_sao.cpp @@ -162,7 +162,7 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) // Each frame, parent position is copied if the object is attached, otherwise it's calculated normally // If the object gets detached this comes into effect automatically from the last known origin if (auto *parent = getParent()) { - m_base_position = parent->getBasePosition(); + setBasePosition(parent->getBasePosition()); m_velocity = v3f(0,0,0); m_acceleration = v3f(0,0,0); } else { @@ -171,7 +171,7 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) box.MinEdge *= BS; box.MaxEdge *= BS; f32 pos_max_d = BS*0.25; // Distance per iteration - v3f p_pos = m_base_position; + v3f p_pos = getBasePosition(); v3f p_velocity = m_velocity; v3f p_acceleration = m_acceleration; moveresult = collisionMoveSimple(m_env, m_env->getGameDef(), @@ -181,11 +181,11 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) moveresult_p = &moveresult; // Apply results - m_base_position = p_pos; + setBasePosition(p_pos); m_velocity = p_velocity; m_acceleration = p_acceleration; } else { - m_base_position += (m_velocity + m_acceleration * 0.5f * dtime) * dtime; + setBasePosition(getBasePosition() + (m_velocity + m_acceleration * 0.5f * dtime) * dtime); m_velocity += dtime * m_acceleration; } @@ -228,7 +228,7 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) } else if(m_last_sent_position_timer > 0.2){ minchange = 0.05*BS; } - float move_d = m_base_position.getDistanceFrom(m_last_sent_position); + float move_d = getBasePosition().getDistanceFrom(m_last_sent_position); move_d += m_last_sent_move_precision; float vel_d = m_velocity.getDistanceFrom(m_last_sent_velocity); if (move_d > minchange || vel_d > minchange || @@ -252,7 +252,7 @@ std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version) os << serializeString16(m_init_name); // name writeU8(os, 0); // is_player writeU16(os, getId()); //id - writeV3F32(os, m_base_position); + writeV3F32(os, getBasePosition()); writeV3F32(os, m_rotation); writeU16(os, m_hp); @@ -381,7 +381,7 @@ void LuaEntitySAO::setPos(const v3f &pos) { if(isAttached()) return; - m_base_position = pos; + setBasePosition(pos); sendPosition(false, true); } @@ -389,7 +389,7 @@ void LuaEntitySAO::moveTo(v3f pos, bool continuous) { if(isAttached()) return; - m_base_position = pos; + setBasePosition(pos); if(!continuous) sendPosition(true, true); } @@ -403,7 +403,7 @@ std::string LuaEntitySAO::getDescription() { std::ostringstream oss; oss << "LuaEntitySAO \"" << m_init_name << "\" "; - auto pos = floatToInt(m_base_position, BS); + auto pos = floatToInt(getBasePosition(), BS); oss << "at " << pos; return oss.str(); } @@ -521,10 +521,10 @@ void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end) // Send attachment updates instantly to the client prior updating position sendOutdatedData(); - m_last_sent_move_precision = m_base_position.getDistanceFrom( + m_last_sent_move_precision = getBasePosition().getDistanceFrom( m_last_sent_position); m_last_sent_position_timer = 0; - m_last_sent_position = m_base_position; + m_last_sent_position = getBasePosition(); m_last_sent_velocity = m_velocity; //m_last_sent_acceleration = m_acceleration; m_last_sent_rotation = m_rotation; @@ -532,7 +532,7 @@ void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end) float update_interval = m_env->getSendRecommendedInterval(); std::string str = generateUpdatePositionCommand( - m_base_position, + getBasePosition(), m_velocity, m_acceleration, m_rotation, @@ -552,8 +552,8 @@ bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const toset->MinEdge = m_prop.collisionbox.MinEdge * BS; toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS; - toset->MinEdge += m_base_position; - toset->MaxEdge += m_base_position; + toset->MinEdge += getBasePosition(); + toset->MaxEdge += getBasePosition(); return true; } diff --git a/src/server/luaentity_sao.h b/src/server/luaentity_sao.h index 2080df9c3..534aa7075 100644 --- a/src/server/luaentity_sao.h +++ b/src/server/luaentity_sao.h @@ -40,7 +40,7 @@ class LuaEntitySAO : public UnitSAO ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_LUAENTITY; } ActiveObjectType getSendType() const { return ACTIVEOBJECT_TYPE_GENERIC; } virtual void addedToEnvironment(u32 dtime_s); - void step(float dtime, bool send_recommended); + void step(float dtime, bool send_recommended) override; std::string getClientInitializationData(u16 protocol_version); bool isStaticAllowed() const { return m_prop.static_save; } diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index b2e2351c9..c9687e464 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -86,11 +86,11 @@ std::string PlayerSAO::getDescription() void PlayerSAO::addedToEnvironment(u32 dtime_s) { ServerActiveObject::addedToEnvironment(dtime_s); - ServerActiveObject::setBasePosition(m_base_position); + ServerActiveObject::setBasePosition(getBasePosition()); m_player->setPlayerSAO(this); m_player->setPeerId(m_peer_id_initial); m_peer_id_initial = PEER_ID_INEXISTENT; // don't try to use it again. - m_last_good_position = m_base_position; + m_last_good_position = getBasePosition(); } // Called before removing from environment @@ -116,7 +116,7 @@ std::string PlayerSAO::getClientInitializationData(u16 protocol_version) os << serializeString16(m_player->getName()); // name writeU8(os, 1); // is_player writeS16(os, getId()); // id - writeV3F32(os, m_base_position); + writeV3F32(os, getBasePosition()); writeV3F32(os, m_rotation); writeU16(os, getHP()); @@ -195,7 +195,7 @@ void PlayerSAO::step(float dtime, bool send_recommended) // Sequence of damage points, starting 0.1 above feet and progressing // upwards in 1 node intervals, stopping below top damage point. for (float dam_height = 0.1f; dam_height < dam_top; dam_height++) { - v3s16 p = floatToInt(m_base_position + + v3s16 p = floatToInt(getBasePosition() + v3f(0.0f, dam_height * BS, 0.0f), BS); MapNode n = m_env->getMap().getNode(p); const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n); @@ -207,7 +207,7 @@ void PlayerSAO::step(float dtime, bool send_recommended) } // Top damage point - v3s16 ptop = floatToInt(m_base_position + + v3s16 ptop = floatToInt(getBasePosition() + v3f(0.0f, dam_top * BS, 0.0f), BS); MapNode ntop = m_env->getMap().getNode(ptop); const ContentFeatures &c = m_env->getGameDef()->ndef()->get(ntop); @@ -285,7 +285,7 @@ void PlayerSAO::step(float dtime, bool send_recommended) if (isAttached()) pos = m_last_good_position; else - pos = m_base_position; + pos = getBasePosition(); std::string str = generateUpdatePositionCommand( pos, @@ -342,9 +342,9 @@ std::string PlayerSAO::generateUpdatePhysicsOverrideCommand() const return os.str(); } -void PlayerSAO::setBasePosition(v3f position) +void PlayerSAO::setBasePosition(const v3f &position) { - if (m_player && position != m_base_position) + if (m_player && position != getBasePosition()) m_player->setDirty(true); // This needs to be ran for attachments too @@ -397,7 +397,7 @@ void PlayerSAO::addPos(const v3f &added_pos) m_env->getGameDef()->SendMovePlayerRel(getPeerID(), added_pos); } -void PlayerSAO::moveTo(v3f pos, bool continuous) +void PlayerSAO::moveTo(const v3f &pos, bool continuous) { if(isAttached()) return; @@ -629,7 +629,7 @@ bool PlayerSAO::checkMovementCheat() if (m_is_singleplayer || isAttached() || g_settings->getBool("disable_anticheat")) { - m_last_good_position = m_base_position; + m_last_good_position = getBasePosition(); return false; } @@ -694,7 +694,7 @@ bool PlayerSAO::checkMovementCheat() if (player_max_jump < 0.0001f) player_max_jump = 0.0001f; - v3f diff = (m_base_position - m_last_good_position); + v3f diff = (getBasePosition() - m_last_good_position); float d_vert = diff.Y; diff.Y = 0; float d_horiz = diff.getLength(); @@ -710,7 +710,7 @@ bool PlayerSAO::checkMovementCheat() } if (m_move_pool.grab(required_time)) { - m_last_good_position = m_base_position; + m_last_good_position = getBasePosition(); } else { const float LAG_POOL_MIN = 5.0; float lag_pool_max = m_env->getMaxLagEstimate() * 2.0; @@ -732,8 +732,8 @@ bool PlayerSAO::getCollisionBox(aabb3f *toset) const toset->MinEdge = m_prop.collisionbox.MinEdge * BS; toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS; - toset->MinEdge += m_base_position; - toset->MaxEdge += m_base_position; + toset->MinEdge += getBasePosition(); + toset->MaxEdge += getBasePosition(); return true; } diff --git a/src/server/player_sao.h b/src/server/player_sao.h index b26304589..8ec951316 100644 --- a/src/server/player_sao.h +++ b/src/server/player_sao.h @@ -88,10 +88,10 @@ class PlayerSAO : public UnitSAO std::string getClientInitializationData(u16 protocol_version) override; void getStaticData(std::string *result) const override; void step(float dtime, bool send_recommended) override; - void setBasePosition(v3f position); + void setBasePosition(const v3f &position); void setPos(const v3f &pos) override; void addPos(const v3f &added_pos) override; - void moveTo(v3f pos, bool continuous) override; + void moveTo(const v3f &pos, bool continuous) override; void setPlayerYaw(const float yaw); // Data should not be sent at player initialization void setPlayerYawAndSend(const float yaw); @@ -182,7 +182,7 @@ class PlayerSAO : public UnitSAO void finalize(RemotePlayer *player, const std::set &privs); - v3f getEyePosition() const { return m_base_position + getEyeOffset(); } + v3f getEyePosition() const { return getBasePosition() + getEyeOffset(); } v3f getEyeOffset() const; float getZoomFOV() const; diff --git a/src/server/serveractiveobject.cpp b/src/server/serveractiveobject.cpp index fb09464cf..db6d55269 100644 --- a/src/server/serveractiveobject.cpp +++ b/src/server/serveractiveobject.cpp @@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "serveractiveobject.h" +#include "serverenvironment.h" #include #include "inventory.h" #include "inventorymanager.h" @@ -31,6 +32,12 @@ ServerActiveObject::ServerActiveObject(ServerEnvironment *env, v3f pos): { } +void ServerActiveObject::setBasePosition(const v3f &pos) { + if(getEnv()) + getEnv()->updateObjectPosition(getId(), m_base_position, pos); + m_base_position = pos; +} + float ServerActiveObject::getMinimumSavedMovement() { return 2.0*BS; diff --git a/src/server/serveractiveobject.h b/src/server/serveractiveobject.h index 2d09e34c1..789b30365 100644 --- a/src/server/serveractiveobject.h +++ b/src/server/serveractiveobject.h @@ -77,7 +77,7 @@ class ServerActiveObject : public ActiveObject Some simple getters/setters */ v3f getBasePosition() const { return m_base_position; } - void setBasePosition(v3f pos){ m_base_position = pos; } + void setBasePosition(const v3f &pos); ServerEnvironment* getEnv(){ return m_env; } /* @@ -87,9 +87,9 @@ class ServerActiveObject : public ActiveObject virtual void setPos(const v3f &pos) { setBasePosition(pos); } virtual void addPos(const v3f &added_pos) - { setBasePosition(m_base_position + added_pos); } + { setBasePosition(getBasePosition() + added_pos); } // continuous: if true, object does not stop immediately at pos - virtual void moveTo(v3f pos, bool continuous) + virtual void moveTo(const v3f &pos, bool continuous) { setBasePosition(pos); } // If object has moved less than this and data has not changed, // saving to disk may be omitted @@ -244,7 +244,6 @@ class ServerActiveObject : public ActiveObject virtual void onDetach(int parent_id) {} ServerEnvironment *m_env; - v3f m_base_position; std::unordered_set m_attached_particle_spawners; /* @@ -272,4 +271,6 @@ class ServerActiveObject : public ActiveObject Queue of messages to be sent to the client */ std::queue m_messages_out; +private: + v3f m_base_position; // setBasePosition updates index and MUST be called }; diff --git a/src/server/spatial_map.cpp b/src/server/spatial_map.cpp new file mode 100644 index 000000000..3fb118eb2 --- /dev/null +++ b/src/server/spatial_map.cpp @@ -0,0 +1,167 @@ +/* +Minetest +Copyright (C) 2024, ExeVirus + +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 "spatial_map.h" +#include + +namespace server +{ + +// all inserted entires go into the uncached vector +void SpatialMap::insert(u16 id, const v3f &pos, bool postIteration) +{ + if(!m_iterators_stopping_insertion_and_deletion) { + SpatialKey key(pos); + if (postIteration) + key = SpatialKey(pos.X, pos.Y, pos.Z, true); + m_cached.insert({key, id}); + } else { + m_pending_inserts.insert(SpatialKey(pos,id)); + } +} + +// Invalidates upon position update +void SpatialMap::updatePosition(u16 id, const v3f &oldPos, const v3f &newPos) +{ + // Try to leave early if already in the same bucket: + auto range = m_cached.equal_range(SpatialKey(newPos)); + for (auto it = range.first; it != range.second; ++it) { + if (it->second == id) { + return; // all good, let's get out of here + } + } + + remove(id, oldPos); // remove from old cache position + insert(id, newPos); // reinsert +} + +void SpatialMap::remove(u16 id, const v3f &pos, bool postIteration) +{ + if(!m_iterators_stopping_insertion_and_deletion) { + SpatialKey key(pos); + if (postIteration) + key = SpatialKey(pos.X, pos.Y, pos.Z, true); + if(m_cached.find(key) != m_cached.end()) { + auto range = m_cached.equal_range(key); + for (auto it = range.first; it != range.second; ++it) { + if (it->second == id) { + m_cached.erase(it); + return; // Erase and leave early + } + } + } + } else { + m_pending_deletes.insert(SpatialKey(pos, id)); + return; + } + remove(id); // should never be hit +} + +void SpatialMap::remove(u16 id) +{ + if(!m_iterators_stopping_insertion_and_deletion) { + for (auto it = m_cached.begin(); it != m_cached.end(); ++it) { + if (it->second == id) { + m_cached.erase(it); + break; // Erase and leave early + } + } + } else { + m_pending_deletes.insert(SpatialKey(v3f(), id)); + } +} + +void SpatialMap::removeAll() +{ + if(!m_iterators_stopping_insertion_and_deletion) { + m_cached.clear(); + } else { + m_remove_all = true; + } +} + +void SpatialMap::getRelevantObjectIds(const aabb3f &box, const std::function &callback) +{ + if(!m_cached.empty()) { + // when searching, we must round to maximum extent of relevant mapblock indexes + auto low = [](f32 val) -> s16 { + s16 _val = static_cast(val / BS); + return (_val >> 4) - 1; + }; + auto high = [](f32 val) -> s16 { + s16 _val = static_cast(val / BS); + return (_val >> 4) + 1; + }; + + v3s16 min(low(box.MinEdge.X), low(box.MinEdge.Y), low(box.MinEdge.Z)), + max(high(box.MaxEdge.X), high(box.MaxEdge.Y), high(box.MaxEdge.Z)); + + // We should only iterate using this spatial map when there are at least 1 objects per mapblocks to check. + // Otherwise, might as well just iterate. + + v3s16 diff = max - min; + uint64_t number_of_mapblocks_to_check = std::abs(diff.X) * std::abs(diff.Y) * std::abs(diff.Z); + if(number_of_mapblocks_to_check <= m_cached.size()) { // might be worth it + for (s16 x = min.X; x < max.X;x++) { + for (s16 y = min.Y; y < max.Y;y++) { + for (s16 z = min.Z; z < max.Z;z++) { + SpatialKey key(x,y,z, false); + if (m_cached.find(key) != m_cached.end()) { + m_iterators_stopping_insertion_and_deletion++; + auto range = m_cached.equal_range(key); + for (auto &it = range.first; it != range.second; ++it) { + callback(it->second); + } + m_iterators_stopping_insertion_and_deletion--; + handleInsertsAndDeletes(); + } + } + } + } + } else { // let's just iterate, it'll be faster + m_iterators_stopping_insertion_and_deletion++; + for (auto it = m_cached.begin(); it != m_cached.end(); ++it) { + callback(it->second); + } + m_iterators_stopping_insertion_and_deletion--; + handleInsertsAndDeletes(); + } + } +} + +void SpatialMap::handleInsertsAndDeletes() +{ + if(!m_iterators_stopping_insertion_and_deletion) { + if(!m_remove_all) { + for (auto key : m_pending_deletes) { + remove(key.padding_or_optional_id, v3f(key.x, key.y, key.z), true); + } + for (auto key : m_pending_inserts) { + insert(key.padding_or_optional_id, v3f(key.x, key.y, key.z), true); + } + } else { + m_cached.clear(); + m_remove_all = false; + } + m_pending_inserts.clear(); + m_pending_deletes.clear(); + } +} + +} // namespace server diff --git a/src/server/spatial_map.h b/src/server/spatial_map.h new file mode 100644 index 000000000..c8792b355 --- /dev/null +++ b/src/server/spatial_map.h @@ -0,0 +1,83 @@ +/* +Minetest +Copyright (C) 2024, ExeVirus + +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. +*/ + +#pragma once + +#include +#include +#include +#include "irrlichttypes_bloated.h" +#include "constants.h" + +namespace server +{ +class SpatialMap +{ +public: + void insert(u16 id, const v3f &pos, bool postIteration = false); + void remove(u16 id, const v3f &pos, bool postIteration = false); + void remove(u16 id); + void removeAll(); + void updatePosition(u16 id, const v3f &oldPos, const v3f &newPos); + void getRelevantObjectIds(const aabb3f &box, const std::function &callback); + void handleInsertsAndDeletes(); + +protected: + struct SpatialKey { + u16 padding_or_optional_id{0}; + s16 x; + s16 y; + s16 z; + + SpatialKey(s16 _x, s16 _y, s16 _z, bool _shrink = true) { + if(_shrink) { + x = _x >> 4; + y = _y >> 4; + z = _z >> 4; + } else { + x = _x; + y = _y; + z = _z; + } + } + SpatialKey(const v3f &_pos) : SpatialKey(_pos.X / BS, _pos.Y / BS, _pos.Z / BS){} + // The following use case is for storing pending insertions and deletions while iterating + // using the extra 16 bit padding makes keeping track of them super efficient for hashing. + SpatialKey(const v3f &_pos, const u16 id) : SpatialKey(_pos.X / BS, _pos.Y / BS, _pos.Z / BS, false){ + padding_or_optional_id = id; + } + + bool operator==(const SpatialKey &other) const { + return (x == other.x && y == other.y && z == other.z); + } + }; + + struct SpatialKeyHash { + auto operator()(const SpatialKey &key) const -> size_t { + return std::hash()(*reinterpret_cast(&key)); + } + }; + + std::unordered_multimap m_cached; + std::unordered_set m_pending_inserts; + std::unordered_set m_pending_deletes; + bool m_remove_all{false}; + u64 m_iterators_stopping_insertion_and_deletion{0}; +}; +} // namespace server diff --git a/src/serverenvironment.h b/src/serverenvironment.h index d5d45d0f4..c5d806a4a 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -348,6 +348,11 @@ class ServerEnvironment final : public Environment return m_ao_manager.getObjectsInArea(box, objects, include_obj_cb); } + void updateObjectPosition(u16 id, const v3f &last_position, const v3f &new_position) + { + m_ao_manager.updateObjectPosition(id, last_position, new_position); + } + // Clear objects, loading and going through every MapBlock void clearObjects(ClearObjectsMode mode);