diff --git a/minetest.conf.example b/minetest.conf.example index f4b905700..de3cf3243 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -94,6 +94,9 @@ #random_input = false # Timeout for client to remove unused map data from memory #client_unload_unused_data_timeout = 600 +# Maximum number of mapblocks for client to be kept in memory +# Set to -1 for unlimited amount +#client_mapblock_limit = 1000 # Whether to fog out the end of the visible area #enable_fog = true # Whether to show the client debug info (has the same effect as hitting F5) diff --git a/src/client.cpp b/src/client.cpp index d4d3b6df6..946f4f1c4 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -421,8 +421,9 @@ void Client::step(float dtime) ScopeProfiler sp(g_profiler, "Client: map timer and unload"); std::vector deleted_blocks; m_env.getMap().timerUpdate(map_timer_and_unload_dtime, - g_settings->getFloat("client_unload_unused_data_timeout"), - &deleted_blocks); + g_settings->getFloat("client_unload_unused_data_timeout"), + g_settings->getS32("client_mapblock_limit"), + &deleted_blocks); /* Send info to server diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 40afc7dd3..92d85c830 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -104,6 +104,7 @@ void set_default_settings(Settings *settings) settings->setDefault("address", ""); settings->setDefault("random_input", "false"); settings->setDefault("client_unload_unused_data_timeout", "600"); + settings->setDefault("client_mapblock_limit", "1000"); settings->setDefault("enable_fog", "true"); settings->setDefault("fov", "72"); settings->setDefault("view_bobbing", "true"); diff --git a/src/map.cpp b/src/map.cpp index 50b50220d..38a700e3c 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -43,6 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "database-dummy.h" #include "database-sqlite3.h" #include +#include #if USE_LEVELDB #include "database-leveldb.h" #endif @@ -1399,10 +1400,25 @@ bool Map::getDayNightDiff(v3s16 blockpos) return false; } +struct TimeOrderedMapBlock { + MapSector *sect; + MapBlock *block; + + TimeOrderedMapBlock(MapSector *sect, MapBlock *block) : + sect(sect), + block(block) + {} + + bool operator<(const TimeOrderedMapBlock &b) const + { + return block->getUsageTimer() < b.block->getUsageTimer(); + }; +}; + /* Updates usage timers */ -void Map::timerUpdate(float dtime, float unload_timeout, +void Map::timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, std::vector *unloaded_blocks) { bool save_before_unloading = (mapType() == MAPTYPE_SERVER); @@ -1416,48 +1432,108 @@ void Map::timerUpdate(float dtime, float unload_timeout, u32 block_count_all = 0; beginSave(); - for(std::map::iterator si = m_sectors.begin(); - si != m_sectors.end(); ++si) { - MapSector *sector = si->second; - bool all_blocks_deleted = true; + // If there is no practical limit, we spare creation of mapblock_queue + if (max_loaded_blocks == (u32)-1) { + for (std::map::iterator si = m_sectors.begin(); + si != m_sectors.end(); ++si) { + MapSector *sector = si->second; - MapBlockVect blocks; - sector->getBlocks(blocks); + bool all_blocks_deleted = true; - for(MapBlockVect::iterator i = blocks.begin(); - i != blocks.end(); ++i) { - MapBlock *block = (*i); + MapBlockVect blocks; + sector->getBlocks(blocks); - block->incrementUsageTimer(dtime); + for (MapBlockVect::iterator i = blocks.begin(); + i != blocks.end(); ++i) { + MapBlock *block = (*i); - if(block->refGet() == 0 && block->getUsageTimer() > unload_timeout) { - v3s16 p = block->getPos(); + block->incrementUsageTimer(dtime); - // Save if modified - if (block->getModified() != MOD_STATE_CLEAN && save_before_unloading) { - modprofiler.add(block->getModifiedReasonString(), 1); - if (!saveBlock(block)) - continue; - saved_blocks_count++; + if (block->refGet() == 0 + && block->getUsageTimer() > unload_timeout) { + v3s16 p = block->getPos(); + + // Save if modified + if (block->getModified() != MOD_STATE_CLEAN + && save_before_unloading) { + modprofiler.add(block->getModifiedReasonString(), 1); + if (!saveBlock(block)) + continue; + saved_blocks_count++; + } + + // Delete from memory + sector->deleteBlock(block); + + if (unloaded_blocks) + unloaded_blocks->push_back(p); + + deleted_blocks_count++; + } else { + all_blocks_deleted = false; + block_count_all++; } - - // Delete from memory - sector->deleteBlock(block); - - if(unloaded_blocks) - unloaded_blocks->push_back(p); - - deleted_blocks_count++; } - else { - all_blocks_deleted = false; - block_count_all++; + + if (all_blocks_deleted) { + sector_deletion_queue.push_back(si->first); } } + } else { + std::priority_queue mapblock_queue; + for (std::map::iterator si = m_sectors.begin(); + si != m_sectors.end(); ++si) { + MapSector *sector = si->second; - if(all_blocks_deleted) { - sector_deletion_queue.push_back(si->first); + MapBlockVect blocks; + sector->getBlocks(blocks); + + for(MapBlockVect::iterator i = blocks.begin(); + i != blocks.end(); ++i) { + MapBlock *block = (*i); + + block->incrementUsageTimer(dtime); + mapblock_queue.push(TimeOrderedMapBlock(sector, block)); + } + } + block_count_all = mapblock_queue.size(); + // Delete old blocks, and blocks over the limit from the memory + while (mapblock_queue.size() > max_loaded_blocks + || mapblock_queue.top().block->getUsageTimer() > unload_timeout) { + TimeOrderedMapBlock b = mapblock_queue.top(); + mapblock_queue.pop(); + + MapBlock *block = b.block; + + if (block->refGet() != 0) + continue; + + v3s16 p = block->getPos(); + + // Save if modified + if (block->getModified() != MOD_STATE_CLEAN && save_before_unloading) { + modprofiler.add(block->getModifiedReasonString(), 1); + if (!saveBlock(block)) + continue; + saved_blocks_count++; + } + + // Delete from memory + b.sect->deleteBlock(block); + + if (unloaded_blocks) + unloaded_blocks->push_back(p); + + deleted_blocks_count++; + block_count_all--; + } + // Delete empty sectors + for (std::map::iterator si = m_sectors.begin(); + si != m_sectors.end(); ++si) { + if (si->second->empty()) { + sector_deletion_queue.push_back(si->first); + } } } endSave(); @@ -1484,7 +1560,7 @@ void Map::timerUpdate(float dtime, float unload_timeout, void Map::unloadUnreferencedBlocks(std::vector *unloaded_blocks) { - timerUpdate(0.0, -1.0, unloaded_blocks); + timerUpdate(0.0, -1.0, 0, unloaded_blocks); } void Map::deleteSectors(std::vector §orList) diff --git a/src/map.h b/src/map.h index 5500ccf91..2afd09639 100644 --- a/src/map.h +++ b/src/map.h @@ -277,7 +277,7 @@ public: Updates usage timers and unloads unused blocks and sectors. Saves modified blocks before unloading on MAPTYPE_SERVER. */ - void timerUpdate(float dtime, float unload_timeout, + void timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, std::vector *unloaded_blocks=NULL); /* diff --git a/src/mapsector.cpp b/src/mapsector.cpp index 3fe81dc90..9ce3c8eb3 100644 --- a/src/mapsector.cpp +++ b/src/mapsector.cpp @@ -59,7 +59,7 @@ MapBlock * MapSector::getBlockBuffered(s16 y) if(m_block_cache != NULL && y == m_block_cache_y){ return m_block_cache; } - + // If block doesn't exist, return NULL std::map::iterator n = m_blocks.find(y); if(n == m_blocks.end()) @@ -70,11 +70,11 @@ MapBlock * MapSector::getBlockBuffered(s16 y) else{ block = n->second; } - + // Cache the last result m_block_cache_y = y; m_block_cache = block; - + return block; } @@ -88,16 +88,16 @@ MapBlock * MapSector::createBlankBlockNoInsert(s16 y) assert(getBlockBuffered(y) == NULL); // Pre-condition v3s16 blockpos_map(m_pos.X, y, m_pos.Y); - + MapBlock *block = new MapBlock(m_parent, blockpos_map, m_gamedef); - + return block; } MapBlock * MapSector::createBlankBlock(s16 y) { MapBlock *block = createBlankBlockNoInsert(y); - + m_blocks[y] = block; return block; @@ -114,7 +114,7 @@ void MapSector::insertBlock(MapBlock *block) v2s16 p2d(block->getPos().X, block->getPos().Z); assert(p2d == m_pos); - + // Insert into container m_blocks[block_y] = block; } @@ -125,7 +125,7 @@ void MapSector::deleteBlock(MapBlock *block) // Clear from cache m_block_cache = NULL; - + // Remove from container m_blocks.erase(block_y); @@ -142,6 +142,11 @@ void MapSector::getBlocks(MapBlockVect &dest) } } +bool MapSector::empty() +{ + return m_blocks.empty(); +} + /* ServerMapSector */ @@ -159,18 +164,18 @@ void ServerMapSector::serialize(std::ostream &os, u8 version) { if(!ser_ver_supported(version)) throw VersionMismatchException("ERROR: MapSector format not supported"); - + /* [0] u8 serialization version + heightmap data */ - + // Server has both of these, no need to support not having them. //assert(m_objects != NULL); // Write version os.write((char*)&version, 1); - + /* Add stuff here, if needed */ @@ -193,18 +198,18 @@ ServerMapSector* ServerMapSector::deSerialize( /* Read stuff */ - + // Read version u8 version = SER_FMT_VER_INVALID; is.read((char*)&version, 1); - + if(!ser_ver_supported(version)) throw VersionMismatchException("ERROR: MapSector format not supported"); - + /* Add necessary reading stuff here */ - + /* Get or create sector */ diff --git a/src/mapsector.h b/src/mapsector.h index e89247a92..4c1ce86a3 100644 --- a/src/mapsector.h +++ b/src/mapsector.h @@ -40,7 +40,7 @@ class IGameDef; class MapSector { public: - + MapSector(Map *parent, v2s16 pos, IGameDef *gamedef); virtual ~MapSector(); @@ -58,16 +58,18 @@ public: MapBlock * createBlankBlock(s16 y); void insertBlock(MapBlock *block); - + void deleteBlock(MapBlock *block); - + void getBlocks(MapBlockVect &dest); - + + bool empty(); + // Always false at the moment, because sector contains no metadata. bool differs_from_disk; protected: - + // The pile of MapBlocks std::map m_blocks; @@ -76,12 +78,12 @@ protected: v2s16 m_pos; IGameDef *m_gamedef; - + // Last-used block is cached here for quicker access. - // Be sure to set this to NULL when the cached block is deleted + // Be sure to set this to NULL when the cached block is deleted MapBlock *m_block_cache; s16 m_block_cache_y; - + /* Private methods */ @@ -94,7 +96,7 @@ class ServerMapSector : public MapSector public: ServerMapSector(Map *parent, v2s16 pos, IGameDef *gamedef); ~ServerMapSector(); - + u32 getId() const { return MAPSECTOR_SERVER; @@ -106,7 +108,7 @@ public: */ void serialize(std::ostream &os, u8 version); - + static ServerMapSector* deSerialize( std::istream &is, Map *parent, @@ -114,7 +116,7 @@ public: std::map & sectors, IGameDef *gamedef ); - + private: }; @@ -124,7 +126,7 @@ class ClientMapSector : public MapSector public: ClientMapSector(Map *parent, v2s16 pos, IGameDef *gamedef); ~ClientMapSector(); - + u32 getId() const { return MAPSECTOR_CLIENT; @@ -133,6 +135,6 @@ public: private: }; #endif - + #endif diff --git a/src/server.cpp b/src/server.cpp index 144107675..dc7b101a6 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -594,7 +594,8 @@ void Server::AsyncRunStep(bool initial_step) // Run Map's timers and unload unused data ScopeProfiler sp(g_profiler, "Server: map timer and unload"); m_env->getMap().timerUpdate(map_timer_and_unload_dtime, - g_settings->getFloat("server_unload_unused_data_timeout")); + g_settings->getFloat("server_unload_unused_data_timeout"), + (u32)-1); } /*