diff --git a/src/mapblock.cpp b/src/mapblock.cpp index 6987c36a6..348c02a1e 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -24,6 +24,55 @@ #include "util/serialize.h" #include "util/basic_macros.h" +// Like a std::unordered_map, but faster. +// +// Unassigned entries are marked with 0xFFFF. +// +// The static memory requires about 65535 * 2 bytes RAM in order to be +// sure we can handle all content ids. +class IdIdMapping +{ + static_assert(sizeof(content_t) == 2, "content_t must be 16-bit"); + +private: + std::unique_ptr m_mapping; + std::vector m_dirty; + +public: + IdIdMapping() + { + m_mapping = std::make_unique(CONTENT_MAX + 1); + memset(m_mapping.get(), 0xFF, (CONTENT_MAX + 1) * sizeof(content_t)); + } + + DISABLE_CLASS_COPY(IdIdMapping) + + content_t get(content_t k) const + { + return m_mapping[k]; + } + + void set(content_t k, content_t v) + { + m_mapping[k] = v; + m_dirty.push_back(k); + } + + void clear() + { + for (auto k : m_dirty) + m_mapping[k] = 0xFFFF; + m_dirty.clear(); + } + + static IdIdMapping &giveClearedThreadLocalInstance() + { + static thread_local IdIdMapping tl_ididmapping; + tl_ididmapping.clear(); + return tl_ididmapping; + } +}; + static const char *modified_reason_strings[] = { "reallocate or initial", "setIsUnderground", @@ -212,16 +261,7 @@ void MapBlock::expireIsAirCache() static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes, const NodeDefManager *nodedef) { - // The static memory requires about 65535 * 2 bytes RAM in order to be - // sure we can handle all content ids. But it's absolutely worth it as it's - // a speedup of 4 for one of the major time consuming functions on storing - // mapblocks. - thread_local std::unique_ptr mapping; - static_assert(sizeof(content_t) == 2, "content_t must be 16-bit"); - if (!mapping) - mapping = std::make_unique(CONTENT_MAX + 1); - - memset(mapping.get(), 0xFF, (CONTENT_MAX + 1) * sizeof(content_t)); + IdIdMapping &mapping = IdIdMapping::giveClearedThreadLocalInstance(); content_t id_counter = 0; for (u32 i = 0; i < MapBlock::nodecount; i++) { @@ -229,12 +269,12 @@ static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes, content_t id = CONTENT_IGNORE; // Try to find an existing mapping - if (mapping[global_id] != 0xFFFF) { - id = mapping[global_id]; + if (auto found = mapping.get(global_id); found != 0xFFFF) { + id = found; } else { // We have to assign a new mapping id = id_counter++; - mapping[global_id] = id; + mapping.set(global_id, id); const auto &name = nodedef->get(global_id).name; nimap->set(id, name); @@ -259,25 +299,20 @@ static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes, std::unordered_set unnamed_contents; std::unordered_set unallocatable_contents; - bool previous_exists = false; - content_t previous_local_id = CONTENT_IGNORE; - content_t previous_global_id = CONTENT_IGNORE; + // Used to cache local to global id lookup. + IdIdMapping &mapping_cache = IdIdMapping::giveClearedThreadLocalInstance(); for (u32 i = 0; i < MapBlock::nodecount; i++) { content_t local_id = nodes[i].getContent(); - // If previous node local_id was found and same than before, don't lookup maps - // apply directly previous resolved id - // This permits to massively improve loading performance when nodes are similar - // example: default:air, default:stone are massively present - if (previous_exists && local_id == previous_local_id) { - nodes[i].setContent(previous_global_id); + + if (auto found = mapping_cache.get(local_id); found != 0xFFFF) { + nodes[i].setContent(found); continue; } std::string name; if (!nimap->getName(local_id, name)) { unnamed_contents.insert(local_id); - previous_exists = false; continue; } @@ -286,16 +321,13 @@ static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes, global_id = gamedef->allocateUnknownNodeId(name); if (global_id == CONTENT_IGNORE) { unallocatable_contents.insert(name); - previous_exists = false; continue; } } nodes[i].setContent(global_id); // Save previous node local_id & global_id result - previous_local_id = local_id; - previous_global_id = global_id; - previous_exists = true; + mapping_cache.set(local_id, global_id); } for (const content_t c: unnamed_contents) {