1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-06-27 16:36:03 +00:00

Optimize name-id-lookup for MapBlock serialization (#16000)

This commit is contained in:
DS 2025-04-14 17:18:21 +02:00 committed by GitHub
parent 75862e33b6
commit 60c47c51e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -24,6 +24,55 @@
#include "util/serialize.h"
#include "util/basic_macros.h"
// Like a std::unordered_map<content_t, content_t>, 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<content_t[]> m_mapping;
std::vector<content_t> m_dirty;
public:
IdIdMapping()
{
m_mapping = std::make_unique<content_t[]>(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<content_t[]> mapping;
static_assert(sizeof(content_t) == 2, "content_t must be 16-bit");
if (!mapping)
mapping = std::make_unique<content_t[]>(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<content_t> unnamed_contents;
std::unordered_set<std::string> 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) {