diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index df3b83d86..83cb6ea4d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -788,22 +788,25 @@ if (USE_DEBUG_HELPERS) endif () set(FMcommon_SRCS ${FMcommon_SRCS} - circuit.cpp - circuit_element.cpp circuit_element_virtual.cpp - key_value_storage.cpp + circuit_element.cpp + circuit.cpp + fm_abm_world.cpp fm_bitset.cpp + fm_liquid.cpp + fm_map.cpp + fm_server.cpp + fm_world_merge.cpp + key_value_storage.cpp log_types.cpp profiler.cpp stat.cpp - fm_liquid.cpp - fm_map.cpp + content_abm_grow_tree.cpp + content_abm.cpp fm_abm.cpp fm_clientiface.cpp - content_abm.cpp - content_abm_grow_tree.cpp fm_serverenvironment.cpp -) + ) set(common_SRCS diff --git a/src/fm_abm_world.cpp b/src/fm_abm_world.cpp new file mode 100644 index 000000000..d8ca93fd6 --- /dev/null +++ b/src/fm_abm_world.cpp @@ -0,0 +1,274 @@ +/* +Copyright (C) 2024 proller +*/ + +/* +This file is part of Freeminer. + +Freeminer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Freeminer 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Freeminer. If not, see . +*/ + +#include "database/database.h" +#include "debug.h" +#include "fm_server.h" +#include "profiler.h" +#include "settings.h" +#include "server.h" +#include "util/directiontables.h" + +AbmWorldThread::AbmWorldThread(Server *server) : + thread_vector("AbmWorld", 20), m_server(server) +{ +} + +void *AbmWorldThread::run() +{ + BEGIN_DEBUG_EXCEPTION_HANDLER + + { + u64 abm_world = 0; + g_settings->getU64NoEx("abm_world", abm_world); + if (!abm_world) + return nullptr; + } + + int16_t abm_world_load_all = -1; // -1 : auto; 0 : disable; 1 : force + g_settings->getS16NoEx("abm_world_load_all", abm_world_load_all); + u64 abm_world_throttle = m_server->isSingleplayer() ? 10 : 0; + g_settings->getU64NoEx("abm_world_throttle", abm_world_throttle); + u64 abm_world_max_clients = m_server->isSingleplayer() ? 1 : 0; + g_settings->getU64NoEx("abm_world_max_clients", abm_world_max_clients); + u64 abm_world_max_blocks = m_server->isSingleplayer() ? 2000 : 10000; + g_settings->getU64NoEx("abm_world_max_blocks", abm_world_max_blocks); + + auto &abm_world_last = m_server->getEnv().abm_world_last; + + const auto can_work = [&]() { + return (m_server->getEnv().getPlayerCount() <= abm_world_max_clients && + m_server->getMap().m_blocks.size() <= abm_world_max_blocks); + }; + + int32_t run = 0; + size_t pos_dir; // random start + + while (!stopRequested()) { + ++run; + + if (!can_work()) { + tracestream << "Abm world wait" << '\n'; + sleep(10); + continue; + } + + std::vector loadable_blocks; + + auto time_start = porting::getTimeMs(); + + if (abm_world_load_all <= 0) { +// Yes, very bad +#if USE_LEVELDB + if (const auto it = + m_server->getEnv().blocks_with_abm.database.new_iterator(); + it) { + for (it->SeekToFirst(); it->Valid(); it->Next()) { + const auto &key = it->key().ToString(); + if (key.starts_with("a")) { + const v3bpos_t pos = MapDatabase::getStringAsBlock(key); + loadable_blocks.emplace_back(pos); + } + } + } +#endif + } + + // Load whole world firts time, fill blocks_with_abm + if (abm_world_load_all && loadable_blocks.empty()) { + actionstream << "Abm world full load" << '\n'; + m_server->getEnv().getServerMap().listAllLoadableBlocks(loadable_blocks); + } + + std::map>> volume; + + size_t cur_n = 0; + + const auto loadable_blocks_size = loadable_blocks.size(); + infostream << "Abm world run " << run << " blocks " << loadable_blocks_size + << " per " << (porting::getTimeMs() - time_start) / 1000 << "s from " + << abm_world_last << " max_clients " << abm_world_max_clients + << " throttle " << abm_world_throttle << " vxs " << volume.size() + << '\n'; + size_t processed = 0, triggers_total = 0; + + time_start = porting::getTimeMs(); + + const auto printstat = [&]() { + auto time = porting::getTimeMs(); + + infostream << "Abm world run " << run << " " << cur_n << "/" + << loadable_blocks_size << " blocks loaded " + << m_server->getMap().m_blocks.size() << " processed " << processed + << " triggers " << triggers_total << " per " + << (time - time_start) / 1000 << " speed " + << processed / (((time - time_start) / 1000) ?: 1) << " vxs " + << volume.size() << '\n'; + }; + +#if 1 + for (const auto &pos : loadable_blocks) { + volume[pos.X][pos.Y].emplace(pos.Z); + } + + const auto contains = [&](const v3bpos_t &pos) -> bool { + if (!volume.contains(pos.X)) + return false; + if (!volume[pos.X].contains(pos.Y)) + return false; + return volume[pos.X][pos.Y].contains(pos.Z); + }; + + const auto erase = [&](const v3bpos_t &pos) { + if (!volume.contains(pos.X)) + return; + if (!volume[pos.X].contains(pos.Y)) + return; + if (!volume[pos.X][pos.Y].contains(pos.Z)) + return; + volume[pos.X][pos.Y].erase(pos.Z); + if (volume[pos.X][pos.Y].empty()) + volume[pos.X].erase(pos.Y); + if (volume[pos.X].empty()) + volume.erase(pos.X); + }; + + std::optional pos_opt; + while (!volume.empty()) { + if (pos_opt.has_value()) { + const auto pos_old = pos_opt.value(); + pos_opt.reset(); + // Random better + for (size_t dirs = 0; dirs < 6; ++dirs, ++pos_dir) { + const auto pos_new = pos_old + g_6dirs[pos_dir % sizeof(g_6dirs)]; + //DUMP(dirs, pos_new, pos_dir); + if (contains(pos_new)) { + //DUMP("ok", dirs, pos_opt, "->", pos_new); + pos_opt = pos_new; + break; + } + } + } + + if (!pos_opt.has_value()) { + // always first: pos_opt = {volume.begin()->first,volume.begin()->second.begin()->first,*volume.begin()->second.begin()->second.begin()}; + + auto xend = volume.end(); + --xend; + const auto xi = pos_dir & 1 ? volume.begin() : xend; + auto yend = xi->second.end(); + --yend; + const auto yi = pos_dir & 2 ? xi->second.begin() : yend; + auto zend = yi->second.end(); + --zend; + const auto zi = pos_dir & 4 ? yi->second.begin() : zend; + pos_opt = {xi->first, yi->first, *zi}; + } + const auto pos = pos_opt.value(); + erase(pos); + ++cur_n; + +#else + cur_n = 0; + for (const auto &pos : loadable_blocks) { + ++cur_n; + + if (cur_n < abm_world_last) { + continue; + } + abm_world_last = cur_n; +#endif + + if (stopRequested()) { + return nullptr; + } + try { + const auto load_block = [&](const v3bpos_t &pos) -> MapBlockP { + auto block = m_server->getEnv().getServerMap().getBlock(pos); + if (block) { + return block; + } + block.reset(m_server->getEnv().getServerMap().emergeBlock(pos)); + if (!block) { + return nullptr; + } + if (!block->isGenerated()) { + return nullptr; + } + return block; + }; + + auto block = load_block(pos); + if (!block) { + continue; + } + + // Load neighbours for better liquids flows + for (const auto &dir : g_6dirs) { + load_block(pos + dir); + } + + g_profiler->add("Server: Abm world blocks", 1); + + ++processed; + + //m_server->getEnv().activateBlock(block); + + const auto activate = (1 << 2) | m_server->getEnv().analyzeBlock(block); + const auto triggers = m_server->getEnv().blockStep(block, 0, activate); + triggers_total += triggers; + + //DUMP("ok", pos, cur_n, m_server->getMap().m_blocks.size(), block->getTimestamp(), block->getActualTimestamp(), m_server->getEnv().getGameTime(), triggers); + + if (!(cur_n % 10000)) { + printstat(); + } + + if (!can_work()) { + tracestream << "Abm world throttle" << '\n'; + + std::this_thread::sleep_for(std::chrono::seconds(1)); + } else if (abm_world_throttle) { + std::this_thread::sleep_for( + std::chrono::milliseconds(abm_world_throttle)); + } + +#if !EXCEPTION_DEBUG + } catch (const std::exception &e) { + errorstream << m_name << ": exception: " << e.what() << "\n" + << stacktrace() << '\n'; + } catch (...) { + errorstream << m_name << ": Unknown unhandled exception at " + << __PRETTY_FUNCTION__ << ":" << __LINE__ << '\n' + << stacktrace() << '\n'; +#else + } catch (int) { // nothing +#endif + } + } + printstat(); + abm_world_last = 0; + + sleep(60); + } + END_DEBUG_EXCEPTION_HANDLER + return nullptr; +} diff --git a/src/fm_server.cpp b/src/fm_server.cpp index d929f730f..33cbbb4ee 100644 --- a/src/fm_server.cpp +++ b/src/fm_server.cpp @@ -19,6 +19,8 @@ You should have received a copy of the GNU General Public License along with Freeminer. If not, see . */ +#include "fm_server.h" + #include #include #include @@ -39,16 +41,9 @@ along with Freeminer. If not, see . #include "util/directiontables.h" #include "util/timetaker.h" -class ServerThread : public thread_vector +ServerThread::ServerThread(Server *server) : thread_vector("Server", 40), m_server(server) { -public: - ServerThread(Server *server) : thread_vector("Server", 40), m_server(server) {} - - void *run(); - -private: - Server *m_server; -}; +} void *ServerThread::run() { @@ -140,459 +135,184 @@ void *ServerThread::run() return NULL; } - -class MapThread : public thread_vector +MapThread::MapThread(Server *server) : thread_vector("Map", 15), m_server(server) { - Server *m_server; - -public: - MapThread(Server *server) : thread_vector("Map", 15), m_server(server) {} +} - void *run() - { - auto time = porting::getTimeMs(); - while (!stopRequested()) { - auto time_now = porting::getTimeMs(); - try { - m_server->getEnv().getMap().getBlockCacheFlush(); - if (!m_server->AsyncRunMapStep((time_now - time) / 1000.0f, 1)) - std::this_thread::sleep_for(std::chrono::milliseconds(200)); - else - std::this_thread::sleep_for(std::chrono::milliseconds(10)); +void *MapThread::run() +{ + auto time = porting::getTimeMs(); + while (!stopRequested()) { + auto time_now = porting::getTimeMs(); + try { + m_server->getEnv().getMap().getBlockCacheFlush(); + if (!m_server->AsyncRunMapStep((time_now - time) / 1000.0f, 1)) + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + else + std::this_thread::sleep_for(std::chrono::milliseconds(10)); #if !EXCEPTION_DEBUG - } catch (const std::exception &e) { - errorstream << m_name << ": exception: " << e.what() << std::endl - << stacktrace() << std::endl; - } catch (...) { - errorstream << m_name << ": Unknown unhandled exception at " - << __PRETTY_FUNCTION__ << ":" << __LINE__ << std::endl - << stacktrace() << std::endl; + } catch (const std::exception &e) { + errorstream << m_name << ": exception: " << e.what() << std::endl + << stacktrace() << std::endl; + } catch (...) { + errorstream << m_name << ": Unknown unhandled exception at " + << __PRETTY_FUNCTION__ << ":" << __LINE__ << std::endl + << stacktrace() << std::endl; #else - } catch (int) { // nothing + } catch (int) { // nothing #endif - } - time = time_now; } - // END_DEBUG_EXCEPTION_HANDLER - return nullptr; + time = time_now; } -}; + // END_DEBUG_EXCEPTION_HANDLER + return nullptr; +} -class SendBlocksThread : public thread_vector +SendBlocksThread::SendBlocksThread(Server *server) : + thread_vector("SendBlocks", 30), m_server(server) { - Server *m_server; +} -public: - SendBlocksThread(Server *server) : thread_vector("SendBlocks", 30), m_server(server) - { - } +void *SendBlocksThread::run() +{ + BEGIN_DEBUG_EXCEPTION_HANDLER - void *run() - { - BEGIN_DEBUG_EXCEPTION_HANDLER - - auto time = porting::getTimeMs(); - while (!stopRequested()) { - // infostream<<"S run d="<m_step_dtime<< " - // myt="<<(porting::getTimeMs() - time)/1000.0f<getEnv().getMap().getBlockCacheFlush(); - auto time_now = porting::getTimeMs(); - auto sent = m_server->SendBlocks((time_now - time) / 1000.0f); - time = time_now; - std::this_thread::sleep_for(std::chrono::milliseconds(sent ? 5 : 100)); + auto time = porting::getTimeMs(); + while (!stopRequested()) { + // infostream<<"S run d="<m_step_dtime<< " + // myt="<<(porting::getTimeMs() - time)/1000.0f<getEnv().getMap().getBlockCacheFlush(); + auto time_now = porting::getTimeMs(); + auto sent = m_server->SendBlocks((time_now - time) / 1000.0f); + time = time_now; + std::this_thread::sleep_for(std::chrono::milliseconds(sent ? 5 : 100)); #if !EXCEPTION_DEBUG - } catch (const std::exception &e) { - errorstream << m_name << ": exception: " << e.what() << std::endl - << stacktrace() << std::endl; - } catch (...) { - errorstream << m_name << ": Unknown unhandled exception at " - << __PRETTY_FUNCTION__ << ":" << __LINE__ << std::endl - << stacktrace() << std::endl; + } catch (const std::exception &e) { + errorstream << m_name << ": exception: " << e.what() << std::endl + << stacktrace() << std::endl; + } catch (...) { + errorstream << m_name << ": Unknown unhandled exception at " + << __PRETTY_FUNCTION__ << ":" << __LINE__ << std::endl + << stacktrace() << std::endl; #else - } catch (int) { // nothing + } catch (int) { // nothing #endif - } } - END_DEBUG_EXCEPTION_HANDLER - return nullptr; } -}; + END_DEBUG_EXCEPTION_HANDLER + return nullptr; +} -class LiquidThread : public thread_vector +LiquidThread::LiquidThread(Server *server) : thread_vector("Liquid", 4), m_server(server) { - Server *m_server; - -public: - LiquidThread(Server *server) : thread_vector("Liquid", 4), m_server(server) {} - - void *run() - { - BEGIN_DEBUG_EXCEPTION_HANDLER - - unsigned int max_cycle_ms = 1000; - while (!stopRequested()) { - try { - m_server->getEnv().getMap().getBlockCacheFlush(); - const auto time_start = porting::getTimeMs(); - m_server->getEnv().getMap().getBlockCacheFlush(); - std::map modified_blocks; // not used by fm - const auto processed = m_server->getEnv().getServerMap().transformLiquids( - modified_blocks, &m_server->getEnv(), m_server, max_cycle_ms); - const auto time_spend = porting::getTimeMs() - time_start; - - thread_local const auto static liquid_step = - g_settings->getBool("liquid_step"); - const auto sleep = - (processed < 10 ? 100 : 3) + - (time_spend > liquid_step ? 1 : liquid_step - time_spend); - std::this_thread::sleep_for(std::chrono::milliseconds(sleep)); - -#if !EXCEPTION_DEBUG - } catch (const std::exception &e) { - errorstream << m_name << ": exception: " << e.what() << std::endl - << stacktrace() << std::endl; - } catch (...) { - errorstream << m_name << ": Unknown unhandled exception at " - << __PRETTY_FUNCTION__ << ":" << __LINE__ << std::endl - << stacktrace() << std::endl; -#else - } catch (int) { // nothing -#endif - } - } - END_DEBUG_EXCEPTION_HANDLER - return nullptr; - } -}; +} -class EnvThread : public thread_vector +void *LiquidThread::run() { - Server *m_server; + BEGIN_DEBUG_EXCEPTION_HANDLER -public: - EnvThread(Server *server) : thread_vector("Env", 20), m_server(server) {} + unsigned int max_cycle_ms = 1000; + while (!stopRequested()) { + try { + const auto time_start = porting::getTimeMs(); + m_server->getEnv().getMap().getBlockCacheFlush(); + std::map modified_blocks; // not used by fm + const auto processed = m_server->getEnv().getServerMap().transformLiquids( + modified_blocks, &m_server->getEnv(), m_server, max_cycle_ms); + const auto time_spend = porting::getTimeMs() - time_start; + + thread_local const auto static liquid_step = + g_settings->getBool("liquid_step"); + const auto sleep = (processed < 10 ? 100 : 3) + + (time_spend > liquid_step ? 1 : liquid_step - time_spend); + std::this_thread::sleep_for(std::chrono::milliseconds(sleep)); - void *run() - { - unsigned int max_cycle_ms = 1000; - auto time = porting::getTimeMs(); - while (!stopRequested()) { - try { - m_server->getEnv().getMap().getBlockCacheFlush(); - auto ctime = porting::getTimeMs(); - auto dtimems = ctime - time; - time = ctime; - m_server->getEnv().step(dtimems / 1000.0f, - m_server->m_uptime_counter->get(), max_cycle_ms); - std::this_thread::sleep_for( - std::chrono::milliseconds(dtimems > 100 ? 1 : 100 - dtimems)); #if !EXCEPTION_DEBUG - } catch (const std::exception &e) { - errorstream << m_name << ": exception: " << e.what() << std::endl - << stacktrace() << std::endl; - } catch (...) { - errorstream << m_name << ": Unknown unhandled exception at " - << __PRETTY_FUNCTION__ << ":" << __LINE__ << std::endl - << stacktrace() << std::endl; + } catch (const std::exception &e) { + errorstream << m_name << ": exception: " << e.what() << std::endl + << stacktrace() << std::endl; + } catch (...) { + errorstream << m_name << ": Unknown unhandled exception at " + << __PRETTY_FUNCTION__ << ":" << __LINE__ << std::endl + << stacktrace() << std::endl; #else - } catch (int) { // nothing + } catch (int) { // nothing #endif - } } - return nullptr; } -}; + END_DEBUG_EXCEPTION_HANDLER + return nullptr; +} -class AbmThread : public thread_vector +EnvThread::EnvThread(Server *server) : thread_vector("Env", 20), m_server(server) { - Server *m_server; - -public: - AbmThread(Server *server) : thread_vector("Abm", 20), m_server(server) {} +} - void *run() - { - BEGIN_DEBUG_EXCEPTION_HANDLER - - unsigned int max_cycle_ms = 10000; - auto time = porting::getTimeMs(); - while (!stopRequested()) { - try { - auto ctime = porting::getTimeMs(); - auto dtimems = ctime - time; - time = ctime; - m_server->getEnv().analyzeBlocks(dtimems / 1000.0f, max_cycle_ms); - std::this_thread::sleep_for( - std::chrono::milliseconds(dtimems > 1000 ? 100 : 1000 - dtimems)); +void *EnvThread::run() +{ + unsigned int max_cycle_ms = 1000; + auto time = porting::getTimeMs(); + while (!stopRequested()) { + try { + m_server->getEnv().getMap().getBlockCacheFlush(); + auto ctime = porting::getTimeMs(); + auto dtimems = ctime - time; + time = ctime; + m_server->getEnv().step( + dtimems / 1000.0f, m_server->m_uptime_counter->get(), max_cycle_ms); + std::this_thread::sleep_for( + std::chrono::milliseconds(dtimems > 100 ? 1 : 100 - dtimems)); #if !EXCEPTION_DEBUG - } catch (const std::exception &e) { - errorstream << m_name << ": exception: " << e.what() << '\n' - << stacktrace() << '\n'; - } catch (...) { - errorstream << m_name << ": Unknown unhandled exception at " - << __PRETTY_FUNCTION__ << ":" << __LINE__ << std::endl - << stacktrace() << '\n'; + } catch (const std::exception &e) { + errorstream << m_name << ": exception: " << e.what() << std::endl + << stacktrace() << std::endl; + } catch (...) { + errorstream << m_name << ": Unknown unhandled exception at " + << __PRETTY_FUNCTION__ << ":" << __LINE__ << std::endl + << stacktrace() << std::endl; #else - } catch (int) { // nothing + } catch (int) { // nothing #endif - } } - END_DEBUG_EXCEPTION_HANDLER - return nullptr; } -}; + return nullptr; +} -class AbmWorldThread : public thread_vector +AbmThread::AbmThread(Server *server) : thread_vector("Abm", 20), m_server(server) { - Server *m_server; - -public: - AbmWorldThread(Server *server) : thread_vector("AbmWorld", 20), m_server(server) {} - - void *run() - { - BEGIN_DEBUG_EXCEPTION_HANDLER - - { - u64 abm_world = 0; - g_settings->getU64NoEx("abm_world", abm_world); - if (!abm_world) - return nullptr; - } - - int16_t abm_world_load_all = -1; // -1 : auto; 0 : disable; 1 : force - g_settings->getS16NoEx("abm_world_load_all", abm_world_load_all); - u64 abm_world_throttle = m_server->isSingleplayer() ? 10 : 0; - g_settings->getU64NoEx("abm_world_throttle", abm_world_throttle); - u64 abm_world_max_clients = m_server->isSingleplayer() ? 1 : 0; - g_settings->getU64NoEx("abm_world_max_clients", abm_world_max_clients); - u64 abm_world_max_blocks = m_server->isSingleplayer() ? 2000 : 10000; - g_settings->getU64NoEx("abm_world_max_blocks", abm_world_max_blocks); - - auto &abm_world_last = m_server->getEnv().abm_world_last; - - auto can_work = [&]() { - return (m_server->getEnv().getPlayerCount() <= abm_world_max_clients && - m_server->getMap().m_blocks.size() <= abm_world_max_blocks); - }; - - int32_t run = 0; - size_t pos_dir; // random start - - while (!stopRequested()) { - ++run; - - if (!can_work()) { - tracestream << "Abm world wait" << '\n'; - sleep(10); - continue; - } - - std::vector loadable_blocks; - - auto time_start = porting::getTimeMs(); - - if (abm_world_load_all <= 0) { -// Yes, very bad -#if USE_LEVELDB - if (const auto it = m_server->getEnv() - .blocks_with_abm.database.new_iterator(); - it) { - for (it->SeekToFirst(); it->Valid(); it->Next()) { - const auto &key = it->key().ToString(); - if (key.starts_with("a")) { - const v3bpos_t pos = MapDatabase::getStringAsBlock(key); - loadable_blocks.emplace_back(pos); - } - } - } -#endif - } - - // Load whole world firts time, fill blocks_with_abm - if (abm_world_load_all && loadable_blocks.empty()) { - actionstream << "Abm world full load" << '\n'; - m_server->getEnv().getServerMap().listAllLoadableBlocks(loadable_blocks); - } - - std::map>> volume; - - size_t cur_n = 0; - - const auto loadable_blocks_size = loadable_blocks.size(); - infostream << "Abm world run " << run << " blocks " << loadable_blocks_size - << " per " << (porting::getTimeMs() - time_start) / 1000 - << "s from " << abm_world_last << " max_clients " - << abm_world_max_clients << " throttle " << abm_world_throttle - << " vxs " << volume.size() << '\n'; - size_t processed = 0, triggers_total = 0; - - time_start = porting::getTimeMs(); - - const auto printstat = [&]() { - auto time = porting::getTimeMs(); - - infostream << "Abm world run " << run << " " << cur_n << "/" - << loadable_blocks_size << " blocks loaded " - << m_server->getMap().m_blocks.size() << " processed " - << processed << " triggers " << triggers_total << " per " - << (time - time_start) / 1000 << " speed " - << processed / (((time - time_start) / 1000) ?: 1) << " vxs " - << volume.size() << '\n'; - }; - -#if 1 - for (const auto &pos : loadable_blocks) { - volume[pos.X][pos.Y].emplace(pos.Z); - } - - const auto contains = [&](const v3bpos_t &pos) -> bool { - if (!volume.contains(pos.X)) - return false; - if (!volume[pos.X].contains(pos.Y)) - return false; - return volume[pos.X][pos.Y].contains(pos.Z); - }; - - const auto erase = [&](const v3bpos_t &pos) { - if (!volume.contains(pos.X)) - return; - if (!volume[pos.X].contains(pos.Y)) - return; - if (!volume[pos.X][pos.Y].contains(pos.Z)) - return; - volume[pos.X][pos.Y].erase(pos.Z); - if (volume[pos.X][pos.Y].empty()) - volume[pos.X].erase(pos.Y); - if (volume[pos.X].empty()) - volume.erase(pos.X); - }; - - std::optional pos_opt; - while (!volume.empty()) { - if (pos_opt.has_value()) { - const auto pos_old = pos_opt.value(); - pos_opt.reset(); - // Random better - for (size_t dirs = 0; dirs < 6; ++dirs, ++pos_dir) { - const auto pos_new = pos_old + g_6dirs[pos_dir % sizeof(g_6dirs)]; - //DUMP(dirs, pos_new, pos_dir); - if (contains(pos_new)) { - //DUMP("ok", dirs, pos_opt, "->", pos_new); - pos_opt = pos_new; - break; - } - } - } - - if (!pos_opt.has_value()) { - // always first: pos_opt = {volume.begin()->first,volume.begin()->second.begin()->first,*volume.begin()->second.begin()->second.begin()}; - - auto xend = volume.end(); - --xend; - const auto xi = pos_dir & 1 ? volume.begin() : xend; - auto yend = xi->second.end(); - --yend; - const auto yi = pos_dir & 2 ? xi->second.begin() : yend; - auto zend = yi->second.end(); - --zend; - const auto zi = pos_dir & 4 ? yi->second.begin() : zend; - pos_opt = {xi->first, yi->first, *zi}; - } - const auto pos = pos_opt.value(); - erase(pos); - ++cur_n; - -#else - cur_n = 0; - for (const auto &pos : loadable_blocks) { - ++cur_n; - - if (cur_n < abm_world_last) { - continue; - } - abm_world_last = cur_n; -#endif +} - if (stopRequested()) { - return nullptr; - } - try { - const auto load_block = [&](const v3bpos_t &pos) -> MapBlockP { - auto block = m_server->getEnv().getServerMap().getBlock(pos); - if (block) { - return block; - } - block.reset(m_server->getEnv().getServerMap().emergeBlock(pos)); - if (!block) { - return nullptr; - } - if (!block->isGenerated()) { - return nullptr; - } - return block; - }; - - auto block = load_block(pos); - if (!block) { - continue; - } - - // Load neighbours for better liquids flows - for (const auto &dir : g_6dirs) { - load_block(pos + dir); - } - - g_profiler->add("Server: Abm world blocks", 1); - - ++processed; - - //m_server->getEnv().activateBlock(block); - - const auto activate = - (1 << 2) | m_server->getEnv().analyzeBlock(block); - const auto triggers = - m_server->getEnv().blockStep(block, 0, activate); - triggers_total += triggers; - - //DUMP("ok", pos, cur_n, m_server->getMap().m_blocks.size(), block->getTimestamp(), block->getActualTimestamp(), m_server->getEnv().getGameTime(), triggers); - - if (!(cur_n % 10000)) { - printstat(); - } - - if (!can_work()) { - tracestream << "Abm world throttle" << '\n'; - - std::this_thread::sleep_for(std::chrono::seconds(1)); - } else if (abm_world_throttle) { - std::this_thread::sleep_for( - std::chrono::milliseconds(abm_world_throttle)); - } +void *AbmThread::run() +{ + BEGIN_DEBUG_EXCEPTION_HANDLER + unsigned int max_cycle_ms = 10000; + auto time = porting::getTimeMs(); + while (!stopRequested()) { + try { + auto ctime = porting::getTimeMs(); + auto dtimems = ctime - time; + time = ctime; + m_server->getEnv().analyzeBlocks(dtimems / 1000.0f, max_cycle_ms); + std::this_thread::sleep_for( + std::chrono::milliseconds(dtimems > 1000 ? 100 : 1000 - dtimems)); #if !EXCEPTION_DEBUG - } catch (const std::exception &e) { - errorstream << m_name << ": exception: " << e.what() << "\n" - << stacktrace() << '\n'; - } catch (...) { - errorstream << m_name << ": Unknown unhandled exception at " - << __PRETTY_FUNCTION__ << ":" << __LINE__ << '\n' - << stacktrace() << '\n'; + } catch (const std::exception &e) { + errorstream << m_name << ": exception: " << e.what() << '\n' + << stacktrace() << '\n'; + } catch (...) { + errorstream << m_name << ": Unknown unhandled exception at " + << __PRETTY_FUNCTION__ << ":" << __LINE__ << std::endl + << stacktrace() << '\n'; #else - } catch (int) { // nothing + } catch (int) { // nothing #endif - } - } - printstat(); - abm_world_last = 0; - - sleep(60); } - END_DEBUG_EXCEPTION_HANDLER - return nullptr; } -}; + END_DEBUG_EXCEPTION_HANDLER + return nullptr; +} int Server::AsyncRunMapStep(float dtime, float dedicated_server_step, bool async) { diff --git a/src/fm_server.h b/src/fm_server.h new file mode 100644 index 000000000..f178a3aae --- /dev/null +++ b/src/fm_server.h @@ -0,0 +1,107 @@ +/* +Copyright (C) 2024 proller +*/ + +/* +This file is part of Freeminer. + +Freeminer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Freeminer 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Freeminer. If not, see . +*/ + +#pragma once + +#include "threading/thread_vector.h" + +class Server; + +class ServerThread : public thread_vector +{ +public: + ServerThread(Server *server); + + void *run(); + +private: + Server *m_server; +}; + +class MapThread : public thread_vector +{ + Server *m_server; + +public: + MapThread(Server *server); + + void *run(); +}; + +class SendBlocksThread : public thread_vector +{ + Server *m_server; + +public: + SendBlocksThread(Server *server); + + void *run(); +}; + +class LiquidThread : public thread_vector +{ + Server *m_server; + +public: + LiquidThread(Server *server); + + void *run(); +}; + +class EnvThread : public thread_vector +{ + Server *m_server; + +public: + EnvThread(Server *server); + + void *run(); +}; + +class AbmThread : public thread_vector +{ + Server *m_server; + +public: + AbmThread(Server *server); + + void *run(); +}; + +class AbmWorldThread : public thread_vector +{ + Server *m_server; + +public: + AbmWorldThread(Server *server); + + void *run(); +}; + +class WorldMergeThread : public thread_vector +{ + Server *m_server; + +public: + WorldMergeThread(Server *server); + + void *run(); +}; diff --git a/src/fm_world_merge.cpp b/src/fm_world_merge.cpp new file mode 100644 index 000000000..9d90798ad --- /dev/null +++ b/src/fm_world_merge.cpp @@ -0,0 +1,273 @@ +/* +Copyright (C) 2024 proller +*/ + +/* +This file is part of Freeminer. + +Freeminer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Freeminer 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Freeminer. If not, see . +*/ + +#include "database/database.h" +#include "debug.h" +#include "fm_server.h" +#include "profiler.h" +#include "server.h" +#include "settings.h" +#include "util/directiontables.h" + +WorldMergeThread::WorldMergeThread(Server *server) : + thread_vector("WorldMerge", 20), m_server(server) +{ +} + +void *WorldMergeThread::run() +{ + BEGIN_DEBUG_EXCEPTION_HANDLER + + { + u64 world_merge = 0; + g_settings->getU64NoEx("world_merge", world_merge); + if (!world_merge) + return nullptr; + } + + int16_t world_merge_load_all = -1; // -1 : auto; 0 : disable; 1 : force + g_settings->getS16NoEx("world_merge_load_all", world_merge_load_all); + u64 abm_world_throttle = m_server->isSingleplayer() ? 10 : 0; + g_settings->getU64NoEx("world_merge_throttle", abm_world_throttle); + u64 abm_world_max_clients = m_server->isSingleplayer() ? 1 : 0; + g_settings->getU64NoEx("world_merge_max_clients", abm_world_max_clients); + // u64 abm_world_max_blocks = m_server->isSingleplayer() ? 2000 : 10000; + // g_settings->getU64NoEx("abm_world_max_blocks", abm_world_max_blocks); + + auto &world_merge_last = m_server->getEnv().world_merge_last; + const auto can_work = [&]() { + return (m_server->getEnv().getPlayerCount() <= abm_world_max_clients + //&& m_server->getMap().m_blocks.size() <= abm_world_max_blocks + ); + }; + + int32_t run = 0; + size_t pos_dir; // random start + + while (!stopRequested()) { + ++run; + + if (!can_work()) { + tracestream << "Abm world wait" << '\n'; + sleep(10); + continue; + } + + std::vector loadable_blocks; + + auto time_start = porting::getTimeMs(); + + if (world_merge_load_all <= 0) { +// Yes, very bad +#if USE_LEVELDB + if (const auto it = + m_server->getEnv().blocks_with_abm.database.new_iterator(); + it) { + for (it->SeekToFirst(); it->Valid(); it->Next()) { + const auto &key = it->key().ToString(); + if (key.starts_with("a")) { + const v3bpos_t pos = MapDatabase::getStringAsBlock(key); + loadable_blocks.emplace_back(pos); + } + } + } +#endif + } + + // Load whole world firts time, fill blocks_with_abm + if (world_merge_load_all && loadable_blocks.empty()) { + actionstream << "Abm world full load" << '\n'; + m_server->getEnv().getServerMap().listAllLoadableBlocks(loadable_blocks); + } + + std::map>> volume; + + size_t cur_n = 0; + + const auto loadable_blocks_size = loadable_blocks.size(); + infostream << "Abm world run " << run << " blocks " << loadable_blocks_size + << " per " << (porting::getTimeMs() - time_start) / 1000 << "s from " + << world_merge_last << " max_clients " << abm_world_max_clients + << " throttle " << abm_world_throttle << " vxs " << volume.size() + << '\n'; + size_t processed = 0; + + time_start = porting::getTimeMs(); + + const auto printstat = [&]() { + auto time = porting::getTimeMs(); + + infostream << "Abm world run " << run << " " << cur_n << "/" + << loadable_blocks_size << " blocks loaded " + << m_server->getMap().m_blocks.size() << " processed " + << processed + //<< " triggers " << triggers_total + << " per " << (time - time_start) / 1000 << " speed " + << processed / (((time - time_start) / 1000) ?: 1) << " vxs " + << volume.size() << '\n'; + }; + +#if 1 + for (const auto &pos : loadable_blocks) { + volume[pos.X][pos.Y].emplace(pos.Z); + } + + const auto contains = [&](const v3bpos_t &pos) -> bool { + if (!volume.contains(pos.X)) + return false; + if (!volume[pos.X].contains(pos.Y)) + return false; + return volume[pos.X][pos.Y].contains(pos.Z); + }; + + const auto erase = [&](const v3bpos_t &pos) { + if (!volume.contains(pos.X)) + return; + if (!volume[pos.X].contains(pos.Y)) + return; + if (!volume[pos.X][pos.Y].contains(pos.Z)) + return; + volume[pos.X][pos.Y].erase(pos.Z); + if (volume[pos.X][pos.Y].empty()) + volume[pos.X].erase(pos.Y); + if (volume[pos.X].empty()) + volume.erase(pos.X); + }; + + std::optional pos_opt; + while (!volume.empty()) { + if (pos_opt.has_value()) { + const auto pos_old = pos_opt.value(); + pos_opt.reset(); + // Random better + for (size_t dirs = 0; dirs < 6; ++dirs, ++pos_dir) { + const auto pos_new = pos_old + g_6dirs[pos_dir % sizeof(g_6dirs)]; + //DUMP(dirs, pos_new, pos_dir); + if (contains(pos_new)) { + //DUMP("ok", dirs, pos_opt, "->", pos_new); + pos_opt = pos_new; + break; + } + } + } + + if (!pos_opt.has_value()) { + // always first: pos_opt = {volume.begin()->first,volume.begin()->second.begin()->first,*volume.begin()->second.begin()->second.begin()}; + + auto xend = volume.end(); + --xend; + const auto xi = pos_dir & 1 ? volume.begin() : xend; + auto yend = xi->second.end(); + --yend; + const auto yi = pos_dir & 2 ? xi->second.begin() : yend; + auto zend = yi->second.end(); + --zend; + const auto zi = pos_dir & 4 ? yi->second.begin() : zend; + pos_opt = {xi->first, yi->first, *zi}; + } + const auto pos = pos_opt.value(); + erase(pos); + ++cur_n; + +#else + cur_n = 0; + for (const auto &pos : loadable_blocks) { + ++cur_n; + + if (cur_n < abm_world_last) { + continue; + } + abm_world_last = cur_n; +#endif + + if (stopRequested()) { + return nullptr; + } + try { + const auto load_block = [&](const v3bpos_t &pos) -> MapBlockP { + auto block = m_server->getEnv().getServerMap().getBlock(pos); + if (block) { + return block; + } + block.reset(m_server->getEnv().getServerMap().emergeBlock(pos)); + if (!block) { + return nullptr; + } + if (!block->isGenerated()) { + return nullptr; + } + return block; + }; + + auto block = load_block(pos); + if (!block) { + continue; + } + + // Load neighbours for better liquids flows + for (const auto &dir : g_6dirs) { + load_block(pos + dir); + } + + g_profiler->add("Server: Abm world blocks", 1); + + ++processed; + + { + // DO + } + + //DUMP("ok", pos, cur_n, m_server->getMap().m_blocks.size(), block->getTimestamp(), block->getActualTimestamp(), m_server->getEnv().getGameTime(), triggers); + + if (!(cur_n % 10000)) { + printstat(); + } + + if (!can_work()) { + tracestream << "Abm world throttle" << '\n'; + + std::this_thread::sleep_for(std::chrono::seconds(1)); + } else if (abm_world_throttle) { + std::this_thread::sleep_for( + std::chrono::milliseconds(abm_world_throttle)); + } + +#if !EXCEPTION_DEBUG + } catch (const std::exception &e) { + errorstream << m_name << ": exception: " << e.what() << "\n" + << stacktrace() << '\n'; + } catch (...) { + errorstream << m_name << ": Unknown unhandled exception at " + << __PRETTY_FUNCTION__ << ":" << __LINE__ << '\n' + << stacktrace() << '\n'; +#else + } catch (int) { // nothing +#endif + } + } + printstat(); + world_merge_last = 0; + + sleep(60); + } + END_DEBUG_EXCEPTION_HANDLER + return nullptr; +} diff --git a/src/server.cpp b/src/server.cpp index 067e418e6..22e760622 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -94,12 +94,11 @@ along with Freeminer. If not, see . #include #include "threading/thread_vector.h" #include "key_value_storage.h" +#include "fm_server.h" #if !MINETEST_PROTO #include "network/fm_serverpacketsender.cpp" #endif -#include "fm_server.cpp" - #if 0 class ServerThread : public Thread @@ -456,6 +455,7 @@ void Server::init() m_env_thread = std::make_unique(this); m_abm_thread = std::make_unique< AbmThread>(this); m_abm_world_thread = std::make_unique(this); + m_world_merge_thread = std::make_unique(this); } // Create world if it doesn't exist diff --git a/src/server.h b/src/server.h index ec25e8138..a25f98f0b 100644 --- a/src/server.h +++ b/src/server.h @@ -60,6 +60,7 @@ class LiquidThread; class EnvThread; class AbmThread; class AbmWorldThread; +class WorldMergeThread; class ClientNotFoundException : public BaseException @@ -784,6 +785,7 @@ class Server : public con::PeerHandler, public MapEventReceiver, std::unique_ptr m_env_thread; std::unique_ptr m_abm_thread; std::unique_ptr m_abm_world_thread; + std::unique_ptr m_world_merge_thread; // CSM restrictions byteflag u64 m_csm_restriction_flags = CSMRestrictionFlags::CSM_RF_NONE; diff --git a/src/serverenvironment.h b/src/serverenvironment.h index 35290fc15..09714e714 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -498,6 +498,7 @@ class ServerEnvironment final : public Environment public: KeyValueCached blocks_with_abm; size_t abm_world_last = 0; + size_t world_merge_last = 0; //end of freeminer