From dfba79f8ff974963d86777399123a995a2cafcf2 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 6 Mar 2024 21:04:57 +0100 Subject: [PATCH] Split servermap.cpp/h off from map.cpp/h --- src/CMakeLists.txt | 1 + src/emerge.cpp | 2 +- src/emerge.h | 2 +- src/map.cpp | 1197 -------------------------- src/map.h | 175 +--- src/mapgen/treegen.cpp | 2 +- src/network/clientpackethandler.cpp | 2 +- src/server.cpp | 2 +- src/serverenvironment.cpp | 1 - src/serverenvironment.h | 2 +- src/servermap.cpp | 1230 +++++++++++++++++++++++++++ src/servermap.h | 195 +++++ 12 files changed, 1433 insertions(+), 1378 deletions(-) create mode 100644 src/servermap.cpp create mode 100644 src/servermap.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 120ef277f..00f6b724e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -417,6 +417,7 @@ set(common_SRCS serialization.cpp server.cpp serverenvironment.cpp + servermap.cpp settings.cpp staticobject.cpp terminal_chat_console.cpp diff --git a/src/emerge.cpp b/src/emerge.cpp index bef1e34ac..5b778db69 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlicht_changes/printing.h" #include "filesys.h" #include "log.h" -#include "map.h" +#include "servermap.h" #include "mapblock.h" #include "mapgen/mg_biome.h" #include "mapgen/mg_ore.h" diff --git a/src/emerge.h b/src/emerge.h index ace9f6e46..6e4223c2d 100644 --- a/src/emerge.h +++ b/src/emerge.h @@ -39,7 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc., class EmergeThread; class NodeDefManager; class Settings; - +class MapSettingsManager; class BiomeManager; class OreManager; class DecorationManager; diff --git a/src/map.cpp b/src/map.cpp index 22731f120..cea2ed31c 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -20,13 +20,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "map.h" #include "mapsector.h" #include "mapblock.h" -#include "filesys.h" #include "voxel.h" #include "voxelalgorithms.h" #include "porting.h" -#include "serialization.h" #include "nodemetadata.h" -#include "settings.h" #include "log.h" #include "profiler.h" #include "nodedef.h" @@ -34,29 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/directiontables.h" #include "rollback_interface.h" #include "environment.h" -#include "reflowscan.h" -#include "emerge.h" -#include "mapgen/mapgen_v6.h" -#include "mapgen/mg_biome.h" -#include "config.h" -#include "server.h" -#include "database/database.h" -#include "database/database-dummy.h" -#include "database/database-sqlite3.h" -#include "script/scripting_server.h" #include "irrlicht_changes/printing.h" -#include -#include -#if USE_LEVELDB -#include "database/database-leveldb.h" -#endif -#if USE_REDIS -#include "database/database-redis.h" -#endif -#if USE_POSTGRESQL -#include "database/database-postgresql.h" -#endif - /* Map @@ -478,446 +453,6 @@ void Map::PrintInfo(std::ostream &out) out<<"Map: "; } -#define WATER_DROP_BOOST 4 - -const static v3s16 liquid_6dirs[6] = { - // order: upper before same level before lower - v3s16( 0, 1, 0), - v3s16( 0, 0, 1), - v3s16( 1, 0, 0), - v3s16( 0, 0,-1), - v3s16(-1, 0, 0), - v3s16( 0,-1, 0) -}; - -enum NeighborType : u8 { - NEIGHBOR_UPPER, - NEIGHBOR_SAME_LEVEL, - NEIGHBOR_LOWER -}; - -struct NodeNeighbor { - MapNode n; - NeighborType t; - v3s16 p; - - NodeNeighbor() - : n(CONTENT_AIR), t(NEIGHBOR_SAME_LEVEL) - { } - - NodeNeighbor(const MapNode &node, NeighborType n_type, const v3s16 &pos) - : n(node), - t(n_type), - p(pos) - { } -}; - -static s8 get_max_liquid_level(NodeNeighbor nb, s8 current_max_node_level) -{ - s8 max_node_level = current_max_node_level; - u8 nb_liquid_level = (nb.n.param2 & LIQUID_LEVEL_MASK); - switch (nb.t) { - case NEIGHBOR_UPPER: - if (nb_liquid_level + WATER_DROP_BOOST > current_max_node_level) { - max_node_level = LIQUID_LEVEL_MAX; - if (nb_liquid_level + WATER_DROP_BOOST < LIQUID_LEVEL_MAX) - max_node_level = nb_liquid_level + WATER_DROP_BOOST; - } else if (nb_liquid_level > current_max_node_level) { - max_node_level = nb_liquid_level; - } - break; - case NEIGHBOR_LOWER: - break; - case NEIGHBOR_SAME_LEVEL: - if ((nb.n.param2 & LIQUID_FLOW_DOWN_MASK) != LIQUID_FLOW_DOWN_MASK && - nb_liquid_level > 0 && nb_liquid_level - 1 > max_node_level) - max_node_level = nb_liquid_level - 1; - break; - } - return max_node_level; -} - -void ServerMap::transforming_liquid_add(v3s16 p) { - m_transforming_liquid.push_back(p); -} - -void ServerMap::transformLiquids(std::map &modified_blocks, - ServerEnvironment *env) -{ - u32 loopcount = 0; - u32 initial_size = m_transforming_liquid.size(); - - /*if(initial_size != 0) - infostream<<"transformLiquids(): initial_size="< must_reflow; - - std::vector > changed_nodes; - - std::vector check_for_falling; - - u32 liquid_loop_max = g_settings->getS32("liquid_loop_max"); - u32 loop_max = liquid_loop_max; - - while (m_transforming_liquid.size() != 0) - { - // This should be done here so that it is done when continue is used - if (loopcount >= initial_size || loopcount >= loop_max) - break; - loopcount++; - - /* - Get a queued transforming liquid node - */ - v3s16 p0 = m_transforming_liquid.front(); - m_transforming_liquid.pop_front(); - - MapNode n0 = getNode(p0); - - /* - Collect information about current node - */ - s8 liquid_level = -1; - // The liquid node which will be placed there if - // the liquid flows into this node. - content_t liquid_kind = CONTENT_IGNORE; - // The node which will be placed there if liquid - // can't flow into this node. - content_t floodable_node = CONTENT_AIR; - const ContentFeatures &cf = m_nodedef->get(n0); - LiquidType liquid_type = cf.liquid_type; - switch (liquid_type) { - case LIQUID_SOURCE: - liquid_level = LIQUID_LEVEL_SOURCE; - liquid_kind = cf.liquid_alternative_flowing_id; - break; - case LIQUID_FLOWING: - liquid_level = (n0.param2 & LIQUID_LEVEL_MASK); - liquid_kind = n0.getContent(); - break; - case LIQUID_NONE: - // if this node is 'floodable', it *could* be transformed - // into a liquid, otherwise, continue with the next node. - if (!cf.floodable) - continue; - floodable_node = n0.getContent(); - liquid_kind = CONTENT_AIR; - break; - case LiquidType_END: - break; - } - - /* - Collect information about the environment - */ - NodeNeighbor sources[6]; // surrounding sources - int num_sources = 0; - NodeNeighbor flows[6]; // surrounding flowing liquid nodes - int num_flows = 0; - NodeNeighbor airs[6]; // surrounding air - int num_airs = 0; - NodeNeighbor neutrals[6]; // nodes that are solid or another kind of liquid - int num_neutrals = 0; - bool flowing_down = false; - bool ignored_sources = false; - bool floating_node_above = false; - for (u16 i = 0; i < 6; i++) { - NeighborType nt = NEIGHBOR_SAME_LEVEL; - switch (i) { - case 0: - nt = NEIGHBOR_UPPER; - break; - case 5: - nt = NEIGHBOR_LOWER; - break; - default: - break; - } - v3s16 npos = p0 + liquid_6dirs[i]; - NodeNeighbor nb(getNode(npos), nt, npos); - const ContentFeatures &cfnb = m_nodedef->get(nb.n); - if (nt == NEIGHBOR_UPPER && cfnb.floats) - floating_node_above = true; - switch (cfnb.liquid_type) { - case LIQUID_NONE: - if (cfnb.floodable) { - airs[num_airs++] = nb; - // if the current node is a water source the neighbor - // should be enqueded for transformation regardless of whether the - // current node changes or not. - if (nb.t != NEIGHBOR_UPPER && liquid_type != LIQUID_NONE) - m_transforming_liquid.push_back(npos); - // if the current node happens to be a flowing node, it will start to flow down here. - if (nb.t == NEIGHBOR_LOWER) - flowing_down = true; - } else { - neutrals[num_neutrals++] = nb; - if (nb.n.getContent() == CONTENT_IGNORE) { - // If node below is ignore prevent water from - // spreading outwards and otherwise prevent from - // flowing away as ignore node might be the source - if (nb.t == NEIGHBOR_LOWER) - flowing_down = true; - else - ignored_sources = true; - } - } - break; - case LIQUID_SOURCE: - // if this node is not (yet) of a liquid type, choose the first liquid type we encounter - if (liquid_kind == CONTENT_AIR) - liquid_kind = cfnb.liquid_alternative_flowing_id; - if (cfnb.liquid_alternative_flowing_id != liquid_kind) { - neutrals[num_neutrals++] = nb; - } else { - // Do not count bottom source, it will screw things up - if(nt != NEIGHBOR_LOWER) - sources[num_sources++] = nb; - } - break; - case LIQUID_FLOWING: - if (nb.t != NEIGHBOR_SAME_LEVEL || - (nb.n.param2 & LIQUID_FLOW_DOWN_MASK) != LIQUID_FLOW_DOWN_MASK) { - // if this node is not (yet) of a liquid type, choose the first liquid type we encounter - // but exclude falling liquids on the same level, they cannot flow here anyway - - // used to determine if the neighbor can even flow into this node - s8 max_level_from_neighbor = get_max_liquid_level(nb, -1); - u8 range = m_nodedef->get(cfnb.liquid_alternative_flowing_id).liquid_range; - - if (liquid_kind == CONTENT_AIR && - max_level_from_neighbor >= (LIQUID_LEVEL_MAX + 1 - range)) - liquid_kind = cfnb.liquid_alternative_flowing_id; - } - if (cfnb.liquid_alternative_flowing_id != liquid_kind) { - neutrals[num_neutrals++] = nb; - } else { - flows[num_flows++] = nb; - if (nb.t == NEIGHBOR_LOWER) - flowing_down = true; - } - break; - case LiquidType_END: - break; - } - } - - /* - decide on the type (and possibly level) of the current node - */ - content_t new_node_content; - s8 new_node_level = -1; - s8 max_node_level = -1; - - u8 range = m_nodedef->get(liquid_kind).liquid_range; - if (range > LIQUID_LEVEL_MAX + 1) - range = LIQUID_LEVEL_MAX + 1; - - if ((num_sources >= 2 && m_nodedef->get(liquid_kind).liquid_renewable) || liquid_type == LIQUID_SOURCE) { - // liquid_kind will be set to either the flowing alternative of the node (if it's a liquid) - // or the flowing alternative of the first of the surrounding sources (if it's air), so - // it's perfectly safe to use liquid_kind here to determine the new node content. - new_node_content = m_nodedef->get(liquid_kind).liquid_alternative_source_id; - } else if (num_sources >= 1 && sources[0].t != NEIGHBOR_LOWER) { - // liquid_kind is set properly, see above - max_node_level = new_node_level = LIQUID_LEVEL_MAX; - if (new_node_level >= (LIQUID_LEVEL_MAX + 1 - range)) - new_node_content = liquid_kind; - else - new_node_content = floodable_node; - } else if (ignored_sources && liquid_level >= 0) { - // Maybe there are neighboring sources that aren't loaded yet - // so prevent flowing away. - new_node_level = liquid_level; - new_node_content = liquid_kind; - } else { - // no surrounding sources, so get the maximum level that can flow into this node - for (u16 i = 0; i < num_flows; i++) { - max_node_level = get_max_liquid_level(flows[i], max_node_level); - } - - u8 viscosity = m_nodedef->get(liquid_kind).liquid_viscosity; - if (viscosity > 1 && max_node_level != liquid_level) { - // amount to gain, limited by viscosity - // must be at least 1 in absolute value - s8 level_inc = max_node_level - liquid_level; - if (level_inc < -viscosity || level_inc > viscosity) - new_node_level = liquid_level + level_inc/viscosity; - else if (level_inc < 0) - new_node_level = liquid_level - 1; - else if (level_inc > 0) - new_node_level = liquid_level + 1; - if (new_node_level != max_node_level) - must_reflow.push_back(p0); - } else { - new_node_level = max_node_level; - } - - if (max_node_level >= (LIQUID_LEVEL_MAX + 1 - range)) - new_node_content = liquid_kind; - else - new_node_content = floodable_node; - - } - - /* - check if anything has changed. if not, just continue with the next node. - */ - if (new_node_content == n0.getContent() && - (m_nodedef->get(n0.getContent()).liquid_type != LIQUID_FLOWING || - ((n0.param2 & LIQUID_LEVEL_MASK) == (u8)new_node_level && - ((n0.param2 & LIQUID_FLOW_DOWN_MASK) == LIQUID_FLOW_DOWN_MASK) - == flowing_down))) - continue; - - /* - check if there is a floating node above that needs to be updated. - */ - if (floating_node_above && new_node_content == CONTENT_AIR) - check_for_falling.push_back(p0); - - /* - update the current node - */ - MapNode n00 = n0; - //bool flow_down_enabled = (flowing_down && ((n0.param2 & LIQUID_FLOW_DOWN_MASK) != LIQUID_FLOW_DOWN_MASK)); - if (m_nodedef->get(new_node_content).liquid_type == LIQUID_FLOWING) { - // set level to last 3 bits, flowing down bit to 4th bit - n0.param2 = (flowing_down ? LIQUID_FLOW_DOWN_MASK : 0x00) | (new_node_level & LIQUID_LEVEL_MASK); - } else { - // set the liquid level and flow bits to 0 - n0.param2 &= ~(LIQUID_LEVEL_MASK | LIQUID_FLOW_DOWN_MASK); - } - - // change the node. - n0.setContent(new_node_content); - - // on_flood() the node - if (floodable_node != CONTENT_AIR) { - if (env->getScriptIface()->node_on_flood(p0, n00, n0)) - continue; - } - - // Ignore light (because calling voxalgo::update_lighting_nodes) - ContentLightingFlags f0 = m_nodedef->getLightingFlags(n0); - n0.setLight(LIGHTBANK_DAY, 0, f0); - n0.setLight(LIGHTBANK_NIGHT, 0, f0); - - // Find out whether there is a suspect for this action - std::string suspect; - if (m_gamedef->rollback()) - suspect = m_gamedef->rollback()->getSuspect(p0, 83, 1); - - if (m_gamedef->rollback() && !suspect.empty()) { - // Blame suspect - RollbackScopeActor rollback_scope(m_gamedef->rollback(), suspect, true); - // Get old node for rollback - RollbackNode rollback_oldnode(this, p0, m_gamedef); - // Set node - setNode(p0, n0); - // Report - RollbackNode rollback_newnode(this, p0, m_gamedef); - RollbackAction action; - action.setSetNode(p0, rollback_oldnode, rollback_newnode); - m_gamedef->rollback()->reportAction(action); - } else { - // Set node - setNode(p0, n0); - } - - v3s16 blockpos = getNodeBlockPos(p0); - MapBlock *block = getBlockNoCreateNoEx(blockpos); - if (block != NULL) { - modified_blocks[blockpos] = block; - changed_nodes.emplace_back(p0, n00); - } - - /* - enqueue neighbors for update if necessary - */ - switch (m_nodedef->get(n0.getContent()).liquid_type) { - case LIQUID_SOURCE: - case LIQUID_FLOWING: - // make sure source flows into all neighboring nodes - for (u16 i = 0; i < num_flows; i++) - if (flows[i].t != NEIGHBOR_UPPER) - m_transforming_liquid.push_back(flows[i].p); - for (u16 i = 0; i < num_airs; i++) - if (airs[i].t != NEIGHBOR_UPPER) - m_transforming_liquid.push_back(airs[i].p); - break; - case LIQUID_NONE: - // this flow has turned to air; neighboring flows might need to do the same - for (u16 i = 0; i < num_flows; i++) - m_transforming_liquid.push_back(flows[i].p); - break; - case LiquidType_END: - break; - } - } - //infostream<<"Map::transformLiquids(): loopcount="<getScriptIface()->check_for_falling(p); - } - - env->getScriptIface()->on_liquid_transformed(changed_nodes); - - /* ---------------------------------------------------------------------- - * Manage the queue so that it does not grow indefinitely - */ - u16 time_until_purge = g_settings->getU16("liquid_queue_purge_time"); - - if (time_until_purge == 0) - return; // Feature disabled - - time_until_purge *= 1000; // seconds -> milliseconds - - u64 curr_time = porting::getTimeMs(); - u32 prev_unprocessed = m_unprocessed_count; - m_unprocessed_count = m_transforming_liquid.size(); - - // if unprocessed block count is decreasing or stable - if (m_unprocessed_count <= prev_unprocessed) { - m_queue_size_timer_started = false; - } else { - if (!m_queue_size_timer_started) - m_inc_trending_up_start_time = curr_time; - m_queue_size_timer_started = true; - } - - // Account for curr_time overflowing - if (m_queue_size_timer_started && m_inc_trending_up_start_time > curr_time) - m_queue_size_timer_started = false; - - /* If the queue has been growing for more than liquid_queue_purge_time seconds - * and the number of unprocessed blocks is still > liquid_loop_max then we - * cannot keep up; dump the oldest blocks from the queue so that the queue - * has liquid_loop_max items in it - */ - if (m_queue_size_timer_started - && curr_time - m_inc_trending_up_start_time > time_until_purge - && m_unprocessed_count > liquid_loop_max) { - - size_t dump_qty = m_unprocessed_count - liquid_loop_max; - - infostream << "transformLiquids(): DUMPING " << dump_qty - << " blocks from the queue" << std::endl; - - while (dump_qty--) - m_transforming_liquid.pop_front(); - - m_queue_size_timer_started = false; // optimistically assume we can keep up now - m_unprocessed_count = m_transforming_liquid.size(); - } -} - std::vector Map::findNodesWithMetadata(v3s16 p1, v3s16 p2) { std::vector positions_with_meta; @@ -1225,738 +760,6 @@ bool Map::isBlockOccluded(v3s16 pos_relative, v3s16 cam_pos_nodes, bool simple_c return true; } -/* - ServerMap -*/ -ServerMap::ServerMap(const std::string &savedir, IGameDef *gamedef, - EmergeManager *emerge, MetricsBackend *mb): - Map(gamedef), - settings_mgr(savedir + DIR_DELIM + "map_meta.txt"), - m_emerge(emerge) -{ - verbosestream<map_settings_mgr = &settings_mgr; - - /* - Try to load map; if not found, create a new one. - */ - - // Determine which database backend to use - std::string conf_path = savedir + DIR_DELIM + "world.mt"; - Settings conf; - bool succeeded = conf.readConfigFile(conf_path.c_str()); - if (!succeeded || !conf.exists("backend")) { - // fall back to sqlite3 - conf.set("backend", "sqlite3"); - } - std::string backend = conf.get("backend"); - dbase = createDatabase(backend, savedir, conf); - if (conf.exists("readonly_backend")) { - std::string readonly_dir = savedir + DIR_DELIM + "readonly"; - dbase_ro = createDatabase(conf.get("readonly_backend"), readonly_dir, conf); - } - if (!conf.updateConfigFile(conf_path.c_str())) - errorstream << "ServerMap::ServerMap(): Failed to update world.mt!" << std::endl; - - m_savedir = savedir; - m_map_saving_enabled = false; - - m_save_time_counter = mb->addCounter( - "minetest_map_save_time", "Time spent saving blocks (in microseconds)"); - m_save_count_counter = mb->addCounter( - "minetest_map_saved_blocks", "Number of blocks saved"); - m_loaded_blocks_gauge = mb->addGauge( - "minetest_map_loaded_blocks", "Number of loaded blocks"); - - m_map_compression_level = rangelim(g_settings->getS16("map_compression_level_disk"), -1, 9); - - try { - // If directory exists, check contents and load if possible - if (fs::PathExists(m_savedir)) { - // If directory is empty, it is safe to save into it. - if (fs::GetDirListing(m_savedir).empty()) { - infostream<<"ServerMap: Empty save directory is valid." - <seed; -} - -bool ServerMap::blockpos_over_mapgen_limit(v3s16 p) -{ - const s16 mapgen_limit_bp = rangelim( - getMapgenParams()->mapgen_limit, 0, MAX_MAP_GENERATION_LIMIT) / - MAP_BLOCKSIZE; - return p.X < -mapgen_limit_bp || - p.X > mapgen_limit_bp || - p.Y < -mapgen_limit_bp || - p.Y > mapgen_limit_bp || - p.Z < -mapgen_limit_bp || - p.Z > mapgen_limit_bp; -} - -bool ServerMap::initBlockMake(v3s16 blockpos, BlockMakeData *data) -{ - s16 csize = getMapgenParams()->chunksize; - v3s16 bpmin = EmergeManager::getContainingChunk(blockpos, csize); - v3s16 bpmax = bpmin + v3s16(1, 1, 1) * (csize - 1); - - if (!m_chunks_in_progress.insert(bpmin).second) - return false; - - bool enable_mapgen_debug_info = m_emerge->enable_mapgen_debug_info; - EMERGE_DBG_OUT("initBlockMake(): " << bpmin << " - " << bpmax); - - v3s16 extra_borders(1, 1, 1); - v3s16 full_bpmin = bpmin - extra_borders; - v3s16 full_bpmax = bpmax + extra_borders; - - // Do nothing if not inside mapgen limits (+-1 because of neighbors) - if (blockpos_over_mapgen_limit(full_bpmin) || - blockpos_over_mapgen_limit(full_bpmax)) - return false; - - data->seed = getSeed(); - data->blockpos_min = bpmin; - data->blockpos_max = bpmax; - data->nodedef = m_nodedef; - - /* - Create the whole area of this and the neighboring blocks - */ - for (s16 x = full_bpmin.X; x <= full_bpmax.X; x++) - for (s16 z = full_bpmin.Z; z <= full_bpmax.Z; z++) { - v2s16 sectorpos(x, z); - // Sector metadata is loaded from disk if not already loaded. - MapSector *sector = createSector(sectorpos); - FATAL_ERROR_IF(sector == NULL, "createSector() failed"); - - for (s16 y = full_bpmin.Y; y <= full_bpmax.Y; y++) { - v3s16 p(x, y, z); - - MapBlock *block = emergeBlock(p, false); - if (block == NULL) { - block = createBlock(p); - - // Block gets sunlight if this is true. - // Refer to the map generator heuristics. - bool ug = m_emerge->isBlockUnderground(p); - block->setIsUnderground(ug); - } - } - } - - /* - Now we have a big empty area. - - Make a ManualMapVoxelManipulator that contains this and the - neighboring blocks - */ - - data->vmanip = new MMVManip(this); - data->vmanip->initialEmerge(full_bpmin, full_bpmax); - - // Data is ready now. - return true; -} - -void ServerMap::finishBlockMake(BlockMakeData *data, - std::map *changed_blocks) -{ - v3s16 bpmin = data->blockpos_min; - v3s16 bpmax = data->blockpos_max; - - bool enable_mapgen_debug_info = m_emerge->enable_mapgen_debug_info; - EMERGE_DBG_OUT("finishBlockMake(): " << bpmin << " - " << bpmax); - - /* - Blit generated stuff to map - NOTE: blitBackAll adds nearly everything to changed_blocks - */ - data->vmanip->blitBackAll(changed_blocks); - - EMERGE_DBG_OUT("finishBlockMake: changed_blocks.size()=" - << changed_blocks->size()); - - /* - Copy transforming liquid information - */ - while (data->transforming_liquid.size()) { - m_transforming_liquid.push_back(data->transforming_liquid.front()); - data->transforming_liquid.pop_front(); - } - - for (auto &changed_block : *changed_blocks) { - MapBlock *block = changed_block.second; - if (!block) - continue; - /* - Update is air cache of the MapBlocks - */ - block->expireIsAirCache(); - /* - Set block as modified - */ - block->raiseModified(MOD_STATE_WRITE_NEEDED, - MOD_REASON_EXPIRE_IS_AIR); - } - - /* - Set central blocks as generated - */ - for (s16 x = bpmin.X; x <= bpmax.X; x++) - for (s16 z = bpmin.Z; z <= bpmax.Z; z++) - for (s16 y = bpmin.Y; y <= bpmax.Y; y++) { - MapBlock *block = getBlockNoCreateNoEx(v3s16(x, y, z)); - if (!block) - continue; - - block->setGenerated(true); - } - - /* - Save changed parts of map - NOTE: Will be saved later. - */ - //save(MOD_STATE_WRITE_AT_UNLOAD); - m_chunks_in_progress.erase(bpmin); -} - -MapSector *ServerMap::createSector(v2s16 p2d) -{ - /* - Check if it exists already in memory - */ - MapSector *sector = getSectorNoGenerate(p2d); - if (sector) - return sector; - - /* - Do not create over max mapgen limit - */ - if (blockpos_over_max_limit(v3s16(p2d.X, 0, p2d.Y))) - throw InvalidPositionException("createSector(): pos over max mapgen limit"); - - /* - Generate blank sector - */ - sector = new MapSector(this, p2d, m_gamedef); - - /* - Insert to container - */ - m_sectors[p2d] = sector; - - return sector; -} - -MapBlock * ServerMap::createBlock(v3s16 p) -{ - v2s16 p2d(p.X, p.Z); - s16 block_y = p.Y; - - /* - This will create or load a sector if not found in memory. - */ - MapSector *sector; - try { - sector = createSector(p2d); - } catch (InvalidPositionException &e) { - infostream<<"createBlock: createSector() failed"<getBlockNoCreateNoEx(block_y); - if (block) - return block; - - // Create blank - try { - block = sector->createBlankBlock(block_y); - } catch (InvalidPositionException &e) { - infostream << "createBlock: createBlankBlock() failed" << std::endl; - throw e; - } - - return block; -} - -MapBlock * ServerMap::emergeBlock(v3s16 p, bool create_blank) -{ - { - MapBlock *block = getBlockNoCreateNoEx(p); - if (block) - return block; - } - - { - MapBlock *block = loadBlock(p); - if(block) - return block; - } - - if (create_blank) { - try { - MapSector *sector = createSector(v2s16(p.X, p.Z)); - return sector->createBlankBlock(p.Y); - } catch (InvalidPositionException &e) {} - } - - return NULL; -} - -MapBlock *ServerMap::getBlockOrEmerge(v3s16 p3d, bool generate) -{ - MapBlock *block = getBlockNoCreateNoEx(p3d); - if (block == NULL) - m_emerge->enqueueBlockEmerge(PEER_ID_INEXISTENT, p3d, generate); - - return block; -} - -bool ServerMap::isBlockInQueue(v3s16 pos) -{ - return m_emerge && m_emerge->isBlockInQueue(pos); -} - -void ServerMap::addNodeAndUpdate(v3s16 p, MapNode n, - std::map &modified_blocks, - bool remove_metadata) -{ - Map::addNodeAndUpdate(p, n, modified_blocks, remove_metadata); - - /* - Add neighboring liquid nodes and this node to transform queue. - (it's vital for the node itself to get updated last, if it was removed.) - */ - - for (const v3s16 &dir : g_7dirs) { - v3s16 p2 = p + dir; - - bool is_valid_position; - MapNode n2 = getNode(p2, &is_valid_position); - if(is_valid_position && - (m_nodedef->get(n2).isLiquid() || - n2.getContent() == CONTENT_AIR)) - m_transforming_liquid.push_back(p2); - } -} - -// N.B. This requires no synchronization, since data will not be modified unless -// the VoxelManipulator being updated belongs to the same thread. -void ServerMap::updateVManip(v3s16 pos) -{ - Mapgen *mg = m_emerge->getCurrentMapgen(); - if (!mg) - return; - - MMVManip *vm = mg->vm; - if (!vm) - return; - - if (!vm->m_area.contains(pos)) - return; - - s32 idx = vm->m_area.index(pos); - vm->m_data[idx] = getNode(pos); - vm->m_flags[idx] &= ~VOXELFLAG_NO_DATA; - - vm->m_is_dirty = true; -} - -void ServerMap::reportMetrics(u64 save_time_us, u32 saved_blocks, u32 all_blocks) -{ - m_loaded_blocks_gauge->set(all_blocks); - m_save_time_counter->increment(save_time_us); - m_save_count_counter->increment(saved_blocks); -} - -void ServerMap::save(ModifiedState save_level) -{ - if (!m_map_saving_enabled) { - warningstream<<"Not saving map, saving disabled."<getBlocks(blocks); - - for (MapBlock *block : blocks) { - block_count_all++; - - if(block->getModified() >= (u32)save_level) { - // Lazy beginSave() - if(!save_started) { - beginSave(); - save_started = true; - } - - modprofiler.add(block->getModifiedReasonString(), 1); - - saveBlock(block); - block_count++; - } - } - } - - if(save_started) - endSave(); - - /* - Only print if something happened or saved whole map - */ - if(save_level == MOD_STATE_CLEAN - || block_count != 0) { - infostream << "ServerMap: Written: " - << block_count << " blocks" - << ", " << block_count_all << " blocks in memory." - << std::endl; - PrintInfo(infostream); // ServerMap/ClientMap: - infostream<<"Blocks modified by: "< &dst) -{ - dbase->listAllLoadableBlocks(dst); - if (dbase_ro) - dbase_ro->listAllLoadableBlocks(dst); -} - -void ServerMap::listAllLoadedBlocks(std::vector &dst) -{ - for (auto §or_it : m_sectors) { - MapSector *sector = sector_it.second; - - MapBlockVect blocks; - sector->getBlocks(blocks); - - for (MapBlock *block : blocks) { - v3s16 p = block->getPos(); - dst.push_back(p); - } - } -} - -MapDatabase *ServerMap::createDatabase( - const std::string &name, - const std::string &savedir, - Settings &conf) -{ - if (name == "sqlite3") - return new MapDatabaseSQLite3(savedir); - if (name == "dummy") - return new Database_Dummy(); - #if USE_LEVELDB - if (name == "leveldb") - return new Database_LevelDB(savedir); - #endif - #if USE_REDIS - if (name == "redis") - return new Database_Redis(conf); - #endif - #if USE_POSTGRESQL - if (name == "postgresql") { - std::string connect_string; - conf.getNoEx("pgsql_connection", connect_string); - return new MapDatabasePostgreSQL(connect_string); - } - #endif - - throw BaseException(std::string("Database backend ") + name + " not supported."); -} - -void ServerMap::beginSave() -{ - dbase->beginSave(); -} - -void ServerMap::endSave() -{ - dbase->endSave(); -} - -bool ServerMap::saveBlock(MapBlock *block) -{ - return saveBlock(block, dbase, m_map_compression_level); -} - -bool ServerMap::saveBlock(MapBlock *block, MapDatabase *db, int compression_level) -{ - v3s16 p3d = block->getPos(); - - // Format used for writing - u8 version = SER_FMT_VER_HIGHEST_WRITE; - - /* - [0] u8 serialization version - [1] data - */ - std::ostringstream o(std::ios_base::binary); - o.write((char*) &version, 1); - block->serialize(o, version, true, compression_level); - - // FIXME: zero copy possible in c++20 or with custom rdbuf - bool ret = db->saveBlock(p3d, o.str()); - if (ret) { - // We just wrote it to the disk so clear modified flag - block->resetModified(); - } - return ret; -} - -void ServerMap::loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, bool save_after_load) -{ - try { - std::istringstream is(*blob, std::ios_base::binary); - - u8 version = SER_FMT_VER_INVALID; - is.read((char*)&version, 1); - - if(is.fail()) - throw SerializationError("ServerMap::loadBlock(): Failed" - " to read MapBlock version"); - - MapBlock *block = nullptr; - std::unique_ptr block_created_new; - block = sector->getBlockNoCreateNoEx(p3d.Y); - if (!block) { - block_created_new = sector->createBlankBlockNoInsert(p3d.Y); - block = block_created_new.get(); - } - - { - ScopeProfiler sp(g_profiler, "ServerMap: deSer block", SPT_AVG); - // Read basic data - block->deSerialize(is, version, true); - } - - // If it's a new block, insert it to the map - if (block_created_new) { - sector->insertBlock(std::move(block_created_new)); - ReflowScan scanner(this, m_emerge->ndef); - scanner.scan(block, &m_transforming_liquid); - } - - /* - Save blocks loaded in old format in new format - */ - - //if(version < SER_FMT_VER_HIGHEST_READ || save_after_load) - // Only save if asked to; no need to update version - if(save_after_load) - saveBlock(block); - - // We just loaded it from, so it's up-to-date. - block->resetModified(); - } - catch(SerializationError &e) - { - errorstream<<"Invalid block data in database" - <<" ("<getBool("ignore_world_load_errors")){ - errorstream<<"Ignoring block load error. Duck and cover! " - <<"(ignore_world_load_errors)"<loadBlock(blockpos, &ret); - if (!ret.empty()) { - loadBlock(&ret, blockpos, createSector(p2d), false); - } else if (dbase_ro) { - dbase_ro->loadBlock(blockpos, &ret); - if (!ret.empty()) { - loadBlock(&ret, blockpos, createSector(p2d), false); - } - } else { - return NULL; - } - - MapBlock *block = getBlockNoCreateNoEx(blockpos); - if (created_new && (block != NULL)) { - std::map modified_blocks; - // Fix lighting if necessary - voxalgo::update_block_border_lighting(this, block, modified_blocks); - if (!modified_blocks.empty()) { - //Modified lighting, send event - MapEditEvent event; - event.type = MEET_OTHER; - event.setModifiedBlocks(modified_blocks); - dispatchEvent(event); - } - } - return block; -} - -bool ServerMap::deleteBlock(v3s16 blockpos) -{ - if (!dbase->deleteBlock(blockpos)) - return false; - - MapBlock *block = getBlockNoCreateNoEx(blockpos); - if (block) { - v2s16 p2d(blockpos.X, blockpos.Z); - MapSector *sector = getSectorNoGenerate(p2d); - if (!sector) - return false; - // It may not be safe to delete the block from memory at the moment - // (pointers to it could still be in use) - m_detached_blocks.push_back(sector->detachBlock(block)); - } - - return true; -} - -void ServerMap::deleteDetachedBlocks() -{ - for (const auto &block : m_detached_blocks) { - assert(block->isOrphan()); - (void)block; // silence unused-variable warning in release builds - } - - m_detached_blocks.clear(); -} - -void ServerMap::step() -{ - // Delete from memory blocks removed by deleteBlocks() only when pointers - // to them are (probably) no longer in use - deleteDetachedBlocks(); -} - -void ServerMap::PrintInfo(std::ostream &out) -{ - out<<"ServerMap: "; -} - -bool ServerMap::repairBlockLight(v3s16 blockpos, - std::map *modified_blocks) -{ - MapBlock *block = emergeBlock(blockpos, false); - if (!block || !block->isGenerated()) - return false; - voxalgo::repair_block_light(this, block, modified_blocks); - return true; -} - MMVManip::MMVManip(Map *map): VoxelManipulator(), m_map(map) diff --git a/src/map.h b/src/map.h index 482ff2859..44362bb5d 100644 --- a/src/map.h +++ b/src/map.h @@ -20,10 +20,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include -#include #include #include -#include #include "irrlichttypes_bloated.h" #include "mapblock.h" @@ -31,32 +29,20 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "constants.h" #include "voxel.h" #include "modifiedstate.h" -#include "util/container.h" -#include "util/metricsbackend.h" #include "util/numeric.h" #include "nodetimer.h" -#include "map_settings_manager.h" #include "debug.h" -class Settings; -class MapDatabase; -class ClientMap; class MapSector; -class ServerMapSector; -class MapBlock; class NodeMetadata; class IGameDef; class IRollbackManager; -class EmergeManager; -class MetricsBackend; -class ServerEnvironment; -struct BlockMakeData; /* MapEditEvent */ -enum MapEditEventType{ +enum MapEditEventType { // Node added (changed from air or something else to something) MEET_ADDNODE, // Node removed (changed to air) @@ -335,165 +321,6 @@ protected: u32 needed_count); }; -/* - ServerMap - - This is the only map class that is able to generate map. -*/ - -class ServerMap : public Map -{ -public: - /* - savedir: directory to which map data should be saved - */ - ServerMap(const std::string &savedir, IGameDef *gamedef, EmergeManager *emerge, MetricsBackend *mb); - ~ServerMap(); - - /* - Get a sector from somewhere. - - Check memory - - Check disk (doesn't load blocks) - - Create blank one - */ - MapSector *createSector(v2s16 p); - - /* - Blocks are generated by using these and makeBlock(). - */ - bool blockpos_over_mapgen_limit(v3s16 p); - bool initBlockMake(v3s16 blockpos, BlockMakeData *data); - void finishBlockMake(BlockMakeData *data, - std::map *changed_blocks); - - /* - Get a block from somewhere. - - Memory - - Create blank - */ - MapBlock *createBlock(v3s16 p); - - /* - Forcefully get a block from somewhere. - - Memory - - Load from disk - - Create blank filled with CONTENT_IGNORE - - */ - MapBlock *emergeBlock(v3s16 p, bool create_blank=true) override; - - /* - Try to get a block. - If it does not exist in memory, add it to the emerge queue. - - Memory - - Emerge Queue (deferred disk or generate) - */ - MapBlock *getBlockOrEmerge(v3s16 p3d, bool generate); - - bool isBlockInQueue(v3s16 pos); - - void addNodeAndUpdate(v3s16 p, MapNode n, - std::map &modified_blocks, - bool remove_metadata) override; - - /* - Database functions - */ - static MapDatabase *createDatabase(const std::string &name, const std::string &savedir, Settings &conf); - - // Call these before and after saving of blocks - void beginSave() override; - void endSave() override; - - void save(ModifiedState save_level) override; - void listAllLoadableBlocks(std::vector &dst); - void listAllLoadedBlocks(std::vector &dst); - - MapgenParams *getMapgenParams(); - - bool saveBlock(MapBlock *block) override; - static bool saveBlock(MapBlock *block, MapDatabase *db, int compression_level = -1); - MapBlock* loadBlock(v3s16 p); - // Database version - void loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, bool save_after_load=false); - - // Blocks are removed from the map but not deleted from memory until - // deleteDetachedBlocks() is called, since pointers to them may still exist - // when deleteBlock() is called. - bool deleteBlock(v3s16 blockpos) override; - - void deleteDetachedBlocks(); - - void step(); - - void updateVManip(v3s16 pos); - - // For debug printing - void PrintInfo(std::ostream &out) override; - - bool isSavingEnabled(){ return m_map_saving_enabled; } - - u64 getSeed(); - - /*! - * Fixes lighting in one map block. - * May modify other blocks as well, as light can spread - * out of the specified block. - * Returns false if the block is not generated (so nothing - * changed), true otherwise. - */ - bool repairBlockLight(v3s16 blockpos, - std::map *modified_blocks); - - void transformLiquids(std::map & modified_blocks, - ServerEnvironment *env); - - void transforming_liquid_add(v3s16 p); - - MapSettingsManager settings_mgr; - -protected: - - void reportMetrics(u64 save_time_us, u32 saved_blocks, u32 all_blocks) override; - -private: - friend class ModApiMapgen; // for m_transforming_liquid - - // Emerge manager - EmergeManager *m_emerge; - - std::string m_savedir; - bool m_map_saving_enabled; - - int m_map_compression_level; - - std::set m_chunks_in_progress; - - // used by deleteBlock() and deleteDetachedBlocks() - std::vector> m_detached_blocks; - - // Queued transforming water nodes - UniqueQueue m_transforming_liquid; - f32 m_transforming_liquid_loop_count_multiplier = 1.0f; - u32 m_unprocessed_count = 0; - u64 m_inc_trending_up_start_time = 0; // milliseconds - bool m_queue_size_timer_started = false; - - /* - Metadata is re-written on disk only if this is true. - This is reset to false when written on disk. - */ - bool m_map_metadata_changed = true; - MapDatabase *dbase = nullptr; - MapDatabase *dbase_ro = nullptr; - - // Map metrics - MetricGaugePtr m_loaded_blocks_gauge; - MetricCounterPtr m_save_time_counter; - MetricCounterPtr m_save_count_counter; -}; - - #define VMANIP_BLOCK_DATA_INEXIST 1 #define VMANIP_BLOCK_CONTAINS_CIGNORE 2 diff --git a/src/mapgen/treegen.cpp b/src/mapgen/treegen.cpp index adff72950..ab12233c1 100644 --- a/src/mapgen/treegen.cpp +++ b/src/mapgen/treegen.cpp @@ -23,7 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "util/pointer.h" #include "util/numeric.h" -#include "map.h" +#include "servermap.h" #include "mapblock.h" #include "nodedef.h" #include "treegen.h" diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 10ecdd34c..90f2bed5b 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "chatmessage.h" #include "client/clientmedia.h" #include "log.h" -#include "map.h" +#include "servermap.h" #include "mapsector.h" #include "client/minimap.h" #include "modchannels.h" diff --git a/src/server.cpp b/src/server.cpp index e85bbafd8..9e07c8c85 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -26,7 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "network/serveropcodes.h" #include "server/ban.h" #include "environment.h" -#include "map.h" +#include "servermap.h" #include "threading/mutex_auto_lock.h" #include "constants.h" #include "voxel.h" diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index 6f45f51c6..6b8e645a7 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -27,7 +27,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "nodedef.h" #include "nodemetadata.h" #include "gamedef.h" -#include "map.h" #include "porting.h" #include "profiler.h" #include "raycast.h" diff --git a/src/serverenvironment.h b/src/serverenvironment.h index 21accff7e..93244760d 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "activeobject.h" #include "environment.h" -#include "map.h" +#include "servermap.h" #include "settings.h" #include "server/activeobjectmgr.h" #include "util/numeric.h" diff --git a/src/servermap.cpp b/src/servermap.cpp new file mode 100644 index 000000000..0a2e54b4d --- /dev/null +++ b/src/servermap.cpp @@ -0,0 +1,1230 @@ +/* +Minetest +Copyright (C) 2010-2024 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "map.h" +#include "mapsector.h" +#include "filesys.h" +#include "voxel.h" +#include "voxelalgorithms.h" +#include "porting.h" +#include "serialization.h" +#include "settings.h" +#include "log.h" +#include "profiler.h" +#include "gamedef.h" +#include "util/directiontables.h" +#include "rollback_interface.h" +#include "reflowscan.h" +#include "emerge.h" +#include "mapgen/mapgen_v6.h" +#include "mapgen/mg_biome.h" +#include "config.h" +#include "server.h" +#include "database/database.h" +#include "database/database-dummy.h" +#include "database/database-sqlite3.h" +#include "script/scripting_server.h" +#include "irrlicht_changes/printing.h" +#if USE_LEVELDB +#include "database/database-leveldb.h" +#endif +#if USE_REDIS +#include "database/database-redis.h" +#endif +#if USE_POSTGRESQL +#include "database/database-postgresql.h" +#endif + +/* + ServerMap +*/ + +ServerMap::ServerMap(const std::string &savedir, IGameDef *gamedef, + EmergeManager *emerge, MetricsBackend *mb): + Map(gamedef), + settings_mgr(savedir + DIR_DELIM + "map_meta.txt"), + m_emerge(emerge) +{ + verbosestream<map_settings_mgr = &settings_mgr; + + /* + Try to load map; if not found, create a new one. + */ + + // Determine which database backend to use + std::string conf_path = savedir + DIR_DELIM + "world.mt"; + Settings conf; + bool succeeded = conf.readConfigFile(conf_path.c_str()); + if (!succeeded || !conf.exists("backend")) { + // fall back to sqlite3 + conf.set("backend", "sqlite3"); + } + std::string backend = conf.get("backend"); + dbase = createDatabase(backend, savedir, conf); + if (conf.exists("readonly_backend")) { + std::string readonly_dir = savedir + DIR_DELIM + "readonly"; + dbase_ro = createDatabase(conf.get("readonly_backend"), readonly_dir, conf); + } + if (!conf.updateConfigFile(conf_path.c_str())) + errorstream << "ServerMap::ServerMap(): Failed to update world.mt!" << std::endl; + + m_savedir = savedir; + m_map_saving_enabled = false; + + m_save_time_counter = mb->addCounter( + "minetest_map_save_time", "Time spent saving blocks (in microseconds)"); + m_save_count_counter = mb->addCounter( + "minetest_map_saved_blocks", "Number of blocks saved"); + m_loaded_blocks_gauge = mb->addGauge( + "minetest_map_loaded_blocks", "Number of loaded blocks"); + + m_map_compression_level = rangelim(g_settings->getS16("map_compression_level_disk"), -1, 9); + + try { + // If directory exists, check contents and load if possible + if (fs::PathExists(m_savedir)) { + // If directory is empty, it is safe to save into it. + if (fs::GetDirListing(m_savedir).empty()) { + infostream<<"ServerMap: Empty save directory is valid." + <seed; +} + +bool ServerMap::blockpos_over_mapgen_limit(v3s16 p) +{ + const s16 mapgen_limit_bp = rangelim( + getMapgenParams()->mapgen_limit, 0, MAX_MAP_GENERATION_LIMIT) / + MAP_BLOCKSIZE; + return p.X < -mapgen_limit_bp || + p.X > mapgen_limit_bp || + p.Y < -mapgen_limit_bp || + p.Y > mapgen_limit_bp || + p.Z < -mapgen_limit_bp || + p.Z > mapgen_limit_bp; +} + +bool ServerMap::initBlockMake(v3s16 blockpos, BlockMakeData *data) +{ + s16 csize = getMapgenParams()->chunksize; + v3s16 bpmin = EmergeManager::getContainingChunk(blockpos, csize); + v3s16 bpmax = bpmin + v3s16(1, 1, 1) * (csize - 1); + + if (!m_chunks_in_progress.insert(bpmin).second) + return false; + + bool enable_mapgen_debug_info = m_emerge->enable_mapgen_debug_info; + EMERGE_DBG_OUT("initBlockMake(): " << bpmin << " - " << bpmax); + + v3s16 extra_borders(1, 1, 1); + v3s16 full_bpmin = bpmin - extra_borders; + v3s16 full_bpmax = bpmax + extra_borders; + + // Do nothing if not inside mapgen limits (+-1 because of neighbors) + if (blockpos_over_mapgen_limit(full_bpmin) || + blockpos_over_mapgen_limit(full_bpmax)) + return false; + + data->seed = getSeed(); + data->blockpos_min = bpmin; + data->blockpos_max = bpmax; + data->nodedef = m_nodedef; + + /* + Create the whole area of this and the neighboring blocks + */ + for (s16 x = full_bpmin.X; x <= full_bpmax.X; x++) + for (s16 z = full_bpmin.Z; z <= full_bpmax.Z; z++) { + v2s16 sectorpos(x, z); + // Sector metadata is loaded from disk if not already loaded. + MapSector *sector = createSector(sectorpos); + FATAL_ERROR_IF(sector == NULL, "createSector() failed"); + + for (s16 y = full_bpmin.Y; y <= full_bpmax.Y; y++) { + v3s16 p(x, y, z); + + MapBlock *block = emergeBlock(p, false); + if (block == NULL) { + block = createBlock(p); + + // Block gets sunlight if this is true. + // Refer to the map generator heuristics. + bool ug = m_emerge->isBlockUnderground(p); + block->setIsUnderground(ug); + } + } + } + + /* + Now we have a big empty area. + + Make a ManualMapVoxelManipulator that contains this and the + neighboring blocks + */ + + data->vmanip = new MMVManip(this); + data->vmanip->initialEmerge(full_bpmin, full_bpmax); + + // Data is ready now. + return true; +} + +void ServerMap::finishBlockMake(BlockMakeData *data, + std::map *changed_blocks) +{ + v3s16 bpmin = data->blockpos_min; + v3s16 bpmax = data->blockpos_max; + + bool enable_mapgen_debug_info = m_emerge->enable_mapgen_debug_info; + EMERGE_DBG_OUT("finishBlockMake(): " << bpmin << " - " << bpmax); + + /* + Blit generated stuff to map + NOTE: blitBackAll adds nearly everything to changed_blocks + */ + data->vmanip->blitBackAll(changed_blocks); + + EMERGE_DBG_OUT("finishBlockMake: changed_blocks.size()=" + << changed_blocks->size()); + + /* + Copy transforming liquid information + */ + while (data->transforming_liquid.size()) { + m_transforming_liquid.push_back(data->transforming_liquid.front()); + data->transforming_liquid.pop_front(); + } + + for (auto &changed_block : *changed_blocks) { + MapBlock *block = changed_block.second; + if (!block) + continue; + /* + Update is air cache of the MapBlocks + */ + block->expireIsAirCache(); + /* + Set block as modified + */ + block->raiseModified(MOD_STATE_WRITE_NEEDED, + MOD_REASON_EXPIRE_IS_AIR); + } + + /* + Set central blocks as generated + */ + for (s16 x = bpmin.X; x <= bpmax.X; x++) + for (s16 z = bpmin.Z; z <= bpmax.Z; z++) + for (s16 y = bpmin.Y; y <= bpmax.Y; y++) { + MapBlock *block = getBlockNoCreateNoEx(v3s16(x, y, z)); + if (!block) + continue; + + block->setGenerated(true); + } + + /* + Save changed parts of map + NOTE: Will be saved later. + */ + //save(MOD_STATE_WRITE_AT_UNLOAD); + m_chunks_in_progress.erase(bpmin); +} + +MapSector *ServerMap::createSector(v2s16 p2d) +{ + /* + Check if it exists already in memory + */ + MapSector *sector = getSectorNoGenerate(p2d); + if (sector) + return sector; + + /* + Do not create over max mapgen limit + */ + if (blockpos_over_max_limit(v3s16(p2d.X, 0, p2d.Y))) + throw InvalidPositionException("createSector(): pos over max mapgen limit"); + + /* + Generate blank sector + */ + sector = new MapSector(this, p2d, m_gamedef); + + /* + Insert to container + */ + m_sectors[p2d] = sector; + + return sector; +} + +MapBlock * ServerMap::createBlock(v3s16 p) +{ + v2s16 p2d(p.X, p.Z); + s16 block_y = p.Y; + + /* + This will create or load a sector if not found in memory. + */ + MapSector *sector; + try { + sector = createSector(p2d); + } catch (InvalidPositionException &e) { + infostream<<"createBlock: createSector() failed"<getBlockNoCreateNoEx(block_y); + if (block) + return block; + + // Create blank + try { + block = sector->createBlankBlock(block_y); + } catch (InvalidPositionException &e) { + infostream << "createBlock: createBlankBlock() failed" << std::endl; + throw e; + } + + return block; +} + +MapBlock * ServerMap::emergeBlock(v3s16 p, bool create_blank) +{ + { + MapBlock *block = getBlockNoCreateNoEx(p); + if (block) + return block; + } + + { + MapBlock *block = loadBlock(p); + if(block) + return block; + } + + if (create_blank) { + try { + MapSector *sector = createSector(v2s16(p.X, p.Z)); + return sector->createBlankBlock(p.Y); + } catch (InvalidPositionException &e) {} + } + + return NULL; +} + +MapBlock *ServerMap::getBlockOrEmerge(v3s16 p3d, bool generate) +{ + MapBlock *block = getBlockNoCreateNoEx(p3d); + if (block == NULL) + m_emerge->enqueueBlockEmerge(PEER_ID_INEXISTENT, p3d, generate); + + return block; +} + +bool ServerMap::isBlockInQueue(v3s16 pos) +{ + return m_emerge && m_emerge->isBlockInQueue(pos); +} + +void ServerMap::addNodeAndUpdate(v3s16 p, MapNode n, + std::map &modified_blocks, + bool remove_metadata) +{ + Map::addNodeAndUpdate(p, n, modified_blocks, remove_metadata); + + /* + Add neighboring liquid nodes and this node to transform queue. + (it's vital for the node itself to get updated last, if it was removed.) + */ + + for (const v3s16 &dir : g_7dirs) { + v3s16 p2 = p + dir; + + bool is_valid_position; + MapNode n2 = getNode(p2, &is_valid_position); + if(is_valid_position && + (m_nodedef->get(n2).isLiquid() || + n2.getContent() == CONTENT_AIR)) + m_transforming_liquid.push_back(p2); + } +} + +// N.B. This requires no synchronization, since data will not be modified unless +// the VoxelManipulator being updated belongs to the same thread. +void ServerMap::updateVManip(v3s16 pos) +{ + Mapgen *mg = m_emerge->getCurrentMapgen(); + if (!mg) + return; + + MMVManip *vm = mg->vm; + if (!vm) + return; + + if (!vm->m_area.contains(pos)) + return; + + s32 idx = vm->m_area.index(pos); + vm->m_data[idx] = getNode(pos); + vm->m_flags[idx] &= ~VOXELFLAG_NO_DATA; + + vm->m_is_dirty = true; +} + +void ServerMap::reportMetrics(u64 save_time_us, u32 saved_blocks, u32 all_blocks) +{ + m_loaded_blocks_gauge->set(all_blocks); + m_save_time_counter->increment(save_time_us); + m_save_count_counter->increment(saved_blocks); +} + +void ServerMap::save(ModifiedState save_level) +{ + if (!m_map_saving_enabled) { + warningstream<<"Not saving map, saving disabled."<getBlocks(blocks); + + for (MapBlock *block : blocks) { + block_count_all++; + + if(block->getModified() >= (u32)save_level) { + // Lazy beginSave() + if(!save_started) { + beginSave(); + save_started = true; + } + + modprofiler.add(block->getModifiedReasonString(), 1); + + saveBlock(block); + block_count++; + } + } + } + + if(save_started) + endSave(); + + /* + Only print if something happened or saved whole map + */ + if(save_level == MOD_STATE_CLEAN + || block_count != 0) { + infostream << "ServerMap: Written: " + << block_count << " blocks" + << ", " << block_count_all << " blocks in memory." + << std::endl; + PrintInfo(infostream); // ServerMap/ClientMap: + infostream<<"Blocks modified by: "< &dst) +{ + dbase->listAllLoadableBlocks(dst); + if (dbase_ro) + dbase_ro->listAllLoadableBlocks(dst); +} + +void ServerMap::listAllLoadedBlocks(std::vector &dst) +{ + for (auto §or_it : m_sectors) { + MapSector *sector = sector_it.second; + + MapBlockVect blocks; + sector->getBlocks(blocks); + + for (MapBlock *block : blocks) { + v3s16 p = block->getPos(); + dst.push_back(p); + } + } +} + +MapDatabase *ServerMap::createDatabase( + const std::string &name, + const std::string &savedir, + Settings &conf) +{ + if (name == "sqlite3") + return new MapDatabaseSQLite3(savedir); + if (name == "dummy") + return new Database_Dummy(); + #if USE_LEVELDB + if (name == "leveldb") + return new Database_LevelDB(savedir); + #endif + #if USE_REDIS + if (name == "redis") + return new Database_Redis(conf); + #endif + #if USE_POSTGRESQL + if (name == "postgresql") { + std::string connect_string; + conf.getNoEx("pgsql_connection", connect_string); + return new MapDatabasePostgreSQL(connect_string); + } + #endif + + throw BaseException(std::string("Database backend ") + name + " not supported."); +} + +void ServerMap::beginSave() +{ + dbase->beginSave(); +} + +void ServerMap::endSave() +{ + dbase->endSave(); +} + +bool ServerMap::saveBlock(MapBlock *block) +{ + return saveBlock(block, dbase, m_map_compression_level); +} + +bool ServerMap::saveBlock(MapBlock *block, MapDatabase *db, int compression_level) +{ + v3s16 p3d = block->getPos(); + + // Format used for writing + u8 version = SER_FMT_VER_HIGHEST_WRITE; + + /* + [0] u8 serialization version + [1] data + */ + std::ostringstream o(std::ios_base::binary); + o.write((char*) &version, 1); + block->serialize(o, version, true, compression_level); + + // FIXME: zero copy possible in c++20 or with custom rdbuf + bool ret = db->saveBlock(p3d, o.str()); + if (ret) { + // We just wrote it to the disk so clear modified flag + block->resetModified(); + } + return ret; +} + +void ServerMap::loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, bool save_after_load) +{ + try { + std::istringstream is(*blob, std::ios_base::binary); + + u8 version = SER_FMT_VER_INVALID; + is.read((char*)&version, 1); + + if(is.fail()) + throw SerializationError("ServerMap::loadBlock(): Failed" + " to read MapBlock version"); + + MapBlock *block = nullptr; + std::unique_ptr block_created_new; + block = sector->getBlockNoCreateNoEx(p3d.Y); + if (!block) { + block_created_new = sector->createBlankBlockNoInsert(p3d.Y); + block = block_created_new.get(); + } + + { + ScopeProfiler sp(g_profiler, "ServerMap: deSer block", SPT_AVG); + // Read basic data + block->deSerialize(is, version, true); + } + + // If it's a new block, insert it to the map + if (block_created_new) { + sector->insertBlock(std::move(block_created_new)); + ReflowScan scanner(this, m_emerge->ndef); + scanner.scan(block, &m_transforming_liquid); + } + + /* + Save blocks loaded in old format in new format + */ + + //if(version < SER_FMT_VER_HIGHEST_READ || save_after_load) + // Only save if asked to; no need to update version + if(save_after_load) + saveBlock(block); + + // We just loaded it from, so it's up-to-date. + block->resetModified(); + } + catch(SerializationError &e) + { + errorstream<<"Invalid block data in database" + <<" ("<getBool("ignore_world_load_errors")){ + errorstream<<"Ignoring block load error. Duck and cover! " + <<"(ignore_world_load_errors)"<loadBlock(blockpos, &ret); + if (!ret.empty()) { + loadBlock(&ret, blockpos, createSector(p2d), false); + } else if (dbase_ro) { + dbase_ro->loadBlock(blockpos, &ret); + if (!ret.empty()) { + loadBlock(&ret, blockpos, createSector(p2d), false); + } + } else { + return NULL; + } + + MapBlock *block = getBlockNoCreateNoEx(blockpos); + if (created_new && (block != NULL)) { + std::map modified_blocks; + // Fix lighting if necessary + voxalgo::update_block_border_lighting(this, block, modified_blocks); + if (!modified_blocks.empty()) { + //Modified lighting, send event + MapEditEvent event; + event.type = MEET_OTHER; + event.setModifiedBlocks(modified_blocks); + dispatchEvent(event); + } + } + return block; +} + +bool ServerMap::deleteBlock(v3s16 blockpos) +{ + if (!dbase->deleteBlock(blockpos)) + return false; + + MapBlock *block = getBlockNoCreateNoEx(blockpos); + if (block) { + v2s16 p2d(blockpos.X, blockpos.Z); + MapSector *sector = getSectorNoGenerate(p2d); + if (!sector) + return false; + // It may not be safe to delete the block from memory at the moment + // (pointers to it could still be in use) + m_detached_blocks.push_back(sector->detachBlock(block)); + } + + return true; +} + +void ServerMap::deleteDetachedBlocks() +{ + for (const auto &block : m_detached_blocks) { + assert(block->isOrphan()); + (void)block; // silence unused-variable warning in release builds + } + + m_detached_blocks.clear(); +} + +void ServerMap::step() +{ + // Delete from memory blocks removed by deleteBlocks() only when pointers + // to them are (probably) no longer in use + deleteDetachedBlocks(); +} + +void ServerMap::PrintInfo(std::ostream &out) +{ + out<<"ServerMap: "; +} + +bool ServerMap::repairBlockLight(v3s16 blockpos, + std::map *modified_blocks) +{ + MapBlock *block = emergeBlock(blockpos, false); + if (!block || !block->isGenerated()) + return false; + voxalgo::repair_block_light(this, block, modified_blocks); + return true; +} + +/* + Liquids +*/ + +#define WATER_DROP_BOOST 4 + +const static v3s16 liquid_6dirs[6] = { + // order: upper before same level before lower + v3s16( 0, 1, 0), + v3s16( 0, 0, 1), + v3s16( 1, 0, 0), + v3s16( 0, 0,-1), + v3s16(-1, 0, 0), + v3s16( 0,-1, 0) +}; + +enum NeighborType : u8 { + NEIGHBOR_UPPER, + NEIGHBOR_SAME_LEVEL, + NEIGHBOR_LOWER +}; + +struct NodeNeighbor { + MapNode n; + NeighborType t; + v3s16 p; + + NodeNeighbor() + : n(CONTENT_AIR), t(NEIGHBOR_SAME_LEVEL) + { } + + NodeNeighbor(const MapNode &node, NeighborType n_type, const v3s16 &pos) + : n(node), + t(n_type), + p(pos) + { } +}; + +static s8 get_max_liquid_level(NodeNeighbor nb, s8 current_max_node_level) +{ + s8 max_node_level = current_max_node_level; + u8 nb_liquid_level = (nb.n.param2 & LIQUID_LEVEL_MASK); + switch (nb.t) { + case NEIGHBOR_UPPER: + if (nb_liquid_level + WATER_DROP_BOOST > current_max_node_level) { + max_node_level = LIQUID_LEVEL_MAX; + if (nb_liquid_level + WATER_DROP_BOOST < LIQUID_LEVEL_MAX) + max_node_level = nb_liquid_level + WATER_DROP_BOOST; + } else if (nb_liquid_level > current_max_node_level) { + max_node_level = nb_liquid_level; + } + break; + case NEIGHBOR_LOWER: + break; + case NEIGHBOR_SAME_LEVEL: + if ((nb.n.param2 & LIQUID_FLOW_DOWN_MASK) != LIQUID_FLOW_DOWN_MASK && + nb_liquid_level > 0 && nb_liquid_level - 1 > max_node_level) + max_node_level = nb_liquid_level - 1; + break; + } + return max_node_level; +} + +void ServerMap::transforming_liquid_add(v3s16 p) +{ + m_transforming_liquid.push_back(p); +} + +void ServerMap::transformLiquids(std::map &modified_blocks, + ServerEnvironment *env) +{ + u32 loopcount = 0; + u32 initial_size = m_transforming_liquid.size(); + + /*if(initial_size != 0) + infostream<<"transformLiquids(): initial_size="< must_reflow; + + std::vector > changed_nodes; + + std::vector check_for_falling; + + u32 liquid_loop_max = g_settings->getS32("liquid_loop_max"); + u32 loop_max = liquid_loop_max; + + while (m_transforming_liquid.size() != 0) + { + // This should be done here so that it is done when continue is used + if (loopcount >= initial_size || loopcount >= loop_max) + break; + loopcount++; + + /* + Get a queued transforming liquid node + */ + v3s16 p0 = m_transforming_liquid.front(); + m_transforming_liquid.pop_front(); + + MapNode n0 = getNode(p0); + + /* + Collect information about current node + */ + s8 liquid_level = -1; + // The liquid node which will be placed there if + // the liquid flows into this node. + content_t liquid_kind = CONTENT_IGNORE; + // The node which will be placed there if liquid + // can't flow into this node. + content_t floodable_node = CONTENT_AIR; + const ContentFeatures &cf = m_nodedef->get(n0); + LiquidType liquid_type = cf.liquid_type; + switch (liquid_type) { + case LIQUID_SOURCE: + liquid_level = LIQUID_LEVEL_SOURCE; + liquid_kind = cf.liquid_alternative_flowing_id; + break; + case LIQUID_FLOWING: + liquid_level = (n0.param2 & LIQUID_LEVEL_MASK); + liquid_kind = n0.getContent(); + break; + case LIQUID_NONE: + // if this node is 'floodable', it *could* be transformed + // into a liquid, otherwise, continue with the next node. + if (!cf.floodable) + continue; + floodable_node = n0.getContent(); + liquid_kind = CONTENT_AIR; + break; + case LiquidType_END: + break; + } + + /* + Collect information about the environment + */ + NodeNeighbor sources[6]; // surrounding sources + int num_sources = 0; + NodeNeighbor flows[6]; // surrounding flowing liquid nodes + int num_flows = 0; + NodeNeighbor airs[6]; // surrounding air + int num_airs = 0; + NodeNeighbor neutrals[6]; // nodes that are solid or another kind of liquid + int num_neutrals = 0; + bool flowing_down = false; + bool ignored_sources = false; + bool floating_node_above = false; + for (u16 i = 0; i < 6; i++) { + NeighborType nt = NEIGHBOR_SAME_LEVEL; + switch (i) { + case 0: + nt = NEIGHBOR_UPPER; + break; + case 5: + nt = NEIGHBOR_LOWER; + break; + default: + break; + } + v3s16 npos = p0 + liquid_6dirs[i]; + NodeNeighbor nb(getNode(npos), nt, npos); + const ContentFeatures &cfnb = m_nodedef->get(nb.n); + if (nt == NEIGHBOR_UPPER && cfnb.floats) + floating_node_above = true; + switch (cfnb.liquid_type) { + case LIQUID_NONE: + if (cfnb.floodable) { + airs[num_airs++] = nb; + // if the current node is a water source the neighbor + // should be enqueded for transformation regardless of whether the + // current node changes or not. + if (nb.t != NEIGHBOR_UPPER && liquid_type != LIQUID_NONE) + m_transforming_liquid.push_back(npos); + // if the current node happens to be a flowing node, it will start to flow down here. + if (nb.t == NEIGHBOR_LOWER) + flowing_down = true; + } else { + neutrals[num_neutrals++] = nb; + if (nb.n.getContent() == CONTENT_IGNORE) { + // If node below is ignore prevent water from + // spreading outwards and otherwise prevent from + // flowing away as ignore node might be the source + if (nb.t == NEIGHBOR_LOWER) + flowing_down = true; + else + ignored_sources = true; + } + } + break; + case LIQUID_SOURCE: + // if this node is not (yet) of a liquid type, choose the first liquid type we encounter + if (liquid_kind == CONTENT_AIR) + liquid_kind = cfnb.liquid_alternative_flowing_id; + if (cfnb.liquid_alternative_flowing_id != liquid_kind) { + neutrals[num_neutrals++] = nb; + } else { + // Do not count bottom source, it will screw things up + if(nt != NEIGHBOR_LOWER) + sources[num_sources++] = nb; + } + break; + case LIQUID_FLOWING: + if (nb.t != NEIGHBOR_SAME_LEVEL || + (nb.n.param2 & LIQUID_FLOW_DOWN_MASK) != LIQUID_FLOW_DOWN_MASK) { + // if this node is not (yet) of a liquid type, choose the first liquid type we encounter + // but exclude falling liquids on the same level, they cannot flow here anyway + + // used to determine if the neighbor can even flow into this node + s8 max_level_from_neighbor = get_max_liquid_level(nb, -1); + u8 range = m_nodedef->get(cfnb.liquid_alternative_flowing_id).liquid_range; + + if (liquid_kind == CONTENT_AIR && + max_level_from_neighbor >= (LIQUID_LEVEL_MAX + 1 - range)) + liquid_kind = cfnb.liquid_alternative_flowing_id; + } + if (cfnb.liquid_alternative_flowing_id != liquid_kind) { + neutrals[num_neutrals++] = nb; + } else { + flows[num_flows++] = nb; + if (nb.t == NEIGHBOR_LOWER) + flowing_down = true; + } + break; + case LiquidType_END: + break; + } + } + + /* + decide on the type (and possibly level) of the current node + */ + content_t new_node_content; + s8 new_node_level = -1; + s8 max_node_level = -1; + + u8 range = m_nodedef->get(liquid_kind).liquid_range; + if (range > LIQUID_LEVEL_MAX + 1) + range = LIQUID_LEVEL_MAX + 1; + + if ((num_sources >= 2 && m_nodedef->get(liquid_kind).liquid_renewable) || liquid_type == LIQUID_SOURCE) { + // liquid_kind will be set to either the flowing alternative of the node (if it's a liquid) + // or the flowing alternative of the first of the surrounding sources (if it's air), so + // it's perfectly safe to use liquid_kind here to determine the new node content. + new_node_content = m_nodedef->get(liquid_kind).liquid_alternative_source_id; + } else if (num_sources >= 1 && sources[0].t != NEIGHBOR_LOWER) { + // liquid_kind is set properly, see above + max_node_level = new_node_level = LIQUID_LEVEL_MAX; + if (new_node_level >= (LIQUID_LEVEL_MAX + 1 - range)) + new_node_content = liquid_kind; + else + new_node_content = floodable_node; + } else if (ignored_sources && liquid_level >= 0) { + // Maybe there are neighboring sources that aren't loaded yet + // so prevent flowing away. + new_node_level = liquid_level; + new_node_content = liquid_kind; + } else { + // no surrounding sources, so get the maximum level that can flow into this node + for (u16 i = 0; i < num_flows; i++) { + max_node_level = get_max_liquid_level(flows[i], max_node_level); + } + + u8 viscosity = m_nodedef->get(liquid_kind).liquid_viscosity; + if (viscosity > 1 && max_node_level != liquid_level) { + // amount to gain, limited by viscosity + // must be at least 1 in absolute value + s8 level_inc = max_node_level - liquid_level; + if (level_inc < -viscosity || level_inc > viscosity) + new_node_level = liquid_level + level_inc/viscosity; + else if (level_inc < 0) + new_node_level = liquid_level - 1; + else if (level_inc > 0) + new_node_level = liquid_level + 1; + if (new_node_level != max_node_level) + must_reflow.push_back(p0); + } else { + new_node_level = max_node_level; + } + + if (max_node_level >= (LIQUID_LEVEL_MAX + 1 - range)) + new_node_content = liquid_kind; + else + new_node_content = floodable_node; + + } + + /* + check if anything has changed. if not, just continue with the next node. + */ + if (new_node_content == n0.getContent() && + (m_nodedef->get(n0.getContent()).liquid_type != LIQUID_FLOWING || + ((n0.param2 & LIQUID_LEVEL_MASK) == (u8)new_node_level && + ((n0.param2 & LIQUID_FLOW_DOWN_MASK) == LIQUID_FLOW_DOWN_MASK) + == flowing_down))) + continue; + + /* + check if there is a floating node above that needs to be updated. + */ + if (floating_node_above && new_node_content == CONTENT_AIR) + check_for_falling.push_back(p0); + + /* + update the current node + */ + MapNode n00 = n0; + //bool flow_down_enabled = (flowing_down && ((n0.param2 & LIQUID_FLOW_DOWN_MASK) != LIQUID_FLOW_DOWN_MASK)); + if (m_nodedef->get(new_node_content).liquid_type == LIQUID_FLOWING) { + // set level to last 3 bits, flowing down bit to 4th bit + n0.param2 = (flowing_down ? LIQUID_FLOW_DOWN_MASK : 0x00) | (new_node_level & LIQUID_LEVEL_MASK); + } else { + // set the liquid level and flow bits to 0 + n0.param2 &= ~(LIQUID_LEVEL_MASK | LIQUID_FLOW_DOWN_MASK); + } + + // change the node. + n0.setContent(new_node_content); + + // on_flood() the node + if (floodable_node != CONTENT_AIR) { + if (env->getScriptIface()->node_on_flood(p0, n00, n0)) + continue; + } + + // Ignore light (because calling voxalgo::update_lighting_nodes) + ContentLightingFlags f0 = m_nodedef->getLightingFlags(n0); + n0.setLight(LIGHTBANK_DAY, 0, f0); + n0.setLight(LIGHTBANK_NIGHT, 0, f0); + + // Find out whether there is a suspect for this action + std::string suspect; + if (m_gamedef->rollback()) + suspect = m_gamedef->rollback()->getSuspect(p0, 83, 1); + + if (m_gamedef->rollback() && !suspect.empty()) { + // Blame suspect + RollbackScopeActor rollback_scope(m_gamedef->rollback(), suspect, true); + // Get old node for rollback + RollbackNode rollback_oldnode(this, p0, m_gamedef); + // Set node + setNode(p0, n0); + // Report + RollbackNode rollback_newnode(this, p0, m_gamedef); + RollbackAction action; + action.setSetNode(p0, rollback_oldnode, rollback_newnode); + m_gamedef->rollback()->reportAction(action); + } else { + // Set node + setNode(p0, n0); + } + + v3s16 blockpos = getNodeBlockPos(p0); + MapBlock *block = getBlockNoCreateNoEx(blockpos); + if (block != NULL) { + modified_blocks[blockpos] = block; + changed_nodes.emplace_back(p0, n00); + } + + /* + enqueue neighbors for update if necessary + */ + switch (m_nodedef->get(n0.getContent()).liquid_type) { + case LIQUID_SOURCE: + case LIQUID_FLOWING: + // make sure source flows into all neighboring nodes + for (u16 i = 0; i < num_flows; i++) + if (flows[i].t != NEIGHBOR_UPPER) + m_transforming_liquid.push_back(flows[i].p); + for (u16 i = 0; i < num_airs; i++) + if (airs[i].t != NEIGHBOR_UPPER) + m_transforming_liquid.push_back(airs[i].p); + break; + case LIQUID_NONE: + // this flow has turned to air; neighboring flows might need to do the same + for (u16 i = 0; i < num_flows; i++) + m_transforming_liquid.push_back(flows[i].p); + break; + case LiquidType_END: + break; + } + } + //infostream<<"Map::transformLiquids(): loopcount="<getScriptIface()->check_for_falling(p); + } + + env->getScriptIface()->on_liquid_transformed(changed_nodes); + + /* ---------------------------------------------------------------------- + * Manage the queue so that it does not grow indefinitely + */ + u16 time_until_purge = g_settings->getU16("liquid_queue_purge_time"); + + if (time_until_purge == 0) + return; // Feature disabled + + time_until_purge *= 1000; // seconds -> milliseconds + + u64 curr_time = porting::getTimeMs(); + u32 prev_unprocessed = m_unprocessed_count; + m_unprocessed_count = m_transforming_liquid.size(); + + // if unprocessed block count is decreasing or stable + if (m_unprocessed_count <= prev_unprocessed) { + m_queue_size_timer_started = false; + } else { + if (!m_queue_size_timer_started) + m_inc_trending_up_start_time = curr_time; + m_queue_size_timer_started = true; + } + + // Account for curr_time overflowing + if (m_queue_size_timer_started && m_inc_trending_up_start_time > curr_time) + m_queue_size_timer_started = false; + + /* If the queue has been growing for more than liquid_queue_purge_time seconds + * and the number of unprocessed blocks is still > liquid_loop_max then we + * cannot keep up; dump the oldest blocks from the queue so that the queue + * has liquid_loop_max items in it + */ + if (m_queue_size_timer_started + && curr_time - m_inc_trending_up_start_time > time_until_purge + && m_unprocessed_count > liquid_loop_max) { + + size_t dump_qty = m_unprocessed_count - liquid_loop_max; + + infostream << "transformLiquids(): DUMPING " << dump_qty + << " blocks from the queue" << std::endl; + + while (dump_qty--) + m_transforming_liquid.pop_front(); + + m_queue_size_timer_started = false; // optimistically assume we can keep up now + m_unprocessed_count = m_transforming_liquid.size(); + } +} diff --git a/src/servermap.h b/src/servermap.h new file mode 100644 index 000000000..7a8a84b9b --- /dev/null +++ b/src/servermap.h @@ -0,0 +1,195 @@ +/* +Minetest +Copyright (C) 2010-2024 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include +#include + +#include "map.h" +#include "util/container.h" +#include "util/metricsbackend.h" +#include "map_settings_manager.h" + +class Settings; +class MapDatabase; +class IRollbackManager; +class EmergeManager; +class ServerEnvironment; +struct BlockMakeData; + +class MetricsBackend; + +/* + ServerMap + + This is the only map class that is able to generate map. +*/ + +class ServerMap : public Map +{ +public: + /* + savedir: directory to which map data should be saved + */ + ServerMap(const std::string &savedir, IGameDef *gamedef, EmergeManager *emerge, MetricsBackend *mb); + ~ServerMap(); + + /* + Get a sector from somewhere. + - Check memory + - Check disk (doesn't load blocks) + - Create blank one + */ + MapSector *createSector(v2s16 p); + + /* + Blocks are generated by using these and makeBlock(). + */ + bool blockpos_over_mapgen_limit(v3s16 p); + bool initBlockMake(v3s16 blockpos, BlockMakeData *data); + void finishBlockMake(BlockMakeData *data, + std::map *changed_blocks); + + /* + Get a block from somewhere. + - Memory + - Create blank + */ + MapBlock *createBlock(v3s16 p); + + /* + Forcefully get a block from somewhere. + - Memory + - Load from disk + - Create blank filled with CONTENT_IGNORE + + */ + MapBlock *emergeBlock(v3s16 p, bool create_blank=true) override; + + /* + Try to get a block. + If it does not exist in memory, add it to the emerge queue. + - Memory + - Emerge Queue (deferred disk or generate) + */ + MapBlock *getBlockOrEmerge(v3s16 p3d, bool generate); + + bool isBlockInQueue(v3s16 pos); + + void addNodeAndUpdate(v3s16 p, MapNode n, + std::map &modified_blocks, + bool remove_metadata) override; + + /* + Database functions + */ + static MapDatabase *createDatabase(const std::string &name, const std::string &savedir, Settings &conf); + + // Call these before and after saving of blocks + void beginSave() override; + void endSave() override; + + void save(ModifiedState save_level) override; + void listAllLoadableBlocks(std::vector &dst); + void listAllLoadedBlocks(std::vector &dst); + + MapgenParams *getMapgenParams(); + + bool saveBlock(MapBlock *block) override; + static bool saveBlock(MapBlock *block, MapDatabase *db, int compression_level = -1); + MapBlock* loadBlock(v3s16 p); + // Database version + void loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, bool save_after_load=false); + + // Blocks are removed from the map but not deleted from memory until + // deleteDetachedBlocks() is called, since pointers to them may still exist + // when deleteBlock() is called. + bool deleteBlock(v3s16 blockpos) override; + + void deleteDetachedBlocks(); + + void step(); + + void updateVManip(v3s16 pos); + + // For debug printing + void PrintInfo(std::ostream &out) override; + + bool isSavingEnabled(){ return m_map_saving_enabled; } + + u64 getSeed(); + + /*! + * Fixes lighting in one map block. + * May modify other blocks as well, as light can spread + * out of the specified block. + * Returns false if the block is not generated (so nothing + * changed), true otherwise. + */ + bool repairBlockLight(v3s16 blockpos, + std::map *modified_blocks); + + void transformLiquids(std::map & modified_blocks, + ServerEnvironment *env); + + void transforming_liquid_add(v3s16 p); + + MapSettingsManager settings_mgr; + +protected: + + void reportMetrics(u64 save_time_us, u32 saved_blocks, u32 all_blocks) override; + +private: + friend class ModApiMapgen; // for m_transforming_liquid + + // Emerge manager + EmergeManager *m_emerge; + + std::string m_savedir; + bool m_map_saving_enabled; + + int m_map_compression_level; + + std::set m_chunks_in_progress; + + // used by deleteBlock() and deleteDetachedBlocks() + std::vector> m_detached_blocks; + + // Queued transforming water nodes + UniqueQueue m_transforming_liquid; + f32 m_transforming_liquid_loop_count_multiplier = 1.0f; + u32 m_unprocessed_count = 0; + u64 m_inc_trending_up_start_time = 0; // milliseconds + bool m_queue_size_timer_started = false; + + /* + Metadata is re-written on disk only if this is true. + This is reset to false when written on disk. + */ + bool m_map_metadata_changed = true; + MapDatabase *dbase = nullptr; + MapDatabase *dbase_ro = nullptr; + + // Map metrics + MetricGaugePtr m_loaded_blocks_gauge; + MetricCounterPtr m_save_time_counter; + MetricCounterPtr m_save_count_counter; +};