Codebase list minetestmapper / 05371ee
Update upstream source from tag 'upstream/20200328' Update to upstream version '20200328' with Debian dir 30e871b920df75c7019f61624ef738b66cfd8e67 Julien Puydt 4 years ago
21 changed file(s) with 821 addition(s) and 403 deletion(s). Raw diff Collapse all Expand all
22 - gcc
33 - clang
44 dist: bionic
5 before_install: sudo apt-get install -y cmake libgd-dev libsqlite3-dev libleveldb-dev
5 addons:
6 apt:
7 packages:
8 - cmake
9 - libgd-dev
10 - libsqlite3-dev
11 - libleveldb-dev
12 - libpq-dev
13 - postgresql-server-dev-all
614 script: ./util/travis/script.sh
715 notifications:
816 email: false
00 project(minetestmapper CXX)
11 cmake_minimum_required(VERSION 2.6)
22 cmake_policy(SET CMP0003 NEW)
3 set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
43
54 set(VERSION_MAJOR 1)
65 set(VERSION_MINOR 0)
1211 set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
1312 endif()
1413
14 set(CMAKE_CXX_STANDARD 11)
1515 set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall -DNDEBUG")
1616 set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g2 -Wall")
1717
4444 message(STATUS "Using DOCDIR=${DOCDIR}")
4545 endif()
4646
47 #set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
48 find_package(PkgConfig)
49 include(FindPackageHandleStandardArgs)
4750
4851 # Libraries: gd
4952
6568 message(FATAL_ERROR "zlib not found!")
6669 endif(NOT ZLIB_LIBRARY OR NOT ZLIB_INCLUDE_DIR)
6770
68 find_package(PkgConfig)
69 include(FindPackageHandleStandardArgs)
70
7171 # Libraries: sqlite3
7272
7373 find_library(SQLITE3_LIBRARY sqlite3)
8181 # Libraries: postgresql
8282
8383 option(ENABLE_POSTGRESQL "Enable PostgreSQL backend" TRUE)
84 set(USE_POSTGRESQL 0)
84 set(USE_POSTGRESQL FALSE)
8585
8686 if(ENABLE_POSTGRESQL)
87 find_program(POSTGRESQL_CONFIG_EXECUTABLE pg_config DOC "pg_config")
88 find_library(POSTGRESQL_LIBRARY pq)
89 if(POSTGRESQL_CONFIG_EXECUTABLE)
90 execute_process(COMMAND ${POSTGRESQL_CONFIG_EXECUTABLE} --includedir-server
91 OUTPUT_VARIABLE POSTGRESQL_SERVER_INCLUDE_DIRS
92 OUTPUT_STRIP_TRAILING_WHITESPACE)
93 execute_process(COMMAND ${POSTGRESQL_CONFIG_EXECUTABLE}
94 OUTPUT_VARIABLE POSTGRESQL_CLIENT_INCLUDE_DIRS
95 OUTPUT_STRIP_TRAILING_WHITESPACE)
96 # This variable is case sensitive for the cmake PostgreSQL module
97 set(PostgreSQL_ADDITIONAL_SEARCH_PATHS ${POSTGRESQL_SERVER_INCLUDE_DIRS} ${POSTGRESQL_CLIENT_INCLUDE_DIRS})
98 endif()
99
10087 find_package("PostgreSQL")
10188
102 if(POSTGRESQL_FOUND)
103 set(USE_POSTGRESQL 1)
89 if(PostgreSQL_FOUND)
90 set(USE_POSTGRESQL TRUE)
10491 message(STATUS "PostgreSQL backend enabled")
10592 # This variable is case sensitive, don't try to change it to POSTGRESQL_INCLUDE_DIR
106 message(STATUS "PostgreSQL includes: ${PostgreSQL_INCLUDE_DIR}")
107 include_directories(${PostgreSQL_INCLUDE_DIR})
108 set(POSTGRESQL_LIBRARY ${PostgreSQL_LIBRARIES})
93 message(STATUS "PostgreSQL includes: ${PostgreSQL_INCLUDE_DIRS}")
94 include_directories(${PostgreSQL_INCLUDE_DIRS})
10995 else()
110 message(STATUS "PostgreSQL not found.")
111 set(POSTGRESQL_LIBRARY "")
96 message(STATUS "PostgreSQL not found!")
97 set(PostgreSQL_LIBRARIES "")
11298 endif()
11399 endif(ENABLE_POSTGRESQL)
114100
115101 # Libraries: leveldb
116102
117 set(USE_LEVELDB 0)
118
119 OPTION(ENABLE_LEVELDB "Enable LevelDB backend")
103 OPTION(ENABLE_LEVELDB "Enable LevelDB backend" TRUE)
104 set(USE_LEVELDB FALSE)
120105
121106 if(ENABLE_LEVELDB)
122107 find_library(LEVELDB_LIBRARY leveldb)
123 find_path(LEVELDB_INCLUDE_DIR db.h PATH_SUFFIXES leveldb)
108 find_path(LEVELDB_INCLUDE_DIR leveldb/db.h)
124109 message (STATUS "LevelDB library: ${LEVELDB_LIBRARY}")
125110 message (STATUS "LevelDB headers: ${LEVELDB_INCLUDE_DIR}")
126111 if(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR)
127 set(USE_LEVELDB 1)
112 set(USE_LEVELDB TRUE)
128113 message(STATUS "LevelDB backend enabled")
129114 include_directories(${LEVELDB_INCLUDE_DIR})
130 else(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR)
131 set(USE_LEVELDB 0)
115 else()
132116 message(STATUS "LevelDB not found!")
133 endif(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR)
117 set(LEVELDB_LIBRARY "")
118 endif()
134119 endif(ENABLE_LEVELDB)
135120
136121 # Libraries: redis
137122
138 set(USE_REDIS 0)
139
140 OPTION(ENABLE_REDIS "Enable redis backend")
123 OPTION(ENABLE_REDIS "Enable redis backend" TRUE)
124 set(USE_REDIS FALSE)
141125
142126 if(ENABLE_REDIS)
143127 find_library(REDIS_LIBRARY hiredis)
144 find_path(REDIS_INCLUDE_DIR hiredis.h PATH_SUFFIXES hiredis)
128 find_path(REDIS_INCLUDE_DIR hiredis/hiredis.h)
145129 message (STATUS "redis library: ${REDIS_LIBRARY}")
146130 message (STATUS "redis headers: ${REDIS_INCLUDE_DIR}")
147131 if(REDIS_LIBRARY AND REDIS_INCLUDE_DIR)
148 set(USE_REDIS 1)
132 set(USE_REDIS TRUE)
149133 message(STATUS "redis backend enabled")
150134 include_directories(${REDIS_INCLUDE_DIR})
151 else(REDIS_LIBRARY AND REDIS_INCLUDE_DIR)
152 set(USE_REDIS 0)
135 else()
153136 message(STATUS "redis not found!")
154 endif(REDIS_LIBRARY AND REDIS_INCLUDE_DIR)
137 set(REDIS_LIBRARY "")
138 endif()
155139 endif(ENABLE_REDIS)
156140
157141 # Compiling & Linking
202186 target_link_libraries(
203187 minetestmapper
204188 ${SQLITE3_LIBRARY}
205 ${POSTGRESQL_LIBRARY}
189 ${PostgreSQL_LIBRARIES}
206190 ${LEVELDB_LIBRARY}
207191 ${REDIS_LIBRARY}
208192 ${LIBGD_LIBRARY}
66 * =====================================================================
77 */
88
9 #include <cstdlib>
9 #include "PixelAttributes.h"
1010 #include <cstring>
11 #include "PixelAttributes.h"
12
13 using namespace std;
1411
1512 PixelAttributes::PixelAttributes():
1613 m_width(0)
1714 {
1815 for (size_t i = 0; i < LineCount; ++i) {
19 m_pixelAttributes[i] = 0;
16 m_pixelAttributes[i] = nullptr;
2017 }
2118 }
2219
4643 void PixelAttributes::freeAttributes()
4744 {
4845 for (size_t i = 0; i < LineCount; ++i) {
49 if (m_pixelAttributes[i] != 0) {
46 if (m_pixelAttributes[i] != nullptr) {
5047 delete[] m_pixelAttributes[i];
51 m_pixelAttributes[i] = 0;
48 m_pixelAttributes[i] = nullptr;
5249 }
5350 }
5451 }
105105
106106 scales:
107107 Draw scales on specified image edges (letters *t b l r* meaning top, bottom, left and right), e.g. ``--scales tbr``
108
109 exhaustive:
110 | Select if database should be traversed exhaustively or using range queries, available: *never*, *y*, *full*, *auto*
111 | Defaults to *auto*. You shouldn't need to change this, but doing so can improve rendering times on large maps.
112 | For these optimizations to work it is important that you set ``min-y`` and ``max-y`` when you don't care about the world below e.g. -60 and above 1000 nodes.
00 #include <cstdio>
11 #include <cstdlib>
22 #include <climits>
3 #include <cassert>
34 #include <fstream>
45 #include <iostream>
56 #include <sstream>
8889 m_xMax(INT_MIN),
8990 m_zMin(INT_MAX),
9091 m_zMax(INT_MIN),
91 m_yMin(-30000),
92 m_yMax(30000),
92 m_yMin(INT16_MIN),
93 m_yMax(INT16_MAX),
9394 m_geomX(-2048),
9495 m_geomY(-2048),
9596 m_geomX2(2048),
9697 m_geomY2(2048),
98 m_exhaustiveSearch(EXH_AUTO),
9799 m_zoom(1),
98100 m_scales(SCALE_LEFT | SCALE_TOP)
99101 {
183185
184186 void TileGenerator::setGeometry(int x, int y, int w, int h)
185187 {
188 assert(w > 0 && h > 0);
186189 m_geomX = round_multiple_nosign(x, 16) / 16;
187190 m_geomY = round_multiple_nosign(y, 16) / 16;
188191 m_geomX2 = round_multiple_nosign(x + w, 16) / 16;
192195 void TileGenerator::setMinY(int y)
193196 {
194197 m_yMin = y;
198 if (m_yMin > m_yMax)
199 std::swap(m_yMin, m_yMax);
195200 }
196201
197202 void TileGenerator::setMaxY(int y)
198203 {
199204 m_yMax = y;
205 if (m_yMin > m_yMax)
206 std::swap(m_yMin, m_yMax);
207 }
208
209 void TileGenerator::setExhaustiveSearch(int mode)
210 {
211 m_exhaustiveSearch = mode;
200212 }
201213
202214 void TileGenerator::parseColorsFile(const std::string &fileName)
215227 input_path += PATH_SEPARATOR;
216228 }
217229
230 setExhaustiveSearch(EXH_NEVER);
218231 openDb(input_path);
219232 loadBlocks();
220233
240253 input_path += PATH_SEPARATOR;
241254 }
242255
256 if (m_dontWriteEmpty) // FIXME: possible too, just needs to be done differently
257 setExhaustiveSearch(EXH_NEVER);
243258 openDb(input_path);
244259 loadBlocks();
245260
246 if (m_dontWriteEmpty && ! m_positions.size())
261 if (m_dontWriteEmpty && m_positions.empty())
247262 {
248263 closeDatabase();
249264 return;
267282
268283 void TileGenerator::parseColorsStream(std::istream &in)
269284 {
270 char line[128];
285 char line[512];
271286 while (in.good()) {
272 in.getline(line, 128);
287 in.getline(line, sizeof(line));
273288
274289 for(char *p = line; *p; p++) {
275290 if(*p != '#')
280295 if(strlen(line) == 0)
281296 continue;
282297
283 char name[64 + 1];
298 char name[128 + 1] = {0};
284299 unsigned int r, g, b, a, t;
285300 a = 255;
286301 t = 0;
287 int items = sscanf(line, "%64s %u %u %u %u %u", name, &r, &g, &b, &a, &t);
302 int items = sscanf(line, "%128s %u %u %u %u %u", name, &r, &g, &b, &a, &t);
288303 if(items < 4) {
289304 std::cerr << "Failed to parse color entry '" << line << "'" << std::endl;
290305 continue;
291306 }
292307
293 ColorEntry color = ColorEntry(r, g, b, a, t);
308 ColorEntry color(r, g, b, a, t);
294309 m_colorMap[name] = color;
295310 }
311 }
312
313 std::set<std::string> TileGenerator::getSupportedBackends()
314 {
315 std::set<std::string> r;
316 r.insert("sqlite3");
317 #if USE_POSTGRESQL
318 r.insert("postgresql");
319 #endif
320 #if USE_LEVELDB
321 r.insert("leveldb");
322 #endif
323 #if USE_REDIS
324 r.insert("redis");
325 #endif
326 return r;
296327 }
297328
298329 void TileGenerator::openDb(const std::string &input)
322353 #endif
323354 else
324355 throw std::runtime_error(((std::string) "Unknown map backend: ") + backend);
356
357 // Determine how we're going to traverse the database (heuristic)
358 if (m_exhaustiveSearch == EXH_AUTO) {
359 using u64 = uint64_t;
360 u64 y_range = (m_yMax / 16 + 1) - (m_yMin / 16);
361 u64 blocks = (u64)(m_geomX2 - m_geomX) * y_range * (u64)(m_geomY2 - m_geomY);
362 #ifndef NDEBUG
363 std::cout << "Heuristic parameters:"
364 << " preferRangeQueries()=" << m_db->preferRangeQueries()
365 << " y_range=" << y_range << " blocks=" << blocks << std::endl;
366 #endif
367 if (m_db->preferRangeQueries())
368 m_exhaustiveSearch = EXH_NEVER;
369 else if (blocks < 200000)
370 m_exhaustiveSearch = EXH_FULL;
371 else if (y_range < 42)
372 m_exhaustiveSearch = EXH_Y;
373 else
374 m_exhaustiveSearch = EXH_NEVER;
375 } else if (m_exhaustiveSearch == EXH_FULL || m_exhaustiveSearch == EXH_Y) {
376 if (m_db->preferRangeQueries()) {
377 std::cerr << "Note: The current database backend supports efficient "
378 "range queries, forcing exhaustive search should always result "
379 " in worse performance." << std::endl;
380 }
381 }
382 assert(m_exhaustiveSearch != EXH_AUTO);
325383 }
326384
327385 void TileGenerator::closeDatabase()
332390
333391 void TileGenerator::loadBlocks()
334392 {
335 std::vector<BlockPos> vec = m_db->getBlockPos();
336 for (std::vector<BlockPos>::iterator it = vec.begin(); it != vec.end(); ++it) {
337 BlockPos pos = *it;
338 // Check that it's in geometry (from --geometry option)
339 if (pos.x < m_geomX || pos.x >= m_geomX2 || pos.z < m_geomY || pos.z >= m_geomY2)
340 continue;
341 // Check that it's between --min-y and --max-y
342 if (pos.y * 16 < m_yMin || pos.y * 16 > m_yMax)
343 continue;
344 // Adjust minimum and maximum positions to the nearest block
345 if (pos.x < m_xMin)
346 m_xMin = pos.x;
347 if (pos.x > m_xMax)
348 m_xMax = pos.x;
349
350 if (pos.z < m_zMin)
351 m_zMin = pos.z;
352 if (pos.z > m_zMax)
353 m_zMax = pos.z;
354 m_positions.push_back(std::pair<int, int>(pos.x, pos.z));
355 }
356 m_positions.sort();
357 m_positions.unique();
393 const int16_t yMax = m_yMax / 16 + 1;
394
395 if (m_exhaustiveSearch == EXH_NEVER || m_exhaustiveSearch == EXH_Y) {
396 std::vector<BlockPos> vec = m_db->getBlockPos(
397 BlockPos(m_geomX, m_yMin / 16, m_geomY),
398 BlockPos(m_geomX2, yMax, m_geomY2)
399 );
400
401 for (auto pos : vec) {
402 assert(pos.x >= m_geomX && pos.x < m_geomX2);
403 assert(pos.y >= m_yMin / 16 && pos.y < yMax);
404 assert(pos.z >= m_geomY && pos.z < m_geomY2);
405
406 // Adjust minimum and maximum positions to the nearest block
407 if (pos.x < m_xMin)
408 m_xMin = pos.x;
409 if (pos.x > m_xMax)
410 m_xMax = pos.x;
411
412 if (pos.z < m_zMin)
413 m_zMin = pos.z;
414 if (pos.z > m_zMax)
415 m_zMax = pos.z;
416
417 m_positions[pos.z].emplace(pos.x);
418 }
419
420 #ifndef NDEBUG
421 int count = 0;
422 for (const auto &it : m_positions)
423 count += it.second.size();
424 std::cout << "Loaded " << count
425 << " positions (across Z: " << m_positions.size() << ") for rendering" << std::endl;
426 #endif
427 }
358428 }
359429
360430 void TileGenerator::createImage()
392462 image_height = (m_mapHeight * m_zoom) + m_yBorder;
393463 image_height += (m_scales & SCALE_BOTTOM) ? scale_d : 0;
394464
395 if(image_width > 4096 || image_height > 4096)
465 if(image_width > 4096 || image_height > 4096) {
396466 std::cerr << "Warning: The width or height of the image to be created exceeds 4096 pixels!"
397467 << " (Dimensions: " << image_width << "x" << image_height << ")"
398468 << std::endl;
469 }
399470 m_image = new Image(image_width, image_height);
400471 m_image->drawFilledRect(0, 0, image_width, image_height, m_bgColor); // Background
401472 }
403474 void TileGenerator::renderMap()
404475 {
405476 BlockDecoder blk;
406 std::list<int> zlist = getZValueList();
407 for (std::list<int>::iterator zPosition = zlist.begin(); zPosition != zlist.end(); ++zPosition) {
408 int zPos = *zPosition;
409 std::map<int16_t, BlockList> blocks;
410 m_db->getBlocksOnZ(blocks, zPos);
411 for (std::list<std::pair<int, int> >::const_iterator position = m_positions.begin(); position != m_positions.end(); ++position) {
412 if (position->second != zPos)
477 const int16_t yMax = m_yMax / 16 + 1;
478
479 auto renderSingle = [&] (int16_t xPos, int16_t zPos, BlockList &blockStack) {
480 m_readPixels.reset();
481 m_readInfo.reset();
482 for (int i = 0; i < 16; i++) {
483 for (int j = 0; j < 16; j++) {
484 m_color[i][j] = m_bgColor; // This will be drawn by renderMapBlockBottom() for y-rows with only 'air', 'ignore' or unknown nodes if --drawalpha is used
485 m_color[i][j].a = 0; // ..but set alpha to 0 to tell renderMapBlock() not to use this color to mix a shade
486 m_thickness[i][j] = 0;
487 }
488 }
489
490 for (const auto &it : blockStack) {
491 const BlockPos pos = it.first;
492 assert(pos.x == xPos && pos.z == zPos);
493 assert(pos.y >= m_yMin / 16 && pos.y < yMax);
494
495 blk.reset();
496 blk.decode(it.second);
497 if (blk.isEmpty())
413498 continue;
414
415 m_readPixels.reset();
416 m_readInfo.reset();
417 for (int i = 0; i < 16; i++) {
418 for (int j = 0; j < 16; j++) {
419 m_color[i][j] = m_bgColor; // This will be drawn by renderMapBlockBottom() for y-rows with only 'air', 'ignore' or unknown nodes if --drawalpha is used
420 m_color[i][j].a = 0; // ..but set alpha to 0 to tell renderMapBlock() not to use this color to mix a shade
421 m_thickness[i][j] = 0;
422 }
423 }
424
425 int xPos = position->first;
426 blocks[xPos].sort();
427 const BlockList &blockStack = blocks[xPos];
428 for (BlockList::const_iterator it = blockStack.begin(); it != blockStack.end(); ++it) {
429 const BlockPos &pos = it->first;
430
431 blk.reset();
432 blk.decode(it->second);
433 if (blk.isEmpty())
434 continue;
435 renderMapBlock(blk, pos);
436
437 // Exit out if all pixels for this MapBlock are covered
438 if (m_readPixels.full())
439 break;
440 }
441 if (!m_readPixels.full())
442 renderMapBlockBottom(blockStack.begin()->first);
443 }
499 renderMapBlock(blk, pos);
500
501 // Exit out if all pixels for this MapBlock are covered
502 if (m_readPixels.full())
503 break;
504 }
505 if (!m_readPixels.full())
506 renderMapBlockBottom(blockStack.begin()->first);
507 };
508 auto postRenderRow = [&] (int16_t zPos) {
444509 if (m_shading)
445510 renderShading(zPos);
511 };
512
513 if (m_exhaustiveSearch == EXH_NEVER) {
514 for (auto it = m_positions.rbegin(); it != m_positions.rend(); ++it) {
515 int16_t zPos = it->first;
516 for (auto it2 = it->second.rbegin(); it2 != it->second.rend(); ++it2) {
517 int16_t xPos = *it2;
518
519 BlockList blockStack;
520 m_db->getBlocksOnXZ(blockStack, xPos, zPos, m_yMin / 16, yMax);
521 blockStack.sort();
522
523 renderSingle(xPos, zPos, blockStack);
524 }
525 postRenderRow(zPos);
526 }
527 } else if (m_exhaustiveSearch == EXH_Y) {
528 #ifndef NDEBUG
529 std::cerr << "Exhaustively searching height of "
530 << (yMax - (m_yMin / 16)) << " blocks" << std::endl;
531 #endif
532 std::vector<BlockPos> positions;
533 positions.reserve(yMax - (m_yMin / 16));
534 for (auto it = m_positions.rbegin(); it != m_positions.rend(); ++it) {
535 int16_t zPos = it->first;
536 for (auto it2 = it->second.rbegin(); it2 != it->second.rend(); ++it2) {
537 int16_t xPos = *it2;
538
539 positions.clear();
540 for (int16_t yPos = m_yMin / 16; yPos < yMax; yPos++)
541 positions.emplace_back(xPos, yPos, zPos);
542
543 BlockList blockStack;
544 m_db->getBlocksByPos(blockStack, positions);
545 blockStack.sort();
546
547 renderSingle(xPos, zPos, blockStack);
548 }
549 postRenderRow(zPos);
550 }
551 } else if (m_exhaustiveSearch == EXH_FULL) {
552 #ifndef NDEBUG
553 std::cerr << "Exhaustively searching "
554 << (m_geomX2 - m_geomX) << "x" << (yMax - (m_yMin / 16)) << "x"
555 << (m_geomY2 - m_geomY) << " blocks" << std::endl;
556 #endif
557 std::vector<BlockPos> positions;
558 positions.reserve(yMax - (m_yMin / 16));
559 for (int16_t zPos = m_geomY2 - 1; zPos >= m_geomY; zPos--) {
560 for (int16_t xPos = m_geomX2 - 1; xPos >= m_geomX; xPos--) {
561 positions.clear();
562 for (int16_t yPos = m_yMin / 16; yPos < yMax; yPos++)
563 positions.emplace_back(xPos, yPos, zPos);
564
565 BlockList blockStack;
566 m_db->getBlocksByPos(blockStack, positions);
567 blockStack.sort();
568
569 renderSingle(xPos, zPos, blockStack);
570 }
571 postRenderRow(zPos);
572 }
446573 }
447574 }
448575
451578 int xBegin = (pos.x - m_xMin) * 16;
452579 int zBegin = (m_zMax - pos.z) * 16;
453580 int minY = (pos.y * 16 > m_yMin) ? 0 : m_yMin - pos.y * 16;
454 int maxY = (pos.y * 16 < m_yMax) ? 15 : m_yMax - pos.y * 16;
581 int maxY = (pos.y * 16 + 15 < m_yMax) ? 15 : m_yMax - pos.y * 16;
455582 for (int z = 0; z < 16; ++z) {
456583 int imageY = zBegin + 15 - z;
457584 for (int x = 0; x < 16; ++x) {
635762 void TileGenerator::renderPlayers(const std::string &inputPath)
636763 {
637764 PlayerAttributes players(inputPath);
638 for (PlayerAttributes::Players::iterator player = players.begin(); player != players.end(); ++player) {
639 if (player->x < m_xMin * 16 || player->x > m_xMax * 16 ||
640 player->z < m_zMin * 16 || player->z > m_zMax * 16)
765 for (auto &player : players) {
766 if (player.x < m_xMin * 16 || player.x > m_xMax * 16 ||
767 player.z < m_zMin * 16 || player.z > m_zMax * 16)
641768 continue;
642 if (player->y < m_yMin || player->y > m_yMax)
769 if (player.y < m_yMin || player.y > m_yMax)
643770 continue;
644 int imageX = getImageX(player->x, true),
645 imageY = getImageY(player->z, true);
771 int imageX = getImageX(player.x, true),
772 imageY = getImageY(player.z, true);
646773
647774 m_image->drawFilledRect(imageX - 1, imageY, 3, 1, m_playerColor);
648775 m_image->drawFilledRect(imageX, imageY - 1, 1, 3, m_playerColor);
649 m_image->drawText(imageX + 2, imageY, player->name, m_playerColor);
650 }
651 }
652
653 inline std::list<int> TileGenerator::getZValueList() const
654 {
655 std::list<int> zlist;
656 for (std::list<std::pair<int, int> >::const_iterator position = m_positions.begin(); position != m_positions.end(); ++position)
657 zlist.push_back(position->second);
658 zlist.sort();
659 zlist.unique();
660 zlist.reverse();
661 return zlist;
776 m_image->drawText(imageX + 2, imageY, player.name, m_playerColor);
777 }
662778 }
663779
664780 void TileGenerator::writeImage(const std::string &output)
673789 if (m_unknownNodes.size() == 0)
674790 return;
675791 std::cerr << "Unknown nodes:" << std::endl;
676 for (NameSet::iterator node = m_unknownNodes.begin(); node != m_unknownNodes.end(); ++node)
677 std::cerr << "\t" << *node << std::endl;
792 for (const auto &node : m_unknownNodes)
793 std::cerr << "\t" << node << std::endl;
678794 }
679795
680796 inline int TileGenerator::getImageX(int val, bool absolute) const
1010 return t;
1111 }
1212
13
1413 static inline std::string i64tos(int64_t i)
1514 {
1615 std::ostringstream os;
1716 os << i;
1817 return os.str();
1918 }
19
2020
2121 DBLevelDB::DBLevelDB(const std::string &mapdir)
2222 {
2727 throw std::runtime_error(std::string("Failed to open Database: ") + status.ToString());
2828 }
2929
30 /* LevelDB is a dumb key-value store, so the only optimization we can do
31 * is to cache the block positions that exist in the db.
32 */
3033 loadPosCache();
3134 }
3235
3740 }
3841
3942
40 std::vector<BlockPos> DBLevelDB::getBlockPos()
43 std::vector<BlockPos> DBLevelDB::getBlockPos(BlockPos min, BlockPos max)
4144 {
42 return posCache;
45 std::vector<BlockPos> res;
46 for (const auto &it : posCache) {
47 if (it.first < min.z || it.first >= max.z)
48 continue;
49 for (auto pos2 : it.second) {
50 if (pos2.first < min.x || pos2.first >= max.x)
51 continue;
52 if (pos2.second < min.y || pos2.second >= max.y)
53 continue;
54 res.emplace_back(pos2.first, pos2.second, it.first);
55 }
56 }
57 return res;
4358 }
4459
4560
4863 leveldb::Iterator * it = db->NewIterator(leveldb::ReadOptions());
4964 for (it->SeekToFirst(); it->Valid(); it->Next()) {
5065 int64_t posHash = stoi64(it->key().ToString());
51 posCache.push_back(decodeBlockPos(posHash));
66 BlockPos pos = decodeBlockPos(posHash);
67
68 posCache[pos.z].emplace_back(pos.x, pos.y);
5269 }
5370 delete it;
5471 }
5572
5673
57 void DBLevelDB::getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos)
74 void DBLevelDB::getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
75 int16_t min_y, int16_t max_y)
5876 {
5977 std::string datastr;
6078 leveldb::Status status;
6179
62 for (std::vector<BlockPos>::iterator it = posCache.begin(); it != posCache.end(); ++it) {
63 if (it->z != zPos) {
80 auto it = posCache.find(z);
81 if (it == posCache.cend())
82 return;
83 for (auto pos2 : it->second) {
84 if (pos2.first != x)
6485 continue;
65 }
66 status = db->Get(leveldb::ReadOptions(), i64tos(encodeBlockPos(*it)), &datastr);
86 if (pos2.second < min_y || pos2.second >= max_y)
87 continue;
88
89 BlockPos pos(x, pos2.second, z);
90 status = db->Get(leveldb::ReadOptions(), i64tos(encodeBlockPos(pos)), &datastr);
6791 if (status.ok()) {
68 Block b(*it, ustring((const unsigned char *) datastr.data(), datastr.size()));
69 blocks[b.first.x].push_back(b);
92 blocks.emplace_back(
93 pos, ustring((unsigned char *) datastr.data(), datastr.size())
94 );
7095 }
7196 }
7297 }
7398
99 void DBLevelDB::getBlocksByPos(BlockList &blocks,
100 const std::vector<BlockPos> &positions)
101 {
102 std::string datastr;
103 leveldb::Status status;
104
105 for (auto pos : positions) {
106 status = db->Get(leveldb::ReadOptions(), i64tos(encodeBlockPos(pos)), &datastr);
107 if (status.ok()) {
108 blocks.emplace_back(
109 pos, ustring((unsigned char *) datastr.data(), datastr.size())
110 );
111 }
112 }
113 }
2626
2727 prepareStatement(
2828 "get_block_pos",
29 "SELECT posX, posY, posZ FROM blocks"
29 "SELECT posX::int4, posY::int4, posZ::int4 FROM blocks WHERE"
30 " (posX BETWEEN $1::int4 AND $2::int4) AND"
31 " (posY BETWEEN $3::int4 AND $4::int4) AND"
32 " (posZ BETWEEN $5::int4 AND $6::int4)"
3033 );
3134 prepareStatement(
32 "get_blocks_z",
33 "SELECT posX, posY, data FROM blocks WHERE posZ = $1::int4"
35 "get_blocks",
36 "SELECT posY::int4, data FROM blocks WHERE"
37 " posX = $1::int4 AND posZ = $2::int4"
38 " AND (posY BETWEEN $3::int4 AND $4::int4)"
39 );
40 prepareStatement(
41 "get_block_exact",
42 "SELECT data FROM blocks WHERE"
43 " posX = $1::int4 AND posY = $2::int4 AND posZ = $3::int4"
3444 );
3545
3646 checkResults(PQexec(db, "START TRANSACTION;"));
4252 {
4353 try {
4454 checkResults(PQexec(db, "COMMIT;"));
45 } catch (std::exception& caught) {
55 } catch (const std::exception& caught) {
4656 std::cerr << "could not finalize: " << caught.what() << std::endl;
4757 }
4858 PQfinish(db);
4959 }
5060
51 std::vector<BlockPos> DBPostgreSQL::getBlockPos()
52 {
61
62 std::vector<BlockPos> DBPostgreSQL::getBlockPos(BlockPos min, BlockPos max)
63 {
64 int32_t const x1 = htonl(min.x);
65 int32_t const x2 = htonl(max.x - 1);
66 int32_t const y1 = htonl(min.y);
67 int32_t const y2 = htonl(max.y - 1);
68 int32_t const z1 = htonl(min.z);
69 int32_t const z2 = htonl(max.z - 1);
70
71 const void *args[] = { &x1, &x2, &y1, &y2, &z1, &z2 };
72 const int argLen[] = { 4, 4, 4, 4, 4, 4 };
73 const int argFmt[] = { 1, 1, 1, 1, 1, 1 };
74
75 PGresult *results = execPrepared(
76 "get_block_pos", ARRLEN(args), args,
77 argLen, argFmt, false
78 );
79
80 int numrows = PQntuples(results);
81
5382 std::vector<BlockPos> positions;
83 positions.reserve(numrows);
84
85 for (int row = 0; row < numrows; ++row)
86 positions.emplace_back(pg_to_blockpos(results, row, 0));
87
88 PQclear(results);
89
90 return positions;
91 }
92
93
94 void DBPostgreSQL::getBlocksOnXZ(BlockList &blocks, int16_t xPos, int16_t zPos,
95 int16_t min_y, int16_t max_y)
96 {
97 int32_t const x = htonl(xPos);
98 int32_t const z = htonl(zPos);
99 int32_t const y1 = htonl(min_y);
100 int32_t const y2 = htonl(max_y - 1);
101
102 const void *args[] = { &x, &z, &y1, &y2 };
103 const int argLen[] = { 4, 4, 4, 4 };
104 const int argFmt[] = { 1, 1, 1, 1 };
54105
55106 PGresult *results = execPrepared(
56 "get_block_pos", 0,
57 NULL, NULL, NULL, false, false
58 );
59
60 int numrows = PQntuples(results);
61
62 for (int row = 0; row < numrows; ++row)
63 positions.push_back(pg_to_blockpos(results, row, 0));
64
65 PQclear(results);
66
67 return positions;
68 }
69
70
71 void DBPostgreSQL::getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos)
72 {
73 int32_t const z = htonl(zPos);
74
75 const void *args[] = { &z };
76 const int argLen[] = { sizeof(z) };
77 const int argFmt[] = { 1 };
78
79 PGresult *results = execPrepared(
80 "get_blocks_z", ARRLEN(args), args,
107 "get_blocks", ARRLEN(args), args,
81108 argLen, argFmt, false
82109 );
83110
85112
86113 for (int row = 0; row < numrows; ++row) {
87114 BlockPos position;
88 position.x = pg_binary_to_int(results, row, 0);
89 position.y = pg_binary_to_int(results, row, 1);
115 position.x = xPos;
116 position.y = pg_binary_to_int(results, row, 0);
90117 position.z = zPos;
91 Block const b(
118 blocks.emplace_back(
92119 position,
93120 ustring(
94121 reinterpret_cast<unsigned char*>(
95 PQgetvalue(results, row, 2)
122 PQgetvalue(results, row, 1)
96123 ),
97 PQgetlength(results, row, 2)
124 PQgetlength(results, row, 1)
98125 )
99126 );
100 blocks[position.x].push_back(b);
101127 }
102128
103129 PQclear(results);
104130 }
131
132
133 void DBPostgreSQL::getBlocksByPos(BlockList &blocks,
134 const std::vector<BlockPos> &positions)
135 {
136 int32_t x, y, z;
137
138 const void *args[] = { &x, &y, &z };
139 const int argLen[] = { 4, 4, 4 };
140 const int argFmt[] = { 1, 1, 1 };
141
142 for (auto pos : positions) {
143 x = htonl(pos.x);
144 y = htonl(pos.y);
145 z = htonl(pos.z);
146
147 PGresult *results = execPrepared(
148 "get_block_exact", ARRLEN(args), args,
149 argLen, argFmt, false
150 );
151
152 if (PQntuples(results) > 0) {
153 blocks.emplace_back(
154 pos,
155 ustring(
156 reinterpret_cast<unsigned char*>(
157 PQgetvalue(results, 0, 0)
158 ),
159 PQgetlength(results, 0, 0)
160 )
161 );
162 }
163
164 PQclear(results);
165 }
166 }
167
105168
106169 PGresult *DBPostgreSQL::checkResults(PGresult *res, bool clear)
107170 {
137200 const char *stmtName, const int paramsNumber,
138201 const void **params,
139202 const int *paramsLengths, const int *paramsFormats,
140 bool clear, bool nobinary
203 bool clear
141204 )
142205 {
143206 return checkResults(PQexecPrepared(db, stmtName, paramsNumber,
144207 (const char* const*) params, paramsLengths, paramsFormats,
145 nobinary ? 1 : 0), clear
146 );
147 }
148
149 int DBPostgreSQL::pg_to_int(PGresult *res, int row, int col)
150 {
151 return atoi(PQgetvalue(res, row, col));
208 1 /* binary output */), clear
209 );
152210 }
153211
154212 int DBPostgreSQL::pg_binary_to_int(PGresult *res, int row, int col)
160218 BlockPos DBPostgreSQL::pg_to_blockpos(PGresult *res, int row, int col)
161219 {
162220 BlockPos result;
163 result.x = pg_to_int(res, row, col);
164 result.y = pg_to_int(res, row, col + 1);
165 result.z = pg_to_int(res, row, col + 2);
221 result.x = pg_binary_to_int(res, row, col);
222 result.y = pg_binary_to_int(res, row, col + 1);
223 result.z = pg_binary_to_int(res, row, col + 2);
166224 return result;
167225 }
5050 throw std::runtime_error(err);
5151 }
5252
53 /* Redis is just a key-value store, so the only optimization we can do
54 * is to cache the block positions that exist in the db.
55 */
5356 loadPosCache();
5457 }
5558
6063 }
6164
6265
63 std::vector<BlockPos> DBRedis::getBlockPos()
66 std::vector<BlockPos> DBRedis::getBlockPos(BlockPos min, BlockPos max)
6467 {
65 return posCache;
68 std::vector<BlockPos> res;
69 for (const auto &it : posCache) {
70 if (it.first < min.z || it.first >= max.z)
71 continue;
72 for (auto pos2 : it.second) {
73 if (pos2.first < min.x || pos2.first >= max.x)
74 continue;
75 if (pos2.second < min.y || pos2.second >= max.y)
76 continue;
77 res.emplace_back(pos2.first, pos2.second, it.first);
78 }
79 }
80 return res;
6681 }
6782
6883
69 std::string DBRedis::replyTypeStr(int type) {
84 const char *DBRedis::replyTypeStr(int type) {
7085 switch(type) {
7186 case REDIS_REPLY_STATUS:
7287 return "REDIS_REPLY_STATUS";
97112 for(size_t i = 0; i < reply->elements; i++) {
98113 if(reply->element[i]->type != REDIS_REPLY_STRING)
99114 REPLY_TYPE_ERR(reply->element[i], "HKEYS subreply");
100 posCache.push_back(decodeBlockPos(stoi64(reply->element[i]->str)));
115 BlockPos pos = decodeBlockPos(stoi64(reply->element[i]->str));
116 posCache[pos.z].emplace_back(pos.x, pos.y);
101117 }
102118
103119 freeReplyObject(reply);
104120 }
105121
106122
107 void DBRedis::HMGET(const std::vector<BlockPos> &positions, std::vector<ustring> *result)
123 void DBRedis::HMGET(const std::vector<BlockPos> &positions,
124 std::function<void(std::size_t, ustring)> result)
108125 {
109126 const char *argv[DB_REDIS_HMGET_NUMFIELDS + 2];
110127 argv[0] = "HMGET";
112129
113130 std::vector<BlockPos>::const_iterator position = positions.begin();
114131 std::size_t remaining = positions.size();
132 std::size_t abs_i = 0;
115133 while (remaining > 0) {
116134 const std::size_t batch_size =
117135 (remaining > DB_REDIS_HMGET_NUMFIELDS) ? DB_REDIS_HMGET_NUMFIELDS : remaining;
129147
130148 if(!reply)
131149 throw std::runtime_error("Redis command HMGET failed");
132 if (reply->type != REDIS_REPLY_ARRAY) {
133 freeReplyObject(reply);
134 REPLY_TYPE_ERR(reply, "HKEYS subreply");
135 }
150 if (reply->type != REDIS_REPLY_ARRAY)
151 REPLY_TYPE_ERR(reply, "HMGET reply");
136152 if (reply->elements != batch_size) {
137153 freeReplyObject(reply);
138154 throw std::runtime_error("HMGET wrong number of elements");
139155 }
140 for (std::size_t i = 0; i < batch_size; ++i) {
156 for (std::size_t i = 0; i < reply->elements; ++i) {
141157 redisReply *subreply = reply->element[i];
142 if(!subreply)
143 throw std::runtime_error("Redis command HMGET failed");
144 if (subreply->type != REDIS_REPLY_STRING) {
145 freeReplyObject(reply);
146 REPLY_TYPE_ERR(reply, "HKEYS subreply");
147 }
148 if (subreply->len == 0) {
149 freeReplyObject(reply);
158 if (subreply->type == REDIS_REPLY_NIL)
159 continue;
160 else if (subreply->type != REDIS_REPLY_STRING)
161 REPLY_TYPE_ERR(subreply, "HMGET subreply");
162 if (subreply->len == 0)
150163 throw std::runtime_error("HMGET empty string");
151 }
152 result->push_back(ustring((const unsigned char *) subreply->str, subreply->len));
164 result(abs_i + i, ustring((const unsigned char *) subreply->str, subreply->len));
153165 }
154166 freeReplyObject(reply);
167 abs_i += reply->elements;
155168 remaining -= batch_size;
156169 }
157170 }
158171
159172
160 void DBRedis::getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos)
173 void DBRedis::getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
174 int16_t min_y, int16_t max_y)
161175 {
162 std::vector<BlockPos> z_positions;
163 for (std::vector<BlockPos>::const_iterator it = posCache.begin(); it != posCache.end(); ++it) {
164 if (it->z != zPos) {
165 continue;
166 }
167 z_positions.push_back(*it);
176 auto it = posCache.find(z);
177 if (it == posCache.cend())
178 return;
179
180 std::vector<BlockPos> positions;
181 for (auto pos2 : it->second) {
182 if (pos2.first == x && pos2.second >= min_y && pos2.second < max_y)
183 positions.emplace_back(x, pos2.second, z);
168184 }
169 std::vector<ustring> z_blocks;
170 HMGET(z_positions, &z_blocks);
171185
172 std::vector<ustring>::const_iterator z_block = z_blocks.begin();
173 for (std::vector<BlockPos>::const_iterator pos = z_positions.begin();
174 pos != z_positions.end();
175 ++pos, ++z_block) {
176 blocks[pos->x].push_back(Block(*pos, *z_block));
177 }
186 getBlocksByPos(blocks, positions);
178187 }
188
189
190 void DBRedis::getBlocksByPos(BlockList &blocks,
191 const std::vector<BlockPos> &positions)
192 {
193 auto result = [&] (std::size_t i, ustring data) {
194 blocks.emplace_back(positions[i], std::move(data));
195 };
196 HMGET(positions, result);
197 }
00 #include <stdexcept>
11 #include <unistd.h> // for usleep
22 #include <iostream>
3 #include <algorithm>
4 #include <time.h>
35 #include "db-sqlite3.h"
46 #include "types.h"
57
911 throw std::runtime_error(sqlite3_errmsg(db));\
1012 }
1113 #define SQLOK(f) SQLRES(f, SQLITE_OK)
12
1314
1415 DBSQLite3::DBSQLite3(const std::string &mapdir)
1516 {
2425 -1, &stmt_get_blocks_z, NULL))
2526
2627 SQLOK(prepare_v2(db,
28 "SELECT data FROM blocks WHERE pos = ?",
29 -1, &stmt_get_block_exact, NULL))
30
31 SQLOK(prepare_v2(db,
2732 "SELECT pos FROM blocks",
2833 -1, &stmt_get_block_pos, NULL))
34
35 SQLOK(prepare_v2(db,
36 "SELECT pos FROM blocks WHERE pos BETWEEN ? AND ?",
37 -1, &stmt_get_block_pos_z, NULL))
2938 }
3039
3140
3342 {
3443 sqlite3_finalize(stmt_get_blocks_z);
3544 sqlite3_finalize(stmt_get_block_pos);
45 sqlite3_finalize(stmt_get_block_pos_z);
46 sqlite3_finalize(stmt_get_block_exact);
3647
3748 if (sqlite3_close(db) != SQLITE_OK) {
3849 std::cerr << "Error closing SQLite database." << std::endl;
3950 };
4051 }
4152
42 std::vector<BlockPos> DBSQLite3::getBlockPos()
53
54 inline void DBSQLite3::getPosRange(int64_t &min, int64_t &max, int16_t zPos,
55 int16_t zPos2) const
56 {
57 /* The range of block positions is [-2048, 2047], which turns into [0, 4095]
58 * when casted to unsigned. This didn't actually help me understand the
59 * numbers below, but I wanted to write it down.
60 */
61
62 // Magic numbers!
63 min = encodeBlockPos(BlockPos(0, -2048, zPos));
64 max = encodeBlockPos(BlockPos(0, 2048, zPos2)) - 1;
65 }
66
67
68 std::vector<BlockPos> DBSQLite3::getBlockPos(BlockPos min, BlockPos max)
4369 {
4470 int result;
71 sqlite3_stmt *stmt;
72
73 if(min.z <= -2048 && max.z >= 2048) {
74 stmt = stmt_get_block_pos;
75 } else {
76 stmt = stmt_get_block_pos_z;
77 int64_t minPos, maxPos;
78 if (min.z < -2048)
79 min.z = -2048;
80 if (max.z > 2048)
81 max.z = 2048;
82 getPosRange(minPos, maxPos, min.z, max.z - 1);
83 SQLOK(bind_int64(stmt, 1, minPos))
84 SQLOK(bind_int64(stmt, 2, maxPos))
85 }
86
4587 std::vector<BlockPos> positions;
46 while ((result = sqlite3_step(stmt_get_block_pos)) != SQLITE_DONE) {
47 if (result == SQLITE_ROW) {
48 int64_t posHash = sqlite3_column_int64(stmt_get_block_pos, 0);
49 positions.push_back(decodeBlockPos(posHash));
50 } else if (result == SQLITE_BUSY) { // Wait some time and try again
88 while ((result = sqlite3_step(stmt)) != SQLITE_DONE) {
89 if (result == SQLITE_BUSY) { // Wait some time and try again
5190 usleep(10000);
52 } else {
91 } else if (result != SQLITE_ROW) {
5392 throw std::runtime_error(sqlite3_errmsg(db));
5493 }
94
95 int64_t posHash = sqlite3_column_int64(stmt, 0);
96 BlockPos pos = decodeBlockPos(posHash);
97 if(pos.x >= min.x && pos.x < max.x && pos.y >= min.y && pos.y < max.y)
98 positions.emplace_back(pos);
5599 }
56 SQLOK(reset(stmt_get_block_pos));
100 SQLOK(reset(stmt));
57101 return positions;
58102 }
59103
60104
61 void DBSQLite3::getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos)
105 void DBSQLite3::loadBlockCache(int16_t zPos)
62106 {
63107 int result;
108 blockCache.clear();
64109
65 // Magic numbers!
66 int64_t minPos = encodeBlockPos(BlockPos(0, -2048, zPos));
67 int64_t maxPos = encodeBlockPos(BlockPos(0, 2048, zPos)) - 1;
110 int64_t minPos, maxPos;
111 getPosRange(minPos, maxPos, zPos, zPos);
68112
69113 SQLOK(bind_int64(stmt_get_blocks_z, 1, minPos));
70114 SQLOK(bind_int64(stmt_get_blocks_z, 2, maxPos));
71115
72116 while ((result = sqlite3_step(stmt_get_blocks_z)) != SQLITE_DONE) {
73 if (result == SQLITE_ROW) {
74 int64_t posHash = sqlite3_column_int64(stmt_get_blocks_z, 0);
75 const unsigned char *data = reinterpret_cast<const unsigned char *>(
76 sqlite3_column_blob(stmt_get_blocks_z, 1));
77 size_t size = sqlite3_column_bytes(stmt_get_blocks_z, 1);
78 Block b(decodeBlockPos(posHash), ustring(data, size));
79 blocks[b.first.x].push_back(b);
80 } else if (result == SQLITE_BUSY) { // Wait some time and try again
117 if (result == SQLITE_BUSY) { // Wait some time and try again
81118 usleep(10000);
82 } else {
119 } else if (result != SQLITE_ROW) {
83120 throw std::runtime_error(sqlite3_errmsg(db));
84121 }
122
123 int64_t posHash = sqlite3_column_int64(stmt_get_blocks_z, 0);
124 BlockPos pos = decodeBlockPos(posHash);
125 const unsigned char *data = reinterpret_cast<const unsigned char *>(
126 sqlite3_column_blob(stmt_get_blocks_z, 1));
127 size_t size = sqlite3_column_bytes(stmt_get_blocks_z, 1);
128 blockCache[pos.x].emplace_back(pos, ustring(data, size));
85129 }
86 SQLOK(reset(stmt_get_blocks_z));
130 SQLOK(reset(stmt_get_blocks_z))
87131 }
88132
89 #undef SQLRES
90 #undef SQLOK
91133
134 void DBSQLite3::getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
135 int16_t min_y, int16_t max_y)
136 {
137 /* Cache the blocks on the given Z coordinate between calls, this only
138 * works due to order in which the TileGenerator asks for blocks. */
139 if (z != blockCachedZ) {
140 loadBlockCache(z);
141 blockCachedZ = z;
142 }
143
144 auto it = blockCache.find(x);
145 if (it == blockCache.end())
146 return;
147
148 if (it->second.empty()) {
149 /* We have swapped this list before, this is not supposed to happen
150 * because it's bad for performance. But rather than silently breaking
151 * do the right thing and load the blocks again. */
152 #ifndef NDEBUG
153 std::cout << "Warning: suboptimal access pattern for sqlite3 backend" << std::endl;
154 #endif
155 loadBlockCache(z);
156 }
157 // Swap lists to avoid copying contents
158 blocks.clear();
159 std::swap(blocks, it->second);
160
161 for (auto it = blocks.begin(); it != blocks.end(); ) {
162 if (it->first.y < min_y || it->first.y >= max_y)
163 it = blocks.erase(it);
164 else
165 it++;
166 }
167 }
168
169
170 void DBSQLite3::getBlocksByPos(BlockList &blocks,
171 const std::vector<BlockPos> &positions)
172 {
173 int result;
174
175 for (auto pos : positions) {
176 int64_t dbPos = encodeBlockPos(pos);
177 SQLOK(bind_int64(stmt_get_block_exact, 1, dbPos));
178
179 while ((result = sqlite3_step(stmt_get_block_exact)) == SQLITE_BUSY) {
180 usleep(10000); // Wait some time and try again
181 }
182 if (result == SQLITE_DONE) {
183 // no data
184 } else if (result != SQLITE_ROW) {
185 throw std::runtime_error(sqlite3_errmsg(db));
186 } else {
187 const unsigned char *data = reinterpret_cast<const unsigned char *>(
188 sqlite3_column_blob(stmt_get_block_exact, 0));
189 size_t size = sqlite3_column_bytes(stmt_get_block_exact, 0);
190 blocks.emplace_back(pos, ustring(data, size));
191 }
192
193 SQLOK(reset(stmt_get_block_exact))
194 }
195 }
00 #ifndef BLOCKDECODER_H
11 #define BLOCKDECODER_H
22
3 #if __cplusplus >= 201103L
43 #include <unordered_map>
5 #else
6 #include <map>
7 #endif
84
95 #include "types.h"
106
1814 std::string getNode(u8 x, u8 y, u8 z) const; // returns "" for air, ignore and invalid nodes
1915
2016 private:
21 #if __cplusplus >= 201103L
2217 typedef std::unordered_map<int, std::string> NameMap;
23 #else
24 typedef std::map<int, std::string> NameMap;
25 #endif
2618 NameMap m_nameMap;
2719 int m_blockAirId;
2820 int m_blockIgnoreId;
11 #define TILEGENERATOR_HEADER
22
33 #include <iosfwd>
4 #include <list>
4 #include <map>
5 #include <set>
56 #include <config.h>
6 #if __cplusplus >= 201103L
77 #include <unordered_map>
88 #include <unordered_set>
9 #else
10 #include <map>
11 #include <set>
12 #endif
139 #include <stdint.h>
1410 #include <string>
1511
2420 SCALE_BOTTOM = (1 << 1),
2521 SCALE_LEFT = (1 << 2),
2622 SCALE_RIGHT = (1 << 3),
23 };
24
25 enum {
26 EXH_NEVER, // Always use range queries
27 EXH_Y, // Exhaustively search Y space, range queries for X/Z
28 EXH_FULL, // Exhaustively search entire requested geometry
29 EXH_AUTO, // Automatically pick one of the previous modes
2730 };
2831
2932 struct ColorEntry {
5962 class TileGenerator
6063 {
6164 private:
62 #if __cplusplus >= 201103L
6365 typedef std::unordered_map<std::string, ColorEntry> ColorMap;
6466 typedef std::unordered_set<std::string> NameSet;
65 #else
66 typedef std::map<std::string, ColorEntry> ColorMap;
67 typedef std::set<std::string> NameSet;
68 #endif
6967
7068 public:
7169 TileGenerator();
7371 void setBgColor(const std::string &bgColor);
7472 void setScaleColor(const std::string &scaleColor);
7573 void setOriginColor(const std::string &originColor);
76 void setPlayerColor(const std::string &playerColor); Color parseColor(const std::string &color);
74 void setPlayerColor(const std::string &playerColor);
75 Color parseColor(const std::string &color);
7776 void setDrawOrigin(bool drawOrigin);
7877 void setDrawPlayers(bool drawPlayers);
7978 void setDrawScale(bool drawScale);
8281 void setGeometry(int x, int y, int w, int h);
8382 void setMinY(int y);
8483 void setMaxY(int y);
84 void setExhaustiveSearch(int mode);
8585 void parseColorsFile(const std::string &fileName);
8686 void setBackend(std::string backend);
87 void generate(const std::string &input, const std::string &output);
88 void printGeometry(const std::string &input);
8987 void setZoom(int zoom);
9088 void setScales(uint flags);
9189 void setDontWriteEmpty(bool f);
90
91 void generate(const std::string &input, const std::string &output);
92 void printGeometry(const std::string &input);
93 static std::set<std::string> getSupportedBackends();
9294
9395 private:
9496 void parseColorsStream(std::istream &in);
9799 void loadBlocks();
98100 void createImage();
99101 void renderMap();
100 std::list<int> getZValueList() const;
101102 void renderMapBlock(const BlockDecoder &blk, const BlockPos &pos);
102103 void renderMapBlockBottom(const BlockPos &pos);
103104 void renderShading(int zPos);
127128 DB *m_db;
128129 Image *m_image;
129130 PixelAttributes m_blockPixelAttributes;
131 /* smallest/largest seen X or Z block coordinate */
130132 int m_xMin;
131133 int m_xMax;
132134 int m_zMin;
133135 int m_zMax;
136 /* Y limits for rendered area (node units) */
134137 int m_yMin;
135138 int m_yMax;
136 int m_geomX;
137 int m_geomY;
138 int m_geomX2;
139 int m_geomY2;
139 /* limits for rendered area (block units) */
140 int16_t m_geomX;
141 int16_t m_geomY; /* Y in terms of rendered image, Z in the world */
142 int16_t m_geomX2;
143 int16_t m_geomY2;
144 /* */
140145 int m_mapWidth;
141146 int m_mapHeight;
142 std::list<std::pair<int, int> > m_positions;
147 int m_exhaustiveSearch;
148 std::map<int16_t, std::set<int16_t>> m_positions; /* indexed by Z, contains X coords */
143149 ColorMap m_colorMap;
144150 BitmapThing m_readPixels;
145151 BitmapThing m_readInfo;
22 #ifndef CMAKE_CONFIG_H
33 #define CMAKE_CONFIG_H
44
5 #define USE_POSTGRESQL @USE_POSTGRESQL@
6 #define USE_LEVELDB @USE_LEVELDB@
7 #define USE_REDIS @USE_REDIS@
5 #cmakedefine01 USE_POSTGRESQL
6 #cmakedefine01 USE_LEVELDB
7 #cmakedefine01 USE_REDIS
88
99 #define SHAREDIR "@SHAREDIR@"
1010
11 #define DB_LEVELDB_HEADER
22
33 #include "db.h"
4 #include <unordered_map>
5 #include <utility>
46 #include <leveldb/db.h>
57
68 class DBLevelDB : public DB {
79 public:
810 DBLevelDB(const std::string &mapdir);
9 virtual std::vector<BlockPos> getBlockPos();
10 virtual void getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos);
11 virtual ~DBLevelDB();
11 std::vector<BlockPos> getBlockPos(BlockPos min, BlockPos max) override;
12 void getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
13 int16_t min_y, int16_t max_y) override;
14 void getBlocksByPos(BlockList &blocks,
15 const std::vector<BlockPos> &positions) override;
16 ~DBLevelDB() override;
17
18 bool preferRangeQueries() const override { return false; }
19
1220 private:
21 using pos2d = std::pair<int16_t, int16_t>;
22
1323 void loadPosCache();
1424
15 std::vector<BlockPos> posCache;
16
25 // indexed by Z, contains all (x,y) position pairs
26 std::unordered_map<int16_t, std::vector<pos2d>> posCache;
1727 leveldb::DB *db;
1828 };
1929
66 class DBPostgreSQL : public DB {
77 public:
88 DBPostgreSQL(const std::string &mapdir);
9 virtual std::vector<BlockPos> getBlockPos();
10 virtual void getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos);
11 virtual ~DBPostgreSQL();
9 std::vector<BlockPos> getBlockPos(BlockPos min, BlockPos max) override;
10 void getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
11 int16_t min_y, int16_t max_y) override;
12 void getBlocksByPos(BlockList &blocks,
13 const std::vector<BlockPos> &positions) override;
14 ~DBPostgreSQL() override;
15
16 bool preferRangeQueries() const override { return true; }
17
1218 protected:
1319 PGresult *checkResults(PGresult *res, bool clear = true);
1420 void prepareStatement(const std::string &name, const std::string &sql);
1622 const char *stmtName, const int paramsNumber,
1723 const void **params,
1824 const int *paramsLengths = NULL, const int *paramsFormats = NULL,
19 bool clear = true, bool nobinary = true
25 bool clear = true
2026 );
21 int pg_to_int(PGresult *res, int row, int col);
2227 int pg_binary_to_int(PGresult *res, int row, int col);
2328 BlockPos pg_to_blockpos(PGresult *res, int row, int col);
29
2430 private:
2531 PGconn *db;
2632 };
11 #define DB_REDIS_HEADER
22
33 #include "db.h"
4 #include <hiredis.h>
4 #include <unordered_map>
5 #include <utility>
6 #include <functional>
7 #include <hiredis/hiredis.h>
58
69 class DBRedis : public DB {
710 public:
811 DBRedis(const std::string &mapdir);
9 virtual std::vector<BlockPos> getBlockPos();
10 virtual void getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos);
11 virtual ~DBRedis();
12 std::vector<BlockPos> getBlockPos(BlockPos min, BlockPos max) override;
13 void getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
14 int16_t min_y, int16_t max_y) override;
15 void getBlocksByPos(BlockList &blocks,
16 const std::vector<BlockPos> &positions) override;
17 ~DBRedis() override;
18
19 bool preferRangeQueries() const override { return false; }
20
1221 private:
13 static std::string replyTypeStr(int type);
22 using pos2d = std::pair<int16_t, int16_t>;
23 static const char *replyTypeStr(int type);
1424
1525 void loadPosCache();
16 void HMGET(const std::vector<BlockPos> &positions, std::vector<ustring> *result);
26 void HMGET(const std::vector<BlockPos> &positions,
27 std::function<void(std::size_t, ustring)> result);
1728
18 std::vector<BlockPos> posCache;
29 // indexed by Z, contains all (x,y) position pairs
30 std::unordered_map<int16_t, std::vector<pos2d>> posCache;
1931
2032 redisContext *ctx;
2133 std::string hash;
11 #define _DB_SQLITE3_H
22
33 #include "db.h"
4 #include <unordered_map>
45 #include <sqlite3.h>
56
67 class DBSQLite3 : public DB {
78 public:
89 DBSQLite3(const std::string &mapdir);
9 virtual std::vector<BlockPos> getBlockPos();
10 virtual void getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos);
11 virtual ~DBSQLite3();
10 std::vector<BlockPos> getBlockPos(BlockPos min, BlockPos max) override;
11 void getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
12 int16_t min_y, int16_t max_y) override;
13 void getBlocksByPos(BlockList &blocks,
14 const std::vector<BlockPos> &positions) override;
15 ~DBSQLite3() override;
16
17 bool preferRangeQueries() const override { return false; }
18
1219 private:
20 inline void getPosRange(int64_t &min, int64_t &max, int16_t zPos,
21 int16_t zPos2) const;
22 void loadBlockCache(int16_t zPos);
23
1324 sqlite3 *db;
1425
1526 sqlite3_stmt *stmt_get_block_pos;
27 sqlite3_stmt *stmt_get_block_pos_z;
1628 sqlite3_stmt *stmt_get_blocks_z;
29 sqlite3_stmt *stmt_get_block_exact;
30
31 int16_t blockCachedZ = -10000;
32 std::unordered_map<int16_t, BlockList> blockCache; // indexed by X
1733 };
1834
1935 #endif // _DB_SQLITE3_H
44 #include <map>
55 #include <list>
66 #include <vector>
7 #include <string>
87 #include <utility>
98 #include "types.h"
109
1110
12 class BlockPos {
13 public:
11 struct BlockPos {
1412 int16_t x;
1513 int16_t y;
1614 int16_t z;
1715
1816 BlockPos() : x(0), y(0), z(0) {}
1917 BlockPos(int16_t x, int16_t y, int16_t z) : x(x), y(y), z(z) {}
18
19 // Implements the inverse ordering so that (2,2,2) < (1,1,1)
2020 bool operator < (const BlockPos &p) const
2121 {
2222 if (z > p.z)
4242
4343 class DB {
4444 protected:
45 // Helpers that implement the hashed positions used by most backends
4546 inline int64_t encodeBlockPos(const BlockPos pos) const;
4647 inline BlockPos decodeBlockPos(int64_t hash) const;
4748
4849 public:
49 virtual std::vector<BlockPos> getBlockPos() = 0;
50 virtual void getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos) = 0;
51 virtual ~DB() {};
50 /* Return all block positions inside the range given by min and max,
51 * so that min.x <= x < max.x, ...
52 */
53 virtual std::vector<BlockPos> getBlockPos(BlockPos min, BlockPos max) = 0;
54 /* Read all blocks in column given by x and z
55 * and inside the given Y range (min_y <= y < max_y) into list
56 */
57 virtual void getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
58 int16_t min_y, int16_t max_y) = 0;
59 /* Read blocks at given positions into list
60 */
61 virtual void getBlocksByPos(BlockList &blocks,
62 const std::vector<BlockPos> &positions) = 0;
63 /* Can this database efficiently do range queries?
64 * (for large data sets, more efficient that brute force)
65 */
66 virtual bool preferRangeQueries() const = 0;
67
68
69 virtual ~DB() {}
5270 };
5371
5472
99 {
1010 try {
1111 return read_setting(name, is);
12 } catch(std::runtime_error &e) {
12 } catch(const std::runtime_error &e) {
1313 return def;
1414 }
1515 }
0 #include <cstdio>
01 #include <cstdlib>
12 #include <cstring>
23 #include <getopt.h>
34 #include <fstream>
45 #include <iostream>
5 #include <map>
6 #include <utility>
67 #include <string>
78 #include <sstream>
89 #include <stdexcept>
910 #include "cmake_config.h"
1011 #include "TileGenerator.h"
1112
12 void usage()
13 {
14 const char *usage_text = "minetestmapper [options]\n"
15 " -i/--input <world_path>\n"
16 " -o/--output <output_image.png>\n"
17 " --bgcolor <color>\n"
18 " --scalecolor <color>\n"
19 " --playercolor <color>\n"
20 " --origincolor <color>\n"
21 " --drawscale\n"
22 " --drawplayers\n"
23 " --draworigin\n"
24 " --drawalpha\n"
25 " --noshading\n"
26 " --noemptyimage\n"
27 " --min-y <y>\n"
28 " --max-y <y>\n"
29 " --backend <backend>\n"
30 " --geometry x:y+w+h\n"
31 " --extent\n"
32 " --zoom <zoomlevel>\n"
33 " --colors <colors.txt>\n"
34 " --scales [t][b][l][r]\n"
35 "Color format: '#000000'\n";
36 std::cout << usage_text;
37 }
38
39 bool file_exists(const std::string &path)
13 static void usage()
14 {
15 const std::pair<const char*, const char*> options[] = {
16 {"-i/--input", "<world_path>"},
17 {"-o/--output", "<output_image.png>"},
18 {"--bgcolor", "<color>"},
19 {"--scalecolor", "<color>"},
20 {"--playercolor", "<color>"},
21 {"--origincolor", "<color>"},
22 {"--drawscale", ""},
23 {"--drawplayers", ""},
24 {"--draworigin", ""},
25 {"--drawalpha", ""},
26 {"--noshading", ""},
27 {"--noemptyimage", ""},
28 {"--min-y", "<y>"},
29 {"--max-y", "<y>"},
30 {"--backend", "<backend>"},
31 {"--geometry", "x:y+w+h"},
32 {"--extent", ""},
33 {"--zoom", "<zoomlevel>"},
34 {"--colors", "<colors.txt>"},
35 {"--scales", "[t][b][l][r]"},
36 {"--exhaustive", "never|y|full|auto"},
37 };
38 const char *top_text =
39 "minetestmapper -i <world_path> -o <output_image.png> [options]\n"
40 "Generate an overview image of a Minetest map.\n"
41 "\n"
42 "Options:\n";
43 const char *bottom_text =
44 "\n"
45 "Color format: hexadecimal '#RRGGBB', e.g. '#FF0000' = red\n";
46
47 printf("%s", top_text);
48 for (const auto &p : options)
49 printf(" %-18s%s\n", p.first, p.second);
50 printf("%s", bottom_text);
51 auto backends = TileGenerator::getSupportedBackends();
52 printf("Supported backends: ");
53 for (auto s : backends)
54 printf("%s ", s.c_str());
55 printf("\n");
56 }
57
58 static bool file_exists(const std::string &path)
4059 {
4160 std::ifstream ifs(path.c_str());
4261 return ifs.is_open();
4362 }
4463
45 std::string search_colors(const std::string &worldpath)
64 static std::string search_colors(const std::string &worldpath)
4665 {
4766 if(file_exists(worldpath + "/colors.txt"))
4867 return worldpath + "/colors.txt";
5675 }
5776 #endif
5877
59 if(!(SHAREDIR[0] == '.' || SHAREDIR[0] == '\0') && file_exists(SHAREDIR "/colors.txt"))
78 constexpr bool sharedir_valid = !(SHAREDIR[0] == '.' || SHAREDIR[0] == '\0');
79 if(sharedir_valid && file_exists(SHAREDIR "/colors.txt"))
6080 return SHAREDIR "/colors.txt";
6181
6282 std::cerr << "Warning: Falling back to using colors.txt from current directory." << std::endl;
6585
6686 int main(int argc, char *argv[])
6787 {
68 static struct option long_options[] =
88 const static struct option long_options[] =
6989 {
7090 {"help", no_argument, 0, 'h'},
7191 {"input", required_argument, 0, 'i'},
88108 {"colors", required_argument, 0, 'C'},
89109 {"scales", required_argument, 0, 'f'},
90110 {"noemptyimage", no_argument, 0, 'n'},
111 {"exhaustive", required_argument, 0, 'j'},
91112 {0, 0, 0, 0}
92113 };
93114
199220 case 'n':
200221 generator.setDontWriteEmpty(true);
201222 break;
223 case 'j': {
224 int mode;
225 if (!strcmp(optarg, "never"))
226 mode = EXH_NEVER;
227 else if (!strcmp(optarg, "y"))
228 mode = EXH_Y;
229 else if (!strcmp(optarg, "full"))
230 mode = EXH_FULL;
231 else
232 mode = EXH_AUTO;
233 generator.setExhaustiveSearch(mode);
234 }
235 break;
202236 default:
203237 exit(1);
204238 }
6969
7070 .TP
7171 .BR \-\-backend " " \fIbackend\fR
72 Use specific map backend; supported: *sqlite3*, *leveldb*, *redis*, *postgresql*, e.g. "--backend leveldb"
72 Use specific map backend; supported: \fIsqlite3\fP, \fIleveldb\fP, \fIredis\fP, \fIpostgresql\fP, e.g. "--backend leveldb"
7373
7474 .TP
7575 .BR \-\-geometry " " \fIgeometry\fR
76 Limit area to specific geometry (*x:y+w+h* where x and y specify the lower left corner), e.g. "--geometry -800:-800+1600+1600"
76 Limit area to specific geometry (\fIx:y+w+h\fP where x and y specify the lower left corner), e.g. "--geometry -800:-800+1600+1600"
7777
7878 .TP
7979 .BR \-\-extent " " \fIextent\fR
80 Dont render the image, just print the extent of the map that would be generated, in the same format as the geometry above.
80 Don't render the image, just print the extent of the map that would be generated, in the same format as the geometry above.
8181
8282 .TP
8383 .BR \-\-zoom " " \fIfactor\fR
8989
9090 .TP
9191 .BR \-\-scales " " \fIedges\fR
92 Draw scales on specified image edges (letters *t b l r* meaning top, bottom, left and right), e.g. "--scales tbr"
92 Draw scales on specified image edges (letters \fIt b l r\fP meaning top, bottom, left and right), e.g. "--scales tbr"
93
94 .TP
95 .BR \-\-exhaustive " \fImode\fR
96 Select if database should be traversed exhaustively or using range queries, available: \fInever\fP, \fIy\fP, \fIfull\fP, \fIauto\fP
97
98 Defaults to \fIauto\fP. You shouldn't need to change this, but doing so can improve rendering times on large maps.
99 For these optimizations to work it is important that you set
100 .B min-y
101 and
102 .B max-y
103 when you don't care about the world below e.g. -60 and above 1000 nodes.
93104
94105 .SH MORE INFORMATION
95106 Website: https://github.com/minetest/minetestmapper
22
33 #include "util.h"
44
5 inline std::string trim(const std::string &s)
5 static inline std::string trim(const std::string &s)
66 {
7 size_t front = 0;
8 while(s[front] == ' ' ||
9 s[front] == '\t' ||
10 s[front] == '\r' ||
11 s[front] == '\n'
12 )
13 ++front;
7 auto isspace = [] (char c) -> bool { return c == ' ' || c == '\t' || c == '\r' || c == '\n'; };
148
15 size_t back = s.size();
16 while(back > front &&
17 (s[back-1] == ' ' ||
18 s[back-1] == '\t' ||
19 s[back-1] == '\r' ||
20 s[back-1] == '\n'
21 )
22 )
23 --back;
9 size_t front = 0;
10 while(isspace(s[front]))
11 ++front;
12 size_t back = s.size() - 1;
13 while(back > front && isspace(s[back]))
14 --back;
2415
25 return s.substr(front, back - front);
16 return s.substr(front, back - front + 1);
2617 }
27
28 #define EOFCHECK() do { \
29 if (is.eof()) { \
30 std::ostringstream oss; \
31 oss << "Setting '" << name << "' not found."; \
32 throw std::runtime_error(oss.str()); \
33 } \
34 } while(0)
3518
3619 std::string read_setting(const std::string &name, std::istream &is)
3720 {
38 char c;
39 char s[256];
40 std::string nm, value;
21 char linebuf[512];
22 while (is.good()) {
23 is.getline(linebuf, sizeof(linebuf));
4124
42 next:
43 while((c = is.get()) == ' ' || c == '\t' || c == '\r' || c == '\n')
44 ;
45 EOFCHECK();
46 if(c == '#') // Ignore comments
47 is.ignore(0xffff, '\n');
48 EOFCHECK();
49 s[0] = c; // The current char belongs to the name too
50 is.get(&s[1], 255, '=');
51 is.ignore(1); // Jump over the =
52 EOFCHECK();
53 nm = trim(std::string(s));
54 is.get(s, 256, '\n');
55 value = trim(std::string(s));
56 if(name == nm)
57 return value;
58 else
59 goto next;
25 for(char *p = linebuf; *p; p++) {
26 if(*p != '#')
27 continue;
28 *p = '\0'; // Cut off at the first #
29 break;
30 }
31 std::string line(linebuf);
32
33 auto pos = line.find('=');
34 if (pos == std::string::npos)
35 continue;
36 auto key = trim(line.substr(0, pos));
37 if (key != name)
38 continue;
39 return trim(line.substr(pos+1));
40 }
41 std::ostringstream oss;
42 oss << "Setting '" << name << "' not found";
43 throw std::runtime_error(oss.str());
6044 }
61
62 #undef EOFCHECK