diff --git a/src/activeobjectmgr.h b/src/activeobjectmgr.h new file mode 100644 index 000000000..95e7d3344 --- /dev/null +++ b/src/activeobjectmgr.h @@ -0,0 +1,65 @@ +/* +Minetest +Copyright (C) 2010-2018 nerzhul, Loic BLOT + +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 "irrlichttypes.h" + +class TestClientActiveObjectMgr; +class TestServerActiveObjectMgr; + +template class ActiveObjectMgr +{ + friend class ::TestClientActiveObjectMgr; + friend class ::TestServerActiveObjectMgr; + +public: + virtual void step(float dtime, const std::function &f) = 0; + virtual bool registerObject(T *obj) = 0; + virtual void removeObject(u16 id) = 0; + + T *getActiveObject(u16 id) + { + typename std::unordered_map::const_iterator n = + m_active_objects.find(id); + return (n != m_active_objects.end() ? n->second : nullptr); + } + +protected: + u16 getFreeId() const + { + // try to reuse id's as late as possible + static thread_local u16 last_used_id = 0; + u16 startid = last_used_id; + while (!isFreeId(++last_used_id)) { + if (last_used_id == startid) + return 0; + } + + return last_used_id; + } + + bool isFreeId(u16 id) const + { + return id != 0 && m_active_objects.find(id) == m_active_objects.end(); + } + + std::unordered_map m_active_objects; +}; diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index e24b73e80..140814911 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -25,6 +25,7 @@ set(client_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/render/plain.cpp ${CMAKE_CURRENT_SOURCE_DIR}/render/sidebyside.cpp ${CMAKE_CURRENT_SOURCE_DIR}/render/stereo.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/activeobjectmgr.cpp ${CMAKE_CURRENT_SOURCE_DIR}/camera.cpp ${CMAKE_CURRENT_SOURCE_DIR}/client.cpp ${CMAKE_CURRENT_SOURCE_DIR}/clientenvironment.cpp diff --git a/src/client/activeobjectmgr.cpp b/src/client/activeobjectmgr.cpp new file mode 100644 index 000000000..4ed98d79b --- /dev/null +++ b/src/client/activeobjectmgr.cpp @@ -0,0 +1,106 @@ +/* +Minetest +Copyright (C) 2010-2018 nerzhul, Loic BLOT + +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 +#include "profiler.h" +#include "activeobjectmgr.h" + +namespace client +{ + +void ActiveObjectMgr::clear() +{ + // delete active objects + for (auto &active_object : m_active_objects) { + delete active_object.second; + } +} + +void ActiveObjectMgr::step( + float dtime, const std::function &f) +{ + g_profiler->avg("Client::ActiveObjectMgr: num of objects", + m_active_objects.size()); + for (auto &ao_it : m_active_objects) { + f(ao_it.second); + } +} + +// clang-format off +bool ActiveObjectMgr::registerObject(ClientActiveObject *obj) +{ + assert(obj); // Pre-condition + if (obj->getId() == 0) { + u16 new_id = getFreeId(); + if (new_id == 0) { + infostream << "Client::ActiveObjectMgr::registerObject(): " + << "no free id available" << std::endl; + + delete obj; + return false; + } + obj->setId(new_id); + } + + if (!isFreeId(obj->getId())) { + infostream << "Client::ActiveObjectMgr::registerObject(): " + << "id is not free (" << obj->getId() << ")" << std::endl; + delete obj; + return false; + } + infostream << "Client::ActiveObjectMgr::registerObject(): " + << "added (id=" << obj->getId() << ")" << std::endl; + m_active_objects[obj->getId()] = obj; + return true; +} + +void ActiveObjectMgr::removeObject(u16 id) +{ + verbosestream << "Client::ActiveObjectMgr::removeObject(): " + << "id=" << id << std::endl; + ClientActiveObject *obj = getActiveObject(id); + if (!obj) { + infostream << "Client::ActiveObjectMgr::removeObject(): " + << "id=" << id << " not found" << std::endl; + return; + } + + m_active_objects.erase(id); + + obj->removeFromScene(true); + delete obj; +} + +// clang-format on +void ActiveObjectMgr::getActiveObjects(const v3f &origin, f32 max_d, + std::vector &dest) +{ + for (auto &ao_it : m_active_objects) { + ClientActiveObject *obj = ao_it.second; + + f32 d = (obj->getPosition() - origin).getLength(); + + if (d > max_d) + continue; + + dest.emplace_back(obj, d); + } +} + +} // namespace client diff --git a/src/client/activeobjectmgr.h b/src/client/activeobjectmgr.h new file mode 100644 index 000000000..510b2d6e3 --- /dev/null +++ b/src/client/activeobjectmgr.h @@ -0,0 +1,41 @@ +/* +Minetest +Copyright (C) 2010-2018 nerzhul, Loic BLOT + +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 "../activeobjectmgr.h" +#include "clientobject.h" + +namespace client +{ +class ActiveObjectMgr : public ::ActiveObjectMgr +{ +public: + void clear(); + void step(float dtime, + const std::function &f) override; + bool registerObject(ClientActiveObject *obj) override; + void removeObject(u16 id) override; + + void getActiveObjects(const v3f &origin, f32 max_d, + std::vector &dest); +}; +} // namespace client diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp index e2f24aaa3..7c2ec099c 100644 --- a/src/client/clientenvironment.cpp +++ b/src/client/clientenvironment.cpp @@ -53,10 +53,7 @@ ClientEnvironment::ClientEnvironment(ClientMap *map, ClientEnvironment::~ClientEnvironment() { - // delete active objects - for (auto &active_object : m_active_objects) { - delete active_object.second; - } + m_ao_manager.clear(); for (auto &simple_object : m_simple_objects) { delete simple_object; @@ -262,12 +259,10 @@ void ClientEnvironment::step(float dtime) Step active objects and update lighting of them */ - g_profiler->avg("CEnv: num of objects", m_active_objects.size()); bool update_lighting = m_active_object_light_update_interval.step(dtime, 0.21); - for (auto &ao_it : m_active_objects) { - ClientActiveObject* obj = ao_it.second; + auto cb_state = [this, dtime, update_lighting, day_night_ratio] (ClientActiveObject *cao) { // Step object - obj->step(dtime, this); + cao->step(dtime, this); if (update_lighting) { // Update lighting @@ -275,16 +270,18 @@ void ClientEnvironment::step(float dtime) bool pos_ok; // Get node at head - v3s16 p = obj->getLightPosition(); - MapNode n = m_map->getNodeNoEx(p, &pos_ok); + v3s16 p = cao->getLightPosition(); + MapNode n = this->m_map->getNodeNoEx(p, &pos_ok); if (pos_ok) light = n.getLightBlend(day_night_ratio, m_client->ndef()); else light = blend_light(day_night_ratio, LIGHT_SUN, 0); - obj->updateLight(light); + cao->updateLight(light); } - } + }; + + m_ao_manager.step(dtime, cb_state); /* Step and handle simple objects @@ -319,14 +316,6 @@ GenericCAO* ClientEnvironment::getGenericCAO(u16 id) return NULL; } -ClientActiveObject* ClientEnvironment::getActiveObject(u16 id) -{ - auto n = m_active_objects.find(id); - if (n == m_active_objects.end()) - return NULL; - return n->second; -} - bool isFreeClientActiveObjectId(const u16 id, ClientActiveObjectMap &objects) { @@ -336,7 +325,7 @@ bool isFreeClientActiveObjectId(const u16 id, u16 getFreeClientActiveObjectId(ClientActiveObjectMap &objects) { - //try to reuse id's as late as possible + // try to reuse id's as late as possible static u16 last_used_id = 0; u16 startid = last_used_id; for(;;) { @@ -351,43 +340,25 @@ u16 getFreeClientActiveObjectId(ClientActiveObjectMap &objects) u16 ClientEnvironment::addActiveObject(ClientActiveObject *object) { - assert(object); // Pre-condition - if(object->getId() == 0) - { - u16 new_id = getFreeClientActiveObjectId(m_active_objects); - if(new_id == 0) - { - infostream<<"ClientEnvironment::addActiveObject(): " - <<"no free ids available"<setId(new_id); - } - if (!isFreeClientActiveObjectId(object->getId(), m_active_objects)) { - infostream<<"ClientEnvironment::addActiveObject(): " - <<"id is not free ("<getId()<<")"<getId()] = object; + object->addToScene(m_texturesource); - { // Update lighting immediately - u8 light = 0; - bool pos_ok; - // Get node at head - v3s16 p = object->getLightPosition(); - MapNode n = m_map->getNodeNoEx(p, &pos_ok); - if (pos_ok) - light = n.getLightBlend(getDayNightRatio(), m_client->ndef()); - else - light = blend_light(getDayNightRatio(), LIGHT_SUN, 0); + // Update lighting immediately + u8 light = 0; + bool pos_ok; - object->updateLight(light); - } + // Get node at head + v3s16 p = object->getLightPosition(); + MapNode n = m_map->getNodeNoEx(p, &pos_ok); + if (pos_ok) + light = n.getLightBlend(getDayNightRatio(), m_client->ndef()); + else + light = blend_light(getDayNightRatio(), LIGHT_SUN, 0); + + object->updateLight(light); return object->getId(); } @@ -423,21 +394,6 @@ void ClientEnvironment::addActiveObject(u16 id, u8 type, addActiveObject(obj); } -void ClientEnvironment::removeActiveObject(u16 id) -{ - verbosestream<<"ClientEnvironment::removeActiveObject(): " - <<"id="<removeFromScene(true); - delete obj; - m_active_objects.erase(id); -} - void ClientEnvironment::processActiveObjectMessage(u16 id, const std::string &data) { ClientActiveObject *obj = getActiveObject(id); @@ -485,21 +441,6 @@ void ClientEnvironment::damageLocalPlayer(u8 damage, bool handle_hp) Client likes to call these */ -void ClientEnvironment::getActiveObjects(v3f origin, f32 max_d, - std::vector &dest) -{ - for (auto &ao_it : m_active_objects) { - ClientActiveObject* obj = ao_it.second; - - f32 d = (obj->getPosition() - origin).getLength(); - - if (d > max_d) - continue; - - dest.emplace_back(obj, d); - } -} - ClientEnvEvent ClientEnvironment::getClientEnvEvent() { FATAL_ERROR_IF(m_client_event_queue.empty(), diff --git a/src/client/clientenvironment.h b/src/client/clientenvironment.h index 606070e3a..d167902d1 100644 --- a/src/client/clientenvironment.h +++ b/src/client/clientenvironment.h @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "clientobject.h" #include "util/numeric.h" +#include "activeobjectmgr.h" class ClientSimpleObject; class ClientMap; @@ -87,7 +88,10 @@ public: */ GenericCAO* getGenericCAO(u16 id); - ClientActiveObject* getActiveObject(u16 id); + ClientActiveObject* getActiveObject(u16 id) + { + return m_ao_manager.getActiveObject(id); + } /* Adds an active object to the environment. @@ -100,7 +104,10 @@ public: u16 addActiveObject(ClientActiveObject *object); void addActiveObject(u16 id, u8 type, const std::string &init_data); - void removeActiveObject(u16 id); + void removeActiveObject(u16 id) + { + m_ao_manager.removeObject(id); + } void processActiveObjectMessage(u16 id, const std::string &data); @@ -115,8 +122,11 @@ public: */ // Get all nearby objects - void getActiveObjects(v3f origin, f32 max_d, - std::vector &dest); + void getActiveObjects(const v3f &origin, f32 max_d, + std::vector &dest) + { + return m_ao_manager.getActiveObjects(origin, max_d, dest); + } bool hasClientEnvEvents() const { return !m_client_event_queue.empty(); } @@ -142,7 +152,7 @@ private: ITextureSource *m_texturesource; Client *m_client; ClientScripting *m_script = nullptr; - ClientActiveObjectMap m_active_objects; + client::ActiveObjectMgr m_ao_manager; std::vector m_simple_objects; std::queue m_client_event_queue; IntervalLimiter m_active_object_light_update_interval; diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index b892e83b3..e964c69ff 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -1,3 +1,4 @@ set(server_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/activeobjectmgr.cpp ${CMAKE_CURRENT_SOURCE_DIR}/mods.cpp PARENT_SCOPE) diff --git a/src/server/activeobjectmgr.cpp b/src/server/activeobjectmgr.cpp new file mode 100644 index 000000000..56febd76e --- /dev/null +++ b/src/server/activeobjectmgr.cpp @@ -0,0 +1,167 @@ +/* +Minetest +Copyright (C) 2010-2018 nerzhul, Loic BLOT + +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 +#include "mapblock.h" +#include "profiler.h" +#include "activeobjectmgr.h" + +namespace server +{ + +void ActiveObjectMgr::clear(const std::function &cb) +{ + std::vector objects_to_remove; + for (auto &it : m_active_objects) { + if (cb(it.second, it.first)) { + // Id to be removed from m_active_objects + objects_to_remove.push_back(it.first); + } + } + + // Remove references from m_active_objects + for (u16 i : objects_to_remove) { + m_active_objects.erase(i); + } +} + +void ActiveObjectMgr::step( + float dtime, const std::function &f) +{ + g_profiler->avg("Server::ActiveObjectMgr: num of objects", + m_active_objects.size()); + for (auto &ao_it : m_active_objects) { + f(ao_it.second); + } +} + +// clang-format off +bool ActiveObjectMgr::registerObject(ServerActiveObject *obj) +{ + assert(obj); // Pre-condition + if (obj->getId() == 0) { + u16 new_id = getFreeId(); + if (new_id == 0) { + errorstream << "Server::ActiveObjectMgr::addActiveObjectRaw(): " + << "no free id available" << std::endl; + if (obj->environmentDeletes()) + delete obj; + return false; + } + obj->setId(new_id); + } else { + verbosestream << "Server::ActiveObjectMgr::addActiveObjectRaw(): " + << "supplied with id " << obj->getId() << std::endl; + } + + if (!isFreeId(obj->getId())) { + errorstream << "Server::ActiveObjectMgr::addActiveObjectRaw(): " + << "id is not free (" << obj->getId() << ")" << std::endl; + if (obj->environmentDeletes()) + delete obj; + return false; + } + + if (objectpos_over_limit(obj->getBasePosition())) { + v3f p = obj->getBasePosition(); + warningstream << "Server::ActiveObjectMgr::addActiveObjectRaw(): " + << "object position (" << p.X << "," << p.Y << "," << p.Z + << ") outside maximum range" << std::endl; + if (obj->environmentDeletes()) + delete obj; + return false; + } + + m_active_objects[obj->getId()] = obj; + + verbosestream << "Server::ActiveObjectMgr::addActiveObjectRaw(): " + << "Added id=" << obj->getId() << "; there are now " + << m_active_objects.size() << " active objects." << std::endl; + return true; +} + +void ActiveObjectMgr::removeObject(u16 id) +{ + verbosestream << "Server::ActiveObjectMgr::removeObject(): " + << "id=" << id << std::endl; + ServerActiveObject *obj = getActiveObject(id); + if (!obj) { + infostream << "Server::ActiveObjectMgr::removeObject(): " + << "id=" << id << " not found" << std::endl; + return; + } + + m_active_objects.erase(id); + delete obj; +} + +// clang-format on +void ActiveObjectMgr::getObjectsInsideRadius( + const v3f &pos, float radius, std::vector &result) +{ + for (auto &activeObject : m_active_objects) { + ServerActiveObject *obj = activeObject.second; + u16 id = activeObject.first; + const v3f &objectpos = obj->getBasePosition(); + if (objectpos.getDistanceFrom(pos) > radius) + continue; + result.push_back(id); + } +} + +void ActiveObjectMgr::getAddedActiveObjectsAroundPos(const v3f &player_pos, f32 radius, + f32 player_radius, std::set ¤t_objects, + std::queue &added_objects) +{ + /* + Go through the object list, + - discard removed/deactivated objects, + - discard objects that are too far away, + - discard objects that are found in current_objects. + - add remaining objects to added_objects + */ + for (auto &ao_it : m_active_objects) { + u16 id = ao_it.first; + + // Get object + ServerActiveObject *object = ao_it.second; + if (!object) + continue; + + if (object->isGone()) + continue; + + f32 distance_f = object->getBasePosition().getDistanceFrom(player_pos); + if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) { + // Discard if too far + if (distance_f > player_radius && player_radius != 0) + continue; + } else if (distance_f > radius) + continue; + + // Discard if already on current_objects + auto n = current_objects.find(id); + if (n != current_objects.end()) + continue; + // Add to added_objects + added_objects.push(id); + } +} + +} // namespace server diff --git a/src/server/activeobjectmgr.h b/src/server/activeobjectmgr.h new file mode 100644 index 000000000..a502ac6ed --- /dev/null +++ b/src/server/activeobjectmgr.h @@ -0,0 +1,45 @@ +/* +Minetest +Copyright (C) 2010-2018 nerzhul, Loic BLOT + +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 "../activeobjectmgr.h" +#include "serverobject.h" + +namespace server +{ +class ActiveObjectMgr : public ::ActiveObjectMgr +{ +public: + void clear(const std::function &cb); + void step(float dtime, + const std::function &f) override; + bool registerObject(ServerActiveObject *obj) override; + void removeObject(u16 id) override; + + void getObjectsInsideRadius( + const v3f &pos, float radius, std::vector &result); + + void getAddedActiveObjectsAroundPos(const v3f &player_pos, f32 radius, + f32 player_radius, std::set ¤t_objects, + std::queue &added_objects); +}; +} // namespace server diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index efb93b9a2..4837449e6 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -1047,29 +1047,13 @@ bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n) return true; } -void ServerEnvironment::getObjectsInsideRadius(std::vector &objects, v3f pos, - float radius) -{ - for (auto &activeObject : m_active_objects) { - ServerActiveObject* obj = activeObject.second; - u16 id = activeObject.first; - v3f objectpos = obj->getBasePosition(); - if (objectpos.getDistanceFrom(pos) > radius) - continue; - objects.push_back(id); - } -} - void ServerEnvironment::clearObjects(ClearObjectsMode mode) { infostream << "ServerEnvironment::clearObjects(): " << "Removing all active objects" << std::endl; - std::vector objects_to_remove; - for (auto &it : m_active_objects) { - u16 id = it.first; - ServerActiveObject* obj = it.second; + auto cb_removal = [this] (ServerActiveObject *obj, u16 id) { if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER) - continue; + return false; // Delete static object if block is loaded deleteStaticFromBlock(obj, id, MOD_REASON_CLEAR_ALL_OBJECTS, true); @@ -1077,7 +1061,7 @@ void ServerEnvironment::clearObjects(ClearObjectsMode mode) // If known by some client, don't delete immediately if (obj->m_known_by_count > 0) { obj->m_pending_removal = true; - continue; + return false; } // Tell the object about removal @@ -1088,14 +1072,11 @@ void ServerEnvironment::clearObjects(ClearObjectsMode mode) // Delete active object if (obj->environmentDeletes()) delete obj; - // Id to be removed from m_active_objects - objects_to_remove.push_back(id); - } - // Remove references from m_active_objects - for (u16 i : objects_to_remove) { - m_active_objects.erase(i); - } + return true; + }; + + m_ao_manager.clear(cb_removal); // Get list of loaded blocks std::vector loaded_blocks; @@ -1386,32 +1367,28 @@ void ServerEnvironment::step(float dtime) */ { ScopeProfiler sp(g_profiler, "SEnv: step act. objs avg", SPT_AVG); - //TimeTaker timer("Step active objects"); - - g_profiler->avg("SEnv: num of objects", m_active_objects.size()); // This helps the objects to send data at the same time bool send_recommended = false; m_send_recommended_timer += dtime; - if(m_send_recommended_timer > getSendRecommendedInterval()) - { + if (m_send_recommended_timer > getSendRecommendedInterval()) { m_send_recommended_timer -= getSendRecommendedInterval(); send_recommended = true; } - for (auto &ao_it : m_active_objects) { - ServerActiveObject* obj = ao_it.second; + auto cb_state = [this, dtime, send_recommended] (ServerActiveObject *obj) { if (obj->isGone()) - continue; + return; // Step object obj->step(dtime, send_recommended); // Read messages from object while (!obj->m_messages_out.empty()) { - m_active_object_messages.push(obj->m_messages_out.front()); + this->m_active_object_messages.push(obj->m_messages_out.front()); obj->m_messages_out.pop(); } - } + }; + m_ao_manager.step(dtime, cb_state); } /* @@ -1484,44 +1461,6 @@ void ServerEnvironment::deleteParticleSpawner(u32 id, bool remove_from_object) } } -ServerActiveObject* ServerEnvironment::getActiveObject(u16 id) -{ - ServerActiveObjectMap::const_iterator n = m_active_objects.find(id); - return (n != m_active_objects.end() ? n->second : NULL); -} - -/** - * Verify if id is a free active object id - * @param id - * @return true if slot is free - */ -bool ServerEnvironment::isFreeServerActiveObjectId(u16 id) const -{ - if (id == 0) - return false; - - return m_active_objects.find(id) == m_active_objects.end(); -} - -/** - * Retrieve the first free ActiveObject ID - * @return free activeobject ID or 0 if none was found - */ -u16 ServerEnvironment::getFreeServerActiveObjectId() -{ - // try to reuse id's as late as possible - static u16 last_used_id = 0; - u16 startid = last_used_id; - for (;;) { - last_used_id++; - if (isFreeServerActiveObjectId(last_used_id)) - return last_used_id; - - if (last_used_id == startid) - return 0; - } -} - u16 ServerEnvironment::addActiveObject(ServerActiveObject *object) { assert(object); // Pre-condition @@ -1542,43 +1481,11 @@ void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius, f32 radius_f = radius * BS; f32 player_radius_f = player_radius * BS; - if (player_radius_f < 0) - player_radius_f = 0; - /* - Go through the object list, - - discard removed/deactivated objects, - - discard objects that are too far away, - - discard objects that are found in current_objects. - - add remaining objects to added_objects - */ - for (auto &ao_it : m_active_objects) { - u16 id = ao_it.first; + if (player_radius_f < 0.0f) + player_radius_f = 0.0f; - // Get object - ServerActiveObject *object = ao_it.second; - if (object == NULL) - continue; - - if (object->isGone()) - continue; - - f32 distance_f = object->getBasePosition(). - getDistanceFrom(playersao->getBasePosition()); - if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) { - // Discard if too far - if (distance_f > player_radius_f && player_radius_f != 0) - continue; - } else if (distance_f > radius_f) - continue; - - // Discard if already on current_objects - std::set::iterator n; - n = current_objects.find(id); - if(n != current_objects.end()) - continue; - // Add to added_objects - added_objects.push(id); - } + m_ao_manager.getAddedActiveObjectsAroundPos(playersao->getBasePosition(), radius_f, + player_radius_f, current_objects, added_objects); } /* @@ -1639,8 +1546,8 @@ void ServerEnvironment::setStaticForActiveObjectsInBlock( for (auto &so_it : block->m_static_objects.m_active) { // Get the ServerActiveObject counterpart to this StaticObject - ServerActiveObjectMap::const_iterator ao_it = m_active_objects.find(so_it.first); - if (ao_it == m_active_objects.end()) { + ServerActiveObject *sao = m_ao_manager.getActiveObject(so_it.first); + if (!sao) { // If this ever happens, there must be some kind of nasty bug. errorstream << "ServerEnvironment::setStaticForObjectsInBlock(): " "Object from MapBlock::m_static_objects::m_active not found " @@ -1648,7 +1555,6 @@ void ServerEnvironment::setStaticForActiveObjectsInBlock( continue; } - ServerActiveObject *sao = ao_it->second; sao->m_static_exists = static_exists; sao->m_static_block = static_block; } @@ -1703,60 +1609,17 @@ void ServerEnvironment::getSelectedActiveObjects( u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object, bool set_changed, u32 dtime_s) { - assert(object); // Pre-condition - if(object->getId() == 0){ - u16 new_id = getFreeServerActiveObjectId(); - if(new_id == 0) - { - errorstream<<"ServerEnvironment::addActiveObjectRaw(): " - <<"no free ids available"<environmentDeletes()) - delete object; - return 0; - } - object->setId(new_id); - } - else{ - verbosestream<<"ServerEnvironment::addActiveObjectRaw(): " - <<"supplied with id "<getId()<getId())) { - errorstream<<"ServerEnvironment::addActiveObjectRaw(): " - <<"id is not free ("<getId()<<")"<environmentDeletes()) - delete object; + if (!m_ao_manager.registerObject(object)) { return 0; } - if (objectpos_over_limit(object->getBasePosition())) { - v3f p = object->getBasePosition(); - warningstream << "ServerEnvironment::addActiveObjectRaw(): " - << "object position (" << p.X << "," << p.Y << "," << p.Z - << ") outside maximum range" << std::endl; - if (object->environmentDeletes()) - delete object; - return 0; - } - - /*infostream<<"ServerEnvironment::addActiveObjectRaw(): " - <<"added (id="<getId()<<")"<getId()] = object; - - verbosestream<<"ServerEnvironment::addActiveObjectRaw(): " - <<"Added id="<getId()<<"; there are now " - <addObjectReference(object); // Post-initialize object object->addedToEnvironment(dtime_s); // Add static data to block - if(object->isStaticAllowed()) - { + if (object->isStaticAllowed()) { // Add static object to active static list of the block v3f objectpos = object->getBasePosition(); StaticObject s_obj(object, objectpos); @@ -1787,24 +1650,19 @@ u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object, */ void ServerEnvironment::removeRemovedObjects() { - std::vector objects_to_remove; - for (auto &ao_it : m_active_objects) { - u16 id = ao_it.first; - ServerActiveObject* obj = ao_it.second; - + auto clear_cb = [this] (ServerActiveObject *obj, u16 id) { // This shouldn't happen but check it if (!obj) { errorstream << "ServerEnvironment::removeRemovedObjects(): " << "NULL object found. id=" << id << std::endl; - objects_to_remove.push_back(id); - continue; + return true; } /* We will handle objects marked for removal or deactivation */ if (!obj->isGone()) - continue; + return false; /* Delete static data from block if removed @@ -1815,7 +1673,7 @@ void ServerEnvironment::removeRemovedObjects() // If still known by clients, don't actually remove. On some future // invocation this will be 0, which is when removal will continue. if(obj->m_known_by_count > 0) - continue; + return false; /* Move static data from active to stored if deactivated @@ -1823,8 +1681,7 @@ void ServerEnvironment::removeRemovedObjects() if (!obj->m_pending_removal && obj->m_static_exists) { MapBlock *block = m_map->emergeBlock(obj->m_static_block, false); if (block) { - std::map::iterator i = - block->m_static_objects.m_active.find(id); + const auto i = block->m_static_objects.m_active.find(id); if (i != block->m_static_objects.m_active.end()) { block->m_static_objects.m_stored.push_back(i->second); block->m_static_objects.m_active.erase(id); @@ -1848,15 +1705,13 @@ void ServerEnvironment::removeRemovedObjects() m_script->removeObjectReference(obj); // Delete - if(obj->environmentDeletes()) + if (obj->environmentDeletes()) delete obj; - objects_to_remove.push_back(id); - } - // Remove references from m_active_objects - for (u16 i : objects_to_remove) { - m_active_objects.erase(i); - } + return true; + }; + + m_ao_manager.clear(clear_cb); } static void print_hexdump(std::ostream &o, const std::string &data) @@ -1976,24 +1831,19 @@ void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s) */ void ServerEnvironment::deactivateFarObjects(bool _force_delete) { - std::vector objects_to_remove; - for (auto &ao_it : m_active_objects) { + auto cb_deactivate = [this, _force_delete] (ServerActiveObject *obj, u16 id) { // force_delete might be overriden per object bool force_delete = _force_delete; - ServerActiveObject* obj = ao_it.second; - assert(obj); - // Do not deactivate if static data creation not allowed - if(!force_delete && !obj->isStaticAllowed()) - continue; + if (!force_delete && !obj->isStaticAllowed()) + return false; // removeRemovedObjects() is responsible for these - if(!force_delete && obj->isGone()) - continue; + if (!force_delete && obj->isGone()) + return false; - u16 id = ao_it.first; - v3f objectpos = obj->getBasePosition(); + const v3f &objectpos = obj->getBasePosition(); // The block in which the object resides in v3s16 blockpos_o = getNodeBlockPos(floatToInt(objectpos, BS)); @@ -2001,11 +1851,9 @@ void ServerEnvironment::deactivateFarObjects(bool _force_delete) // If object's static data is stored in a deactivated block and object // is actually located in an active block, re-save to the block in // which the object is actually located in. - if(!force_delete && - obj->m_static_exists && - !m_active_blocks.contains(obj->m_static_block) && - m_active_blocks.contains(blockpos_o)) - { + if (!force_delete && obj->m_static_exists && + !m_active_blocks.contains(obj->m_static_block) && + m_active_blocks.contains(blockpos_o)) { // Delete from block where object was located deleteStaticFromBlock(obj, id, MOD_REASON_STATIC_DATA_REMOVED, false); @@ -2013,16 +1861,16 @@ void ServerEnvironment::deactivateFarObjects(bool _force_delete) // Save to block where object is located saveStaticToBlock(blockpos_o, id, obj, s_obj, MOD_REASON_STATIC_DATA_ADDED); - continue; + return false; } // If block is still active, don't remove - if(!force_delete && m_active_blocks.contains(blockpos_o)) - continue; + if (!force_delete && m_active_blocks.contains(blockpos_o)) + return false; verbosestream << "ServerEnvironment::deactivateFarObjects(): " - << "deactivating object id=" << id << " on inactive block " - << PP(blockpos_o) << std::endl; + << "deactivating object id=" << id << " on inactive block " + << PP(blockpos_o) << std::endl; // If known by some client, don't immediately delete. bool pending_delete = (obj->m_known_by_count > 0 && !force_delete); @@ -2045,8 +1893,7 @@ void ServerEnvironment::deactivateFarObjects(bool _force_delete) MapBlock *block = m_map->emergeBlock(obj->m_static_block, false); if (block) { - std::map::iterator n = - block->m_static_objects.m_active.find(id); + const auto n = block->m_static_objects.m_active.find(id); if (n != block->m_static_objects.m_active.end()) { StaticObject static_old = n->second; @@ -2085,18 +1932,18 @@ void ServerEnvironment::deactivateFarObjects(bool _force_delete) If known by some client, set pending deactivation. Otherwise delete it immediately. */ - if(pending_delete && !force_delete) - { + if (pending_delete && !force_delete) { verbosestream << "ServerEnvironment::deactivateFarObjects(): " - << "object id=" << id << " is known by clients" - << "; not deleting yet" << std::endl; + << "object id=" << id << " is known by clients" + << "; not deleting yet" << std::endl; obj->m_pending_deactivation = true; - continue; + return false; } + verbosestream << "ServerEnvironment::deactivateFarObjects(): " - << "object id=" << id << " is not known by clients" - << "; deleting" << std::endl; + << "object id=" << id << " is not known by clients" + << "; deleting" << std::endl; // Tell the object about removal obj->removingFromEnvironment(); @@ -2104,16 +1951,13 @@ void ServerEnvironment::deactivateFarObjects(bool _force_delete) m_script->removeObjectReference(obj); // Delete active object - if(obj->environmentDeletes()) + if (obj->environmentDeletes()) delete obj; - // Id to be removed from m_active_objects - objects_to_remove.push_back(id); - } - // Remove references from m_active_objects - for (u16 i : objects_to_remove) { - m_active_objects.erase(i); - } + return true; + }; + + m_ao_manager.clear(cb_deactivate); } void ServerEnvironment::deleteStaticFromBlock( diff --git a/src/serverenvironment.h b/src/serverenvironment.h index b7a121adf..b883e0dc5 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "environment.h" #include "mapnode.h" #include "settings.h" +#include "server/activeobjectmgr.h" #include "util/numeric.h" #include @@ -243,7 +244,10 @@ public: ------------------------------------------- */ - ServerActiveObject* getActiveObject(u16 id); + ServerActiveObject* getActiveObject(u16 id) + { + return m_ao_manager.getActiveObject(id); + } /* Add an active object to the environment. @@ -255,19 +259,6 @@ public: */ u16 addActiveObject(ServerActiveObject *object); - /** - * Verify if id is a free active object id - * @param id - * @return true if slot is free - */ - bool isFreeServerActiveObjectId(u16 id) const; - - /** - * Retrieve the first free ActiveObject ID - * @return free activeobject ID or 0 if none was found - */ - u16 getFreeServerActiveObjectId(); - /* Add an active object as a static object to the corresponding MapBlock. @@ -331,7 +322,10 @@ public: bool swapNode(v3s16 p, const MapNode &n); // Find all active objects inside a radius around a point - void getObjectsInsideRadius(std::vector &objects, v3f pos, float radius); + void getObjectsInsideRadius(std::vector &objects, const v3f &pos, float radius) + { + return m_ao_manager.getObjectsInsideRadius(pos, radius, objects); + } // Clear objects, loading and going through every MapBlock void clearObjects(ClearObjectsMode mode); @@ -438,10 +432,10 @@ private: ServerScripting* m_script; // Server definition Server *m_server; + // Active Object Manager + server::ActiveObjectMgr m_ao_manager; // World path const std::string m_path_world; - // Active object list - ServerActiveObjectMap m_active_objects; // Outgoing network message buffer for active objects std::queue m_active_object_messages; // Some timers diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt index 3ffe1978e..993137939 100644 --- a/src/unittest/CMakeLists.txt +++ b/src/unittest/CMakeLists.txt @@ -21,6 +21,7 @@ set (UNITTEST_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/test_random.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_schematic.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_serialization.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_serveractiveobjectmgr.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_server_shutdown_state.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_settings.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_socket.cpp @@ -33,6 +34,7 @@ set (UNITTEST_SRCS PARENT_SCOPE) set (UNITTEST_CLIENT_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/test_clientactiveobjectmgr.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_eventmanager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_gameui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_keycode.cpp diff --git a/src/unittest/test_clientactiveobjectmgr.cpp b/src/unittest/test_clientactiveobjectmgr.cpp new file mode 100644 index 000000000..4d2846c8d --- /dev/null +++ b/src/unittest/test_clientactiveobjectmgr.cpp @@ -0,0 +1,117 @@ +/* +Minetest +Copyright (C) 2018 nerzhul, Loic Blot + +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 "client/activeobjectmgr.h" +#include +#include "test.h" + +#include "profiler.h" + +class TestClientActiveObject : public ClientActiveObject +{ +public: + TestClientActiveObject() : ClientActiveObject(0, nullptr, nullptr) {} + ~TestClientActiveObject() = default; + ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_TEST; } +}; + +class TestClientActiveObjectMgr : public TestBase +{ +public: + TestClientActiveObjectMgr() { TestManager::registerTestModule(this); } + const char *getName() { return "TestClientActiveObjectMgr"; } + + void runTests(IGameDef *gamedef); + + void testFreeID(); + void testRegisterObject(); + void testRemoveObject(); +}; + +static TestClientActiveObjectMgr g_test_instance; + +void TestClientActiveObjectMgr::runTests(IGameDef *gamedef) +{ + TEST(testFreeID); + TEST(testRegisterObject) + TEST(testRemoveObject) +} + +//////////////////////////////////////////////////////////////////////////////// + +void TestClientActiveObjectMgr::testFreeID() +{ + client::ActiveObjectMgr caomgr; + std::vector aoids; + + u16 aoid = caomgr.getFreeId(); + // Ensure it's not the same id + UASSERT(caomgr.getFreeId() != aoid); + + aoids.push_back(aoid); + + // Register basic objects, ensure we never found + for (u8 i = 0; i < UINT8_MAX; i++) { + // Register an object + auto tcao = new TestClientActiveObject(); + caomgr.registerObject(tcao); + aoids.push_back(tcao->getId()); + + // Ensure next id is not in registered list + UASSERT(std::find(aoids.begin(), aoids.end(), caomgr.getFreeId()) == + aoids.end()); + } + + caomgr.clear(); +} + +void TestClientActiveObjectMgr::testRegisterObject() +{ + client::ActiveObjectMgr caomgr; + auto tcao = new TestClientActiveObject(); + UASSERT(caomgr.registerObject(tcao)); + + u16 id = tcao->getId(); + + auto tcaoToCompare = caomgr.getActiveObject(id); + UASSERT(tcaoToCompare->getId() == id); + UASSERT(tcaoToCompare == tcao); + + tcao = new TestClientActiveObject(); + UASSERT(caomgr.registerObject(tcao)); + UASSERT(caomgr.getActiveObject(tcao->getId()) == tcao); + UASSERT(caomgr.getActiveObject(tcao->getId()) != tcaoToCompare); + + caomgr.clear(); +} + +void TestClientActiveObjectMgr::testRemoveObject() +{ + client::ActiveObjectMgr caomgr; + auto tcao = new TestClientActiveObject(); + UASSERT(caomgr.registerObject(tcao)); + + u16 id = tcao->getId(); + UASSERT(caomgr.getActiveObject(id) != nullptr) + + caomgr.removeObject(tcao->getId()); + UASSERT(caomgr.getActiveObject(id) == nullptr) + + caomgr.clear(); +} diff --git a/src/unittest/test_serveractiveobjectmgr.cpp b/src/unittest/test_serveractiveobjectmgr.cpp new file mode 100644 index 000000000..0806972ab --- /dev/null +++ b/src/unittest/test_serveractiveobjectmgr.cpp @@ -0,0 +1,188 @@ +/* +Minetest +Copyright (C) 2018 nerzhul, Loic Blot + +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 "server/activeobjectmgr.h" +#include +#include +#include "test.h" + +#include "profiler.h" + +class TestServerActiveObject : public ServerActiveObject +{ +public: + TestServerActiveObject(const v3f &p = v3f()) : ServerActiveObject(nullptr, p) {} + ~TestServerActiveObject() = default; + ActiveObjectType getType() const override { return ACTIVEOBJECT_TYPE_TEST; } + bool getCollisionBox(aabb3f *toset) const override { return false; } + bool getSelectionBox(aabb3f *toset) const override { return false; } + bool collideWithObjects() const override { return false; } +}; + +class TestServerActiveObjectMgr : public TestBase +{ +public: + TestServerActiveObjectMgr() { TestManager::registerTestModule(this); } + const char *getName() { return "TestServerActiveObjectMgr"; } + + void runTests(IGameDef *gamedef); + + void testFreeID(); + void testRegisterObject(); + void testRemoveObject(); + void testGetObjectsInsideRadius(); + void testGetAddedActiveObjectsAroundPos(); +}; + +static TestServerActiveObjectMgr g_test_instance; + +void TestServerActiveObjectMgr::runTests(IGameDef *gamedef) +{ + TEST(testFreeID); + TEST(testRegisterObject) + TEST(testRemoveObject) + TEST(testGetObjectsInsideRadius); + TEST(testGetAddedActiveObjectsAroundPos); +} + +void clearSAOMgr(server::ActiveObjectMgr *saomgr) +{ + auto clear_cb = [](ServerActiveObject *obj, u16 id) { + delete obj; + return true; + }; + saomgr->clear(clear_cb); +} + +//////////////////////////////////////////////////////////////////////////////// + +void TestServerActiveObjectMgr::testFreeID() +{ + server::ActiveObjectMgr saomgr; + std::vector aoids; + + u16 aoid = saomgr.getFreeId(); + // Ensure it's not the same id + UASSERT(saomgr.getFreeId() != aoid); + + aoids.push_back(aoid); + + // Register basic objects, ensure we never found + for (u8 i = 0; i < UINT8_MAX; i++) { + // Register an object + auto tsao = new TestServerActiveObject(); + saomgr.registerObject(tsao); + aoids.push_back(tsao->getId()); + + // Ensure next id is not in registered list + UASSERT(std::find(aoids.begin(), aoids.end(), saomgr.getFreeId()) == + aoids.end()); + } + + clearSAOMgr(&saomgr); +} + +void TestServerActiveObjectMgr::testRegisterObject() +{ + server::ActiveObjectMgr saomgr; + auto tsao = new TestServerActiveObject(); + UASSERT(saomgr.registerObject(tsao)); + + u16 id = tsao->getId(); + + auto tsaoToCompare = saomgr.getActiveObject(id); + UASSERT(tsaoToCompare->getId() == id); + UASSERT(tsaoToCompare == tsao); + + tsao = new TestServerActiveObject(); + UASSERT(saomgr.registerObject(tsao)); + UASSERT(saomgr.getActiveObject(tsao->getId()) == tsao); + UASSERT(saomgr.getActiveObject(tsao->getId()) != tsaoToCompare); + + clearSAOMgr(&saomgr); +} + +void TestServerActiveObjectMgr::testRemoveObject() +{ + server::ActiveObjectMgr saomgr; + auto tsao = new TestServerActiveObject(); + UASSERT(saomgr.registerObject(tsao)); + + u16 id = tsao->getId(); + UASSERT(saomgr.getActiveObject(id) != nullptr) + + saomgr.removeObject(tsao->getId()); + UASSERT(saomgr.getActiveObject(id) == nullptr); + + clearSAOMgr(&saomgr); +} + +void TestServerActiveObjectMgr::testGetObjectsInsideRadius() +{ + server::ActiveObjectMgr saomgr; + static const v3f sao_pos[] = { + v3f(10, 40, 10), + v3f(740, 100, -304), + v3f(-200, 100, -304), + v3f(740, -740, -304), + v3f(1500, -740, -304), + }; + + for (const auto &p : sao_pos) { + saomgr.registerObject(new TestServerActiveObject(p)); + } + + std::vector result; + saomgr.getObjectsInsideRadius(v3f(), 50, result); + UASSERTCMP(int, ==, result.size(), 1); + + result.clear(); + saomgr.getObjectsInsideRadius(v3f(), 750, result); + UASSERTCMP(int, ==, result.size(), 2); + + clearSAOMgr(&saomgr); +} + +void TestServerActiveObjectMgr::testGetAddedActiveObjectsAroundPos() +{ + server::ActiveObjectMgr saomgr; + static const v3f sao_pos[] = { + v3f(10, 40, 10), + v3f(740, 100, -304), + v3f(-200, 100, -304), + v3f(740, -740, -304), + v3f(1500, -740, -304), + }; + + for (const auto &p : sao_pos) { + saomgr.registerObject(new TestServerActiveObject(p)); + } + + std::queue result; + std::set cur_objects; + saomgr.getAddedActiveObjectsAroundPos(v3f(), 100, 50, cur_objects, result); + UASSERTCMP(int, ==, result.size(), 1); + + result = std::queue(); + cur_objects.clear(); + saomgr.getAddedActiveObjectsAroundPos(v3f(), 740, 50, cur_objects, result); + UASSERTCMP(int, ==, result.size(), 2); + + clearSAOMgr(&saomgr); +} diff --git a/src/unittest/test_utilities.cpp b/src/unittest/test_utilities.cpp index 112b53f0b..234f622d5 100644 --- a/src/unittest/test_utilities.cpp +++ b/src/unittest/test_utilities.cpp @@ -171,6 +171,8 @@ void TestUtilities::testWrapDegrees_0_360_v3f() void TestUtilities::testLowercase() { UASSERT(lowercase("Foo bAR") == "foo bar"); + UASSERT(lowercase("eeeeeeaaaaaaaaaaaààààà") == "eeeeeeaaaaaaaaaaaààààà"); + UASSERT(lowercase("MINETEST-powa") == "minetest-powa"); }