diff --git a/src/client/client.cpp b/src/client/client.cpp index 32568e300..6e32a9ec9 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -177,7 +177,7 @@ Client::Client( control.cell_size = m_mesh_grid.cell_size; control.cell_size_pow = log(control.cell_size) / log(2); control.farmesh_quality = g_settings->getU16("farmesh_quality"); - control.farmesh_quality_pow = log(control.farmesh_quality) / log(2); + control.farmesh_quality_pow = log(control.farmesh_quality) / log(2); control.farmesh_stable = g_settings->getU16("farmesh_stable"); } diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index 08ee812bf..1631a8a47 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -828,8 +828,16 @@ void ClientMap::updateDrawListFm(float dtime, unsigned int max_cycle_ms) unordered_map_v3pos occlude_cache; + std::vector> vector; + { + const auto lock = m_blocks.lock_shared_rec(); + vector.reserve(m_blocks.size()); + for (const auto &it : m_blocks) { + vector.emplace_back(it); + } + } - for(const auto & [bp, block] : m_blocks) { + for(const auto & [bp, block] : vector) { if (!block) continue; @@ -856,7 +864,7 @@ void ClientMap::updateDrawListFm(float dtime, unsigned int max_cycle_ms) */ const auto mesh = block->getLodMesh(mesh_step, true); - { + { ++blocks_in_range; const int smesh_size = !mesh ? -1 : mesh->getMesh()->getMeshBufferCount(); @@ -869,13 +877,14 @@ void ClientMap::updateDrawListFm(float dtime, unsigned int max_cycle_ms) blocks_in_range_without_mesh++; if (m_mesh_queued < maxq || range_blocks <= 2) { if (!mesh || speedf < BS * MAP_BLOCKSIZE) { - const auto bts = block->getTimestamp(); - if (block->mesh_requested_timestamp < bts || + if (const auto bts = block->getTimestamp(); + block->mesh_requested_timestamp < bts || block->mesh_requested_step != mesh_step) { - block->mesh_requested_timestamp = bts; + block->mesh_requested_timestamp = bts; block->mesh_requested_step = mesh_step; - m_client->addUpdateMeshTask(bp, false); - ++m_mesh_queued; + //DUMP("goup", bp, m_mesh_queued); + m_client->addUpdateMeshTask(bp, false); + ++m_mesh_queued; } } } @@ -957,7 +966,7 @@ void ClientMap::updateDrawListFm(float dtime, unsigned int max_cycle_ms) // First, perform a simple distance check. if (!m_control.range_all && radius_box(mesh_sphere_center, m_camera_position) > - m_control.wanted_range * BS + mesh_sphere_radius) + m_control.wanted_range * BS + mesh_sphere_radius) continue; // Out of range, skip. } @@ -1037,8 +1046,7 @@ void ClientMap::updateDrawListFm(float dtime, unsigned int max_cycle_ms) if(range_blocks * MAP_BLOCKSIZE > farthest_drawn) farthest_drawn = range_blocks * MAP_BLOCKSIZE; - } - + } } //m_drawlist_last = draw_nearest.size(); @@ -1055,7 +1063,7 @@ void ClientMap::updateDrawListFm(float dtime, unsigned int max_cycle_ms) : m_far_blocks_delete_2; m_far_blocks_delete.clear(); size_t farblocks_drawn = 0; - auto lock = m_far_blocks.lock_unique_rec(); + const auto lock = m_far_blocks.lock_unique_rec(); for (auto it = m_far_blocks.begin(); it != m_far_blocks.end();) { const auto &block = it->second; if (m_far_blocks_clean_timestamp > 0 && @@ -1064,22 +1072,8 @@ void ClientMap::updateDrawListFm(float dtime, unsigned int max_cycle_ms) it = m_far_blocks.erase(it); } else if (block->getTimestamp() >= m_far_blocks_use_timestamp) { if (!blocks_skip_farmesh.contains(it->first)) { - int mesh_step = getFarStep(m_control, - getNodeBlockPos(m_far_blocks_last_cam_pos), - it->first); // m_camera_position_node - if (mesh_step > 1 && - !inFarGrid(it->first, - getNodeBlockPos(m_far_blocks_last_cam_pos), mesh_step, - m_control)) { - } else { - const auto mesh = block->getFarMesh(mesh_step); - if (!mesh) { - //m_client->farmesh_remake.insert_or_assign(it->first, false); - } else { - drawlist.emplace(it->first, block); - ++farblocks_drawn; - } - } + drawlist.emplace(it->first, block); + ++farblocks_drawn; } ++it; } else { @@ -1353,6 +1347,9 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) g_profiler->avg(prefix + "vertices drawn [#]", vertex_count); g_profiler->avg(prefix + "drawcalls [#]", drawcall_count); g_profiler->avg(prefix + "material swaps [#]", material_swaps); + + if(is_transparent_pass) + m_far_blocks_delete.clear(); } static bool getVisibleBrightness(Map *map, const v3f &p0, v3f dir, float step, diff --git a/src/client/fm_client.cpp b/src/client/fm_client.cpp index 1115c2982..90e93d10c 100644 --- a/src/client/fm_client.cpp +++ b/src/client/fm_client.cpp @@ -1,5 +1,7 @@ #include +#include #include "client.h" +#include "client/fm_far_calc.h" #include "client/mapblock_mesh.h" #include "clientmap.h" #include "emerge.h" @@ -51,7 +53,7 @@ void Client::sendGetBlocks() if (!farmesh_server) return; - auto &far_blocks = *m_env.getClientMap().m_far_blocks_use; + auto &far_blocks = m_env.getClientMap().m_far_blocks_ask; const auto lock = far_blocks.lock_unique_rec(); if (far_blocks.empty()) { @@ -151,7 +153,7 @@ void Client::createFarMesh(MapBlockP &block) const auto &m_client = this; const auto &blockpos_actual = block->getPos(); const auto &m_camera_offset = m_camera->getOffset(); - const auto step = block->far_step; + const auto &step = block->far_step; MeshMakeData mdat(m_client, false, 0, step, &m_client->far_container); mdat.m_blockpos = blockpos_actual; auto mbmsh = std::make_shared(&mdat, m_camera_offset); @@ -216,7 +218,6 @@ void Client::handleCommand_BlockDatas(NetworkPacket *pkt) block->humidity = h; if (!step) { - if (m_localdb) { ServerMap::saveBlock(block.get(), m_localdb); } @@ -230,14 +231,40 @@ void Client::handleCommand_BlockDatas(NetworkPacket *pkt) } else { static thread_local const auto farmesh_server = g_settings->getU16("farmesh_server"); - if (farmesh_server) { - far_container.far_blocks[step].insert_or_assign(bpos, block); - auto &far_blocks = getEnv().getClientMap().m_far_blocks; - if (far_blocks.contains(bpos)) { - const auto &block = far_blocks.at(bpos); - block->farmesh_need_remake = m_uptime; - //block->setTimestampNoChangedFlag(-2); + if (!farmesh_server) + return; + + auto &far_blocks_storage = getEnv().getClientMap().far_blocks_storage[step]; + { + const auto lock = far_blocks_storage.lock_unique_rec(); + if (far_blocks_storage.find(bpos) != far_blocks_storage.end()) { + return; } } + far_blocks_storage.insert_or_assign(block->getPos(), block); + ++m_new_farmeshes; + + //todo: step ordered thread pool + std::async(std::launch::async, [this, block]() mutable { + createFarMesh(block); + auto &client_map = getEnv().getClientMap(); + const auto &control = client_map.getControl(); + const auto bpos = block->getPos(); + int fmesh_step_ = getFarStep(control, + getNodeBlockPos(client_map.m_far_blocks_last_cam_pos), + block->getPos()); + if (!inFarGrid(block->getPos(), + getNodeBlockPos(client_map.m_far_blocks_last_cam_pos), + fmesh_step_, control)) { + return; + } + auto &far_blocks = client_map.m_far_blocks; + if (const auto &it = far_blocks.find(bpos); it != far_blocks.end()) { + if (it->second->far_step != block->far_step) { + return; + } + far_blocks.at(bpos) = block; + } + }); } } diff --git a/src/client/fm_far_calc.cpp b/src/client/fm_far_calc.cpp index 58c31aaf6..776d61881 100644 --- a/src/client/fm_far_calc.cpp +++ b/src/client/fm_far_calc.cpp @@ -34,7 +34,7 @@ int getLodStep(const MapDrawControl &draw_control, const v3bpos_t &playerblockpo int range = radius_box(playerblockpos, blockpos); /* todo: make stable, depend on speed increase/decrease const auto speed_blocks = speedf / (BS * MAP_BLOCKSIZE); - if (range > 1 && speed_blocks > 1) { + if (range > 1 && speed_blocks > 1) { range += speed_blocks; } */ @@ -66,7 +66,6 @@ int getLodStep(const MapDrawControl &draw_control, const v3bpos_t &playerblockpo return 0; }; -#if 0 int getFarStepBad(const MapDrawControl &draw_control, const v3bpos_t &playerblockpos, const v3bpos_t &blockpos) { @@ -96,9 +95,8 @@ int getFarStepBad(const MapDrawControl &draw_control, const v3bpos_t &playerbloc skip = FARMESH_STEP_MAX; return skip; }; -#endif -auto align(auto pos, const auto amount) +auto align_shift(auto pos, const auto amount) { (pos.X >>= amount) <<= amount; (pos.Y >>= amount) <<= amount; @@ -110,7 +108,7 @@ v3bpos_t playerBlockAlign( const MapDrawControl &draw_control, const v3bpos_t &playerblockpos) { const auto step_pow2 = draw_control.cell_size_pow + draw_control.farmesh_quality_pow; - return align(playerblockpos, step_pow2) + (step_pow2 >> 1); + return align_shift(playerblockpos, step_pow2) + (step_pow2 >> 1); } #if 1 @@ -136,24 +134,31 @@ struct child_t }; std::optional find(const v3tpos_t &block_pos, const v3tpos_t &player_pos, - const child_t &child, const int cell_size_pow, uint16_t farmesh_quality) + const child_t &child, const int cell_size_pow, uint16_t farmesh_quality, + uint16_t depth = 0) { if (!(block_pos.X >= child.pos.X && block_pos.X < child.pos.X + child.size && block_pos.Y >= child.pos.Y && block_pos.Y < child.pos.Y + child.size && - block_pos.Z >= child.pos.Z && block_pos.Z < child.pos.Z + child.size)) - return {}; - - if (child.size < (1 << (1 + cell_size_pow))) + block_pos.Z >= child.pos.Z && block_pos.Z < child.pos.Z + child.size)) { + if (depth) { + return {}; + } else { + return child; + } + } + if (child.size < (1 << (cell_size_pow))) { return child; + } auto distance = std::max({std::abs((tpos_t)player_pos.X - child.pos.X - (child.size >> 1)), std::abs((tpos_t)player_pos.Y - child.pos.Y - (child.size >> 1)), std::abs((tpos_t)player_pos.Z - child.pos.Z - (child.size >> 1))}); - if (farmesh_quality) + if (farmesh_quality) { distance /= farmesh_quality; - if (distance >= child.size) { + } + if (distance > child.size) { return child; } const tpos_t childSize = child.size >> 1; @@ -181,8 +186,8 @@ 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); + const auto res = find( + block_pos, player_pos, child, cell_size_pow, farmesh_quality, depth + 1); if (res) { return res; } @@ -203,7 +208,7 @@ const auto external_pow = tree_pow - 2; int getFarStepCellSize(const MapDrawControl &draw_control, const v3bpos_t &ppos, const v3bpos_t &blockpos, uint8_t cell_size_pow) { - const auto blockpos_aligned_cell = align(blockpos, draw_control.cell_size_pow); + const auto blockpos_aligned_cell = align_shift(blockpos, cell_size_pow); const auto start = child_t{.pos = v3tpos_t( // TODO: cast to type larger than pos_t_type @@ -216,8 +221,7 @@ int getFarStepCellSize(const MapDrawControl &draw_control, const v3bpos_t &ppos, .size = tree_size}; const auto res = find( {blockpos_aligned_cell.X, blockpos_aligned_cell.Y, blockpos_aligned_cell.Z}, - {ppos.X, ppos.Y, ppos.Z}, start, draw_control.cell_size_pow, - draw_control.farmesh_quality); + {ppos.X, ppos.Y, ppos.Z}, start, cell_size_pow, draw_control.farmesh_quality); if (res) { /* #if !USE_POS32 @@ -230,7 +234,7 @@ int getFarStepCellSize(const MapDrawControl &draw_control, const v3bpos_t &ppos, return {}; #endif */ - const auto step = int(log(res->size) / log(2)) - draw_control.cell_size_pow; + const auto step = int(log(res->size) / log(2)) - cell_size_pow; return step; } return 0; // TODO! fix intersection with cell_size_pow @@ -246,8 +250,7 @@ int getFarStep(const MapDrawControl &draw_control, const v3bpos_t &ppos, v3bpos_t getFarActual(const v3bpos_t &blockpos, const v3bpos_t &ppos, int step, const MapDrawControl &draw_control) { - const auto cell_size_pow = int(log(draw_control.cell_size) / log(2)); - const auto blockpos_aligned_cell = align(blockpos, cell_size_pow); + 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) - @@ -259,7 +262,8 @@ v3bpos_t getFarActual(const v3bpos_t &blockpos, const v3bpos_t &ppos, int step, .size = tree_size}; const auto res = find( {blockpos_aligned_cell.X, blockpos_aligned_cell.Y, blockpos_aligned_cell.Z}, - {ppos.X, ppos.Y, ppos.Z}, start, cell_size_pow, draw_control.farmesh_quality); + {ppos.X, ppos.Y, ppos.Z}, start, draw_control.cell_size_pow, + draw_control.farmesh_quality); if (res) { #if USE_POS32 diff --git a/src/client/fm_far_calc.h b/src/client/fm_far_calc.h index 34828d83e..120bd799a 100644 --- a/src/client/fm_far_calc.h +++ b/src/client/fm_far_calc.h @@ -31,6 +31,8 @@ int 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, const v3bpos_t &block_pos); +int 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, const MapDrawControl &draw_control); v3bpos_t getFarActual(const v3bpos_t &blockpos, const v3bpos_t &playerblockpos, int step, diff --git a/src/client/fm_far_container.cpp b/src/client/fm_far_container.cpp index b00deb869..e0c1b8ca9 100644 --- a/src/client/fm_far_container.cpp +++ b/src/client/fm_far_container.cpp @@ -22,8 +22,9 @@ const MapNode &FarContainer::getNodeRefUnsafe(const v3pos_t &pos) v3bpos_t bpos_aligned((bpos.X >> shift) << shift, (bpos.Y >> shift) << shift, (bpos.Z >> shift) << shift); - if (const auto &it = far_blocks[fmesh_step].find(bpos_aligned); - it != far_blocks[fmesh_step].end()) { + const auto &storage = + m_client->getEnv().getClientMap().far_blocks_storage[fmesh_step]; + if (const auto &it = storage.find(bpos_aligned); it != storage.end()) { const auto &block = it->second; v3pos_t relpos = pos - bpos_aligned * MAP_BLOCKSIZE; diff --git a/src/client/fm_far_container.h b/src/client/fm_far_container.h index 332e91782..0ee296c8a 100644 --- a/src/client/fm_far_container.h +++ b/src/client/fm_far_container.h @@ -13,9 +13,6 @@ class FarContainer : public NodeContainer public: Mapgen *m_mg{}; - std::array, FARMESH_STEP_MAX> - far_blocks; - FarContainer(Client *client); const MapNode &getNodeRefUnsafe(const v3pos_t &p) override; }; diff --git a/src/client/fm_farmesh.cpp b/src/client/fm_farmesh.cpp index 794335786..5cea335ec 100644 --- a/src/client/fm_farmesh.cpp +++ b/src/client/fm_farmesh.cpp @@ -18,6 +18,8 @@ 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 "fm_farmesh.h" @@ -33,7 +35,6 @@ along with Freeminer. If not, see . #include "profiler.h" #include "server.h" #include "threading/lock.h" -#include "util/directiontables.h" #include "util/numeric.h" #include "util/timetaker.h" @@ -52,59 +53,95 @@ void FarMesh::makeFarBlock( { g_profiler->add("Client: Farmesh make", 1); + auto &client_map = m_client->getEnv().getClientMap(); + const auto &draw_control = client_map.getControl(); const auto blockpos_actual = near ? blockpos : getFarActual(blockpos, getNodeBlockPos(m_camera_pos_aligned), step, - m_client->getEnv().getClientMap().getControl()); + draw_control); auto &far_blocks = //near ? m_client->getEnv().getClientMap().m_far_near_blocks : - m_client->getEnv().getClientMap().m_far_blocks; - { - //const auto lock = far_blocks->lock_unique_rec(); - if (!far_blocks.contains(blockpos_actual)) { - far_blocks.emplace(blockpos_actual, - std::make_shared( - &m_client->getEnv().getClientMap(), blockpos, m_client)); + client_map.m_far_blocks; + if (const auto it = client_map.far_blocks_storage[step].find(blockpos_actual); + it != client_map.far_blocks_storage[step].end()) { + auto &block = it->second; + { + const auto lock = far_blocks.lock_unique_rec(); + if (const auto &fbit = far_blocks.find(blockpos_actual); + fbit != far_blocks.end()) { + if (fbit->second.get() == block.get()) { + block->setTimestampNoChangedFlag(timestamp_complete); + return; + } + client_map.m_far_blocks_delete.emplace_back(fbit->second); + } + far_blocks.insert_or_assign(blockpos_actual, block); + ++m_client->m_new_meshes; } + block->setTimestampNoChangedFlag(timestamp_complete); + return; } - const auto &block = far_blocks.at(blockpos_actual); - block->setTimestampNoChangedFlag(timestamp_complete); - const auto &draw_control = m_client->getEnv().getClientMap().getControl(); - const auto block_server_step = step; - const auto blockpos_server = blockpos_actual; - const auto have_block_data = - m_client->far_container.far_blocks[block_server_step].contains( - blockpos_server); - if (block_server_step && !have_block_data) // TODO WHY 0 here??? - m_client->getEnv().getClientMap().m_far_blocks_fill->insert_or_assign( - blockpos_server, block_server_step); - WITH_UNIQUE_LOCK(block->far_mutex) + MapBlockP block; + bool new_block = false; { - if (const auto mesh = block->getFarMesh(step); - !mesh.get() || - (block->farmesh_need_remake && - block->farmesh_need_remake > block->farmesh_created)) { - block->farmesh_created = block->farmesh_need_remake = m_client->m_uptime; - MeshMakeData mdat(m_client, false, 0, step, &m_client->far_container); - mdat.m_blockpos = blockpos_actual; - auto mbmsh = std::make_shared(&mdat, m_camera_offset); - block->setFarMesh(mbmsh, step, m_client->m_uptime); + const auto lock = far_blocks.lock_unique_rec(); + if (const auto &it = far_blocks.find(blockpos_actual); + it != far_blocks.end() && it->second->far_step == step) { + block = it->second; + } else { + if (!block) { + m_client->getEnv().getClientMap().m_far_blocks_ask.emplace( + blockpos_actual, std::make_pair(step, timestamp_complete)); + + new_block = true; + block = std::make_shared( + &client_map, blockpos_actual, m_client); + block->far_step = step; + far_blocks.insert_or_assign(blockpos_actual, block); + ++m_client->m_new_meshes; + } + block->setTimestampNoChangedFlag(timestamp_complete); } } + if (new_block) { + std::async(std::launch::async, [this, block]() mutable { + m_client->createFarMesh(block); + }); + } + return; } void FarMesh::makeFarBlocks(const v3bpos_t &blockpos, MapBlock::block_step_t step) { - const auto step_width = pow(2, step); - for (const auto &dir : { - v3pos_t(0, 0, 0), // self - v3pos_t(0, 0, 1), // back - v3pos_t(1, 0, 0), // right - v3pos_t(0, 0, -1), // front - v3pos_t(-1, 0, 0), // left - v3pos_t(0, 1, 0), // top - v3pos_t(0, -1, 0), // bottom - }) { - makeFarBlock(blockpos + dir * step_width, step); +#if FARMESH_DEBUG || FARMESH_FAST || 1 + + auto block_step_correct = getFarStep(m_client->getEnv().getClientMap().getControl(), + getNodeBlockPos(m_camera_pos_aligned), blockpos); + + return makeFarBlock(blockpos, block_step_correct); +#endif + + // TODO: fix finding correct near blocks respecting their steps and enable: + + const static auto far = std::vector{ + v3pos_t(0, 0, 0), // self + }; + const static auto near = std::vector{ + v3pos_t(0, 0, 0), // self + v3pos_t(0, 0, 1), // back + v3pos_t(1, 0, 0), // right + v3pos_t(0, 0, -1), // front + v3pos_t(-1, 0, 0), // left + v3pos_t(0, 1, 0), // top + v3pos_t(0, -1, 0), // bottom + }; + const auto &use_dirs = near; + const auto step_width = 1 << step; + for (const auto &dir : use_dirs) { + const auto bpos = blockpos + dir * step_width; + auto block_step_correct = + getFarStep(m_client->getEnv().getClientMap().getControl(), + getNodeBlockPos(m_camera_pos_aligned), bpos); + makeFarBlock(bpos, block_step_correct); } } @@ -198,6 +235,14 @@ FarMesh::~FarMesh() { } +auto align_shift(auto pos, const auto amount) +{ + (pos.X >>= amount) <<= amount; + (pos.Y >>= amount) <<= amount; + (pos.Z >>= amount) <<= amount; + return pos; +} + int FarMesh::go_direction(const size_t dir_n) { TimeTaker time("Cleint: Farmesh [ms]"); @@ -245,20 +290,14 @@ int FarMesh::go_direction(const size_t dir_n) g_profiler->avg("Client: Farmesh processed", 1); #endif //const auto dstep = ray_cache.step_num; // + 1; - const auto block_step = - getFarStep(draw_control, m_camera_pos_aligned / MAP_BLOCKSIZE, - floatToInt(pos_last, BS) / MAP_BLOCKSIZE); - if (!block_step) { - // TODO: FIXME, should be not zero - if (ray_cache.finished > 1000) { - //DUMP("fixme wrong step", ray_cache.finished, m_camera_pos_aligned, pos_last); - break; - } - } - const auto block_step_pow = pow(2, block_step - block_step_reduce); - const auto step_width = MAP_BLOCKSIZE * block_step_pow; - ray_cache.finished += step_width; - const unsigned int depth = ray_cache.finished; + auto block_step_prev = + getFarStepBad(draw_control, getNodeBlockPos(m_camera_pos_aligned), + getNodeBlockPos(floatToInt(pos_last, BS))); + + const auto step_width_shift = (block_step_prev - block_step_reduce); + const auto step_width = MAP_BLOCKSIZE + << (step_width_shift > 0 ? step_width_shift : 0); + const auto &depth = ray_cache.finished; //if (depth > last_distance_max) { //ray_cache.finished = distance_min + step_width;// * (dstep - 1); @@ -271,8 +310,7 @@ int FarMesh::go_direction(const size_t dir_n) #if !USE_POS32 const auto step_width_real = - MAP_BLOCKSIZE * - pow(2, block_step + log(draw_control.cell_size) / log(2)); + MAP_BLOCKSIZE << (block_step_prev + draw_control.cell_size_pow); #else const auto step_width_real = step_width; #endif @@ -286,40 +324,36 @@ int FarMesh::go_direction(const size_t dir_n) ray_cache.finished = -1; break; } - ++processed; - const int step_aligned = - pow(2, ceil(log(step_width) / log(2)) - align_reduce); + const int step_aligned_pow = ceil(log(step_width) / log(2)) - align_reduce; + const auto pos_int = align_shift( + floatToInt(pos, BS), step_aligned_pow > 0 ? step_aligned_pow : 0); - v3pos_t pos_int_raw = floatToInt(pos, BS); - v3pos_t pos_int((pos_int_raw.X / step_aligned) * step_aligned, - (pos_int_raw.Y / step_aligned) * step_aligned, - (pos_int_raw.Z / step_aligned) * step_aligned); + if (radius_box(pos_int, m_camera_pos_aligned) > last_distance_max) + { + break; + } - content_t visible = 0; + ++processed; - { - if (const auto &it = mg_cache.find(pos_int); it != mg_cache.end()) { - visible = it->second; - } else { - visible = mg->visible(pos_int) || mg->visible_water_level(pos_int); - mg_cache[pos_int] = visible; + if (depth >= draw_control.wanted_range) { + auto &visible = ray_cache.visible; + if (!visible) { + if (const auto &it = mg_cache.find(pos_int); it != mg_cache.end()) { + visible = it->second; + } else { + visible = + mg->visible(pos_int) || mg->visible_water_level(pos_int); + mg_cache[pos_int] = visible; + } } - } - if (depth > MAP_BLOCKSIZE * 8) { - ray_cache.visible = visible; } - - if (visible) { + if (ray_cache.visible) { if (depth > MAP_BLOCKSIZE * 8) { ray_cache.finished = -1; } - const auto blockpos = getNodeBlockPos(pos_int); - TimeTaker timer_step("makeFarBlock"); - - //DUMP(block_step_pow, block_step); - //DUMP(blockpos, m_client->getEnv().getClientMap().blocks_skip_farmesh); + const auto block_pos_unaligned = getNodeBlockPos(pos_int); // /* todo @@ -356,22 +390,14 @@ int FarMesh::go_direction(const size_t dir_n) */ } //else #endif - { -#if FARMESH_FAST - makeFarBlock(blockpos, block_step); -#else - // less holes, more unused meshes: - makeFarBlocks(blockpos, block_step); -#endif - } - if (ray_cache.finished == -1) { + if (block_step_prev && depth >= draw_control.wanted_range) { + makeFarBlocks(block_pos_unaligned, block_step_prev); + ray_cache.finished = -1; break; } } - if (radius_box(pos_int, m_camera_pos_aligned) > last_distance_max) { - break; - } + ray_cache.finished += step_width; } } @@ -381,7 +407,7 @@ int FarMesh::go_direction(const size_t dir_n) return processed; } -void FarMesh::update(v3opos_t camera_pos, +uint8_t FarMesh::update(v3opos_t camera_pos, //v3f camera_dir, //f32 camera_fov, //CameraMode camera_mode, @@ -391,7 +417,9 @@ void FarMesh::update(v3opos_t camera_pos, int render_range, float speed) { if (!mg) - return; + return {}; + + m_speed = speed; const auto camera_pos_aligned_int = playerBlockAlign(*m_control, floatToInt(camera_pos, BS * 16)) * MAP_BLOCKSIZE; @@ -399,47 +427,41 @@ void FarMesh::update(v3opos_t camera_pos, (std::min(render_range, 1.2 * m_client->fog_range / BS) >> 7) << 7; + auto &clientMap = m_client->getEnv().getClientMap(); + const auto far_fast = false; + +/* const auto far_fast = !m_control->farmesh_stable && ( //m_client->getEnv().getClientMap().m_far_fast && m_speed > 200 * BS || 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) + return; + + m_camera_pos_aligned = camera_pos_aligned_int; + m_camera_pos = intToFloat(m_camera_pos_aligned, BS); + plane_processed.fill({}); + + direction_caches.fill({}); + direction_caches_pos = m_camera_pos_aligned; + }; if (!timestamp_complete) { if (!m_camera_pos_aligned.X && !m_camera_pos_aligned.Y && !m_camera_pos_aligned.Z) { - m_camera_pos_aligned = camera_pos_aligned_int; + set_new_cam_pos(); } - m_client->getEnv().getClientMap().m_far_blocks_last_cam_pos = - m_camera_pos_aligned; + clientMap.m_far_blocks_last_cam_pos = m_camera_pos_aligned; if (!last_distance_max) last_distance_max = distance_max; } - m_camera_pos = intToFloat(m_camera_pos_aligned, BS); - - /*m_camera_dir = camera_dir; - m_camera_fov = camera_fov; - m_camera_pitch = camera_pitch; - m_camera_yaw = camera_yaw;*/ - m_camera_offset = camera_offset; - m_speed = speed; - if (direction_caches_pos != m_camera_pos_aligned) { - // maybe buggy - if (far_fast) - m_client->getEnv().getClientMap().m_far_blocks_use_timestamp = - timestamp_complete; // m_client->m_uptime ? - - if (!planes_processed_last) { - //timestamp_clean = m_client->m_uptime - 1; - direction_caches_pos = m_camera_pos_aligned; - direction_caches.fill({}); - plane_processed.fill({}); - - timestamp_complete = m_client->m_uptime; - } - } else if (last_distance_max < distance_max) { + if (last_distance_max < distance_max) { plane_processed.fill({}); last_distance_max = distance_max; // * 1.1; } @@ -454,8 +476,13 @@ void FarMesh::update(v3opos_t camera_pos, } else */ { - size_t planes_processed = 0; - for (size_t i = 0; i < sizeof(g_6dirso) / sizeof(g_6dirso[0]); ++i) { + 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; + } +#endif if (!plane_processed[i].processed) continue; ++planes_processed; @@ -472,25 +499,30 @@ void FarMesh::update(v3opos_t camera_pos, if (planes_processed) { complete_set = false; } else if (!far_fast) { - m_camera_pos_aligned = camera_pos_aligned_int; + set_new_cam_pos(); } if (m_camera_pos_aligned != camera_pos_aligned_int) { - m_client->getEnv().getClientMap().m_far_blocks_last_cam_pos = + clientMap.m_far_blocks_last_cam_pos = far_fast ? camera_pos_aligned_int : m_camera_pos_aligned; - if (far_fast) - m_camera_pos_aligned = camera_pos_aligned_int; + if (far_fast) { + set_new_cam_pos(); + } } if (!planes_processed && !complete_set) { - auto &clientMap = m_client->getEnv().getClientMap(); - constexpr auto clean_old_time = 30; + clientMap.m_far_blocks_last_cam_pos = m_camera_pos_aligned; clientMap.m_far_blocks_use_timestamp = timestamp_complete; - if (timestamp_complete - clean_old_time > 0) +// TODO: test correct times +#if FARMESH_DEBUG + constexpr auto clean_old_time = 2; +#else + constexpr auto clean_old_time = 30; +#endif + if (timestamp_complete > clean_old_time) clientMap.m_far_blocks_clean_timestamp = timestamp_complete - clean_old_time; - //timestamp_complete = m_client->m_uptime; + timestamp_complete = m_client->m_uptime; complete_set = true; - ++m_client->m_new_meshes; } /* { @@ -508,5 +540,6 @@ void FarMesh::update(v3opos_t camera_pos, //clientMap.far_blocks_sent_timer = 0; } */ + return planes_processed; } } diff --git a/src/client/fm_farmesh.h b/src/client/fm_farmesh.h index 5e4139504..0f3bde887 100644 --- a/src/client/fm_farmesh.h +++ b/src/client/fm_farmesh.h @@ -39,6 +39,10 @@ class Server; #define FARMESH_FAST 1 #endif +// #define FARMESH_FAST 1 +// #define FARMESH_DEBUG 1 // One dirction, one thread, no neighborhoods + + class FarMesh { public: @@ -46,7 +50,7 @@ class FarMesh ~FarMesh(); - void update(v3opos_t camera_pos, + uint8_t update(v3opos_t camera_pos, //v3f camera_dir, f32 camera_fov, CameraMode camera_mode, f32 camera_pitch, f32 camera_yaw, v3pos_t m_camera_offset, //float brightness, @@ -68,29 +72,29 @@ class FarMesh Client *m_client; MapDrawControl *m_control; pos_t distance_min{MAP_BLOCKSIZE * 9}; - v3pos_t m_camera_offset; + //v3pos_t m_camera_offset; float m_speed; #if FARMESH_FAST - constexpr static uint16_t grid_size_max_y = 32; + constexpr static uint16_t grid_size_max_y{32}; #else - constexpr static uint16_t grid_size_max_y = 64; + constexpr static uint16_t grid_size_max_y{64}; #endif //constexpr static uint16_t grid_size_max_y = 48; //constexpr static uint16_t grid_size_max_y = 128; //constexpr static uint16_t grid_size_max_y = 256; - constexpr static uint16_t grid_size_max_x = grid_size_max_y; - 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; - Mapgen *mg = nullptr; + constexpr static uint16_t grid_size_max_x{grid_size_max_y}; + 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}; + Mapgen *mg{}; struct ray_cache { - unsigned int finished = 0; // last depth, -1 if visible - content_t visible = {}; - size_t step_num = {}; + unsigned int finished{MAP_BLOCKSIZE * 2}; // last depth, -1 if visible + content_t visible{}; + size_t step_num{}; }; using direction_cache = std::array; std::array direction_caches; @@ -98,15 +102,14 @@ class FarMesh std::array, 6> mg_caches; struct plane_cache { - int processed = -1; + int processed{-1}; }; std::array plane_processed; - std::atomic_uint last_distance_max = 0; + std::atomic_uint last_distance_max{}; int go_direction(const size_t dir_n); - int timestamp_complete = 0; - //int timestamp_clean = 0; + uint32_t timestamp_complete{}; bool complete_set = false; - int planes_processed_last = 0; + uint8_t planes_processed_last{}; concurrent_shared_unordered_map> far_blocks_list; std::array async; diff --git a/src/client/game.cpp b/src/client/game.cpp index 65b507b49..e76b75c73 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -4555,11 +4555,14 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, camera_pos = camera->getPosition(), camera_offset = camera->getOffset(), speed = player->getSpeed().getLength()]() { - farmesh->update(camera_pos, + const auto processed = farmesh->update(camera_pos, //camera->getDirection(), camera->getFovMax(), camera->getCameraMode(), pitch, yaw, camera_offset, //sky->getBrightness(), farmesh_range, speed); + if (!processed) { + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + } }); } diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 015131635..97d4d973e 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -938,7 +938,10 @@ void Hud::drawBlockBounds() .getClientMap() .m_far_blocks_last_cam_pos), mesh_step, client->getEnv().getClientMap().getControl())) - continue; + { + // DUMP("Not in grid", blockPos, block->far_step, mesh_step, block->getTimestamp(), client->getEnv() .getClientMap() .m_far_blocks_last_cam_pos); + // continue; + } int fscale = 1; int lod_step = 0; @@ -966,9 +969,10 @@ void Hud::drawBlockBounds() } else if (m_block_bounds_mode == BLOCK_BOUNDS_FAR_REQUEST) { const auto offset = intToFloat(client->getCamera()->getOffset(), BS); const auto halfNode = v3f(BS, BS, BS) / 2.0f; - const auto &far_blocks = *client->getEnv().getClientMap().m_far_blocks_use; + const auto &far_blocks = client->getEnv().getClientMap().m_far_blocks_ask; { - for (const auto &[blockPos, mesh_step] : far_blocks) { + for (const auto &[blockPos, step_ts] : far_blocks) { + const auto &[mesh_step, ts] = step_ts; int fscale = pow(2, mesh_step - 1); int lod_step = 0; int far_step = 0; diff --git a/src/clientiface.h b/src/clientiface.h index 16ef7f936..2c30d22d6 100644 --- a/src/clientiface.h +++ b/src/clientiface.h @@ -265,7 +265,7 @@ class RemoteClient std::unordered_map blocks; void SetBlocksNotSent(); void SetBlockDeleted(v3bpos_t p); - std::vector>> + std::vector>> far_blocks_requested{FARMESH_STEP_MAX}; std::mutex far_blocks_requested_mutex; int GetNextBlocksFm(ServerEnvironment *env, EmergeManager *emerge, float dtime, diff --git a/src/debug/iostream_debug_helpers.h b/src/debug/iostream_debug_helpers.h index 28bb2e52c..e62d893c2 100644 --- a/src/debug/iostream_debug_helpers.h +++ b/src/debug/iostream_debug_helpers.h @@ -21,6 +21,10 @@ #define DUMP_DEMANGLE demangle #endif +#if !defined(DUMP_TYPE) +#define DUMP_TYPE 0 +#endif + template Out & dumpValue(Out &, T &&); @@ -174,7 +178,11 @@ Out & dump(Out & out, const char * name, T && x) } } - out << DUMP_DEMANGLE(typeid(x).name()) << ' ' << name << " = "; + out +#if DUMP_TYPE + << DUMP_DEMANGLE(typeid(x).name()) << ' ' +#endif + << name << " = "; dumpValue(out, x) << "; "; return out; } diff --git a/src/fm_clientiface.cpp b/src/fm_clientiface.cpp index 3fbd5dbf6..5990a2298 100644 --- a/src/fm_clientiface.cpp +++ b/src/fm_clientiface.cpp @@ -1,6 +1,11 @@ +#include +#include #include "clientiface.h" +#include "constants.h" #include "irr_v3d.h" +#include "irrlichttypes.h" #include "map.h" +#include "mapblock.h" #include "profiler.h" #include "remoteplayer.h" #include "server/player_sao.h" @@ -559,10 +564,11 @@ int RemoteClient::GetNextBlocksFm(ServerEnvironment *env, EmergeManager *emerge, TRY_UNIQUE_LOCK(far_blocks_requested_mutex) { + std::multimap ordered; for (auto &far_blocks : far_blocks_requested) { for (auto &[bpos, step_sent] : far_blocks) { - auto &[step, sent] = step_sent; - if (sent) { + auto &[step, sent_ts] = step_sent; + if (!sent_ts) { continue; } if (step >= FARMESH_STEP_MAX - 1) { @@ -577,11 +583,14 @@ int RemoteClient::GetNextBlocksFm(ServerEnvironment *env, EmergeManager *emerge, continue; } block->far_step = step; - step_sent.second = true; - m_env->m_server->SendBlockFm( - peer_id, block, serialization_version, net_proto_version); + step_sent.second = 0; + ordered.emplace(sent_ts - step, block); } } + for (const auto &[key, block] : std::views::reverse(ordered)) { + m_env->m_server->SendBlockFm( + peer_id, block, serialization_version, net_proto_version); + } } return num_blocks_selected - num_blocks_sending; diff --git a/src/fm_server.cpp b/src/fm_server.cpp index c6a9796e1..3ce15fd3d 100644 --- a/src/fm_server.cpp +++ b/src/fm_server.cpp @@ -24,16 +24,15 @@ along with Freeminer. If not, see . #include #include #include -#include #include #include +#include #include "database/database.h" #include "debug/iostream_debug_helpers.h" #include "emerge.h" #include "filesys.h" #include "irrTypes.h" #include "irr_v3d.h" -#include "irrlichttypes.h" #include "log.h" #include "mapblock.h" #include "mapnode.h" @@ -41,8 +40,6 @@ along with Freeminer. If not, see . #include "profiler.h" #include "server.h" #include "debug/stacktrace.h" -#include "util/directiontables.h" -#include "util/hex.h" #include "util/timetaker.h" ServerThread::ServerThread(Server *server) : thread_vector("Server", 40), m_server(server) @@ -506,9 +503,10 @@ void Server::handleCommand_GetBlocks(NetworkPacket *pkt) auto &packet = *(pkt->packet); WITH_UNIQUE_LOCK(client->far_blocks_requested_mutex) { - std::unordered_map blocks; + ServerMap::far_blocks_req_t blocks; packet[TOSERVER_GET_BLOCKS_BLOCKS].convert(blocks); - for (const auto &[bpos, step] : blocks) { + for (const auto &[bpos, step_ts] : blocks) { + const auto &[step, ts] = step_ts; if (step >= FARMESH_STEP_MAX - 1) { continue; } @@ -516,6 +514,7 @@ void Server::handleCommand_GetBlocks(NetworkPacket *pkt) client->far_blocks_requested.resize(step); } client->far_blocks_requested[step][bpos].first = step; + client->far_blocks_requested[step][bpos].second = ts; } } } diff --git a/src/map.h b/src/map.h index 191817954..012a0d756 100644 --- a/src/map.h +++ b/src/map.h @@ -289,25 +289,21 @@ class Map : public NodeContainer virtual s16 getHumidity(const v3pos_t &p, bool no_random = 0); // from old mapsector: - typedef concurrent_unordered_map - m_blocks_type; + using m_blocks_type = concurrent_unordered_map; m_blocks_type m_blocks; - typedef concurrent_shared_unordered_map, v3posHash, - v3posEqual> - m_far_blocks_type; + using m_far_blocks_type = concurrent_shared_unordered_map; m_far_blocks_type m_far_blocks; std::vector> m_far_blocks_delete; bool m_far_blocks_currrent = false; - using far_blocks_list_type = concurrent_shared_unordered_map; - far_blocks_list_type m_far_blocks_1, m_far_blocks_2; - far_blocks_list_type *m_far_blocks_use = &m_far_blocks_1, - *m_far_blocks_fill = &m_far_blocks_1; + using far_blocks_req_t = std::unordered_map>; // server + using far_blocks_ask_t = concurrent_shared_unordered_map>; // client + far_blocks_ask_t m_far_blocks_ask; std::array, FARMESH_STEP_MAX> far_blocks_storage; //double m_far_blocks_created = 0; //double m_far_blocks_sent = 0; float far_blocks_sent_timer = 1; v3pos_t m_far_blocks_last_cam_pos; - std::vector> m_far_blocks_delete_1, m_far_blocks_delete_2; + std::vector m_far_blocks_delete_1, m_far_blocks_delete_2; bool m_far_blocks_delete_current = false; //static constexpr bool m_far_fast = true; // show generated far farmesh stable(0) or instant(1) diff --git a/src/mapblock.h b/src/mapblock.h index 55329a85a..b58a4df00 100644 --- a/src/mapblock.h +++ b/src/mapblock.h @@ -534,8 +534,6 @@ class MapBlock std::mutex far_mutex; u32 mesh_requested_timestamp = 0; uint8_t mesh_requested_step = 0; - uint32_t farmesh_need_remake {}; - uint32_t farmesh_created {}; private: std::array m_lod_mesh; diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 1625ca9d7..b5c9b24a5 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -342,6 +342,7 @@ void Client::handleCommand_BlockData(NetworkPacket* pkt) return; }; block->deSerializeNetworkSpecific(istr); + ++m_new_meshes; } if (m_localdb) { diff --git a/src/threading/concurrent_list.h b/src/threading/concurrent_list.h index bcacb6c85..1463bafc6 100644 --- a/src/threading/concurrent_list.h +++ b/src/threading/concurrent_list.h @@ -25,13 +25,15 @@ along with Freeminer. If not, see . #include "lock.h" -template > +template > class concurrent_list_ : public std::list, public LOCKER { public: typedef typename std::list full_type; typedef T mapped_type; + ~concurrent_list_() { clear(); } + template decltype(auto) assign(Args &&...args) { @@ -119,26 +121,25 @@ class concurrent_list_ : public std::list, public LOCKER } }; -template > +template > using concurrent_list = concurrent_list_, T, Allocator>; #if ENABLE_THREADS -template > +template > using maybe_concurrent_list = concurrent_list; #else -template > -class not_concurrent_list : public std::list, - public dummy_locker +template > +class not_concurrent_list : public std::list, public dummy_locker { public: typedef typename std::list full_type; typedef T mapped_type; }; -template > +template > using maybe_concurrent_list = not_concurrent_list; #endif diff --git a/src/threading/concurrent_map.h b/src/threading/concurrent_map.h index d0fdfa563..80ccc998d 100644 --- a/src/threading/concurrent_map.h +++ b/src/threading/concurrent_map.h @@ -39,6 +39,8 @@ class concurrent_map_ : public std::map, public LOCK mapped_type nothing = {}; + ~concurrent_map_() { clear(); } + template mapped_type &get(Args &&...args) { diff --git a/src/threading/concurrent_set.h b/src/threading/concurrent_set.h index c75ce5332..16ae8036f 100644 --- a/src/threading/concurrent_set.h +++ b/src/threading/concurrent_set.h @@ -56,6 +56,8 @@ class concurrent_set_ : public std::set, public LOCKER mapped_type nothing = {}; + ~concurrent_set_() { clear(); } + template mapped_type &get(Args &&...args) { diff --git a/src/threading/concurrent_unordered_map.h b/src/threading/concurrent_unordered_map.h index d4ab5b22a..a21b8316b 100644 --- a/src/threading/concurrent_unordered_map.h +++ b/src/threading/concurrent_unordered_map.h @@ -49,6 +49,8 @@ class concurrent_unordered_map_ : public std::unordered_map decltype(auto) operator=(Args &&...args) { diff --git a/src/threading/concurrent_unordered_set.h b/src/threading/concurrent_unordered_set.h index 45bc2a78c..2e94348e5 100644 --- a/src/threading/concurrent_unordered_set.h +++ b/src/threading/concurrent_unordered_set.h @@ -33,6 +33,8 @@ class concurrent_unordered_set_ : public std::unordered_set; + ~concurrent_unordered_set_() { clear(); } + template Value &at_or(Args &&...args, const Value ¬hing = {}) { diff --git a/src/threading/concurrent_vector.h b/src/threading/concurrent_vector.h index f099157c1..297f49e6f 100644 --- a/src/threading/concurrent_vector.h +++ b/src/threading/concurrent_vector.h @@ -41,6 +41,8 @@ class concurrent_vector_ : public std::vector, public LOCKER typedef typename full_type::const_iterator const_iterator; typedef typename full_type::iterator iterator; + ~concurrent_vector_() { clear(); } + template decltype(auto) operator=(Args &&...args) { diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt index 34007ffa2..9768916ce 100644 --- a/src/unittest/CMakeLists.txt +++ b/src/unittest/CMakeLists.txt @@ -1,5 +1,5 @@ set (UNITTEST_SRCS - ${CMAKE_CURRENT_SOURCE_DIR}/test_lock.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/fm_test_lock.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_address.cpp diff --git a/src/unittest/test_lock.cpp b/src/unittest/fm_test_lock.cpp similarity index 100% rename from src/unittest/test_lock.cpp rename to src/unittest/fm_test_lock.cpp diff --git a/util/autotest/auto.pl b/util/autotest/auto.pl index 56057e266..936b333c9 100755 --- a/util/autotest/auto.pl +++ b/util/autotest/auto.pl @@ -840,7 +840,7 @@ () sub { $config->{cmake_opt}{CMAKE_INSTALL_PREFIX} = $config->{logdir} . '/install'; 0 }, 'build', sub { sy qq{nice cmake --install .}; }, ], - test => ['build', {'---show_profiler_graph' => 0, -show_profiler_graph => 0}, 'run_test'], + test => ['build_client', {'---show_profiler_graph' => 0, -show_profiler_graph => 0}, 'run_test'], }; sub dmp (@) { say +(join ' ', (caller)[0 .. 5]), ' ', Data::Dumper::Dumper \@_ } @@ -892,7 +892,7 @@ (;$$) sub sy (@) { say 'running ', join ' ', @_; file_append("$config->{logdir}/run.sh", join(' ', @_), "\n"); - return sig(system @_); + return sig system 'bash', '-c', join ' ', @_; } sub sytee (@) {