1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-09-30 19:22:14 +00:00

Monoblocks: optimize blocks that contain a single type of node (#16293)

Reduces memory usage on the server, especially with many user and/or large viewing distances.
Currently disabled on the client due to known data races on a block's data.
This commit is contained in:
lhofhansl 2025-09-21 13:19:30 -07:00 committed by GitHub
parent afd681d013
commit 08b7870c79
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 245 additions and 47 deletions

View file

@ -4,6 +4,7 @@
#include "mapblock.h"
#include <memory>
#include <sstream>
#include "map.h"
#include "light.h"
@ -101,11 +102,13 @@ static const char *modified_reason_strings[] = {
MapBlock::MapBlock(v3s16 pos, IGameDef *gamedef):
m_pos(pos),
m_pos_relative(pos * MAP_BLOCKSIZE),
data(new MapNode[nodecount]),
m_gamedef(gamedef)
m_gamedef(gamedef),
m_is_mono_block(false)
{
reallocate();
assert(m_modified > MOD_STATE_CLEAN);
// We start with nodecount nodes, because in the vast
// majority of the cases a block is created just before
// it is de-serialized or generated.
reallocate(nodecount, MapNode(CONTENT_IGNORE));
}
MapBlock::~MapBlock()
@ -118,7 +121,8 @@ MapBlock::~MapBlock()
#endif
delete[] data;
porting::TrackFreedMemory(sizeof(MapNode) * nodecount);
if (!m_is_mono_block)
porting::TrackFreedMemory(sizeof(MapNode) * nodecount);
}
static inline size_t get_max_objects_per_block()
@ -214,7 +218,7 @@ void MapBlock::copyTo(VoxelManipulator &dst)
VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
// Copy from data to VoxelManipulator
dst.copyFrom(data, data_area, v3s16(0,0,0),
dst.copyFrom(data, m_is_mono_block, data_area, v3s16(0,0,0),
getPosRelative(), data_size);
}
@ -223,9 +227,61 @@ void MapBlock::copyFrom(const VoxelManipulator &src)
v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
expandNodesIfNeeded();
// Copy from VoxelManipulator to data
src.copyTo(data, data_area, v3s16(0,0,0),
getPosRelative(), data_size);
tryShrinkNodes();
}
void MapBlock::reallocate(u32 count, MapNode n)
{
assert(count == 1 || count == nodecount);
// For now monoblocks are disabled on the client.
// The client has known data races on the block's data (FIXME).
assert(!m_gamedef->isClient() || count == nodecount);
delete[] data;
if (data && !m_is_mono_block && count == 1)
porting::TrackFreedMemory(sizeof(MapNode) * nodecount);
data = new MapNode[count];
std::fill_n(data, count, n);
m_is_mono_block = (count == 1);
}
void MapBlock::tryShrinkNodes()
{
// For now monoblocks are disabled on the client.
// The client has known data races on the block's data (FIXME).
if (m_gamedef->isClient())
return;
if (m_is_mono_block)
return;
MapNode n = data[0];
bool is_mono_block = true;
for (u32 i=1; i<nodecount; i++) {
if (n != data[i]) {
is_mono_block = false;
break;
}
}
if (is_mono_block) {
reallocate(1, n);
m_is_air = n.getContent() == CONTENT_AIR;
m_is_air_expired = false;
}
}
void MapBlock::expandNodesIfNeeded()
{
if (m_is_mono_block) {
reallocate(nodecount, data[0]);
}
}
void MapBlock::actuallyUpdateIsAir()
@ -233,6 +289,10 @@ void MapBlock::actuallyUpdateIsAir()
// Running this function un-expires m_is_air
m_is_air_expired = false;
if (m_is_mono_block) {
m_is_air = data[0].getContent() == CONTENT_AIR;
return;
}
bool only_air = true;
for (u32 i = 0; i < nodecount; i++) {
MapNode &n = data[i];
@ -260,12 +320,12 @@ void MapBlock::expireIsAirCache()
// Note that there's no technical reason why we *have to* renumber the IDs,
// but we do it anyway as it also helps compressability.
void MapBlock::getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
const NodeDefManager *nodedef)
u32 count, const NodeDefManager *nodedef)
{
IdIdMapping &mapping = IdIdMapping::giveClearedThreadLocalInstance();
content_t id_counter = 0;
for (u32 i = 0; i < MapBlock::nodecount; i++) {
for (u32 i = 0; i < count; i++) {
content_t global_id = nodes[i].getContent();
content_t id = CONTENT_IGNORE;
@ -378,13 +438,13 @@ void MapBlock::serialize(std::ostream &os_compressed, u8 version, bool disk, int
const u8 params_width = 2;
if(disk)
{
MapNode *tmp_nodes = new MapNode[nodecount];
memcpy(tmp_nodes, data, nodecount * sizeof(MapNode));
getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
const size_t size = m_is_mono_block ? 1 : nodecount;
std::unique_ptr<MapNode[]> tmp_nodes(new MapNode[size]);
std::copy_n(data, size, tmp_nodes.get());
getBlockNodeIdMapping(&nimap, tmp_nodes.get(), size, m_gamedef->ndef());
buf = MapNode::serializeBulk(version, tmp_nodes, nodecount,
content_width, params_width);
delete[] tmp_nodes;
buf = MapNode::serializeBulk(version, tmp_nodes.get(), nodecount,
content_width, params_width, m_is_mono_block);
// write timestamp and node/id mapping first
if (version >= 29) {
@ -396,7 +456,7 @@ void MapBlock::serialize(std::ostream &os_compressed, u8 version, bool disk, int
else
{
buf = MapNode::serializeBulk(version, data, nodecount,
content_width, params_width);
content_width, params_width, m_is_mono_block);
}
writeU8(os, content_width);
@ -465,6 +525,7 @@ void MapBlock::deSerialize(std::istream &in_compressed, u8 version, bool disk)
TRACESTREAM(<<"MapBlock::deSerialize "<<getPos()<<std::endl);
m_is_air_expired = true;
expandNodesIfNeeded();
if(version <= 21)
{
@ -593,9 +654,13 @@ void MapBlock::deSerialize(std::istream &in_compressed, u8 version, bool disk)
m_node_timers.deSerialize(is, version);
}
u16 dummy;
m_is_air = nimap.size() == 1 && nimap.getId("air", dummy);
m_is_air_expired = false;
if (nimap.size() == 1) {
tryShrinkNodes();
u16 dummy;
m_is_air = nimap.getId("air", dummy);
m_is_air_expired = false;
}
}
TRACESTREAM(<<"MapBlock::deSerialize "<<getPos()