diff --git a/CHANGELOG.md b/CHANGELOG.md index d34f344af..b200baf20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Changelog * Farmesh alpha * Multi protocol network: Auto use enet for freeminer servers and mt for minetest * Run abm's in whole world: to enable: abm_world=1 + * Mapgen Earth (whole terrain 1:1 with seabeds) ### 5.7.0.0 (?) * Tree growth diff --git a/build_tools/build.sh b/build_tools/build.sh index b58be5484..03ebfdf4c 100755 --- a/build_tools/build.sh +++ b/build_tools/build.sh @@ -20,7 +20,7 @@ if [ -z "$NO_DEPS" ]; then sudo apt install -y $PACKAGE ||: done elif [ -e /etc/arch-release ]; then - sudo pacman --needed --noconfirm -S git subversion cmake ninja ccache bzip2 zstd libjpeg-turbo freetype2 libxxf86vm libxi sqlite3 hiredis libvorbis openal curl luajit gettext msgpack-cxx boost clang lld llvm libc++ libc++abi libpng12 libpng libunwind + sudo pacman --needed --noconfirm -S git subversion cmake ninja ccache bzip2 zstd libjpeg-turbo freetype2 glfw-x11 libxxf86vm libxi sqlite3 hiredis libvorbis openal curl luajit gettext msgpack-cxx boost clang lld llvm libc++ libc++abi libpng12 libpng libunwind echo Todo fi fi diff --git a/src/client/client.cpp b/src/client/client.cpp index 5ee54a07b..cda03da66 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -135,6 +135,7 @@ Client::Client( ELoginRegister allow_login_or_register ): far_container{this}, + mesh_thread_pool(rangelim(rangelim(g_settings->getS32("mesh_generation_threads"), 0, 8) ?: Thread::getNumberOfProcessors()/2, 2,8)), m_simple_singleplayer_mode(is_simple_singleplayer_game), m_tsrc(tsrc), @@ -358,11 +359,12 @@ void Client::Stop() m_localdb->endSave(); } + merger.reset(); // before m_localdb + mesh_thread_pool.wait_until_empty(); + if (m_mods_loaded) delete m_script; - merger.reset(); // before m_localdb - if (m_localdb) delete m_localdb; } @@ -417,7 +419,6 @@ Client::~Client() for (auto &csp : m_sounds_client_to_server) m_sound->freeId(csp.first); m_sounds_client_to_server.clear(); - last_async.wait(); } void Client::connect(Address address, bool is_local_server) @@ -1071,7 +1072,7 @@ void Client::initLocalMapSaving(const Address &address, if (!m_simple_singleplayer_mode) { far_dbases[0].reset(m_localdb, [](auto) {}); if (!merger) { - merger = std::make_unique(WorldMerger{ + merger.reset(new WorldMerger{ .get_time_func{[this]() { return m_uptime.load(std::memory_order::relaxed); }}, // find client game time == server time? diff --git a/src/client/client.h b/src/client/client.h index 79f073155..e711b79a2 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -23,13 +23,13 @@ along with Freeminer. If not, see . #pragma once // fm: -#include #include "client/fm_far_container.h" #include "map.h" #include "map_settings_manager.h" #include "mapgen/mapgen.h" #include "msgpack_fix.h" #include "network/fm_connection_use.h" +#include "threading/ThreadPool.h" constexpr const auto FARMESH_DEFAULT_MAPGEN = MAPGEN_FLAT; // == @@ -167,7 +167,7 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef FarContainer far_container; ServerMap::far_dbases_t far_dbases; std::unique_ptr merger; - std::future last_async; + progschj::ThreadPool mesh_thread_pool; // == public: diff --git a/src/client/clientmap.h b/src/client/clientmap.h index f0ca6b488..4b605d733 100644 --- a/src/client/clientmap.h +++ b/src/client/clientmap.h @@ -35,32 +35,30 @@ along with Freeminer. If not, see . struct MapDrawControl { -// freeminer: - int32_t farmesh = 30000; - uint16_t farmesh_quality = 0; - bool farmesh_stable = false; - int32_t lodmesh = 4; - int cell_size = 1; - uint8_t cell_size_pow = 0; - uint8_t farmesh_quality_pow = 0; - - float fps = 30; - float fps_avg = 30; - float fps_wanted = 30; - float drawtime_avg = 30; - - float fov = 180; - float fov_add = 0; - float fov_want = 180; // smooth change - - float farthest_drawn = 0; - - //bool block_overflow; + // freeminer: + int32_t farmesh{30000}; + uint16_t farmesh_quality{}; + bool farmesh_stable{}; + int32_t lodmesh{4}; + int cell_size{1}; + uint8_t cell_size_pow{}; + uint8_t farmesh_quality_pow{}; + + float fps{30}; + float fps_avg{30}; + float fps_wanted{30}; + float drawtime_avg{30}; + + float fov{180}; + float fov_add{}; + float fov_want{180}; // smooth change + + float farthest_drawn{}; + void fm_init(); - MapDrawControl() { - fm_init(); - } -// == + MapDrawControl() { fm_init(); } + // == + // Wanted drawing range std::atomic_int32_t wanted_range = 0.0f; diff --git a/src/client/fm_client.cpp b/src/client/fm_client.cpp index d351f896a..8dbe6eea0 100644 --- a/src/client/fm_client.cpp +++ b/src/client/fm_client.cpp @@ -184,7 +184,7 @@ void Client::handleCommand_BlockDataFm(NetworkPacket *pkt) } auto &packet = *(pkt->packet); v3bpos_t bpos = packet[TOCLIENT_BLOCKDATA_POS].as(); - MapBlock::block_step_t step = 0; + block_step_t step = 0; packet[TOCLIENT_BLOCKDATA_STEP].convert(step); std::istringstream istr( packet[TOCLIENT_BLOCKDATA_DATA].as(), std::ios_base::binary); @@ -266,7 +266,7 @@ void Client::handleCommand_BlockDataFm(NetworkPacket *pkt) ++m_new_farmeshes; //todo: step ordered thread pool - last_async = std::async(std::launch::async, [this, block]() mutable { + mesh_thread_pool.enqueue([this, block]() mutable { createFarMesh(block); auto &client_map = getEnv().getClientMap(); const auto &control = client_map.getControl(); diff --git a/src/client/fm_far_calc.cpp b/src/client/fm_far_calc.cpp index 776d61881..0001ce30a 100644 --- a/src/client/fm_far_calc.cpp +++ b/src/client/fm_far_calc.cpp @@ -27,7 +27,7 @@ along with Freeminer. If not, see . #include "irr_v3d.h" #include "irrlichttypes.h" -int getLodStep(const MapDrawControl &draw_control, const v3bpos_t &playerblockpos, +block_step_t getLodStep(const MapDrawControl &draw_control, const v3bpos_t &playerblockpos, const v3bpos_t &blockpos, const pos_t speedf) { if (draw_control.lodmesh) { @@ -66,7 +66,7 @@ int getLodStep(const MapDrawControl &draw_control, const v3bpos_t &playerblockpo return 0; }; -int getFarStepBad(const MapDrawControl &draw_control, const v3bpos_t &playerblockpos, +block_step_t getFarStepBad(const MapDrawControl &draw_control, const v3bpos_t &playerblockpos, const v3bpos_t &blockpos) { if (!draw_control.farmesh) @@ -120,7 +120,7 @@ using v3tpos_t = v3bpos_t; using tpos_t = int32_t; using v3tpos_t = v3s32; #endif -bool inFarGrid(const v3bpos_t &blockpos, const v3bpos_t &playerblockpos, int step, +bool inFarGrid(const v3bpos_t &blockpos, const v3bpos_t &playerblockpos, block_step_t step, const MapDrawControl &draw_control) { const auto act = getFarActual(blockpos, playerblockpos, step, draw_control); @@ -186,9 +186,10 @@ std::optional find(const v3tpos_t &block_pos, const v3tpos_t &player_po child.pos.Z + childSize), .size = childSize}, }) { - const auto res = find( - block_pos, player_pos, child, cell_size_pow, farmesh_quality, depth + 1); - if (res) { + + if (const auto res = find(block_pos, player_pos, child, cell_size_pow, + farmesh_quality, depth + 1); + res) { return res; } } @@ -205,7 +206,7 @@ const auto tree_align = tree_pow - 1; const auto tree_align_size = 1 << (tree_align); const auto external_pow = tree_pow - 2; -int getFarStepCellSize(const MapDrawControl &draw_control, const v3bpos_t &ppos, +block_step_t getFarStepCellSize(const MapDrawControl &draw_control, const v3bpos_t &ppos, const v3bpos_t &blockpos, uint8_t cell_size_pow) { const auto blockpos_aligned_cell = align_shift(blockpos, cell_size_pow); @@ -241,17 +242,16 @@ int getFarStepCellSize(const MapDrawControl &draw_control, const v3bpos_t &ppos, //return external_pow; //+ draw_control.cell_size_pow; } -int getFarStep(const MapDrawControl &draw_control, const v3bpos_t &ppos, +block_step_t getFarStep(const MapDrawControl &draw_control, const v3bpos_t &ppos, const v3bpos_t &blockpos) { return getFarStepCellSize(draw_control, ppos, blockpos, draw_control.cell_size_pow); } -v3bpos_t getFarActual(const v3bpos_t &blockpos, const v3bpos_t &ppos, int step, +v3bpos_t getFarActual(const v3bpos_t &blockpos, const v3bpos_t &ppos, block_step_t step, const MapDrawControl &draw_control) { const auto blockpos_aligned_cell = align_shift(blockpos, draw_control.cell_size_pow); - const auto start = child_t{.pos = v3tpos_t((((tpos_t)ppos.X >> tree_align) << tree_align) - (tree_align_size >> 1), @@ -288,4 +288,104 @@ v3bpos_t getFarActual(const v3bpos_t &blockpos, const v3bpos_t &ppos, int step, (blockpos.Z >> ext_align) << ext_align); } +struct each_param_t +{ + const v3tpos_t &player_pos; + const int cell_size_pow; + const uint16_t farmesh_quality; + const std::function &func; + const bool two_d{false}; +}; + +void each(const each_param_t ¶m, const child_t &child) +{ + auto distance = std::max({std::abs((tpos_t)param.player_pos.X - child.pos.X - + (child.size >> 1)), + std::abs((tpos_t)param.player_pos.Y - child.pos.Y - (child.size >> 1)), + std::abs((tpos_t)param.player_pos.Z - child.pos.Z - (child.size >> 1))}); + + if (param.farmesh_quality) { + distance /= param.farmesh_quality; + } + + if (distance > child.size) { + param.func(child); + return; + } + + const tpos_t childSize = child.size >> 1; + uint8_t i{0}; + for (const auto &child : { + // first with unchanged Y for 2d + child_t{.pos{child.pos}, .size = childSize}, + child_t{.pos = v3tpos_t( + child.pos.X + childSize, child.pos.Y, child.pos.Z), + .size = childSize}, + child_t{.pos = v3tpos_t( + child.pos.X, child.pos.Y, child.pos.Z + childSize), + .size = childSize}, + child_t{.pos = v3tpos_t(child.pos.X + childSize, child.pos.Y, + child.pos.Z + childSize), + .size = childSize}, + + // two_d ends here + + child_t{.pos = v3tpos_t( + child.pos.X, child.pos.Y + childSize, child.pos.Z), + .size = childSize}, + child_t{.pos = v3tpos_t(child.pos.X + childSize, child.pos.Y + childSize, + child.pos.Z), + .size = childSize}, + child_t{.pos = v3tpos_t(child.pos.X, child.pos.Y + childSize, + child.pos.Z + childSize), + .size = childSize}, + child_t{.pos = v3tpos_t(child.pos.X + childSize, child.pos.Y + childSize, + child.pos.Z + childSize), + .size = childSize}, + }) { + + if (param.two_d && i++ >= 4) { + break; + } + + if (child.size < (1 << (param.cell_size_pow))) { + if (param.func(child)) { + return; + } + continue; + } + + each(param, child); + } +} + +void runFarAll(const MapDrawControl &draw_control, const v3bpos_t &ppos, + uint8_t cell_size_pow, pos_t two_d, + const std::function &func) +{ + + const auto start = + child_t{.pos = v3tpos_t((((tpos_t)ppos.X >> tree_align) << tree_align) - + (tree_align_size >> 1), + two_d + ?: (((tpos_t)(ppos.Y) >> tree_align) << tree_align) - + (tree_align_size >> 1), + (((tpos_t)(ppos.Z) >> tree_align) << tree_align) - + (tree_align_size >> 1)), + .size{tree_size}}; + + const auto func_convert = [&func](const child_t &child) { + return func(v3bpos_t(child.pos.X, child.pos.Y, child.pos.Z), child.size); + }; + + // DUMP(start.pos, start.size, tree_align); + + each({.player_pos{ppos.X, ppos.Y, ppos.Z}, + .cell_size_pow{cell_size_pow}, + .farmesh_quality{draw_control.farmesh_quality}, + .func{func_convert}, + .two_d{static_cast(two_d)}}, + start); +} + #endif diff --git a/src/client/fm_far_calc.h b/src/client/fm_far_calc.h index 120bd799a..3f31f5543 100644 --- a/src/client/fm_far_calc.h +++ b/src/client/fm_far_calc.h @@ -22,20 +22,24 @@ along with Freeminer. If not, see . #pragma once #include "irr_v3d.h" +#include "irrlichttypes.h" struct MapDrawControl; -int getLodStep(const MapDrawControl &draw_control, const v3bpos_t &playerblockpos, +block_step_t getLodStep(const MapDrawControl &draw_control, const v3bpos_t &playerblockpos, const v3bpos_t &block_pos, const pos_t speedf); -int getFarStepCellSize(const MapDrawControl &draw_control, const v3bpos_t &ppos, +block_step_t getFarStepCellSize(const MapDrawControl &draw_control, const v3bpos_t &ppos, const v3bpos_t &blockpos, uint8_t cell_size_pow); -int getFarStep(const MapDrawControl &draw_control, const v3bpos_t &playerblockpos, +block_step_t getFarStep(const MapDrawControl &draw_control, const v3bpos_t &playerblockpos, const v3bpos_t &block_pos); -int getFarStepBad(const MapDrawControl &draw_control, const v3bpos_t &playerblockpos, +block_step_t getFarStepBad(const MapDrawControl &draw_control, const v3bpos_t &playerblockpos, const v3bpos_t &block_pos); -bool inFarGrid(const v3bpos_t &blockpos, const v3bpos_t &playerblockpos, int step, +bool inFarGrid(const v3bpos_t &blockpos, const v3bpos_t &playerblockpos, block_step_t step, const MapDrawControl &draw_control); -v3bpos_t getFarActual(const v3bpos_t &blockpos, const v3bpos_t &playerblockpos, int step, +v3bpos_t getFarActual(const v3bpos_t &blockpos, const v3bpos_t &playerblockpos, block_step_t step, const MapDrawControl &draw_control); v3bpos_t playerBlockAlign( const MapDrawControl &draw_control, const v3bpos_t &playerblockpos); +void runFarAll(const MapDrawControl &draw_control, const v3bpos_t &ppos, + uint8_t cell_size_pow, pos_t two_d, + const std::function &func); diff --git a/src/client/fm_farmesh.cpp b/src/client/fm_farmesh.cpp index daebe001b..1b26d0f0e 100644 --- a/src/client/fm_farmesh.cpp +++ b/src/client/fm_farmesh.cpp @@ -18,7 +18,10 @@ 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 +#include #include +#include #include #include "fm_farmesh.h" @@ -30,6 +33,7 @@ along with Freeminer. If not, see . #include "constants.h" #include "emerge.h" #include "irr_v3d.h" +#include "irrlichttypes.h" #include "mapblock.h" #include "mapgen/mapgen.h" #include "mapnode.h" @@ -48,8 +52,7 @@ const v3opos_t g_6dirso[6] = { v3opos_t(0, 1, 0), // top }; -void FarMesh::makeFarBlock( - const v3bpos_t &blockpos, MapBlock::block_step_t step, bool near) +void FarMesh::makeFarBlock(const v3bpos_t &blockpos, block_step_t step, bool near) { g_profiler->add("Client: Farmesh make", 1); @@ -81,7 +84,6 @@ void FarMesh::makeFarBlock( return; } MapBlockP block; - bool new_block = false; { const auto lock = far_blocks.lock_unique_rec(); if (const auto &it = far_blocks.find(blockpos_actual); @@ -92,23 +94,27 @@ void FarMesh::makeFarBlock( m_client->getEnv().getClientMap().m_far_blocks_ask.emplace( blockpos_actual, std::make_pair(step, far_iteration_complete)); - new_block = true; block.reset(client_map.createBlankBlockNoInsert(blockpos_actual)); block->far_step = step; + reset_timestamp = block->far_make_mesh_timestamp = + m_client->m_uptime + wait_server_far_bock; far_blocks.insert_or_assign(blockpos_actual, block); ++m_client->m_new_meshes; } } } + block->far_iteration = far_iteration_complete; - if (new_block) { - last_async = std::async(std::launch::async, + + if (m_client->m_uptime >= block->far_make_mesh_timestamp) { + block->far_make_mesh_timestamp = -1; + m_client->mesh_thread_pool.enqueue( [this, block]() mutable { m_client->createFarMesh(block); }); } return; } -void FarMesh::makeFarBlocks(const v3bpos_t &blockpos, MapBlock::block_step_t step) +void FarMesh::makeFarBlocks(const v3bpos_t &blockpos, block_step_t step) { #if FARMESH_DEBUG || FARMESH_FAST { @@ -212,7 +218,6 @@ FarMesh::FarMesh(Client *client, Server *server, MapDrawControl *control) : EmergeManager *emerge_use = server ? server->getEmergeManager() : client->m_emerge ? client->m_emerge.get() - : nullptr; if (!emerge_use) { @@ -249,7 +254,9 @@ FarMesh::FarMesh(Client *client, Server *server, MapDrawControl *control) : FarMesh::~FarMesh() { - last_async.wait(); + if (last_async.valid()) { + last_async.wait(); + } } auto align_shift(auto pos, const auto amount) @@ -259,6 +266,54 @@ auto align_shift(auto pos, const auto amount) (pos.Z >>= amount) <<= amount; return pos; } +int FarMesh::go_flat() +{ + const auto &draw_control = m_client->getEnv().getClientMap().getControl(); + + auto &dcache = direction_caches[0][0]; + auto &last_step = dcache.step_num; + // todo: slowly increase range here + if (last_step > 0) { + return 0; + } + + const auto cbpos = getNodeBlockPos(m_camera_pos_aligned); + + std::array, FARMESH_STEP_MAX> blocks; + runFarAll(draw_control, cbpos, draw_control.cell_size_pow, cbpos.Y ?: 1, + [this, &draw_control, &blocks]( + const v3bpos_t &bpos, const bpos_t &size) -> bool { + for (const auto &add : { + v2bpos_t(0, 0), v2bpos_t(0, size - 1), v2bpos_t(size - 1, 0), + v2bpos_t(size - 1, size - 1), v2bpos_t(size >> 1, size >> 1), + }) { + v3bpos_t bpos_new(bpos.X + add.X, 0, bpos.Z + add.Y); + + bpos_new.Y = mg->getGroundLevelAtPoint( + v2pos_t((bpos_new.X << MAP_BLOCKP) - 1, + (bpos_new.Z << MAP_BLOCKP) - 1)) >> + MAP_BLOCKP; + + auto step_new = getFarStep(draw_control, + getNodeBlockPos(m_camera_pos_aligned), bpos_new); + blocks[step_new].emplace(bpos_new); + } + return false; + }); + + for (; last_step < blocks.size(); ++last_step) { + for (const auto &bpos : blocks[last_step]) { + // just first suggestion + if (1 << (last_step + MAP_BLOCKP) > draw_control.farmesh && + radius_box(bpos, cbpos) << MAP_BLOCKP > last_distance_max) { + return last_step; + } + makeFarBlocks(bpos, last_step); + } + } + + return last_step; +} int FarMesh::go_direction(const size_t dir_n) { @@ -271,7 +326,7 @@ int FarMesh::go_direction(const size_t dir_n) auto &cache = direction_caches[dir_n]; auto &mg_cache = mg_caches[dir_n]; - auto &draw_control = m_client->getEnv().getClientMap().getControl(); + const auto &draw_control = m_client->getEnv().getClientMap().getControl(); const auto dir = g_6dirso[dir_n]; const auto grid_size_xy = grid_size_x * grid_size_y; @@ -431,8 +486,9 @@ uint8_t FarMesh::update(v3opos_t camera_pos, //float brightness, int render_range, float speed) { - if (!mg) + if (!mg) { return {}; + } m_speed = speed; @@ -451,8 +507,9 @@ uint8_t FarMesh::update(v3opos_t camera_pos, m_camera_pos_aligned.getDistanceFrom(camera_pos_aligned_int) > 1000); const auto set_new_cam_pos = [&]() { - if (m_camera_pos_aligned == camera_pos_aligned_int) + if (m_camera_pos_aligned == camera_pos_aligned_int) { return false; + } ++far_iteration_complete; @@ -471,8 +528,9 @@ uint8_t FarMesh::update(v3opos_t camera_pos, set_new_cam_pos(); } clientMap.far_blocks_last_cam_pos = m_camera_pos_aligned; - if (!last_distance_max) + if (!last_distance_max) { last_distance_max = distance_max; + } } if (complete_set) { @@ -485,30 +543,35 @@ uint8_t FarMesh::update(v3opos_t camera_pos, m_client->m_new_farmeshes = 0; plane_processed.fill({}); } + if (m_client->m_uptime > reset_timestamp) { + reset_timestamp = -1; + plane_processed.fill({}); + direction_caches.fill({}); + } } - /* - if (mg->surface_2d()) { - // TODO: use fast simple quadtree based direct mesh create - } else - */ + { - uint8_t planes_processed = 0; - for (uint8_t i = 0; i < sizeof(g_6dirso) / sizeof(g_6dirso[0]); ++i) { -#if FARMESH_DEBUG - if (i) { - break; + uint8_t planes_processed{}; + if (mg->surface_2d()) { + if (plane_processed[0].processed) { + ++planes_processed; + async[0].step([this]() { plane_processed[0].processed = go_flat(); }); } + } else { + for (uint8_t i = 0; i < sizeof(g_6dirso) / sizeof(g_6dirso[0]); ++i) { +#if FARMESH_DEBUG + if (i) { + break; + } #endif - if (!plane_processed[i].processed) - continue; - ++planes_processed; - async[i].step([this, i = i]() { - //for (int depth = 0; depth < 100; ++depth) { - plane_processed[i].processed = go_direction(i); - // if (!plane_processed[i].processed) - // break; - //} - }); + if (!plane_processed[i].processed) { + continue; + } + ++planes_processed; + async[i].step([this, i = i]() { + plane_processed[i].processed = go_direction(i); + }); + } } planes_processed_last = planes_processed; @@ -517,15 +580,17 @@ uint8_t FarMesh::update(v3opos_t camera_pos, } bool cam_pos_updated{}; - if (far_fast || !planes_processed) + if (far_fast || !planes_processed) { cam_pos_updated = set_new_cam_pos(); + } if (!cam_pos_updated) { if (!planes_processed && !complete_set) { clientMap.far_blocks_last_cam_pos = m_camera_pos_aligned; clientMap.far_iteration_use = far_iteration_complete; - if (far_iteration_complete) + if (far_iteration_complete) { clientMap.far_iteration_clean = far_iteration_complete - 1; + } complete_set = true; } } else if (far_fast) { diff --git a/src/client/fm_farmesh.h b/src/client/fm_farmesh.h index 10e3b8833..122b03543 100644 --- a/src/client/fm_farmesh.h +++ b/src/client/fm_farmesh.h @@ -23,6 +23,7 @@ along with Freeminer. If not, see . #include #include +#include #include "client/camera.h" #include "irr_v3d.h" #include "irrlichttypes.h" @@ -55,9 +56,8 @@ class FarMesh v3pos_t m_camera_offset, //float brightness, int render_range, float speed); - void makeFarBlock( - const v3bpos_t &blockpos, MapBlock::block_step_t step, bool near = false); - void makeFarBlocks(const v3bpos_t &blockpos, MapBlock::block_step_t step); + void makeFarBlock(const v3bpos_t &blockpos, block_step_t step, bool near = false); + void makeFarBlocks(const v3bpos_t &blockpos, block_step_t step); //void makeFarBlocks(const v3bpos_t &blockpos); private: @@ -74,6 +74,7 @@ class FarMesh pos_t distance_min{MAP_BLOCKSIZE * 9}; //v3pos_t m_camera_offset; float m_speed; + std::future last_async; #if FARMESH_FAST constexpr static uint16_t grid_size_max_y{32}; @@ -88,6 +89,10 @@ class FarMesh static constexpr uint16_t grid_size_x{grid_size_max_x}; static constexpr uint16_t grid_size_y{grid_size_max_y}; static constexpr uint16_t grid_size_xy{grid_size_x * grid_size_y}; + + static constexpr uint8_t wait_server_far_bock{ + 5}; // minimum 1 ; maybe make dynamic depend on avg server ask/response time, or on fast mode + Mapgen *mg{}; struct ray_cache @@ -107,12 +112,12 @@ class FarMesh std::array plane_processed; std::atomic_uint last_distance_max{}; int go_direction(const size_t dir_n); - uint32_t far_iteration_complete {}; - bool complete_set = false; + int go_flat(); + uint32_t far_iteration_complete{}; + bool complete_set{}; + uint32_t reset_timestamp{static_cast(-1)}; uint8_t planes_processed_last{}; concurrent_shared_unordered_map> far_blocks_list; std::array async; - - std::future last_async; }; diff --git a/src/client/sound/sound_data.cpp b/src/client/sound/sound_data.cpp index f0cbc6dbf..d2d626e2c 100644 --- a/src/client/sound/sound_data.cpp +++ b/src/client/sound/sound_data.cpp @@ -25,6 +25,7 @@ with this program; ifnot, write to the Free Software Foundation, Inc., #include "sound_data.h" #include "sound_constants.h" +#include namespace sound { diff --git a/src/fm_server.cpp b/src/fm_server.cpp index 57728b3f3..0a70c6503 100644 --- a/src/fm_server.cpp +++ b/src/fm_server.cpp @@ -60,7 +60,7 @@ void *ServerThreadBase::run() while (!stopRequested()) { try { const auto time_now = porting::getTimeMs(); - const auto result = step(time_now - time_last); + const auto result = step((time_now - time_last)/1000.0); time_last = time_now; std::this_thread::sleep_for( std::chrono::milliseconds(result ? sleep_result : sleep_nothing)); @@ -546,7 +546,7 @@ void Server::handleCommand_GetBlocks(NetworkPacket *pkt) } MapDatabase *GetFarDatabase(MapDatabase *dbase, ServerMap::far_dbases_t &far_dbases, - const std::string &savedir, MapBlock::block_step_t step) + const std::string &savedir, block_step_t step) { if (step <= 0) { if (dbase) { diff --git a/src/fm_world_merge.cpp b/src/fm_world_merge.cpp index eeec96cbd..e739e59b1 100644 --- a/src/fm_world_merge.cpp +++ b/src/fm_world_merge.cpp @@ -60,11 +60,14 @@ static const auto load_block = [](Map *smap, MapDatabase *dbase, WorldMerger::~WorldMerger() { + if (last_async.valid()) { + last_async.wait(); + } merge_changed(); } void WorldMerger::merge_one_block(MapDatabase *dbase, MapDatabase *dbase_up, - const v3bpos_t &bpos_aligned, MapBlock::block_step_t step) + const v3bpos_t &bpos_aligned, block_step_t step) { const auto step_pow = 1; const auto step_size = 1 << step_pow; @@ -215,7 +218,7 @@ void WorldMerger::merge_one_block(MapDatabase *dbase, MapDatabase *dbase_up, } bool WorldMerger::merge_one_step( - MapBlock::block_step_t step, std::unordered_set &blocks_todo) + block_step_t step, std::unordered_set &blocks_todo) { auto *dbase_current = GetFarDatabase(dbase, far_dbases, save_dir, step); auto *dbase_up = GetFarDatabase({}, far_dbases, save_dir, step + 1); @@ -329,7 +332,7 @@ bool WorldMerger::merge_one_step( bool WorldMerger::merge_list(std::unordered_set &blocks_todo) { - for (MapBlock::block_step_t step = 0; step < FARMESH_STEP_MAX - 1; ++step) { + for (block_step_t step = 0; step < FARMESH_STEP_MAX - 1; ++step) { if (merge_one_step(step, blocks_todo)) { return true; } @@ -345,7 +348,6 @@ bool WorldMerger::merge_all() bool WorldMerger::merge_changed() { - DUMP("wantmerge", changed_blocks_for_merge.size()); if (!changed_blocks_for_merge.empty()) { const auto res = merge_list(changed_blocks_for_merge); changed_blocks_for_merge.clear(); @@ -386,9 +388,9 @@ bool WorldMerger::add_changed(const v3bpos_t &bpos) if (changed_blocks_for_merge.size() < 1000) { return false; } - //last_async = - std::async(std::launch::async, [copy = std::move(changed_blocks_for_merge), - this]() mutable { merge_list(copy); }); + last_async = + std::async(std::launch::async, [copy = std::move(changed_blocks_for_merge), + this]() mutable { merge_list(copy); }); changed_blocks_for_merge.clear(); return true; } diff --git a/src/fm_world_merge.h b/src/fm_world_merge.h index 3ceb8e1aa..7613a67b3 100644 --- a/src/fm_world_merge.h +++ b/src/fm_world_merge.h @@ -22,6 +22,7 @@ along with Freeminer. If not, see . #pragma once #include +#include #include #include "map.h" #include "mapblock.h" @@ -41,22 +42,22 @@ class WorldMerger bool partial{}; uint32_t lazy_up{}; const NodeDefManager *const ndef{}; - Map * const smap{}; + Map *const smap{}; ServerMap::far_dbases_t &far_dbases; std::unordered_set changed_blocks_for_merge; int16_t m_map_compression_level{7}; MapDatabase *const dbase{}; std::string save_dir; + std::future last_async; ~WorldMerger(); void init(); bool stop(); bool throttle(); void merge_one_block(MapDatabase *dbase, MapDatabase *dbase_up, - const v3bpos_t &bpos_aligned, MapBlock::block_step_t step); + const v3bpos_t &bpos_aligned, block_step_t step); - bool merge_one_step( - MapBlock::block_step_t step, std::unordered_set &blocks_todo); + bool merge_one_step(block_step_t step, std::unordered_set &blocks_todo); bool merge_list(std::unordered_set &blocks_todo); bool merge_all(); bool merge_changed(); diff --git a/src/irrlichttypes.h b/src/irrlichttypes.h index 80e69df42..ef653273e 100644 --- a/src/irrlichttypes.h +++ b/src/irrlichttypes.h @@ -85,3 +85,5 @@ using opos_t = double; #else using opos_t = float; #endif + +using block_step_t = uint8_t; diff --git a/src/map.h b/src/map.h index 5de969c01..9619553f8 100644 --- a/src/map.h +++ b/src/map.h @@ -295,11 +295,11 @@ class Map : public NodeContainer m_far_blocks_type m_far_blocks; std::vector> m_far_blocks_delete; bool m_far_blocks_currrent {}; - //using far_blocks_ask_t = concurrent_shared_unordered_map; + //using far_blocks_ask_t = concurrent_shared_unordered_map; using far_blocks_req_t = std::unordered_map>; // server + std::pair>; // server using far_blocks_ask_t = concurrent_shared_unordered_map>; // client + std::pair>; // client far_blocks_ask_t m_far_blocks_ask; std::array, FARMESH_STEP_MAX> far_blocks_storage; diff --git a/src/mapblock.h b/src/mapblock.h index a21623eb6..fad6d3aab 100644 --- a/src/mapblock.h +++ b/src/mapblock.h @@ -524,7 +524,6 @@ class MapBlock } using mesh_type = std::shared_ptr; - using block_step_t = uint8_t; #if BUILD_CLIENT // Only on client const MapBlock::mesh_type getLodMesh(block_step_t step, bool allow_other = false); @@ -532,18 +531,19 @@ class MapBlock const MapBlock::mesh_type getFarMesh(block_step_t step); void setFarMesh(const MapBlock::mesh_type &rmesh, block_step_t step); std::mutex far_mutex; - u32 mesh_requested_timestamp {}; - uint8_t mesh_requested_step {}; + uint32_t mesh_requested_timestamp{}; + block_step_t mesh_requested_step{}; private: std::array m_lod_mesh; std::array m_far_mesh; MapBlock::mesh_type delete_mesh; -public: +public: #endif block_step_t far_step{}; + uint32_t far_make_mesh_timestamp{static_cast(-1)}; std::atomic_uint32_t far_iteration{}; std::atomic_bool creating_far_mesh{}; std::atomic_short heat{}; @@ -557,12 +557,12 @@ class MapBlock // Last really changed time (need send to client) std::atomic_uint m_changed_timestamp{}; - u32 m_next_analyze_timestamp{}; + uint32_t m_next_analyze_timestamp{}; typedef std::list abm_triggers_type; std::unique_ptr abm_triggers; std::mutex abm_triggers_mutex; size_t abmTriggersRun(ServerEnvironment *m_env, u32 time, uint8_t activate = 0); - u32 m_abm_timestamp = 0; + uint32_t m_abm_timestamp{}; u32 getActualTimestamp() { diff --git a/src/mapgen/mapgen_earth.cpp b/src/mapgen/mapgen_earth.cpp index d67d9b283..ec8feb0c5 100644 --- a/src/mapgen/mapgen_earth.cpp +++ b/src/mapgen/mapgen_earth.cpp @@ -146,7 +146,7 @@ MapgenEarth::MapgenEarth(MapgenEarthParams *params_, EmergeParams *emerge) : scale = {params["scale"]["x"].asDouble(), params["scale"]["y"].asDouble(), params["scale"]["z"].asDouble()}; - /* todomake test + /* todomake test static bool shown = 0; if (!shown) { shown = true; @@ -168,7 +168,7 @@ MapgenEarth::MapgenEarth(MapgenEarthParams *params_, EmergeParams *emerge) : } } */ - /* + /* hgt_reader.debug = 1; std::vector> a{ {0, 0}, {-30000, -30000}, {-30000, 30000}, {30000, -30000}, {30000, 30000}}; @@ -270,6 +270,11 @@ int MapgenEarth::getSpawnLevelAtPoint(v2pos_t p) return std::max(2, get_height(p.X, p.Y) + 2); } +int MapgenEarth::getGroundLevelAtPoint(v2pos_t p) +{ + return get_height(p.X, p.Y); // + MGV6_AVERAGE_MUD_AMOUNT; +} + // https://www.roguebasin.com/index.php?title=Bresenham%27s_Line_Algorithm void MapgenEarth::bresenham(pos_t x1, pos_t y1, const pos_t x2, const pos_t y2, pos_t y, pos_t h, const MapNode &n) @@ -371,5 +376,4 @@ void MapgenEarth::generateBuildings() if (handler) handler->apply(); - } diff --git a/src/mapgen/mapgen_earth.h b/src/mapgen/mapgen_earth.h index 6b942460e..23f0a569e 100644 --- a/src/mapgen/mapgen_earth.h +++ b/src/mapgen/mapgen_earth.h @@ -76,6 +76,7 @@ class MapgenEarth : public MapgenV7 int generateTerrain() override; void generateBuildings() override; int getSpawnLevelAtPoint(v2pos_t p) override; + int getGroundLevelAtPoint(v2pos_t p) override; v3d scale{1, 1, 1}; v3d center{0, 0, 0}; diff --git a/src/server.h b/src/server.h index 14ac23fa7..0cff71934 100644 --- a/src/server.h +++ b/src/server.h @@ -840,6 +840,6 @@ void dedicated_server_loop(Server &server, bool &kill); // fm: MapDatabase *GetFarDatabase(MapDatabase *dbase, ServerMap::far_dbases_t &far_dbases, - const std::string &savedir, MapBlock::block_step_t step); + const std::string &savedir, block_step_t step); MapBlockP loadBlockNoStore(Map *smap, MapDatabase *dbase, const v3bpos_t &pos); // == diff --git a/src/threading/ThreadPool.h b/src/threading/ThreadPool.h new file mode 100644 index 000000000..914c69ca5 --- /dev/null +++ b/src/threading/ThreadPool.h @@ -0,0 +1,342 @@ +// https://github.com/SeaSalti/ThreadPool-Improved + +// -*- C++ -*- +// Copyright (c) 2012-2015 Jakob Progsch +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source +// distribution. +// +// Modified for log4cplus, copyright (c) 2014-2015 Václav Zeman. + +#ifndef THREAD_POOL_H_7ea1ee6b_4f17_4c09_b76b_3d44e102400c +#define THREAD_POOL_H_7ea1ee6b_4f17_4c09_b76b_3d44e102400c + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace progschj { + +class would_block + : public std::runtime_error +{ + using std::runtime_error::runtime_error; +}; + + +class ThreadPool { +public: + template + using return_type = +#if defined(__cpp_lib_is_invocable) && __cpp_lib_is_invocable >= 201703L + typename std::invoke_result::type; +#else + typename std::result_of::type; +#endif + + explicit ThreadPool(std::size_t threads + = (std::max)(2u, std::thread::hardware_concurrency())); + template + auto enqueue_block(F&& f, Args&&... args) -> std::future>; + template + auto enqueue(F&& f, Args&&... args) -> std::future>; + void wait_until_empty(); + void wait_until_nothing_in_flight(); + void set_queue_size_limit(std::size_t limit); + void set_pool_size(std::size_t limit); + ~ThreadPool(); + +private: + void start_worker(std::size_t worker_number, + std::unique_lock const &lock); + + template + auto enqueue_worker(bool, F&& f, Args&&... args) -> std::future>; + + template + static std::future make_exception_future (std::exception_ptr ex_ptr); + + // need to keep track of threads so we can join them + std::vector< std::thread > workers; + // target pool size + std::size_t pool_size; + // the task queue + std::queue< std::function > tasks; + // queue length limit + std::size_t max_queue_size = 100000; + // stop signal + bool stop = false; + + // synchronization + std::mutex queue_mutex; + std::condition_variable condition_producers; + std::condition_variable condition_consumers; + + std::mutex in_flight_mutex; + std::condition_variable in_flight_condition; + std::atomic in_flight; + + struct handle_in_flight_decrement + { + ThreadPool & tp; + + handle_in_flight_decrement(ThreadPool & tp_) + : tp(tp_) + { } + + ~handle_in_flight_decrement() + { + std::size_t prev + = std::atomic_fetch_sub_explicit(&tp.in_flight, + std::size_t(1), + std::memory_order_acq_rel); + if (prev == 1) + { + std::unique_lock guard(tp.in_flight_mutex); + tp.in_flight_condition.notify_all(); + } + } + }; +}; + +// the constructor just launches some amount of workers +inline ThreadPool::ThreadPool(std::size_t threads) + : pool_size(threads) + , in_flight(0) +{ + std::unique_lock lock(this->queue_mutex); + for (std::size_t i = 0; i != threads; ++i) + start_worker(i, lock); +} + +// add new work item to the pool and block if the queue is full +template +auto ThreadPool::enqueue_block(F&& f, Args&&... args) -> std::future> +{ + return enqueue_worker (true, std::forward (f), std::forward (args)...); +} + +// add new work item to the pool and return future with would_block exception if it is full +template +auto ThreadPool::enqueue(F&& f, Args&&... args) -> std::future> +{ + return enqueue_worker (false, std::forward (f), std::forward (args)...); +} + +template +auto ThreadPool::enqueue_worker(bool block, F&& f, Args&&... args) -> std::future> +{ + auto task = std::make_shared< std::packaged_task()> >( + std::bind(std::forward(f), std::forward(args)...) + ); + + std::future> res = task->get_future(); + + std::unique_lock lock(queue_mutex); + + if (tasks.size () >= max_queue_size) + { + if (block) + { + // wait for the queue to empty or be stopped + condition_producers.wait(lock, + [this] + { + return tasks.size () < max_queue_size + || stop; + }); + } + else + { + return ThreadPool::make_exception_future> ( + std::make_exception_ptr (would_block("queue full"))); + } + } + + + // don't allow enqueueing after stopping the pool + if (stop) + throw std::runtime_error("enqueue on stopped ThreadPool"); + + tasks.emplace([task](){ (*task)(); }); + std::atomic_fetch_add_explicit(&in_flight, + std::size_t(1), + std::memory_order_relaxed); + condition_consumers.notify_one(); + + return res; +} + +// the destructor joins all threads +inline ThreadPool::~ThreadPool() +{ + std::unique_lock lock(queue_mutex); + stop = true; + pool_size = 0; + condition_consumers.notify_all(); + condition_producers.notify_all(); + condition_consumers.wait(lock, [this]{ return this->workers.empty(); }); + assert(in_flight == 0); +} + +inline void ThreadPool::wait_until_empty() +{ + std::unique_lock lock(this->queue_mutex); + this->condition_producers.wait(lock, + [this]{ return this->tasks.empty(); }); +} + +inline void ThreadPool::wait_until_nothing_in_flight() +{ + std::unique_lock lock(this->in_flight_mutex); + this->in_flight_condition.wait(lock, + [this]{ return this->in_flight == 0; }); +} + +inline void ThreadPool::set_queue_size_limit(std::size_t limit) +{ + std::unique_lock lock(this->queue_mutex); + + if (stop) + return; + + std::size_t const old_limit = max_queue_size; + max_queue_size = (std::max)(limit, std::size_t(1)); + if (old_limit < max_queue_size) + condition_producers.notify_all(); +} + +inline void ThreadPool::set_pool_size(std::size_t limit) +{ + if (limit < 1) + limit = 1; + + std::unique_lock lock(this->queue_mutex); + + if (stop) + return; + + std::size_t const old_size = pool_size; + assert(this->workers.size() >= old_size); + + pool_size = limit; + if (pool_size > old_size) + { + // create new worker threads + // it is possible that some of these are still running because + // they have not stopped yet after a pool size reduction, such + // workers will just keep running + for (std::size_t i = old_size; i != pool_size; ++i) + start_worker(i, lock); + } + else if (pool_size < old_size) + // notify all worker threads to start downsizing + this->condition_consumers.notify_all(); +} + +inline void ThreadPool::start_worker( + std::size_t worker_number, std::unique_lock const &lock) +{ + assert(lock.owns_lock() && lock.mutex() == &this->queue_mutex); + assert(worker_number <= this->workers.size()); + + auto worker_func = + [this, worker_number] + { + for(;;) + { + std::function task; + bool notify; + + { + std::unique_lock lock(this->queue_mutex); + this->condition_consumers.wait(lock, + [this, worker_number]{ + return this->stop || !this->tasks.empty() + || pool_size < worker_number + 1; }); + + // deal with downsizing of thread pool or shutdown + if ((this->stop && this->tasks.empty()) + || (!this->stop && pool_size < worker_number + 1)) + { + // detach this worker, effectively marking it stopped + this->workers[worker_number].detach(); + // downsize the workers vector as much as possible + while (this->workers.size() > pool_size + && !this->workers.back().joinable()) + this->workers.pop_back(); + // if this is was last worker, notify the destructor + if (this->workers.empty()) + this->condition_consumers.notify_all(); + return; + } + else if (!this->tasks.empty()) + { + task = std::move(this->tasks.front()); + this->tasks.pop(); + notify = this->tasks.size() + 1 == max_queue_size + || this->tasks.empty(); + } + else + continue; + } + + handle_in_flight_decrement guard(*this); + + if (notify) + { + std::unique_lock lock(this->queue_mutex); + condition_producers.notify_all(); + } + + task(); + } + }; + + if (worker_number < this->workers.size()) { + std::thread & worker = this->workers[worker_number]; + // start only if not already running + if (!worker.joinable()) { + worker = std::thread(worker_func); + } + } else + this->workers.emplace_back(worker_func); +} + +template +inline std::future ThreadPool::make_exception_future (std::exception_ptr ex_ptr) +{ + std::promise p; + p.set_exception (ex_ptr); + return p.get_future (); +} + +} // namespace progschj + +#endif // THREAD_POOL_H_7ea1ee6b_4f17_4c09_b76b_3d44e102400c diff --git a/src/util/numeric.h b/src/util/numeric.h index 8a474b55a..6c40ee659 100644 --- a/src/util/numeric.h +++ b/src/util/numeric.h @@ -535,7 +535,7 @@ inline float cycle_shift(float value, float by = 0, float max = 1) return value + by; } -inline int radius_box(const v3pos_t &a, const v3pos_t &b) +inline unsigned int radius_box(const v3pos_t &a, const v3pos_t &b) { return std::max({std::abs((float)a.X - b.X), std::abs((float)a.Y - b.Y), std::abs((float)a.Z - b.Z)}); } @@ -546,7 +546,7 @@ inline int radius_box(const v3bpos_t & a, const v3bpos_t & b) { } */ -inline int radius_box(const v3opos_t &a, const v3opos_t &b) +inline unsigned int radius_box(const v3opos_t &a, const v3opos_t &b) { return std::max({std::fabs(a.X - b.X), std::fabs(a.Y - b.Y), std::fabs(a.Z - b.Z)}); } diff --git a/util/autotest/auto.pl b/util/autotest/auto.pl index 81081c4c3..53b86d268 100755 --- a/util/autotest/auto.pl +++ b/util/autotest/auto.pl @@ -187,6 +187,7 @@ () vtune_amplifier => '~/intel/vtune_amplifier_xe/bin64/', vtune_collect => 'hotspots', # for full list: ~/intel/vtune_amplifier_xe/bin64/amplxe-cl -help collect world_clear => 0, # remove old world before start client + pid_path => '/tmp/', }; map { /^---(\w+)(?:=(.*))?/ and $config->{$1} = defined $2 ? $2 : 1; } @ARGV; @@ -461,6 +462,7 @@ () sy qq{rm -rf ${root_path}cache/media/* } if $config->{cache_clear} and $root_path; $commands->{world_name}(); sy qq{rm -rf $config->{world} } if $config->{world_clear} and $config->{world}; + $config->{pid_file} = $config->{pid_path} . ($options->{pass}{name} || 'freeminer') . '.pid'; return sytee $config->{runner}, $commands->{env}(), @@ -499,7 +501,7 @@ () qq{--logfile $config->{logdir}/autotest.$g->{task_name}.game.log}, options_make($options->{pass}{config} ? () : [qw(gameid world port config autoexit verbose)]), qq{$config->{run_add}}; - + $config->{pid_file} = $config->{pid_path} . ($options->{pass}{worldname} || 'freeminerserver') . '.pid'; if ($config->{server_bg}) { return sf $cmd . qq{ $config->{tee} $config->{logdir}/autotest.$g->{task_name}.server.out.log}; } else { @@ -551,7 +553,7 @@ () fail => sub { warn 'fail:', join ' ', @_; }, - set_client => [{'---no_build_client' => 0, '---no_build_server' => 1,, '---executable_name' => 'freeminer',}], + set_client => [{'---no_build_client' => 0, '---no_build_server' => 1, '---executable_name' => 'freeminer',}], set_server => [{'---no_build_client' => 1, '---no_build_server' => 0, '----no_exit'=>1, '---executable_name' => 'freeminerserver',}], }; @@ -903,11 +905,18 @@ (@) say 'running ', join ' ', @_; file_append("$config->{logdir}/run.sh", join(' ', @_), "\n"); my $pid = open my $fh, "-|", "@_ 2>&1" or return "can't open @_: $!"; + if ($config->{pid_file}) { + unlink $config->{pid_file}; + file_append($config->{pid_file}, $pid); + } while (defined($_ = <$fh>)) { print $_; file_append($tee, $_); } close($fh); + if ($config->{pid_file}) { + unlink $config->{pid_file}; + } return sig(undef, $pid); } @@ -1003,6 +1012,12 @@ (@) sub commands_run(@) { my @p = @_; my $name = shift @p; + + if ($config->{'no_' . $name}) { + warn 'command disabled ', $name; + return undef; + } + say join ' ', "commands_run", $name, @p if $config->{verbose}; my $c = $commands->{$name} || $tasks->{$name};