]> git.lizzy.rs Git - dragonfireclient.git/blob - src/mapblock.cpp
Get rid of global buffer that would ruin concurrent MapBlock serialization
[dragonfireclient.git] / src / mapblock.cpp
1 /*
2 Minetest
3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
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.
9
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.
14
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.
18 */
19
20 #include "mapblock.h"
21
22 #include <sstream>
23 #include "map.h"
24 #include "light.h"
25 #include "nodedef.h"
26 #include "nodemetadata.h"
27 #include "gamedef.h"
28 #include "log.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"
33 #ifndef SERVER
34 #include "client/mapblock_mesh.h"
35 #endif
36 #include "porting.h"
37 #include "util/string.h"
38 #include "util/serialize.h"
39 #include "util/basic_macros.h"
40
41 static const char *modified_reason_strings[] = {
42         "initial",
43         "reallocate",
44         "setIsUnderground",
45         "setLightingExpired",
46         "setGenerated",
47         "setNode",
48         "setNodeNoCheck",
49         "setTimestamp",
50         "NodeMetaRef::reportMetadataChange",
51         "clearAllObjects",
52         "Timestamp expired (step)",
53         "addActiveObjectRaw",
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",
61         "unknown",
62 };
63
64
65 /*
66         MapBlock
67 */
68
69 MapBlock::MapBlock(Map *parent, v3s16 pos, IGameDef *gamedef, bool dummy):
70                 m_parent(parent),
71                 m_pos(pos),
72                 m_pos_relative(pos * MAP_BLOCKSIZE),
73                 m_gamedef(gamedef)
74 {
75         if (!dummy)
76                 reallocate();
77 }
78
79 MapBlock::~MapBlock()
80 {
81 #ifndef SERVER
82         {
83                 delete mesh;
84                 mesh = nullptr;
85         }
86 #endif
87
88         delete[] data;
89 }
90
91 bool MapBlock::isValidPositionParent(v3s16 p)
92 {
93         if (isValidPosition(p)) {
94                 return true;
95         }
96
97         return m_parent->isValidPosition(getPosRelative() + p);
98 }
99
100 MapNode MapBlock::getNodeParent(v3s16 p, bool *is_valid_position)
101 {
102         if (!isValidPosition(p))
103                 return m_parent->getNode(getPosRelative() + p, is_valid_position);
104
105         if (!data) {
106                 if (is_valid_position)
107                         *is_valid_position = false;
108                 return {CONTENT_IGNORE};
109         }
110         if (is_valid_position)
111                 *is_valid_position = true;
112         return data[p.Z * zstride + p.Y * ystride + p.X];
113 }
114
115 std::string MapBlock::getModifiedReasonString()
116 {
117         std::string reason;
118
119         const u32 ubound = MYMIN(sizeof(m_modified_reason) * CHAR_BIT,
120                 ARRLEN(modified_reason_strings));
121
122         for (u32 i = 0; i != ubound; i++) {
123                 if ((m_modified_reason & (1 << i)) == 0)
124                         continue;
125
126                 reason += modified_reason_strings[i];
127                 reason += ", ";
128         }
129
130         if (reason.length() > 2)
131                 reason.resize(reason.length() - 2);
132
133         return reason;
134 }
135
136
137 void MapBlock::copyTo(VoxelManipulator &dst)
138 {
139         v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
140         VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
141
142         // Copy from data to VoxelManipulator
143         dst.copyFrom(data, data_area, v3s16(0,0,0),
144                         getPosRelative(), data_size);
145 }
146
147 void MapBlock::copyFrom(VoxelManipulator &dst)
148 {
149         v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
150         VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
151
152         // Copy from VoxelManipulator to data
153         dst.copyTo(data, data_area, v3s16(0,0,0),
154                         getPosRelative(), data_size);
155 }
156
157 void MapBlock::actuallyUpdateDayNightDiff()
158 {
159         const NodeDefManager *nodemgr = m_gamedef->ndef();
160
161         // Running this function un-expires m_day_night_differs
162         m_day_night_differs_expired = false;
163
164         if (!data) {
165                 m_day_night_differs = false;
166                 return;
167         }
168
169         bool differs = false;
170
171         /*
172                 Check if any lighting value differs
173         */
174
175         MapNode previous_n(CONTENT_IGNORE);
176         for (u32 i = 0; i < nodecount; i++) {
177                 MapNode n = data[i];
178
179                 // If node is identical to previous node, don't verify if it differs
180                 if (n == previous_n)
181                         continue;
182
183                 differs = !n.isLightDayNightEq(nodemgr);
184                 if (differs)
185                         break;
186                 previous_n = n;
187         }
188
189         /*
190                 If some lighting values differ, check if the whole thing is
191                 just air. If it is just air, differs = false
192         */
193         if (differs) {
194                 bool only_air = true;
195                 for (u32 i = 0; i < nodecount; i++) {
196                         MapNode &n = data[i];
197                         if (n.getContent() != CONTENT_AIR) {
198                                 only_air = false;
199                                 break;
200                         }
201                 }
202                 if (only_air)
203                         differs = false;
204         }
205
206         // Set member variable
207         m_day_night_differs = differs;
208 }
209
210 void MapBlock::expireDayNightDiff()
211 {
212         if (!data) {
213                 m_day_night_differs = false;
214                 m_day_night_differs_expired = false;
215                 return;
216         }
217
218         m_day_night_differs_expired = true;
219 }
220
221 /*
222         Serialization
223 */
224
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)
229 {
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
233         // mapblocks.
234         thread_local std::unique_ptr<content_t[]> mapping;
235         static_assert(sizeof(content_t) == 2, "content_t must be 16-bit");
236         if (!mapping)
237                 mapping = std::make_unique<content_t[]>(USHRT_MAX + 1);
238
239         memset(mapping.get(), 0xFF, (USHRT_MAX + 1) * sizeof(content_t));
240
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;
246
247                 // Try to find an existing mapping
248                 if (mapping[global_id] != 0xFFFF) {
249                         id = mapping[global_id];
250                 } else {
251                         // We have to assign a new mapping
252                         id = id_counter++;
253                         mapping[global_id] = id;
254
255                         const ContentFeatures &f = nodedef->get(global_id);
256                         const std::string &name = f.name;
257                         if (name.empty())
258                                 unknown_contents.insert(global_id);
259                         else
260                                 nimap->set(id, name);
261                 }
262
263                 // Update the MapNode
264                 nodes[i].setContent(id);
265         }
266         for (u16 unknown_content : unknown_contents) {
267                 errorstream << "getBlockNodeIdMapping(): IGNORING ERROR: "
268                                 << "Name for node id " << unknown_content << " not known" << std::endl;
269         }
270 }
271
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,
276                 IGameDef *gamedef)
277 {
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
282         // correct ids.
283         std::unordered_set<content_t> unnamed_contents;
284         std::unordered_set<std::string> unallocatable_contents;
285
286         bool previous_exists = false;
287         content_t previous_local_id = CONTENT_IGNORE;
288         content_t previous_global_id = CONTENT_IGNORE;
289
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);
298                         continue;
299                 }
300
301                 std::string name;
302                 if (!nimap->getName(local_id, name)) {
303                         unnamed_contents.insert(local_id);
304                         previous_exists = false;
305                         continue;
306                 }
307
308                 content_t global_id;
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;
314                                 continue;
315                         }
316                 }
317                 nodes[i].setContent(global_id);
318
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;
323         }
324
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;
329         }
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;
334         }
335 }
336
337 void MapBlock::serialize(std::ostream &os_compressed, u8 version, bool disk, int compression_level)
338 {
339         if(!ser_ver_supported(version))
340                 throw VersionMismatchException("ERROR: MapBlock format not supported");
341
342         if (!data)
343                 throw SerializationError("ERROR: Not writing dummy block.");
344
345         FATAL_ERROR_IF(version < SER_FMT_VER_LOWEST_WRITE, "Serialisation version error");
346
347         std::ostringstream os_raw(std::ios_base::binary);
348         std::ostream &os = version >= 29 ? os_raw : os_compressed;
349
350         // First byte
351         u8 flags = 0;
352         if(is_underground)
353                 flags |= 0x01;
354         if(getDayNightDiff())
355                 flags |= 0x02;
356         if (!m_generated)
357                 flags |= 0x08;
358         writeU8(os, flags);
359         if (version >= 27) {
360                 writeU16(os, m_lighting_complete);
361         }
362
363         /*
364                 Bulk node data
365         */
366         NameIdMapping nimap;
367         SharedBuffer<u8> buf;
368         const u8 content_width = 2;
369         const u8 params_width = 2;
370         if(disk)
371         {
372                 MapNode *tmp_nodes = new MapNode[nodecount];
373                 memcpy(tmp_nodes, data, nodecount * sizeof(MapNode));
374                 getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
375
376                 buf = MapNode::serializeBulk(version, tmp_nodes, nodecount,
377                                 content_width, params_width);
378                 delete[] tmp_nodes;
379
380                 // write timestamp and node/id mapping first
381                 if (version >= 29) {
382                         writeU32(os, getTimestamp());
383
384                         nimap.serialize(os);
385                 }
386         }
387         else
388         {
389                 buf = MapNode::serializeBulk(version, data, nodecount,
390                                 content_width, params_width);
391         }
392
393         writeU8(os, content_width);
394         writeU8(os, params_width);
395         if (version >= 29) {
396                 os.write(reinterpret_cast<char*>(*buf), buf.getSize());
397         } else {
398                 // prior to 29 node data was compressed individually
399                 compress(buf, os, version, compression_level);
400         }
401
402         /*
403                 Node metadata
404         */
405         if (version >= 29) {
406                 m_node_metadata.serialize(os, version, disk);
407         } else {
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);
412         }
413
414         /*
415                 Data that goes to disk, but not the network
416         */
417         if(disk)
418         {
419                 if(version <= 24){
420                         // Node timers
421                         m_node_timers.serialize(os, version);
422                 }
423
424                 // Static objects
425                 m_static_objects.serialize(os);
426
427                 if(version < 29){
428                         // Timestamp
429                         writeU32(os, getTimestamp());
430
431                         // Write block-specific node definition id mapping
432                         nimap.serialize(os);
433                 }
434
435                 if(version >= 25){
436                         // Node timers
437                         m_node_timers.serialize(os, version);
438                 }
439         }
440
441         if (version >= 29) {
442                 // now compress the whole thing
443                 compress(os_raw.str(), os_compressed, version, compression_level);
444         }
445 }
446
447 void MapBlock::serializeNetworkSpecific(std::ostream &os)
448 {
449         if (!data) {
450                 throw SerializationError("ERROR: Not writing dummy block.");
451         }
452
453         writeU8(os, 2); // version
454 }
455
456 void MapBlock::deSerialize(std::istream &in_compressed, u8 version, bool disk)
457 {
458         if(!ser_ver_supported(version))
459                 throw VersionMismatchException("ERROR: MapBlock format not supported");
460
461         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())<<std::endl);
462
463         m_day_night_differs_expired = false;
464
465         if(version <= 21)
466         {
467                 deSerialize_pre22(in_compressed, version, disk);
468                 return;
469         }
470
471         // Decompress the whole block (version >= 29)
472         std::stringstream in_raw(std::ios_base::binary | std::ios_base::in | std::ios_base::out);
473         if (version >= 29)
474                 decompress(in_compressed, in_raw, version);
475         std::istream &is = version >= 29 ? in_raw : in_compressed;
476
477         u8 flags = readU8(is);
478         is_underground = (flags & 0x01) != 0;
479         m_day_night_differs = (flags & 0x02) != 0;
480         if (version < 27)
481                 m_lighting_complete = 0xFFFF;
482         else
483                 m_lighting_complete = readU16(is);
484         m_generated = (flags & 0x08) == 0;
485
486         NameIdMapping nimap;
487         if (disk && version >= 29) {
488                 // Timestamp
489                 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
490                                 <<": Timestamp"<<std::endl);
491                 setTimestampNoChangedFlag(readU32(is));
492                 m_disk_timestamp = m_timestamp;
493
494                 // Node/id mapping
495                 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
496                                 <<": NameIdMapping"<<std::endl);
497                 nimap.deSerialize(is);
498         }
499
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");
508
509         /*
510                 Bulk node data
511         */
512         if (version >= 29) {
513                 MapNode::deSerializeBulk(is, version, data, nodecount,
514                         content_width, params_width);
515         } else {
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);
520         }
521
522         /*
523                 NodeMetadata
524         */
525         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
526                         <<": Node metadata"<<std::endl);
527         if (version >= 29) {
528                 m_node_metadata.deSerialize(is, m_gamedef->idef());
529         } else {
530                 try {
531                         // reuse in_raw
532                         in_raw.str("");
533                         in_raw.clear();
534                         decompress(is, in_raw, version);
535                         if (version >= 23)
536                                 m_node_metadata.deSerialize(in_raw, m_gamedef->idef());
537                         else
538                                 content_nodemeta_deserialize_legacy(in_raw,
539                                         &m_node_metadata, &m_node_timers,
540                                         m_gamedef->idef());
541                 } catch(SerializationError &e) {
542                         warningstream<<"MapBlock::deSerialize(): Ignoring an error"
543                                         <<" while deserializing node metadata at ("
544                                         <<PP(getPos())<<": "<<e.what()<<std::endl;
545                 }
546         }
547
548         /*
549                 Data that is only on disk
550         */
551         if(disk)
552         {
553                 // Node timers
554                 if(version == 23){
555                         // Read unused zero
556                         readU8(is);
557                 }
558                 if(version == 24){
559                         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
560                                         <<": Node timers (ver==24)"<<std::endl);
561                         m_node_timers.deSerialize(is, version);
562                 }
563
564                 // Static objects
565                 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
566                                 <<": Static objects"<<std::endl);
567                 m_static_objects.deSerialize(is);
568
569                 if(version < 29) {
570                         // Timestamp
571                         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
572                                     <<": Timestamp"<<std::endl);
573                         setTimestampNoChangedFlag(readU32(is));
574                         m_disk_timestamp = m_timestamp;
575
576                         // Node/id mapping
577                         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
578                                     <<": NameIdMapping"<<std::endl);
579                         nimap.deSerialize(is);
580                 }
581
582                 // Dynamically re-set ids based on node names
583                 correctBlockNodeIds(&nimap, data, m_gamedef);
584
585                 if(version >= 25){
586                         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
587                                         <<": Node timers (ver>=25)"<<std::endl);
588                         m_node_timers.deSerialize(is, version);
589                 }
590         }
591
592         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
593                         <<": Done."<<std::endl);
594 }
595
596 void MapBlock::deSerializeNetworkSpecific(std::istream &is)
597 {
598         try {
599                 readU8(is);
600                 //const u8 version = readU8(is);
601                 //if (version != 1)
602                         //throw SerializationError("unsupported MapBlock version");
603
604         } catch(SerializationError &e) {
605                 warningstream<<"MapBlock::deSerializeNetworkSpecific(): Ignoring an error"
606                                 <<": "<<e.what()<<std::endl;
607         }
608 }
609
610 /*
611         Legacy serialization
612 */
613
614 void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
615 {
616         // Initialize default flags
617         is_underground = false;
618         m_day_night_differs = false;
619         m_lighting_complete = 0xFFFF;
620         m_generated = true;
621
622         // Make a temporary buffer
623         u32 ser_length = MapNode::serializedLength(version);
624         SharedBuffer<u8> databuf_nodelist(nodecount * ser_length);
625
626         // These have no compression
627         if (version <= 3 || version == 5 || version == 6) {
628                 char tmp;
629                 is.read(&tmp, 1);
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) {
639                 u8 t8;
640                 is.read((char *)&t8, 1);
641                 is_underground = t8;
642
643                 {
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];
653                         }
654                 }
655                 {
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];
665                         }
666                 }
667
668                 if (version >= 10) {
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];
678                         }
679                 }
680         } else { // All other versions (10 to 21)
681                 u8 flags;
682                 is.read((char*)&flags, 1);
683                 is_underground = (flags & 0x01) != 0;
684                 m_day_night_differs = (flags & 0x02) != 0;
685                 if(version >= 18)
686                         m_generated = (flags & 0x08) == 0;
687
688                 // Uncompress data
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");
695
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];
701                 }
702
703                 /*
704                         NodeMetadata
705                 */
706                 if (version >= 14) {
707                         // Ignore errors
708                         try {
709                                 if (version <= 15) {
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,
714                                                 m_gamedef->idef());
715                                 } else {
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,
722                                                 m_gamedef->idef());
723                                 }
724                         } catch(SerializationError &e) {
725                                 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
726                                                 <<" while deserializing node metadata"<<std::endl;
727                         }
728                 }
729         }
730
731         // Deserialize node data
732         for (u32 i = 0; i < nodecount; i++) {
733                 data[i].deSerialize(&databuf_nodelist[i * ser_length], version);
734         }
735
736         if (disk) {
737                 /*
738                         Versions up from 9 have block objects. (DEPRECATED)
739                 */
740                 if (version >= 9) {
741                         u16 count = readU16(is);
742                         // Not supported and length not known if count is not 0
743                         if(count != 0){
744                                 warningstream<<"MapBlock::deSerialize_pre22(): "
745                                                 <<"Ignoring stuff coming at and after MBOs"<<std::endl;
746                                 return;
747                         }
748                 }
749
750                 /*
751                         Versions up from 15 have static objects.
752                 */
753                 if (version >= 15)
754                         m_static_objects.deSerialize(is);
755
756                 // Timestamp
757                 if (version >= 17) {
758                         setTimestampNoChangedFlag(readU32(is));
759                         m_disk_timestamp = m_timestamp;
760                 } else {
761                         setTimestampNoChangedFlag(BLOCK_TIMESTAMP_UNDEFINED);
762                 }
763
764                 // Dynamically re-set ids based on node names
765                 NameIdMapping nimap;
766                 // If supported, read node definition id mapping
767                 if (version >= 21) {
768                         nimap.deSerialize(is);
769                 // Else set the legacy mapping
770                 } else {
771                         content_mapnode_get_name_id_mapping(&nimap);
772                 }
773                 correctBlockNodeIds(&nimap, data, m_gamedef);
774         }
775
776
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++)
781         {
782                 const ContentFeatures &f = nodedef->get(data[i].getContent());
783                 // Mineral
784                 if(nodedef->getId("default:stone") == data[i].getContent()
785                                 && data[i].getParam1() == 1)
786                 {
787                         data[i].setContent(nodedef->getId("default:stone_with_coal"));
788                         data[i].setParam1(0);
789                 }
790                 else if(nodedef->getId("default:stone") == data[i].getContent()
791                                 && data[i].getParam1() == 2)
792                 {
793                         data[i].setContent(nodedef->getId("default:stone_with_iron"));
794                         data[i].setParam1(0);
795                 }
796                 // facedir_simple
797                 if(f.legacy_facedir_simple)
798                 {
799                         data[i].setParam2(data[i].getParam1());
800                         data[i].setParam1(0);
801                 }
802                 // wall_mounted
803                 if(f.legacy_wallmounted)
804                 {
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++)
809                         {
810                                 if((dir_old_format & wallmounted_new_to_old[j]) != 0)
811                                 {
812                                         dir_new_format = j;
813                                         break;
814                                 }
815                         }
816                         data[i].setParam2(dir_new_format);
817                 }
818         }
819
820 }
821
822 /*
823         Get a quick string to describe what a block actually contains
824 */
825 std::string analyze_block(MapBlock *block)
826 {
827         if(block == NULL)
828                 return "NULL";
829
830         std::ostringstream desc;
831
832         v3s16 p = block->getPos();
833         char spos[25];
834         porting::mt_snprintf(spos, sizeof(spos), "(%2d,%2d,%2d), ", p.X, p.Y, p.Z);
835         desc<<spos;
836
837         switch(block->getModified())
838         {
839         case MOD_STATE_CLEAN:
840                 desc<<"CLEAN,           ";
841                 break;
842         case MOD_STATE_WRITE_AT_UNLOAD:
843                 desc<<"WRITE_AT_UNLOAD, ";
844                 break;
845         case MOD_STATE_WRITE_NEEDED:
846                 desc<<"WRITE_NEEDED,    ";
847                 break;
848         default:
849                 desc<<"unknown getModified()="+itos(block->getModified())+", ";
850         }
851
852         if(block->isGenerated())
853                 desc<<"is_gen [X], ";
854         else
855                 desc<<"is_gen [ ], ";
856
857         if(block->getIsUnderground())
858                 desc<<"is_ug [X], ";
859         else
860                 desc<<"is_ug [ ], ";
861
862         desc<<"lighting_complete: "<<block->getLightingComplete()<<", ";
863
864         if(block->isDummy())
865         {
866                 desc<<"Dummy, ";
867         }
868         else
869         {
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++)
877                 {
878                         v3s16 p(x0,y0,z0);
879                         MapNode n = block->getNodeNoEx(p);
880                         content_t c = n.getContent();
881                         if(c == CONTENT_IGNORE)
882                                 some_ignore = true;
883                         else
884                                 full_ignore = false;
885                         if(c == CONTENT_AIR)
886                                 some_air = true;
887                         else
888                                 full_air = false;
889                 }
890
891                 desc<<"content {";
892
893                 std::ostringstream ss;
894
895                 if(full_ignore)
896                         ss<<"IGNORE (full), ";
897                 else if(some_ignore)
898                         ss<<"IGNORE, ";
899
900                 if(full_air)
901                         ss<<"AIR (full), ";
902                 else if(some_air)
903                         ss<<"AIR, ";
904
905                 if(ss.str().size()>=2)
906                         desc<<ss.str().substr(0, ss.str().size()-2);
907
908                 desc<<"}, ";
909         }
910
911         return desc.str().substr(0, desc.str().size()-2);
912 }
913
914
915 //END