]> git.lizzy.rs Git - minetest.git/blobdiff - src/map.h
Store `MapEditEvent` blocks in a vector (#13071)
[minetest.git] / src / map.h
index 8abea896ec8c34655c2ec7c154e9792477b16c50..9a9586fc6a33a334588b6cff7aa1c22de7061591 100644 (file)
--- a/src/map.h
+++ b/src/map.h
@@ -17,8 +17,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
 
-#ifndef MAP_HEADER
-#define MAP_HEADER
+#pragma once
 
 #include <iostream>
 #include <sstream>
@@ -27,35 +26,36 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include <list>
 
 #include "irrlichttypes_bloated.h"
+#include "mapblock.h"
 #include "mapnode.h"
 #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 Database;
+class Settings;
+class MapDatabase;
 class ClientMap;
 class MapSector;
 class ServerMapSector;
 class MapBlock;
 class NodeMetadata;
 class IGameDef;
-class IRollbackReportSink;
+class IRollbackManager;
 class EmergeManager;
+class MetricsBackend;
 class ServerEnvironment;
 struct BlockMakeData;
-struct MapgenParams;
-
 
 /*
        MapEditEvent
 */
 
-#define MAPTYPE_BASE 0
-#define MAPTYPE_SERVER 1
-#define MAPTYPE_CLIENT 2
-
 enum MapEditEventType{
        // Node added (changed from air or something else to something)
        MEET_ADDNODE,
@@ -63,8 +63,7 @@ enum MapEditEventType{
        MEET_REMOVENODE,
        // Node swapped (changed without metadata change)
        MEET_SWAPNODE,
-       // Node metadata of block changed (not knowing which node exactly)
-       // p stores block coordinate
+       // Node metadata changed
        MEET_BLOCK_NODE_METADATA_CHANGED,
        // Anything else (modified_blocks are set unsent)
        MEET_OTHER
@@ -72,51 +71,42 @@ enum MapEditEventType{
 
 struct MapEditEvent
 {
-       MapEditEventType type;
+       MapEditEventType type = MEET_OTHER;
        v3s16 p;
-       MapNode n;
-       std::set<v3s16> modified_blocks;
-       u16 already_known_by_peer;
+       MapNode n = CONTENT_AIR;
+       std::vector<v3s16> modified_blocks; // Represents a set
+       bool is_private_change = false;
 
-       MapEditEvent():
-               type(MEET_OTHER),
-               already_known_by_peer(0)
+       MapEditEvent() = default;
+
+       // Sets the event's position and marks the block as modified.
+       void setPositionModified(v3s16 pos)
        {
+               assert(modified_blocks.empty()); // only meant for initialization (once)
+               p = pos;
+               modified_blocks.push_back(getNodeBlockPos(pos));
        }
 
-       MapEditEvent * clone()
+       void setModifiedBlocks(const std::map<v3s16, MapBlock *> blocks)
        {
-               MapEditEvent *event = new MapEditEvent();
-               event->type = type;
-               event->p = p;
-               event->n = n;
-               event->modified_blocks = modified_blocks;
-               return event;
+               assert(modified_blocks.empty()); // only meant for initialization (once)
+               modified_blocks.reserve(blocks.size());
+               for (const auto &block : blocks)
+                       modified_blocks.push_back(block.first);
        }
 
-       VoxelArea getArea()
+       VoxelArea getArea() const
        {
                switch(type){
                case MEET_ADDNODE:
-                       return VoxelArea(p);
                case MEET_REMOVENODE:
-                       return VoxelArea(p);
                case MEET_SWAPNODE:
-                       return VoxelArea(p);
                case MEET_BLOCK_NODE_METADATA_CHANGED:
-               {
-                       v3s16 np1 = p*MAP_BLOCKSIZE;
-                       v3s16 np2 = np1 + v3s16(1,1,1)*MAP_BLOCKSIZE - v3s16(1,1,1);
-                       return VoxelArea(np1, np2);
-               }
+                       return VoxelArea(p);
                case MEET_OTHER:
                {
                        VoxelArea a;
-                       for(std::set<v3s16>::iterator
-                                       i = modified_blocks.begin();
-                                       i != modified_blocks.end(); ++i)
-                       {
-                               v3s16 p = *i;
+                       for (v3s16 p : modified_blocks) {
                                v3s16 np1 = p*MAP_BLOCKSIZE;
                                v3s16 np2 = np1 + v3s16(1,1,1)*MAP_BLOCKSIZE - v3s16(1,1,1);
                                a.addPoint(np1);
@@ -133,25 +123,16 @@ class MapEventReceiver
 {
 public:
        // event shall be deleted by caller after the call.
-       virtual void onMapEditEvent(MapEditEvent *event) = 0;
+       virtual void onMapEditEvent(const MapEditEvent &event) = 0;
 };
 
 class Map /*: public NodeContainer*/
 {
 public:
 
-       Map(std::ostream &dout, IGameDef *gamedef);
+       Map(IGameDef *gamedef);
        virtual ~Map();
-
-       /*virtual u16 nodeContainerId() const
-       {
-               return NODECONTAINER_ID_MAP;
-       }*/
-
-       virtual s32 mapType() const
-       {
-               return MAPTYPE_BASE;
-       }
+       DISABLE_CLASS_COPY(Map);
 
        /*
                Drop (client) or delete (server) the map.
@@ -164,24 +145,18 @@ class Map /*: public NodeContainer*/
        void addEventReceiver(MapEventReceiver *event_receiver);
        void removeEventReceiver(MapEventReceiver *event_receiver);
        // event shall be deleted by caller after the call.
-       void dispatchEvent(MapEditEvent *event);
+       void dispatchEvent(const MapEditEvent &event);
 
        // On failure returns NULL
-       MapSector * getSectorNoGenerateNoExNoLock(v2s16 p2d);
+       MapSector * getSectorNoGenerateNoLock(v2s16 p2d);
        // Same as the above (there exists no lock anymore)
-       MapSector * getSectorNoGenerateNoEx(v2s16 p2d);
-       // On failure throws InvalidPositionException
        MapSector * getSectorNoGenerate(v2s16 p2d);
-       // Gets an existing sector or creates an empty one
-       //MapSector * getSectorCreate(v2s16 p2d);
 
        /*
                This is overloaded by ClientMap and ServerMap to allow
                their differing fetch methods.
        */
        virtual MapSector * emergeSector(v2s16 p){ return NULL; }
-       virtual MapSector * emergeSector(v2s16 p,
-                       std::map<v3s16, MapBlock*> &changed_blocks){ return NULL; }
 
        // Returns InvalidPositionException if not found
        MapBlock * getBlockNoCreate(v3s16 p);
@@ -189,57 +164,25 @@ class Map /*: public NodeContainer*/
        MapBlock * getBlockNoCreateNoEx(v3s16 p);
 
        /* Server overrides */
-       virtual MapBlock * emergeBlock(v3s16 p, bool allow_generate=true)
+       virtual MapBlock * emergeBlock(v3s16 p, bool create_blank=true)
        { return getBlockNoCreateNoEx(p); }
 
-       // Returns InvalidPositionException if not found
-       bool isNodeUnderground(v3s16 p);
+       inline const NodeDefManager * getNodeDefManager() { return m_nodedef; }
 
        bool isValidPosition(v3s16 p);
 
        // throws InvalidPositionException if not found
-       MapNode getNode(v3s16 p);
-
-       // throws InvalidPositionException if not found
-       void setNode(v3s16 p, MapNode & n);
+       void setNode(v3s16 p, MapNode n);
 
        // Returns a CONTENT_IGNORE node if not found
-       MapNode getNodeNoEx(v3s16 p);
-
-       void unspreadLight(enum LightBank bank,
-                       std::map<v3s16, u8> & from_nodes,
-                       std::set<v3s16> & light_sources,
-                       std::map<v3s16, MapBlock*> & modified_blocks);
-
-       void unLightNeighbors(enum LightBank bank,
-                       v3s16 pos, u8 lightwas,
-                       std::set<v3s16> & light_sources,
-                       std::map<v3s16, MapBlock*> & modified_blocks);
-
-       void spreadLight(enum LightBank bank,
-                       std::set<v3s16> & from_nodes,
-                       std::map<v3s16, MapBlock*> & modified_blocks);
-
-       void lightNeighbors(enum LightBank bank,
-                       v3s16 pos,
-                       std::map<v3s16, MapBlock*> & modified_blocks);
-
-       v3s16 getBrightestNeighbour(enum LightBank bank, v3s16 p);
-
-       s16 propagateSunlight(v3s16 start,
-                       std::map<v3s16, MapBlock*> & modified_blocks);
-
-       void updateLighting(enum LightBank bank,
-                       std::map<v3s16, MapBlock*>  & a_blocks,
-                       std::map<v3s16, MapBlock*> & modified_blocks);
-
-       void updateLighting(std::map<v3s16, MapBlock*>  & a_blocks,
-                       std::map<v3s16, MapBlock*> & modified_blocks);
+       // If is_valid_position is not NULL then this will be set to true if the
+       // position is valid, otherwise false
+       MapNode getNode(v3s16 p, bool *is_valid_position = NULL);
 
        /*
                These handle lighting but not faces.
        */
-       void addNodeAndUpdate(v3s16 p, MapNode n,
+       virtual void addNodeAndUpdate(v3s16 p, MapNode n,
                        std::map<v3s16, MapBlock*> &modified_blocks,
                        bool remove_metadata = true);
        void removeNodeAndUpdate(v3s16 p,
@@ -253,65 +196,50 @@ class Map /*: public NodeContainer*/
        bool addNodeWithEvent(v3s16 p, MapNode n, bool remove_metadata = true);
        bool removeNodeWithEvent(v3s16 p);
 
-       /*
-               Takes the blocks at the edges into account
-       */
-       bool getDayNightDiff(v3s16 blockpos);
-
-       //core::aabbox3d<s16> getDisplayedBlockArea();
-
-       //bool updateChangedVisibleArea();
-
        // Call these before and after saving of many blocks
-       virtual void beginSave() {return;};
-       virtual void endSave() {return;};
+       virtual void beginSave() {}
+       virtual void endSave() {}
 
-       virtual void save(ModifiedState save_level){assert(0);};
+       virtual void save(ModifiedState save_level) { FATAL_ERROR("FIXME"); }
 
-       // Server implements this.
-       // Client leaves it as no-op.
-       virtual void saveBlock(MapBlock *block){};
+       /*
+               Return true unless the map definitely cannot save blocks.
+       */
+       virtual bool maySaveBlocks() { return true; }
+
+       // Server implements these.
+       // Client leaves them as no-op.
+       virtual bool saveBlock(MapBlock *block) { return false; }
+       virtual bool deleteBlock(v3s16 blockpos) { return false; }
 
        /*
                Updates usage timers and unloads unused blocks and sectors.
-               Saves modified blocks before unloading on MAPTYPE_SERVER.
+               Saves modified blocks before unloading if possible.
        */
-       void timerUpdate(float dtime, float unload_timeout,
-                       std::list<v3s16> *unloaded_blocks=NULL);
+       void timerUpdate(float dtime, float unload_timeout, s32 max_loaded_blocks,
+                       std::vector<v3s16> *unloaded_blocks=NULL);
 
        /*
                Unloads all blocks with a zero refCount().
-               Saves modified blocks before unloading on MAPTYPE_SERVER.
+               Saves modified blocks before unloading if possible.
        */
-       void unloadUnreferencedBlocks(std::list<v3s16> *unloaded_blocks=NULL);
+       void unloadUnreferencedBlocks(std::vector<v3s16> *unloaded_blocks=NULL);
 
        // Deletes sectors and their blocks from memory
        // Takes cache into account
        // If deleted sector is in sector cache, clears cache
-       void deleteSectors(std::list<v2s16> &list);
-
-#if 0
-       /*
-               Unload unused data
-               = flush changed to disk and delete from memory, if usage timer of
-                 block is more than timeout
-       */
-       void unloadUnusedData(float timeout,
-                       core::list<v3s16> *deleted_blocks=NULL);
-#endif
+       void deleteSectors(std::vector<v2s16> &list);
 
        // For debug printing. Prints "Map: ", "ServerMap: " or "ClientMap: "
        virtual void PrintInfo(std::ostream &out);
 
-       void transformLiquids(std::map<v3s16, MapBlock*> & modified_blocks);
-       void transformLiquidsFinite(std::map<v3s16, MapBlock*> & modified_blocks);
-
        /*
                Node metadata
                These are basically coordinate wrappers to MapBlock
        */
 
-       NodeMetadata* getNodeMetadata(v3s16 p);
+       std::vector<v3s16> findNodesWithMetadata(v3s16 p1, v3s16 p2);
+       NodeMetadata *getNodeMetadata(v3s16 p);
 
        /**
         * Sets metadata for a node.
@@ -336,41 +264,70 @@ class Map /*: public NodeContainer*/
        */
 
        NodeTimer getNodeTimer(v3s16 p);
-       void setNodeTimer(v3s16 p, NodeTimer t);
+       void setNodeTimer(const NodeTimer &t);
        void removeNodeTimer(v3s16 p);
 
        /*
-               Misc.
-       */
-       std::map<v2s16, MapSector*> *getSectorsPtr(){return &m_sectors;}
-
-       /*
-               Variables
+               Utilities
        */
 
-       void transforming_liquid_add(v3s16 p);
-       s32 transforming_liquid_size();
-
-       virtual s16 getHeat(v3s16 p);
-       virtual s16 getHumidity(v3s16 p);
+       // Iterates through all nodes in the area in an unspecified order.
+       // The given callback takes the position as its first argument and the node
+       // as its second. If it returns false, forEachNodeInArea returns early.
+       template<typename F>
+       void forEachNodeInArea(v3s16 minp, v3s16 maxp, F func)
+       {
+               v3s16 bpmin = getNodeBlockPos(minp);
+               v3s16 bpmax = getNodeBlockPos(maxp);
+               for (s16 bz = bpmin.Z; bz <= bpmax.Z; bz++)
+               for (s16 bx = bpmin.X; bx <= bpmax.X; bx++)
+               for (s16 by = bpmin.Y; by <= bpmax.Y; by++) {
+                       // y is iterated innermost to make use of the sector cache.
+                       v3s16 bp(bx, by, bz);
+                       MapBlock *block = getBlockNoCreateNoEx(bp);
+                       v3s16 basep = bp * MAP_BLOCKSIZE;
+                       s16 minx_block = rangelim(minp.X - basep.X, 0, MAP_BLOCKSIZE - 1);
+                       s16 miny_block = rangelim(minp.Y - basep.Y, 0, MAP_BLOCKSIZE - 1);
+                       s16 minz_block = rangelim(minp.Z - basep.Z, 0, MAP_BLOCKSIZE - 1);
+                       s16 maxx_block = rangelim(maxp.X - basep.X, 0, MAP_BLOCKSIZE - 1);
+                       s16 maxy_block = rangelim(maxp.Y - basep.Y, 0, MAP_BLOCKSIZE - 1);
+                       s16 maxz_block = rangelim(maxp.Z - basep.Z, 0, MAP_BLOCKSIZE - 1);
+                       for (s16 z_block = minz_block; z_block <= maxz_block; z_block++)
+                       for (s16 y_block = miny_block; y_block <= maxy_block; y_block++)
+                       for (s16 x_block = minx_block; x_block <= maxx_block; x_block++) {
+                               v3s16 p = basep + v3s16(x_block, y_block, z_block);
+                               MapNode n = block ?
+                                               block->getNodeNoCheck(x_block, y_block, z_block) :
+                                               MapNode(CONTENT_IGNORE);
+                               if (!func(p, n))
+                                       return;
+                       }
+               }
+       }
 
+       bool isBlockOccluded(MapBlock *block, v3s16 cam_pos_nodes);
 protected:
-       friend class LuaVoxelManip;
-
-       std::ostream &m_dout; // A bit deprecated, could be removed
-
        IGameDef *m_gamedef;
 
        std::set<MapEventReceiver*> m_event_receivers;
 
-       std::map<v2s16, MapSector*> m_sectors;
+       std::unordered_map<v2s16, MapSector*> m_sectors;
 
        // Be sure to set this to NULL when the cached sector is deleted
-       MapSector *m_sector_cache;
+       MapSector *m_sector_cache = nullptr;
        v2s16 m_sector_cache_p;
 
-       // Queued transforming water nodes
-       UniqueQueue<v3s16> m_transforming_liquid;
+       // This stores the properties of the nodes on the map.
+       const NodeDefManager *m_nodedef;
+
+       // Can be implemented by child class
+       virtual void reportMetrics(u64 save_time_us, u32 saved_blocks, u32 all_blocks) {}
+
+       bool determineAdditionalOcclusionCheck(const v3s16 &pos_camera,
+               const core::aabbox3d<s16> &block_bounds, v3s16 &check);
+       bool isOccluded(const v3s16 &pos_camera, const v3s16 &pos_target,
+               float step, float stepfac, float start_offset, float end_offset,
+               u32 needed_count);
 };
 
 /*
@@ -385,35 +342,31 @@ class ServerMap : public Map
        /*
                savedir: directory to which map data should be saved
        */
-       ServerMap(std::string savedir, IGameDef *gamedef, EmergeManager *emerge);
+       ServerMap(const std::string &savedir, IGameDef *gamedef, EmergeManager *emerge, MetricsBackend *mb);
        ~ServerMap();
 
-       s32 mapType() const
-       {
-               return MAPTYPE_SERVER;
-       }
-
        /*
                Get a sector from somewhere.
                - Check memory
                - Check disk (doesn't load blocks)
                - Create blank one
        */
-       ServerMapSector * createSector(v2s16 p);
+       MapSector *createSector(v2s16 p);
 
        /*
                Blocks are generated by using these and makeBlock().
        */
-       bool initBlockMake(BlockMakeData *data, v3s16 blockpos);
-       MapBlock *finishBlockMake(BlockMakeData *data,
-                       std::map<v3s16, MapBlock*> &changed_blocks);
+       bool blockpos_over_mapgen_limit(v3s16 p);
+       bool initBlockMake(v3s16 blockpos, BlockMakeData *data);
+       void finishBlockMake(BlockMakeData *data,
+               std::map<v3s16, MapBlock*> *changed_blocks);
 
        /*
                Get a block from somewhere.
                - Memory
                - Create blank
        */
-       MapBlock * createBlock(v3s16 p);
+       MapBlock *createBlock(v3s16 p);
 
        /*
                Forcefully get a block from somewhere.
@@ -422,121 +375,118 @@ class ServerMap : public Map
                - Create blank filled with CONTENT_IGNORE
 
        */
-       MapBlock * emergeBlock(v3s16 p, bool create_blank=true);
-       
-       // Carries out any initialization necessary before block is sent
-       void prepareBlock(MapBlock *block);
-
-       // Helper for placing objects on ground level
-       s16 findGroundLevel(v2s16 p2d);
+       MapBlock *emergeBlock(v3s16 p, bool create_blank=true) override;
 
        /*
-               Misc. helper functions for fiddling with directory and file
-               names when saving
+               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)
        */
-       void createDirs(std::string path);
-       // returns something like "map/sectors/xxxxxxxx"
-       std::string getSectorDir(v2s16 pos, int layout = 2);
-       // dirname: final directory name
-       v2s16 getSectorPos(std::string dirname);
-       v3s16 getBlockPos(std::string sectordir, std::string blockfile);
-       static std::string getBlockFilename(v3s16 p);
+       MapBlock *getBlockOrEmerge(v3s16 p3d);
+
+       bool isBlockInQueue(v3s16 pos);
+
+       void addNodeAndUpdate(v3s16 p, MapNode n,
+                       std::map<v3s16, MapBlock*> &modified_blocks,
+                       bool remove_metadata) override;
 
        /*
                Database functions
        */
-       // Verify we can read/write to the database
-       void verifyDatabase();
-
-       // Returns true if the database file does not exist
-       bool loadFromFolders();
+       static MapDatabase *createDatabase(const std::string &name, const std::string &savedir, Settings &conf);
 
        // Call these before and after saving of blocks
-       void beginSave();
-       void endSave();
-
-       void save(ModifiedState save_level);
-       void listAllLoadableBlocks(std::list<v3s16> &dst);
-       void listAllLoadedBlocks(std::list<v3s16> &dst);
-       // Saves map seed and possibly other stuff
-       void saveMapMeta();
-       void loadMapMeta();
-
-       /*void saveChunkMeta();
-       void loadChunkMeta();*/
-
-       // The sector mutex should be locked when calling most of these
-
-       // This only saves sector-specific data such as the heightmap
-       // (no MapBlocks)
-       // DEPRECATED? Sectors have no metadata anymore.
-       void saveSectorMeta(ServerMapSector *sector);
-       MapSector* loadSectorMeta(std::string dirname, bool save_after_load);
-       bool loadSectorMeta(v2s16 p2d);
-
-       // Full load of a sector including all blocks.
-       // returns true on success, false on failure.
-       bool loadSectorFull(v2s16 p2d);
-       // If sector is not found in memory, try to load it from disk.
-       // Returns true if sector now resides in memory
-       //bool deFlushSector(v2s16 p2d);
-
-       void saveBlock(MapBlock *block);
-       // This will generate a sector with getSector if not found.
-       void loadBlock(std::string sectordir, std::string blockfile, MapSector *sector, bool save_after_load=false);
+       void beginSave() override;
+       void endSave() override;
+
+       void save(ModifiedState save_level) override;
+       void listAllLoadableBlocks(std::vector<v3s16> &dst);
+       void listAllLoadedBlocks(std::vector<v3s16> &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);
 
+       bool deleteBlock(v3s16 blockpos) override;
+
+       void updateVManip(v3s16 pos);
+
        // For debug printing
-       virtual void PrintInfo(std::ostream &out);
+       void PrintInfo(std::ostream &out) override;
 
        bool isSavingEnabled(){ return m_map_saving_enabled; }
 
-       u64 getSeed(){ return m_seed; }
+       u64 getSeed();
 
-       MapgenParams *getMapgenParams(){ return m_mgparams; }
+       /*!
+        * 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<v3s16, MapBlock *> *modified_blocks);
 
-       // Parameters fed to the Mapgen
-       MapgenParams *m_mgparams;
+       void transformLiquids(std::map<v3s16, MapBlock*> & modified_blocks,
+                       ServerEnvironment *env);
 
-       virtual s16 updateBlockHeat(ServerEnvironment *env, v3s16 p, MapBlock *block = NULL);
-       virtual s16 updateBlockHumidity(ServerEnvironment *env, v3s16 p, MapBlock *block = NULL);
+       void transforming_liquid_add(v3s16 p);
+
+       MapSettingsManager settings_mgr;
+
+protected:
+
+       void reportMetrics(u64 save_time_us, u32 saved_blocks, u32 all_blocks) override;
 
 private:
-       // Seed used for all kinds of randomness in generation
-       u64 m_seed;
-       
+       friend class LuaVoxelManip;
+
        // Emerge manager
        EmergeManager *m_emerge;
 
        std::string m_savedir;
        bool m_map_saving_enabled;
 
-#if 0
-       // Chunk size in MapSectors
-       // If 0, chunks are disabled.
-       s16 m_chunksize;
-       // Chunks
-       core::map<v2s16, MapChunk*> m_chunks;
-#endif
+       int m_map_compression_level;
+
+       std::set<v3s16> m_chunks_in_progress;
+
+       // Queued transforming water nodes
+       UniqueQueue<v3s16> 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;
-       Database *dbase;
+       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
 
-class MapVoxelManipulator : public VoxelManipulator
+class MMVManip : public VoxelManipulator
 {
 public:
-       MapVoxelManipulator(Map *map);
-       virtual ~MapVoxelManipulator();
+       MMVManip(Map *map);
+       virtual ~MMVManip() = default;
 
        virtual void clear()
        {
@@ -544,39 +494,35 @@ class MapVoxelManipulator : public VoxelManipulator
                m_loaded_blocks.clear();
        }
 
-       virtual void emerge(VoxelArea a, s32 caller_id=-1);
+       void initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max,
+               bool load_if_inexistent = true);
 
-       void blitBack(std::map<v3s16, MapBlock*> & modified_blocks);
+       // This is much faster with big chunks of generated data
+       void blitBackAll(std::map<v3s16, MapBlock*> * modified_blocks,
+               bool overwrite_generated = true);
 
-protected:
-       Map *m_map;
        /*
-               key = blockpos
-               value = flags describing the block
+               Creates a copy of this VManip including contents, the copy will not be
+               associated with a Map.
        */
-       std::map<v3s16, u8> m_loaded_blocks;
-};
-
-class ManualMapVoxelManipulator : public MapVoxelManipulator
-{
-public:
-       ManualMapVoxelManipulator(Map *map);
-       virtual ~ManualMapVoxelManipulator();
+       MMVManip *clone() const;
 
-       void setMap(Map *map)
-       {m_map = map;}
+       // Reassociates a copied VManip to a map
+       void reparent(Map *map);
 
-       virtual void emerge(VoxelArea a, s32 caller_id=-1);
+       // Is it impossible to call initialEmerge / blitBackAll?
+       inline bool isOrphan() const { return !m_map; }
 
-       void initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max,
-                                               bool load_if_inexistent = true);
-
-       // This is much faster with big chunks of generated data
-       void blitBackAll(std::map<v3s16, MapBlock*> * modified_blocks);
+       bool m_is_dirty = false;
 
 protected:
-       bool m_create_area;
-};
-
-#endif
+       MMVManip() {};
 
+       // may be null
+       Map *m_map = nullptr;
+       /*
+               key = blockpos
+               value = flags describing the block
+       */
+       std::map<v3s16, u8> m_loaded_blocks;
+};