3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 #include "nodemetadata.h"
29 #include "nameidmapping.h"
30 #include "content_mapnode.h" // For legacy name-id mapping
31 #include "content_nodemeta.h" // For legacy deserialization
32 #include "serialization.h"
34 #include "client/mapblock_mesh.h"
37 #include "util/string.h"
38 #include "util/serialize.h"
39 #include "util/basic_macros.h"
41 static const char *modified_reason_strings[] = {
50 "NodeMetaRef::reportMetadataChange",
52 "Timestamp expired (step)",
54 "removeRemovedObjects/remove",
55 "removeRemovedObjects/deactivate",
56 "Stored list cleared in activateObjects due to overflow",
57 "deactivateFarObjects: Static data moved in",
58 "deactivateFarObjects: Static data moved out",
59 "deactivateFarObjects: Static data changed considerably",
60 "finishBlockMake: expireDayNightDiff",
69 MapBlock::MapBlock(Map *parent, v3s16 pos, IGameDef *gamedef, bool dummy):
72 m_pos_relative(pos * MAP_BLOCKSIZE),
91 bool MapBlock::isValidPositionParent(v3s16 p)
93 if (isValidPosition(p)) {
97 return m_parent->isValidPosition(getPosRelative() + p);
100 MapNode MapBlock::getNodeParent(v3s16 p, bool *is_valid_position)
102 if (!isValidPosition(p))
103 return m_parent->getNode(getPosRelative() + p, is_valid_position);
106 if (is_valid_position)
107 *is_valid_position = false;
108 return {CONTENT_IGNORE};
110 if (is_valid_position)
111 *is_valid_position = true;
112 return data[p.Z * zstride + p.Y * ystride + p.X];
115 std::string MapBlock::getModifiedReasonString()
119 const u32 ubound = MYMIN(sizeof(m_modified_reason) * CHAR_BIT,
120 ARRLEN(modified_reason_strings));
122 for (u32 i = 0; i != ubound; i++) {
123 if ((m_modified_reason & (1 << i)) == 0)
126 reason += modified_reason_strings[i];
130 if (reason.length() > 2)
131 reason.resize(reason.length() - 2);
137 void MapBlock::copyTo(VoxelManipulator &dst)
139 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
140 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
142 // Copy from data to VoxelManipulator
143 dst.copyFrom(data, data_area, v3s16(0,0,0),
144 getPosRelative(), data_size);
147 void MapBlock::copyFrom(VoxelManipulator &dst)
149 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
150 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
152 // Copy from VoxelManipulator to data
153 dst.copyTo(data, data_area, v3s16(0,0,0),
154 getPosRelative(), data_size);
157 void MapBlock::actuallyUpdateDayNightDiff()
159 const NodeDefManager *nodemgr = m_gamedef->ndef();
161 // Running this function un-expires m_day_night_differs
162 m_day_night_differs_expired = false;
165 m_day_night_differs = false;
169 bool differs = false;
172 Check if any lighting value differs
175 MapNode previous_n(CONTENT_IGNORE);
176 for (u32 i = 0; i < nodecount; i++) {
179 // If node is identical to previous node, don't verify if it differs
183 differs = !n.isLightDayNightEq(nodemgr);
190 If some lighting values differ, check if the whole thing is
191 just air. If it is just air, differs = false
194 bool only_air = true;
195 for (u32 i = 0; i < nodecount; i++) {
196 MapNode &n = data[i];
197 if (n.getContent() != CONTENT_AIR) {
206 // Set member variable
207 m_day_night_differs = differs;
210 void MapBlock::expireDayNightDiff()
213 m_day_night_differs = false;
214 m_day_night_differs_expired = false;
218 m_day_night_differs_expired = true;
225 // List relevant id-name pairs for ids in the block using nodedef
226 // Renumbers the content IDs (starting at 0 and incrementing)
227 static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
228 const NodeDefManager *nodedef)
230 // The static memory requires about 65535 * sizeof(int) RAM in order to be
231 // sure we can handle all content ids. But it's absolutely worth it as it's
232 // a speedup of 4 for one of the major time consuming functions on storing
234 thread_local std::unique_ptr<content_t[]> mapping;
235 static_assert(sizeof(content_t) == 2, "content_t must be 16-bit");
237 mapping = std::make_unique<content_t[]>(USHRT_MAX + 1);
239 memset(mapping.get(), 0xFF, (USHRT_MAX + 1) * sizeof(content_t));
241 std::unordered_set<content_t> unknown_contents;
242 content_t id_counter = 0;
243 for (u32 i = 0; i < MapBlock::nodecount; i++) {
244 content_t global_id = nodes[i].getContent();
245 content_t id = CONTENT_IGNORE;
247 // Try to find an existing mapping
248 if (mapping[global_id] != 0xFFFF) {
249 id = mapping[global_id];
251 // We have to assign a new mapping
253 mapping[global_id] = id;
255 const ContentFeatures &f = nodedef->get(global_id);
256 const std::string &name = f.name;
258 unknown_contents.insert(global_id);
260 nimap->set(id, name);
263 // Update the MapNode
264 nodes[i].setContent(id);
266 for (u16 unknown_content : unknown_contents) {
267 errorstream << "getBlockNodeIdMapping(): IGNORING ERROR: "
268 << "Name for node id " << unknown_content << " not known" << std::endl;
272 // Correct ids in the block to match nodedef based on names.
273 // Unknown ones are added to nodedef.
274 // Will not update itself to match id-name pairs in nodedef.
275 static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
278 const NodeDefManager *nodedef = gamedef->ndef();
279 // This means the block contains incorrect ids, and we contain
280 // the information to convert those to names.
281 // nodedef contains information to convert our names to globally
283 std::unordered_set<content_t> unnamed_contents;
284 std::unordered_set<std::string> unallocatable_contents;
286 bool previous_exists = false;
287 content_t previous_local_id = CONTENT_IGNORE;
288 content_t previous_global_id = CONTENT_IGNORE;
290 for (u32 i = 0; i < MapBlock::nodecount; i++) {
291 content_t local_id = nodes[i].getContent();
292 // If previous node local_id was found and same than before, don't lookup maps
293 // apply directly previous resolved id
294 // This permits to massively improve loading performance when nodes are similar
295 // example: default:air, default:stone are massively present
296 if (previous_exists && local_id == previous_local_id) {
297 nodes[i].setContent(previous_global_id);
302 if (!nimap->getName(local_id, name)) {
303 unnamed_contents.insert(local_id);
304 previous_exists = false;
309 if (!nodedef->getId(name, global_id)) {
310 global_id = gamedef->allocateUnknownNodeId(name);
311 if (global_id == CONTENT_IGNORE) {
312 unallocatable_contents.insert(name);
313 previous_exists = false;
317 nodes[i].setContent(global_id);
319 // Save previous node local_id & global_id result
320 previous_local_id = local_id;
321 previous_global_id = global_id;
322 previous_exists = true;
325 for (const content_t c: unnamed_contents) {
326 errorstream << "correctBlockNodeIds(): IGNORING ERROR: "
327 << "Block contains id " << c
328 << " with no name mapping" << std::endl;
330 for (const std::string &node_name: unallocatable_contents) {
331 errorstream << "correctBlockNodeIds(): IGNORING ERROR: "
332 << "Could not allocate global id for node name \""
333 << node_name << "\"" << std::endl;
337 void MapBlock::serialize(std::ostream &os_compressed, u8 version, bool disk, int compression_level)
339 if(!ser_ver_supported(version))
340 throw VersionMismatchException("ERROR: MapBlock format not supported");
343 throw SerializationError("ERROR: Not writing dummy block.");
345 FATAL_ERROR_IF(version < SER_FMT_VER_LOWEST_WRITE, "Serialisation version error");
347 std::ostringstream os_raw(std::ios_base::binary);
348 std::ostream &os = version >= 29 ? os_raw : os_compressed;
354 if(getDayNightDiff())
360 writeU16(os, m_lighting_complete);
367 SharedBuffer<u8> buf;
368 const u8 content_width = 2;
369 const u8 params_width = 2;
372 MapNode *tmp_nodes = new MapNode[nodecount];
373 memcpy(tmp_nodes, data, nodecount * sizeof(MapNode));
374 getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
376 buf = MapNode::serializeBulk(version, tmp_nodes, nodecount,
377 content_width, params_width);
380 // write timestamp and node/id mapping first
382 writeU32(os, getTimestamp());
389 buf = MapNode::serializeBulk(version, data, nodecount,
390 content_width, params_width);
393 writeU8(os, content_width);
394 writeU8(os, params_width);
396 os.write(reinterpret_cast<char*>(*buf), buf.getSize());
398 // prior to 29 node data was compressed individually
399 compress(buf, os, version, compression_level);
406 m_node_metadata.serialize(os, version, disk);
408 // use os_raw from above to avoid allocating another stream object
409 m_node_metadata.serialize(os_raw, version, disk);
410 // prior to 29 node data was compressed individually
411 compress(os_raw.str(), os, version, compression_level);
415 Data that goes to disk, but not the network
421 m_node_timers.serialize(os, version);
425 m_static_objects.serialize(os);
429 writeU32(os, getTimestamp());
431 // Write block-specific node definition id mapping
437 m_node_timers.serialize(os, version);
442 // now compress the whole thing
443 compress(os_raw.str(), os_compressed, version, compression_level);
447 void MapBlock::serializeNetworkSpecific(std::ostream &os)
450 throw SerializationError("ERROR: Not writing dummy block.");
453 writeU8(os, 2); // version
456 void MapBlock::deSerialize(std::istream &in_compressed, u8 version, bool disk)
458 if(!ser_ver_supported(version))
459 throw VersionMismatchException("ERROR: MapBlock format not supported");
461 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())<<std::endl);
463 m_day_night_differs_expired = false;
467 deSerialize_pre22(in_compressed, version, disk);
471 // Decompress the whole block (version >= 29)
472 std::stringstream in_raw(std::ios_base::binary | std::ios_base::in | std::ios_base::out);
474 decompress(in_compressed, in_raw, version);
475 std::istream &is = version >= 29 ? in_raw : in_compressed;
477 u8 flags = readU8(is);
478 is_underground = (flags & 0x01) != 0;
479 m_day_night_differs = (flags & 0x02) != 0;
481 m_lighting_complete = 0xFFFF;
483 m_lighting_complete = readU16(is);
484 m_generated = (flags & 0x08) == 0;
487 if (disk && version >= 29) {
489 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
490 <<": Timestamp"<<std::endl);
491 setTimestampNoChangedFlag(readU32(is));
492 m_disk_timestamp = m_timestamp;
495 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
496 <<": NameIdMapping"<<std::endl);
497 nimap.deSerialize(is);
500 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
501 <<": Bulk node data"<<std::endl);
502 u8 content_width = readU8(is);
503 u8 params_width = readU8(is);
504 if(content_width != 1 && content_width != 2)
505 throw SerializationError("MapBlock::deSerialize(): invalid content_width");
506 if(params_width != 2)
507 throw SerializationError("MapBlock::deSerialize(): invalid params_width");
513 MapNode::deSerializeBulk(is, version, data, nodecount,
514 content_width, params_width);
516 // use in_raw from above to avoid allocating another stream object
517 decompress(is, in_raw, version);
518 MapNode::deSerializeBulk(in_raw, version, data, nodecount,
519 content_width, params_width);
525 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
526 <<": Node metadata"<<std::endl);
528 m_node_metadata.deSerialize(is, m_gamedef->idef());
534 decompress(is, in_raw, version);
536 m_node_metadata.deSerialize(in_raw, m_gamedef->idef());
538 content_nodemeta_deserialize_legacy(in_raw,
539 &m_node_metadata, &m_node_timers,
541 } catch(SerializationError &e) {
542 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
543 <<" while deserializing node metadata at ("
544 <<PP(getPos())<<": "<<e.what()<<std::endl;
549 Data that is only on disk
559 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
560 <<": Node timers (ver==24)"<<std::endl);
561 m_node_timers.deSerialize(is, version);
565 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
566 <<": Static objects"<<std::endl);
567 m_static_objects.deSerialize(is);
571 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
572 <<": Timestamp"<<std::endl);
573 setTimestampNoChangedFlag(readU32(is));
574 m_disk_timestamp = m_timestamp;
577 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
578 <<": NameIdMapping"<<std::endl);
579 nimap.deSerialize(is);
582 // Dynamically re-set ids based on node names
583 correctBlockNodeIds(&nimap, data, m_gamedef);
586 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
587 <<": Node timers (ver>=25)"<<std::endl);
588 m_node_timers.deSerialize(is, version);
592 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
593 <<": Done."<<std::endl);
596 void MapBlock::deSerializeNetworkSpecific(std::istream &is)
600 //const u8 version = readU8(is);
602 //throw SerializationError("unsupported MapBlock version");
604 } catch(SerializationError &e) {
605 warningstream<<"MapBlock::deSerializeNetworkSpecific(): Ignoring an error"
606 <<": "<<e.what()<<std::endl;
614 void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
616 // Initialize default flags
617 is_underground = false;
618 m_day_night_differs = false;
619 m_lighting_complete = 0xFFFF;
622 // Make a temporary buffer
623 u32 ser_length = MapNode::serializedLength(version);
624 SharedBuffer<u8> databuf_nodelist(nodecount * ser_length);
626 // These have no compression
627 if (version <= 3 || version == 5 || version == 6) {
630 if (is.gcount() != 1)
631 throw SerializationError(std::string(FUNCTION_NAME)
632 + ": not enough input data");
633 is_underground = tmp;
634 is.read((char *)*databuf_nodelist, nodecount * ser_length);
635 if ((u32)is.gcount() != nodecount * ser_length)
636 throw SerializationError(std::string(FUNCTION_NAME)
637 + ": not enough input data");
638 } else if (version <= 10) {
640 is.read((char *)&t8, 1);
644 // Uncompress and set material data
645 std::ostringstream os(std::ios_base::binary);
646 decompress(is, os, version);
647 std::string s = os.str();
648 if (s.size() != nodecount)
649 throw SerializationError(std::string(FUNCTION_NAME)
650 + ": not enough input data");
651 for (u32 i = 0; i < s.size(); i++) {
652 databuf_nodelist[i*ser_length] = s[i];
656 // Uncompress and set param data
657 std::ostringstream os(std::ios_base::binary);
658 decompress(is, os, version);
659 std::string s = os.str();
660 if (s.size() != nodecount)
661 throw SerializationError(std::string(FUNCTION_NAME)
662 + ": not enough input data");
663 for (u32 i = 0; i < s.size(); i++) {
664 databuf_nodelist[i*ser_length + 1] = s[i];
669 // Uncompress and set param2 data
670 std::ostringstream os(std::ios_base::binary);
671 decompress(is, os, version);
672 std::string s = os.str();
673 if (s.size() != nodecount)
674 throw SerializationError(std::string(FUNCTION_NAME)
675 + ": not enough input data");
676 for (u32 i = 0; i < s.size(); i++) {
677 databuf_nodelist[i*ser_length + 2] = s[i];
680 } else { // All other versions (10 to 21)
682 is.read((char*)&flags, 1);
683 is_underground = (flags & 0x01) != 0;
684 m_day_night_differs = (flags & 0x02) != 0;
686 m_generated = (flags & 0x08) == 0;
689 std::ostringstream os(std::ios_base::binary);
690 decompress(is, os, version);
691 std::string s = os.str();
692 if (s.size() != nodecount * 3)
693 throw SerializationError(std::string(FUNCTION_NAME)
694 + ": decompress resulted in size other than nodecount*3");
696 // deserialize nodes from buffer
697 for (u32 i = 0; i < nodecount; i++) {
698 databuf_nodelist[i*ser_length] = s[i];
699 databuf_nodelist[i*ser_length + 1] = s[i+nodecount];
700 databuf_nodelist[i*ser_length + 2] = s[i+nodecount*2];
710 std::string data = deSerializeString16(is);
711 std::istringstream iss(data, std::ios_base::binary);
712 content_nodemeta_deserialize_legacy(iss,
713 &m_node_metadata, &m_node_timers,
716 //std::string data = deSerializeString32(is);
717 std::ostringstream oss(std::ios_base::binary);
718 decompressZlib(is, oss);
719 std::istringstream iss(oss.str(), std::ios_base::binary);
720 content_nodemeta_deserialize_legacy(iss,
721 &m_node_metadata, &m_node_timers,
724 } catch(SerializationError &e) {
725 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
726 <<" while deserializing node metadata"<<std::endl;
731 // Deserialize node data
732 for (u32 i = 0; i < nodecount; i++) {
733 data[i].deSerialize(&databuf_nodelist[i * ser_length], version);
738 Versions up from 9 have block objects. (DEPRECATED)
741 u16 count = readU16(is);
742 // Not supported and length not known if count is not 0
744 warningstream<<"MapBlock::deSerialize_pre22(): "
745 <<"Ignoring stuff coming at and after MBOs"<<std::endl;
751 Versions up from 15 have static objects.
754 m_static_objects.deSerialize(is);
758 setTimestampNoChangedFlag(readU32(is));
759 m_disk_timestamp = m_timestamp;
761 setTimestampNoChangedFlag(BLOCK_TIMESTAMP_UNDEFINED);
764 // Dynamically re-set ids based on node names
766 // If supported, read node definition id mapping
768 nimap.deSerialize(is);
769 // Else set the legacy mapping
771 content_mapnode_get_name_id_mapping(&nimap);
773 correctBlockNodeIds(&nimap, data, m_gamedef);
777 // Legacy data changes
778 // This code has to convert from pre-22 to post-22 format.
779 const NodeDefManager *nodedef = m_gamedef->ndef();
780 for(u32 i=0; i<nodecount; i++)
782 const ContentFeatures &f = nodedef->get(data[i].getContent());
784 if(nodedef->getId("default:stone") == data[i].getContent()
785 && data[i].getParam1() == 1)
787 data[i].setContent(nodedef->getId("default:stone_with_coal"));
788 data[i].setParam1(0);
790 else if(nodedef->getId("default:stone") == data[i].getContent()
791 && data[i].getParam1() == 2)
793 data[i].setContent(nodedef->getId("default:stone_with_iron"));
794 data[i].setParam1(0);
797 if(f.legacy_facedir_simple)
799 data[i].setParam2(data[i].getParam1());
800 data[i].setParam1(0);
803 if(f.legacy_wallmounted)
805 u8 wallmounted_new_to_old[8] = {0x04, 0x08, 0x01, 0x02, 0x10, 0x20, 0, 0};
806 u8 dir_old_format = data[i].getParam2();
807 u8 dir_new_format = 0;
808 for(u8 j=0; j<8; j++)
810 if((dir_old_format & wallmounted_new_to_old[j]) != 0)
816 data[i].setParam2(dir_new_format);
823 Get a quick string to describe what a block actually contains
825 std::string analyze_block(MapBlock *block)
830 std::ostringstream desc;
832 v3s16 p = block->getPos();
834 porting::mt_snprintf(spos, sizeof(spos), "(%2d,%2d,%2d), ", p.X, p.Y, p.Z);
837 switch(block->getModified())
839 case MOD_STATE_CLEAN:
842 case MOD_STATE_WRITE_AT_UNLOAD:
843 desc<<"WRITE_AT_UNLOAD, ";
845 case MOD_STATE_WRITE_NEEDED:
846 desc<<"WRITE_NEEDED, ";
849 desc<<"unknown getModified()="+itos(block->getModified())+", ";
852 if(block->isGenerated())
853 desc<<"is_gen [X], ";
855 desc<<"is_gen [ ], ";
857 if(block->getIsUnderground())
862 desc<<"lighting_complete: "<<block->getLightingComplete()<<", ";
870 bool full_ignore = true;
871 bool some_ignore = false;
872 bool full_air = true;
873 bool some_air = false;
874 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
875 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
876 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
879 MapNode n = block->getNodeNoEx(p);
880 content_t c = n.getContent();
881 if(c == CONTENT_IGNORE)
893 std::ostringstream ss;
896 ss<<"IGNORE (full), ";
905 if(ss.str().size()>=2)
906 desc<<ss.str().substr(0, ss.str().size()-2);
911 return desc.str().substr(0, desc.str().size()-2);