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;
221 s16
MapBlock::getGroundLevel(v2s16 p2d
)
227 s16 y
= MAP_BLOCKSIZE
-1;
230 MapNode n
= getNodeRef(p2d
.X
, y
, p2d
.Y
);
231 if (m_gamedef
->ndef()->get(n
).walkable
) {
232 if(y
== MAP_BLOCKSIZE
-1)
240 catch(InvalidPositionException
&e
)
249 // List relevant id-name pairs for ids in the block using nodedef
250 // Renumbers the content IDs (starting at 0 and incrementing
251 // use static memory requires about 65535 * sizeof(int) ram in order to be
252 // sure we can handle all content ids. But it's absolutely worth it as it's
253 // a speedup of 4 for one of the major time consuming functions on storing
255 static content_t getBlockNodeIdMapping_mapping
[USHRT_MAX
+ 1];
256 static void getBlockNodeIdMapping(NameIdMapping
*nimap
, MapNode
*nodes
,
257 const NodeDefManager
*nodedef
)
259 memset(getBlockNodeIdMapping_mapping
, 0xFF, (USHRT_MAX
+ 1) * sizeof(content_t
));
261 std::set
<content_t
> unknown_contents
;
262 content_t id_counter
= 0;
263 for (u32 i
= 0; i
< MapBlock::nodecount
; i
++) {
264 content_t global_id
= nodes
[i
].getContent();
265 content_t id
= CONTENT_IGNORE
;
267 // Try to find an existing mapping
268 if (getBlockNodeIdMapping_mapping
[global_id
] != 0xFFFF) {
269 id
= getBlockNodeIdMapping_mapping
[global_id
];
273 // We have to assign a new mapping
275 getBlockNodeIdMapping_mapping
[global_id
] = id
;
277 const ContentFeatures
&f
= nodedef
->get(global_id
);
278 const std::string
&name
= f
.name
;
280 unknown_contents
.insert(global_id
);
282 nimap
->set(id
, name
);
285 // Update the MapNode
286 nodes
[i
].setContent(id
);
288 for (u16 unknown_content
: unknown_contents
) {
289 errorstream
<< "getBlockNodeIdMapping(): IGNORING ERROR: "
290 << "Name for node id " << unknown_content
<< " not known" << std::endl
;
293 // Correct ids in the block to match nodedef based on names.
294 // Unknown ones are added to nodedef.
295 // Will not update itself to match id-name pairs in nodedef.
296 static void correctBlockNodeIds(const NameIdMapping
*nimap
, MapNode
*nodes
,
299 const NodeDefManager
*nodedef
= gamedef
->ndef();
300 // This means the block contains incorrect ids, and we contain
301 // the information to convert those to names.
302 // nodedef contains information to convert our names to globally
304 std::unordered_set
<content_t
> unnamed_contents
;
305 std::unordered_set
<std::string
> unallocatable_contents
;
307 bool previous_exists
= false;
308 content_t previous_local_id
= CONTENT_IGNORE
;
309 content_t previous_global_id
= CONTENT_IGNORE
;
311 for (u32 i
= 0; i
< MapBlock::nodecount
; i
++) {
312 content_t local_id
= nodes
[i
].getContent();
313 // If previous node local_id was found and same than before, don't lookup maps
314 // apply directly previous resolved id
315 // This permits to massively improve loading performance when nodes are similar
316 // example: default:air, default:stone are massively present
317 if (previous_exists
&& local_id
== previous_local_id
) {
318 nodes
[i
].setContent(previous_global_id
);
323 if (!nimap
->getName(local_id
, name
)) {
324 unnamed_contents
.insert(local_id
);
325 previous_exists
= false;
330 if (!nodedef
->getId(name
, global_id
)) {
331 global_id
= gamedef
->allocateUnknownNodeId(name
);
332 if (global_id
== CONTENT_IGNORE
) {
333 unallocatable_contents
.insert(name
);
334 previous_exists
= false;
338 nodes
[i
].setContent(global_id
);
340 // Save previous node local_id & global_id result
341 previous_local_id
= local_id
;
342 previous_global_id
= global_id
;
343 previous_exists
= true;
346 for (const content_t c
: unnamed_contents
) {
347 errorstream
<< "correctBlockNodeIds(): IGNORING ERROR: "
348 << "Block contains id " << c
349 << " with no name mapping" << std::endl
;
351 for (const std::string
&node_name
: unallocatable_contents
) {
352 errorstream
<< "correctBlockNodeIds(): IGNORING ERROR: "
353 << "Could not allocate global id for node name \""
354 << node_name
<< "\"" << std::endl
;
358 void MapBlock::serialize(std::ostream
&os
, u8 version
, bool disk
)
360 if(!ser_ver_supported(version
))
361 throw VersionMismatchException("ERROR: MapBlock format not supported");
364 throw SerializationError("ERROR: Not writing dummy block.");
366 FATAL_ERROR_IF(version
< SER_FMT_VER_LOWEST_WRITE
, "Serialisation version error");
372 if(getDayNightDiff())
378 writeU16(os
, m_lighting_complete
);
387 MapNode
*tmp_nodes
= new MapNode
[nodecount
];
388 for(u32 i
=0; i
<nodecount
; i
++)
389 tmp_nodes
[i
] = data
[i
];
390 getBlockNodeIdMapping(&nimap
, tmp_nodes
, m_gamedef
->ndef());
392 u8 content_width
= 2;
394 writeU8(os
, content_width
);
395 writeU8(os
, params_width
);
396 MapNode::serializeBulk(os
, version
, tmp_nodes
, nodecount
,
397 content_width
, params_width
, true);
402 u8 content_width
= 2;
404 writeU8(os
, content_width
);
405 writeU8(os
, params_width
);
406 MapNode::serializeBulk(os
, version
, data
, nodecount
,
407 content_width
, params_width
, true);
413 std::ostringstream
oss(std::ios_base::binary
);
414 m_node_metadata
.serialize(oss
, version
, disk
);
415 compressZlib(oss
.str(), os
);
418 Data that goes to disk, but not the network
424 m_node_timers
.serialize(os
, version
);
428 m_static_objects
.serialize(os
);
431 writeU32(os
, getTimestamp());
433 // Write block-specific node definition id mapping
438 m_node_timers
.serialize(os
, version
);
443 void MapBlock::serializeNetworkSpecific(std::ostream
&os
)
446 throw SerializationError("ERROR: Not writing dummy block.");
449 writeU8(os
, 2); // version
452 void MapBlock::deSerialize(std::istream
&is
, u8 version
, bool disk
)
454 if(!ser_ver_supported(version
))
455 throw VersionMismatchException("ERROR: MapBlock format not supported");
457 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())<<std::endl
);
459 m_day_night_differs_expired
= false;
463 deSerialize_pre22(is
, version
, disk
);
467 u8 flags
= readU8(is
);
468 is_underground
= (flags
& 0x01) != 0;
469 m_day_night_differs
= (flags
& 0x02) != 0;
471 m_lighting_complete
= 0xFFFF;
473 m_lighting_complete
= readU16(is
);
474 m_generated
= (flags
& 0x08) == 0;
479 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
480 <<": Bulk node data"<<std::endl
);
481 u8 content_width
= readU8(is
);
482 u8 params_width
= readU8(is
);
483 if(content_width
!= 1 && content_width
!= 2)
484 throw SerializationError("MapBlock::deSerialize(): invalid content_width");
485 if(params_width
!= 2)
486 throw SerializationError("MapBlock::deSerialize(): invalid params_width");
487 MapNode::deSerializeBulk(is
, version
, data
, nodecount
,
488 content_width
, params_width
, true);
493 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
494 <<": Node metadata"<<std::endl
);
497 std::ostringstream
oss(std::ios_base::binary
);
498 decompressZlib(is
, oss
);
499 std::istringstream
iss(oss
.str(), std::ios_base::binary
);
501 m_node_metadata
.deSerialize(iss
, m_gamedef
->idef());
503 content_nodemeta_deserialize_legacy(iss
,
504 &m_node_metadata
, &m_node_timers
,
506 } catch(SerializationError
&e
) {
507 warningstream
<<"MapBlock::deSerialize(): Ignoring an error"
508 <<" while deserializing node metadata at ("
509 <<PP(getPos())<<": "<<e
.what()<<std::endl
;
513 Data that is only on disk
523 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
524 <<": Node timers (ver==24)"<<std::endl
);
525 m_node_timers
.deSerialize(is
, version
);
529 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
530 <<": Static objects"<<std::endl
);
531 m_static_objects
.deSerialize(is
);
534 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
535 <<": Timestamp"<<std::endl
);
536 setTimestampNoChangedFlag(readU32(is
));
537 m_disk_timestamp
= m_timestamp
;
539 // Dynamically re-set ids based on node names
540 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
541 <<": NameIdMapping"<<std::endl
);
543 nimap
.deSerialize(is
);
544 correctBlockNodeIds(&nimap
, data
, m_gamedef
);
547 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
548 <<": Node timers (ver>=25)"<<std::endl
);
549 m_node_timers
.deSerialize(is
, version
);
553 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
554 <<": Done."<<std::endl
);
557 void MapBlock::deSerializeNetworkSpecific(std::istream
&is
)
561 //const u8 version = readU8(is);
563 //throw SerializationError("unsupported MapBlock version");
565 } catch(SerializationError
&e
) {
566 warningstream
<<"MapBlock::deSerializeNetworkSpecific(): Ignoring an error"
567 <<": "<<e
.what()<<std::endl
;
575 void MapBlock::deSerialize_pre22(std::istream
&is
, u8 version
, bool disk
)
577 // Initialize default flags
578 is_underground
= false;
579 m_day_night_differs
= false;
580 m_lighting_complete
= 0xFFFF;
583 // Make a temporary buffer
584 u32 ser_length
= MapNode::serializedLength(version
);
585 SharedBuffer
<u8
> databuf_nodelist(nodecount
* ser_length
);
587 // These have no compression
588 if (version
<= 3 || version
== 5 || version
== 6) {
591 if (is
.gcount() != 1)
592 throw SerializationError(std::string(FUNCTION_NAME
)
593 + ": not enough input data");
594 is_underground
= tmp
;
595 is
.read((char *)*databuf_nodelist
, nodecount
* ser_length
);
596 if ((u32
)is
.gcount() != nodecount
* ser_length
)
597 throw SerializationError(std::string(FUNCTION_NAME
)
598 + ": not enough input data");
599 } else if (version
<= 10) {
601 is
.read((char *)&t8
, 1);
605 // Uncompress and set material data
606 std::ostringstream
os(std::ios_base::binary
);
607 decompress(is
, os
, version
);
608 std::string s
= os
.str();
609 if (s
.size() != nodecount
)
610 throw SerializationError(std::string(FUNCTION_NAME
)
611 + ": not enough input data");
612 for (u32 i
= 0; i
< s
.size(); i
++) {
613 databuf_nodelist
[i
*ser_length
] = s
[i
];
617 // Uncompress and set param data
618 std::ostringstream
os(std::ios_base::binary
);
619 decompress(is
, os
, version
);
620 std::string s
= os
.str();
621 if (s
.size() != nodecount
)
622 throw SerializationError(std::string(FUNCTION_NAME
)
623 + ": not enough input data");
624 for (u32 i
= 0; i
< s
.size(); i
++) {
625 databuf_nodelist
[i
*ser_length
+ 1] = s
[i
];
630 // Uncompress and set param2 data
631 std::ostringstream
os(std::ios_base::binary
);
632 decompress(is
, os
, version
);
633 std::string s
= os
.str();
634 if (s
.size() != nodecount
)
635 throw SerializationError(std::string(FUNCTION_NAME
)
636 + ": not enough input data");
637 for (u32 i
= 0; i
< s
.size(); i
++) {
638 databuf_nodelist
[i
*ser_length
+ 2] = s
[i
];
641 } else { // All other versions (10 to 21)
643 is
.read((char*)&flags
, 1);
644 is_underground
= (flags
& 0x01) != 0;
645 m_day_night_differs
= (flags
& 0x02) != 0;
647 m_generated
= (flags
& 0x08) == 0;
650 std::ostringstream
os(std::ios_base::binary
);
651 decompress(is
, os
, version
);
652 std::string s
= os
.str();
653 if (s
.size() != nodecount
* 3)
654 throw SerializationError(std::string(FUNCTION_NAME
)
655 + ": decompress resulted in size other than nodecount*3");
657 // deserialize nodes from buffer
658 for (u32 i
= 0; i
< nodecount
; i
++) {
659 databuf_nodelist
[i
*ser_length
] = s
[i
];
660 databuf_nodelist
[i
*ser_length
+ 1] = s
[i
+nodecount
];
661 databuf_nodelist
[i
*ser_length
+ 2] = s
[i
+nodecount
*2];
671 std::string data
= deSerializeString16(is
);
672 std::istringstream
iss(data
, std::ios_base::binary
);
673 content_nodemeta_deserialize_legacy(iss
,
674 &m_node_metadata
, &m_node_timers
,
677 //std::string data = deSerializeString32(is);
678 std::ostringstream
oss(std::ios_base::binary
);
679 decompressZlib(is
, oss
);
680 std::istringstream
iss(oss
.str(), std::ios_base::binary
);
681 content_nodemeta_deserialize_legacy(iss
,
682 &m_node_metadata
, &m_node_timers
,
685 } catch(SerializationError
&e
) {
686 warningstream
<<"MapBlock::deSerialize(): Ignoring an error"
687 <<" while deserializing node metadata"<<std::endl
;
692 // Deserialize node data
693 for (u32 i
= 0; i
< nodecount
; i
++) {
694 data
[i
].deSerialize(&databuf_nodelist
[i
* ser_length
], version
);
699 Versions up from 9 have block objects. (DEPRECATED)
702 u16 count
= readU16(is
);
703 // Not supported and length not known if count is not 0
705 warningstream
<<"MapBlock::deSerialize_pre22(): "
706 <<"Ignoring stuff coming at and after MBOs"<<std::endl
;
712 Versions up from 15 have static objects.
715 m_static_objects
.deSerialize(is
);
719 setTimestampNoChangedFlag(readU32(is
));
720 m_disk_timestamp
= m_timestamp
;
722 setTimestampNoChangedFlag(BLOCK_TIMESTAMP_UNDEFINED
);
725 // Dynamically re-set ids based on node names
727 // If supported, read node definition id mapping
729 nimap
.deSerialize(is
);
730 // Else set the legacy mapping
732 content_mapnode_get_name_id_mapping(&nimap
);
734 correctBlockNodeIds(&nimap
, data
, m_gamedef
);
738 // Legacy data changes
739 // This code has to convert from pre-22 to post-22 format.
740 const NodeDefManager
*nodedef
= m_gamedef
->ndef();
741 for(u32 i
=0; i
<nodecount
; i
++)
743 const ContentFeatures
&f
= nodedef
->get(data
[i
].getContent());
745 if(nodedef
->getId("default:stone") == data
[i
].getContent()
746 && data
[i
].getParam1() == 1)
748 data
[i
].setContent(nodedef
->getId("default:stone_with_coal"));
749 data
[i
].setParam1(0);
751 else if(nodedef
->getId("default:stone") == data
[i
].getContent()
752 && data
[i
].getParam1() == 2)
754 data
[i
].setContent(nodedef
->getId("default:stone_with_iron"));
755 data
[i
].setParam1(0);
758 if(f
.legacy_facedir_simple
)
760 data
[i
].setParam2(data
[i
].getParam1());
761 data
[i
].setParam1(0);
764 if(f
.legacy_wallmounted
)
766 u8 wallmounted_new_to_old
[8] = {0x04, 0x08, 0x01, 0x02, 0x10, 0x20, 0, 0};
767 u8 dir_old_format
= data
[i
].getParam2();
768 u8 dir_new_format
= 0;
769 for(u8 j
=0; j
<8; j
++)
771 if((dir_old_format
& wallmounted_new_to_old
[j
]) != 0)
777 data
[i
].setParam2(dir_new_format
);
784 Get a quick string to describe what a block actually contains
786 std::string
analyze_block(MapBlock
*block
)
791 std::ostringstream desc
;
793 v3s16 p
= block
->getPos();
795 porting::mt_snprintf(spos
, sizeof(spos
), "(%2d,%2d,%2d), ", p
.X
, p
.Y
, p
.Z
);
798 switch(block
->getModified())
800 case MOD_STATE_CLEAN
:
803 case MOD_STATE_WRITE_AT_UNLOAD
:
804 desc
<<"WRITE_AT_UNLOAD, ";
806 case MOD_STATE_WRITE_NEEDED
:
807 desc
<<"WRITE_NEEDED, ";
810 desc
<<"unknown getModified()="+itos(block
->getModified())+", ";
813 if(block
->isGenerated())
814 desc
<<"is_gen [X], ";
816 desc
<<"is_gen [ ], ";
818 if(block
->getIsUnderground())
823 desc
<<"lighting_complete: "<<block
->getLightingComplete()<<", ";
831 bool full_ignore
= true;
832 bool some_ignore
= false;
833 bool full_air
= true;
834 bool some_air
= false;
835 for(s16 z0
=0; z0
<MAP_BLOCKSIZE
; z0
++)
836 for(s16 y0
=0; y0
<MAP_BLOCKSIZE
; y0
++)
837 for(s16 x0
=0; x0
<MAP_BLOCKSIZE
; x0
++)
840 MapNode n
= block
->getNodeNoEx(p
);
841 content_t c
= n
.getContent();
842 if(c
== CONTENT_IGNORE
)
854 std::ostringstream ss
;
857 ss
<<"IGNORE (full), ";
866 if(ss
.str().size()>=2)
867 desc
<<ss
.str().substr(0, ss
.str().size()-2);
872 return desc
.str().substr(0, desc
.str().size()-2);