Update upstream source from tag 'upstream/20200328'
Update to upstream version '20200328'
with Debian dir 30e871b920df75c7019f61624ef738b66cfd8e67
Julien Puydt
4 years ago
2 | 2 | - gcc |
3 | 3 | - clang |
4 | 4 | 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 | |
6 | 14 | script: ./util/travis/script.sh |
7 | 15 | notifications: |
8 | 16 | email: false |
0 | 0 | project(minetestmapper CXX) |
1 | 1 | cmake_minimum_required(VERSION 2.6) |
2 | 2 | cmake_policy(SET CMP0003 NEW) |
3 | set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) | |
4 | 3 | |
5 | 4 | set(VERSION_MAJOR 1) |
6 | 5 | set(VERSION_MINOR 0) |
12 | 11 | set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) |
13 | 12 | endif() |
14 | 13 | |
14 | set(CMAKE_CXX_STANDARD 11) | |
15 | 15 | set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall -DNDEBUG") |
16 | 16 | set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g2 -Wall") |
17 | 17 | |
44 | 44 | message(STATUS "Using DOCDIR=${DOCDIR}") |
45 | 45 | endif() |
46 | 46 | |
47 | #set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) | |
48 | find_package(PkgConfig) | |
49 | include(FindPackageHandleStandardArgs) | |
47 | 50 | |
48 | 51 | # Libraries: gd |
49 | 52 | |
65 | 68 | message(FATAL_ERROR "zlib not found!") |
66 | 69 | endif(NOT ZLIB_LIBRARY OR NOT ZLIB_INCLUDE_DIR) |
67 | 70 | |
68 | find_package(PkgConfig) | |
69 | include(FindPackageHandleStandardArgs) | |
70 | ||
71 | 71 | # Libraries: sqlite3 |
72 | 72 | |
73 | 73 | find_library(SQLITE3_LIBRARY sqlite3) |
81 | 81 | # Libraries: postgresql |
82 | 82 | |
83 | 83 | option(ENABLE_POSTGRESQL "Enable PostgreSQL backend" TRUE) |
84 | set(USE_POSTGRESQL 0) | |
84 | set(USE_POSTGRESQL FALSE) | |
85 | 85 | |
86 | 86 | 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 | ||
100 | 87 | find_package("PostgreSQL") |
101 | 88 | |
102 | if(POSTGRESQL_FOUND) | |
103 | set(USE_POSTGRESQL 1) | |
89 | if(PostgreSQL_FOUND) | |
90 | set(USE_POSTGRESQL TRUE) | |
104 | 91 | message(STATUS "PostgreSQL backend enabled") |
105 | 92 | # 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}) | |
109 | 95 | else() |
110 | message(STATUS "PostgreSQL not found.") | |
111 | set(POSTGRESQL_LIBRARY "") | |
96 | message(STATUS "PostgreSQL not found!") | |
97 | set(PostgreSQL_LIBRARIES "") | |
112 | 98 | endif() |
113 | 99 | endif(ENABLE_POSTGRESQL) |
114 | 100 | |
115 | 101 | # Libraries: leveldb |
116 | 102 | |
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) | |
120 | 105 | |
121 | 106 | if(ENABLE_LEVELDB) |
122 | 107 | 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) | |
124 | 109 | message (STATUS "LevelDB library: ${LEVELDB_LIBRARY}") |
125 | 110 | message (STATUS "LevelDB headers: ${LEVELDB_INCLUDE_DIR}") |
126 | 111 | if(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR) |
127 | set(USE_LEVELDB 1) | |
112 | set(USE_LEVELDB TRUE) | |
128 | 113 | message(STATUS "LevelDB backend enabled") |
129 | 114 | include_directories(${LEVELDB_INCLUDE_DIR}) |
130 | else(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR) | |
131 | set(USE_LEVELDB 0) | |
115 | else() | |
132 | 116 | message(STATUS "LevelDB not found!") |
133 | endif(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR) | |
117 | set(LEVELDB_LIBRARY "") | |
118 | endif() | |
134 | 119 | endif(ENABLE_LEVELDB) |
135 | 120 | |
136 | 121 | # Libraries: redis |
137 | 122 | |
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) | |
141 | 125 | |
142 | 126 | if(ENABLE_REDIS) |
143 | 127 | 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) | |
145 | 129 | message (STATUS "redis library: ${REDIS_LIBRARY}") |
146 | 130 | message (STATUS "redis headers: ${REDIS_INCLUDE_DIR}") |
147 | 131 | if(REDIS_LIBRARY AND REDIS_INCLUDE_DIR) |
148 | set(USE_REDIS 1) | |
132 | set(USE_REDIS TRUE) | |
149 | 133 | message(STATUS "redis backend enabled") |
150 | 134 | include_directories(${REDIS_INCLUDE_DIR}) |
151 | else(REDIS_LIBRARY AND REDIS_INCLUDE_DIR) | |
152 | set(USE_REDIS 0) | |
135 | else() | |
153 | 136 | message(STATUS "redis not found!") |
154 | endif(REDIS_LIBRARY AND REDIS_INCLUDE_DIR) | |
137 | set(REDIS_LIBRARY "") | |
138 | endif() | |
155 | 139 | endif(ENABLE_REDIS) |
156 | 140 | |
157 | 141 | # Compiling & Linking |
202 | 186 | target_link_libraries( |
203 | 187 | minetestmapper |
204 | 188 | ${SQLITE3_LIBRARY} |
205 | ${POSTGRESQL_LIBRARY} | |
189 | ${PostgreSQL_LIBRARIES} | |
206 | 190 | ${LEVELDB_LIBRARY} |
207 | 191 | ${REDIS_LIBRARY} |
208 | 192 | ${LIBGD_LIBRARY} |
6 | 6 | * ===================================================================== |
7 | 7 | */ |
8 | 8 | |
9 | #include <cstdlib> | |
9 | #include "PixelAttributes.h" | |
10 | 10 | #include <cstring> |
11 | #include "PixelAttributes.h" | |
12 | ||
13 | using namespace std; | |
14 | 11 | |
15 | 12 | PixelAttributes::PixelAttributes(): |
16 | 13 | m_width(0) |
17 | 14 | { |
18 | 15 | for (size_t i = 0; i < LineCount; ++i) { |
19 | m_pixelAttributes[i] = 0; | |
16 | m_pixelAttributes[i] = nullptr; | |
20 | 17 | } |
21 | 18 | } |
22 | 19 | |
46 | 43 | void PixelAttributes::freeAttributes() |
47 | 44 | { |
48 | 45 | for (size_t i = 0; i < LineCount; ++i) { |
49 | if (m_pixelAttributes[i] != 0) { | |
46 | if (m_pixelAttributes[i] != nullptr) { | |
50 | 47 | delete[] m_pixelAttributes[i]; |
51 | m_pixelAttributes[i] = 0; | |
48 | m_pixelAttributes[i] = nullptr; | |
52 | 49 | } |
53 | 50 | } |
54 | 51 | } |
105 | 105 | |
106 | 106 | scales: |
107 | 107 | 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. |
0 | 0 | #include <cstdio> |
1 | 1 | #include <cstdlib> |
2 | 2 | #include <climits> |
3 | #include <cassert> | |
3 | 4 | #include <fstream> |
4 | 5 | #include <iostream> |
5 | 6 | #include <sstream> |
88 | 89 | m_xMax(INT_MIN), |
89 | 90 | m_zMin(INT_MAX), |
90 | 91 | m_zMax(INT_MIN), |
91 | m_yMin(-30000), | |
92 | m_yMax(30000), | |
92 | m_yMin(INT16_MIN), | |
93 | m_yMax(INT16_MAX), | |
93 | 94 | m_geomX(-2048), |
94 | 95 | m_geomY(-2048), |
95 | 96 | m_geomX2(2048), |
96 | 97 | m_geomY2(2048), |
98 | m_exhaustiveSearch(EXH_AUTO), | |
97 | 99 | m_zoom(1), |
98 | 100 | m_scales(SCALE_LEFT | SCALE_TOP) |
99 | 101 | { |
183 | 185 | |
184 | 186 | void TileGenerator::setGeometry(int x, int y, int w, int h) |
185 | 187 | { |
188 | assert(w > 0 && h > 0); | |
186 | 189 | m_geomX = round_multiple_nosign(x, 16) / 16; |
187 | 190 | m_geomY = round_multiple_nosign(y, 16) / 16; |
188 | 191 | m_geomX2 = round_multiple_nosign(x + w, 16) / 16; |
192 | 195 | void TileGenerator::setMinY(int y) |
193 | 196 | { |
194 | 197 | m_yMin = y; |
198 | if (m_yMin > m_yMax) | |
199 | std::swap(m_yMin, m_yMax); | |
195 | 200 | } |
196 | 201 | |
197 | 202 | void TileGenerator::setMaxY(int y) |
198 | 203 | { |
199 | 204 | 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; | |
200 | 212 | } |
201 | 213 | |
202 | 214 | void TileGenerator::parseColorsFile(const std::string &fileName) |
215 | 227 | input_path += PATH_SEPARATOR; |
216 | 228 | } |
217 | 229 | |
230 | setExhaustiveSearch(EXH_NEVER); | |
218 | 231 | openDb(input_path); |
219 | 232 | loadBlocks(); |
220 | 233 | |
240 | 253 | input_path += PATH_SEPARATOR; |
241 | 254 | } |
242 | 255 | |
256 | if (m_dontWriteEmpty) // FIXME: possible too, just needs to be done differently | |
257 | setExhaustiveSearch(EXH_NEVER); | |
243 | 258 | openDb(input_path); |
244 | 259 | loadBlocks(); |
245 | 260 | |
246 | if (m_dontWriteEmpty && ! m_positions.size()) | |
261 | if (m_dontWriteEmpty && m_positions.empty()) | |
247 | 262 | { |
248 | 263 | closeDatabase(); |
249 | 264 | return; |
267 | 282 | |
268 | 283 | void TileGenerator::parseColorsStream(std::istream &in) |
269 | 284 | { |
270 | char line[128]; | |
285 | char line[512]; | |
271 | 286 | while (in.good()) { |
272 | in.getline(line, 128); | |
287 | in.getline(line, sizeof(line)); | |
273 | 288 | |
274 | 289 | for(char *p = line; *p; p++) { |
275 | 290 | if(*p != '#') |
280 | 295 | if(strlen(line) == 0) |
281 | 296 | continue; |
282 | 297 | |
283 | char name[64 + 1]; | |
298 | char name[128 + 1] = {0}; | |
284 | 299 | unsigned int r, g, b, a, t; |
285 | 300 | a = 255; |
286 | 301 | 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); | |
288 | 303 | if(items < 4) { |
289 | 304 | std::cerr << "Failed to parse color entry '" << line << "'" << std::endl; |
290 | 305 | continue; |
291 | 306 | } |
292 | 307 | |
293 | ColorEntry color = ColorEntry(r, g, b, a, t); | |
308 | ColorEntry color(r, g, b, a, t); | |
294 | 309 | m_colorMap[name] = color; |
295 | 310 | } |
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; | |
296 | 327 | } |
297 | 328 | |
298 | 329 | void TileGenerator::openDb(const std::string &input) |
322 | 353 | #endif |
323 | 354 | else |
324 | 355 | 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); | |
325 | 383 | } |
326 | 384 | |
327 | 385 | void TileGenerator::closeDatabase() |
332 | 390 | |
333 | 391 | void TileGenerator::loadBlocks() |
334 | 392 | { |
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 | } | |
358 | 428 | } |
359 | 429 | |
360 | 430 | void TileGenerator::createImage() |
392 | 462 | image_height = (m_mapHeight * m_zoom) + m_yBorder; |
393 | 463 | image_height += (m_scales & SCALE_BOTTOM) ? scale_d : 0; |
394 | 464 | |
395 | if(image_width > 4096 || image_height > 4096) | |
465 | if(image_width > 4096 || image_height > 4096) { | |
396 | 466 | std::cerr << "Warning: The width or height of the image to be created exceeds 4096 pixels!" |
397 | 467 | << " (Dimensions: " << image_width << "x" << image_height << ")" |
398 | 468 | << std::endl; |
469 | } | |
399 | 470 | m_image = new Image(image_width, image_height); |
400 | 471 | m_image->drawFilledRect(0, 0, image_width, image_height, m_bgColor); // Background |
401 | 472 | } |
403 | 474 | void TileGenerator::renderMap() |
404 | 475 | { |
405 | 476 | 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()) | |
413 | 498 | 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) { | |
444 | 509 | if (m_shading) |
445 | 510 | 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 | } | |
446 | 573 | } |
447 | 574 | } |
448 | 575 | |
451 | 578 | int xBegin = (pos.x - m_xMin) * 16; |
452 | 579 | int zBegin = (m_zMax - pos.z) * 16; |
453 | 580 | 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; | |
455 | 582 | for (int z = 0; z < 16; ++z) { |
456 | 583 | int imageY = zBegin + 15 - z; |
457 | 584 | for (int x = 0; x < 16; ++x) { |
635 | 762 | void TileGenerator::renderPlayers(const std::string &inputPath) |
636 | 763 | { |
637 | 764 | 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) | |
641 | 768 | continue; |
642 | if (player->y < m_yMin || player->y > m_yMax) | |
769 | if (player.y < m_yMin || player.y > m_yMax) | |
643 | 770 | 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); | |
646 | 773 | |
647 | 774 | m_image->drawFilledRect(imageX - 1, imageY, 3, 1, m_playerColor); |
648 | 775 | 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 | } | |
662 | 778 | } |
663 | 779 | |
664 | 780 | void TileGenerator::writeImage(const std::string &output) |
673 | 789 | if (m_unknownNodes.size() == 0) |
674 | 790 | return; |
675 | 791 | 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; | |
678 | 794 | } |
679 | 795 | |
680 | 796 | inline int TileGenerator::getImageX(int val, bool absolute) const |
10 | 10 | return t; |
11 | 11 | } |
12 | 12 | |
13 | ||
14 | 13 | static inline std::string i64tos(int64_t i) |
15 | 14 | { |
16 | 15 | std::ostringstream os; |
17 | 16 | os << i; |
18 | 17 | return os.str(); |
19 | 18 | } |
19 | ||
20 | 20 | |
21 | 21 | DBLevelDB::DBLevelDB(const std::string &mapdir) |
22 | 22 | { |
27 | 27 | throw std::runtime_error(std::string("Failed to open Database: ") + status.ToString()); |
28 | 28 | } |
29 | 29 | |
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 | */ | |
30 | 33 | loadPosCache(); |
31 | 34 | } |
32 | 35 | |
37 | 40 | } |
38 | 41 | |
39 | 42 | |
40 | std::vector<BlockPos> DBLevelDB::getBlockPos() | |
43 | std::vector<BlockPos> DBLevelDB::getBlockPos(BlockPos min, BlockPos max) | |
41 | 44 | { |
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; | |
43 | 58 | } |
44 | 59 | |
45 | 60 | |
48 | 63 | leveldb::Iterator * it = db->NewIterator(leveldb::ReadOptions()); |
49 | 64 | for (it->SeekToFirst(); it->Valid(); it->Next()) { |
50 | 65 | 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); | |
52 | 69 | } |
53 | 70 | delete it; |
54 | 71 | } |
55 | 72 | |
56 | 73 | |
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) | |
58 | 76 | { |
59 | 77 | std::string datastr; |
60 | 78 | leveldb::Status status; |
61 | 79 | |
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) | |
64 | 85 | 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); | |
67 | 91 | 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 | ); | |
70 | 95 | } |
71 | 96 | } |
72 | 97 | } |
73 | 98 | |
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 | } |
26 | 26 | |
27 | 27 | prepareStatement( |
28 | 28 | "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)" | |
30 | 33 | ); |
31 | 34 | 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" | |
34 | 44 | ); |
35 | 45 | |
36 | 46 | checkResults(PQexec(db, "START TRANSACTION;")); |
42 | 52 | { |
43 | 53 | try { |
44 | 54 | checkResults(PQexec(db, "COMMIT;")); |
45 | } catch (std::exception& caught) { | |
55 | } catch (const std::exception& caught) { | |
46 | 56 | std::cerr << "could not finalize: " << caught.what() << std::endl; |
47 | 57 | } |
48 | 58 | PQfinish(db); |
49 | 59 | } |
50 | 60 | |
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 | ||
53 | 82 | 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 }; | |
54 | 105 | |
55 | 106 | 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, | |
81 | 108 | argLen, argFmt, false |
82 | 109 | ); |
83 | 110 | |
85 | 112 | |
86 | 113 | for (int row = 0; row < numrows; ++row) { |
87 | 114 | 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); | |
90 | 117 | position.z = zPos; |
91 | Block const b( | |
118 | blocks.emplace_back( | |
92 | 119 | position, |
93 | 120 | ustring( |
94 | 121 | reinterpret_cast<unsigned char*>( |
95 | PQgetvalue(results, row, 2) | |
122 | PQgetvalue(results, row, 1) | |
96 | 123 | ), |
97 | PQgetlength(results, row, 2) | |
124 | PQgetlength(results, row, 1) | |
98 | 125 | ) |
99 | 126 | ); |
100 | blocks[position.x].push_back(b); | |
101 | 127 | } |
102 | 128 | |
103 | 129 | PQclear(results); |
104 | 130 | } |
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 | ||
105 | 168 | |
106 | 169 | PGresult *DBPostgreSQL::checkResults(PGresult *res, bool clear) |
107 | 170 | { |
137 | 200 | const char *stmtName, const int paramsNumber, |
138 | 201 | const void **params, |
139 | 202 | const int *paramsLengths, const int *paramsFormats, |
140 | bool clear, bool nobinary | |
203 | bool clear | |
141 | 204 | ) |
142 | 205 | { |
143 | 206 | return checkResults(PQexecPrepared(db, stmtName, paramsNumber, |
144 | 207 | (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 | ); | |
152 | 210 | } |
153 | 211 | |
154 | 212 | int DBPostgreSQL::pg_binary_to_int(PGresult *res, int row, int col) |
160 | 218 | BlockPos DBPostgreSQL::pg_to_blockpos(PGresult *res, int row, int col) |
161 | 219 | { |
162 | 220 | 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); | |
166 | 224 | return result; |
167 | 225 | } |
50 | 50 | throw std::runtime_error(err); |
51 | 51 | } |
52 | 52 | |
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 | */ | |
53 | 56 | loadPosCache(); |
54 | 57 | } |
55 | 58 | |
60 | 63 | } |
61 | 64 | |
62 | 65 | |
63 | std::vector<BlockPos> DBRedis::getBlockPos() | |
66 | std::vector<BlockPos> DBRedis::getBlockPos(BlockPos min, BlockPos max) | |
64 | 67 | { |
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; | |
66 | 81 | } |
67 | 82 | |
68 | 83 | |
69 | std::string DBRedis::replyTypeStr(int type) { | |
84 | const char *DBRedis::replyTypeStr(int type) { | |
70 | 85 | switch(type) { |
71 | 86 | case REDIS_REPLY_STATUS: |
72 | 87 | return "REDIS_REPLY_STATUS"; |
97 | 112 | for(size_t i = 0; i < reply->elements; i++) { |
98 | 113 | if(reply->element[i]->type != REDIS_REPLY_STRING) |
99 | 114 | 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); | |
101 | 117 | } |
102 | 118 | |
103 | 119 | freeReplyObject(reply); |
104 | 120 | } |
105 | 121 | |
106 | 122 | |
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) | |
108 | 125 | { |
109 | 126 | const char *argv[DB_REDIS_HMGET_NUMFIELDS + 2]; |
110 | 127 | argv[0] = "HMGET"; |
112 | 129 | |
113 | 130 | std::vector<BlockPos>::const_iterator position = positions.begin(); |
114 | 131 | std::size_t remaining = positions.size(); |
132 | std::size_t abs_i = 0; | |
115 | 133 | while (remaining > 0) { |
116 | 134 | const std::size_t batch_size = |
117 | 135 | (remaining > DB_REDIS_HMGET_NUMFIELDS) ? DB_REDIS_HMGET_NUMFIELDS : remaining; |
129 | 147 | |
130 | 148 | if(!reply) |
131 | 149 | 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"); | |
136 | 152 | if (reply->elements != batch_size) { |
137 | 153 | freeReplyObject(reply); |
138 | 154 | throw std::runtime_error("HMGET wrong number of elements"); |
139 | 155 | } |
140 | for (std::size_t i = 0; i < batch_size; ++i) { | |
156 | for (std::size_t i = 0; i < reply->elements; ++i) { | |
141 | 157 | 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) | |
150 | 163 | 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)); | |
153 | 165 | } |
154 | 166 | freeReplyObject(reply); |
167 | abs_i += reply->elements; | |
155 | 168 | remaining -= batch_size; |
156 | 169 | } |
157 | 170 | } |
158 | 171 | |
159 | 172 | |
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) | |
161 | 175 | { |
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); | |
168 | 184 | } |
169 | std::vector<ustring> z_blocks; | |
170 | HMGET(z_positions, &z_blocks); | |
171 | 185 | |
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); | |
178 | 187 | } |
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 | } |
0 | 0 | #include <stdexcept> |
1 | 1 | #include <unistd.h> // for usleep |
2 | 2 | #include <iostream> |
3 | #include <algorithm> | |
4 | #include <time.h> | |
3 | 5 | #include "db-sqlite3.h" |
4 | 6 | #include "types.h" |
5 | 7 | |
9 | 11 | throw std::runtime_error(sqlite3_errmsg(db));\ |
10 | 12 | } |
11 | 13 | #define SQLOK(f) SQLRES(f, SQLITE_OK) |
12 | ||
13 | 14 | |
14 | 15 | DBSQLite3::DBSQLite3(const std::string &mapdir) |
15 | 16 | { |
24 | 25 | -1, &stmt_get_blocks_z, NULL)) |
25 | 26 | |
26 | 27 | SQLOK(prepare_v2(db, |
28 | "SELECT data FROM blocks WHERE pos = ?", | |
29 | -1, &stmt_get_block_exact, NULL)) | |
30 | ||
31 | SQLOK(prepare_v2(db, | |
27 | 32 | "SELECT pos FROM blocks", |
28 | 33 | -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)) | |
29 | 38 | } |
30 | 39 | |
31 | 40 | |
33 | 42 | { |
34 | 43 | sqlite3_finalize(stmt_get_blocks_z); |
35 | 44 | sqlite3_finalize(stmt_get_block_pos); |
45 | sqlite3_finalize(stmt_get_block_pos_z); | |
46 | sqlite3_finalize(stmt_get_block_exact); | |
36 | 47 | |
37 | 48 | if (sqlite3_close(db) != SQLITE_OK) { |
38 | 49 | std::cerr << "Error closing SQLite database." << std::endl; |
39 | 50 | }; |
40 | 51 | } |
41 | 52 | |
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) | |
43 | 69 | { |
44 | 70 | 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 | ||
45 | 87 | 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 | |
51 | 90 | usleep(10000); |
52 | } else { | |
91 | } else if (result != SQLITE_ROW) { | |
53 | 92 | throw std::runtime_error(sqlite3_errmsg(db)); |
54 | 93 | } |
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); | |
55 | 99 | } |
56 | SQLOK(reset(stmt_get_block_pos)); | |
100 | SQLOK(reset(stmt)); | |
57 | 101 | return positions; |
58 | 102 | } |
59 | 103 | |
60 | 104 | |
61 | void DBSQLite3::getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos) | |
105 | void DBSQLite3::loadBlockCache(int16_t zPos) | |
62 | 106 | { |
63 | 107 | int result; |
108 | blockCache.clear(); | |
64 | 109 | |
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); | |
68 | 112 | |
69 | 113 | SQLOK(bind_int64(stmt_get_blocks_z, 1, minPos)); |
70 | 114 | SQLOK(bind_int64(stmt_get_blocks_z, 2, maxPos)); |
71 | 115 | |
72 | 116 | 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 | |
81 | 118 | usleep(10000); |
82 | } else { | |
119 | } else if (result != SQLITE_ROW) { | |
83 | 120 | throw std::runtime_error(sqlite3_errmsg(db)); |
84 | 121 | } |
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)); | |
85 | 129 | } |
86 | SQLOK(reset(stmt_get_blocks_z)); | |
130 | SQLOK(reset(stmt_get_blocks_z)) | |
87 | 131 | } |
88 | 132 | |
89 | #undef SQLRES | |
90 | #undef SQLOK | |
91 | 133 | |
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 | } |
0 | 0 | #ifndef BLOCKDECODER_H |
1 | 1 | #define BLOCKDECODER_H |
2 | 2 | |
3 | #if __cplusplus >= 201103L | |
4 | 3 | #include <unordered_map> |
5 | #else | |
6 | #include <map> | |
7 | #endif | |
8 | 4 | |
9 | 5 | #include "types.h" |
10 | 6 | |
18 | 14 | std::string getNode(u8 x, u8 y, u8 z) const; // returns "" for air, ignore and invalid nodes |
19 | 15 | |
20 | 16 | private: |
21 | #if __cplusplus >= 201103L | |
22 | 17 | typedef std::unordered_map<int, std::string> NameMap; |
23 | #else | |
24 | typedef std::map<int, std::string> NameMap; | |
25 | #endif | |
26 | 18 | NameMap m_nameMap; |
27 | 19 | int m_blockAirId; |
28 | 20 | int m_blockIgnoreId; |
1 | 1 | #define TILEGENERATOR_HEADER |
2 | 2 | |
3 | 3 | #include <iosfwd> |
4 | #include <list> | |
4 | #include <map> | |
5 | #include <set> | |
5 | 6 | #include <config.h> |
6 | #if __cplusplus >= 201103L | |
7 | 7 | #include <unordered_map> |
8 | 8 | #include <unordered_set> |
9 | #else | |
10 | #include <map> | |
11 | #include <set> | |
12 | #endif | |
13 | 9 | #include <stdint.h> |
14 | 10 | #include <string> |
15 | 11 | |
24 | 20 | SCALE_BOTTOM = (1 << 1), |
25 | 21 | SCALE_LEFT = (1 << 2), |
26 | 22 | 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 | |
27 | 30 | }; |
28 | 31 | |
29 | 32 | struct ColorEntry { |
59 | 62 | class TileGenerator |
60 | 63 | { |
61 | 64 | private: |
62 | #if __cplusplus >= 201103L | |
63 | 65 | typedef std::unordered_map<std::string, ColorEntry> ColorMap; |
64 | 66 | 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 | |
69 | 67 | |
70 | 68 | public: |
71 | 69 | TileGenerator(); |
73 | 71 | void setBgColor(const std::string &bgColor); |
74 | 72 | void setScaleColor(const std::string &scaleColor); |
75 | 73 | 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); | |
77 | 76 | void setDrawOrigin(bool drawOrigin); |
78 | 77 | void setDrawPlayers(bool drawPlayers); |
79 | 78 | void setDrawScale(bool drawScale); |
82 | 81 | void setGeometry(int x, int y, int w, int h); |
83 | 82 | void setMinY(int y); |
84 | 83 | void setMaxY(int y); |
84 | void setExhaustiveSearch(int mode); | |
85 | 85 | void parseColorsFile(const std::string &fileName); |
86 | 86 | void setBackend(std::string backend); |
87 | void generate(const std::string &input, const std::string &output); | |
88 | void printGeometry(const std::string &input); | |
89 | 87 | void setZoom(int zoom); |
90 | 88 | void setScales(uint flags); |
91 | 89 | 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(); | |
92 | 94 | |
93 | 95 | private: |
94 | 96 | void parseColorsStream(std::istream &in); |
97 | 99 | void loadBlocks(); |
98 | 100 | void createImage(); |
99 | 101 | void renderMap(); |
100 | std::list<int> getZValueList() const; | |
101 | 102 | void renderMapBlock(const BlockDecoder &blk, const BlockPos &pos); |
102 | 103 | void renderMapBlockBottom(const BlockPos &pos); |
103 | 104 | void renderShading(int zPos); |
127 | 128 | DB *m_db; |
128 | 129 | Image *m_image; |
129 | 130 | PixelAttributes m_blockPixelAttributes; |
131 | /* smallest/largest seen X or Z block coordinate */ | |
130 | 132 | int m_xMin; |
131 | 133 | int m_xMax; |
132 | 134 | int m_zMin; |
133 | 135 | int m_zMax; |
136 | /* Y limits for rendered area (node units) */ | |
134 | 137 | int m_yMin; |
135 | 138 | 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 | /* */ | |
140 | 145 | int m_mapWidth; |
141 | 146 | 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 */ | |
143 | 149 | ColorMap m_colorMap; |
144 | 150 | BitmapThing m_readPixels; |
145 | 151 | BitmapThing m_readInfo; |
2 | 2 | #ifndef CMAKE_CONFIG_H |
3 | 3 | #define CMAKE_CONFIG_H |
4 | 4 | |
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 | |
8 | 8 | |
9 | 9 | #define SHAREDIR "@SHAREDIR@" |
10 | 10 |
1 | 1 | #define DB_LEVELDB_HEADER |
2 | 2 | |
3 | 3 | #include "db.h" |
4 | #include <unordered_map> | |
5 | #include <utility> | |
4 | 6 | #include <leveldb/db.h> |
5 | 7 | |
6 | 8 | class DBLevelDB : public DB { |
7 | 9 | public: |
8 | 10 | 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 | ||
12 | 20 | private: |
21 | using pos2d = std::pair<int16_t, int16_t>; | |
22 | ||
13 | 23 | void loadPosCache(); |
14 | 24 | |
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; | |
17 | 27 | leveldb::DB *db; |
18 | 28 | }; |
19 | 29 |
6 | 6 | class DBPostgreSQL : public DB { |
7 | 7 | public: |
8 | 8 | 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 | ||
12 | 18 | protected: |
13 | 19 | PGresult *checkResults(PGresult *res, bool clear = true); |
14 | 20 | void prepareStatement(const std::string &name, const std::string &sql); |
16 | 22 | const char *stmtName, const int paramsNumber, |
17 | 23 | const void **params, |
18 | 24 | const int *paramsLengths = NULL, const int *paramsFormats = NULL, |
19 | bool clear = true, bool nobinary = true | |
25 | bool clear = true | |
20 | 26 | ); |
21 | int pg_to_int(PGresult *res, int row, int col); | |
22 | 27 | int pg_binary_to_int(PGresult *res, int row, int col); |
23 | 28 | BlockPos pg_to_blockpos(PGresult *res, int row, int col); |
29 | ||
24 | 30 | private: |
25 | 31 | PGconn *db; |
26 | 32 | }; |
1 | 1 | #define DB_REDIS_HEADER |
2 | 2 | |
3 | 3 | #include "db.h" |
4 | #include <hiredis.h> | |
4 | #include <unordered_map> | |
5 | #include <utility> | |
6 | #include <functional> | |
7 | #include <hiredis/hiredis.h> | |
5 | 8 | |
6 | 9 | class DBRedis : public DB { |
7 | 10 | public: |
8 | 11 | 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 | ||
12 | 21 | private: |
13 | static std::string replyTypeStr(int type); | |
22 | using pos2d = std::pair<int16_t, int16_t>; | |
23 | static const char *replyTypeStr(int type); | |
14 | 24 | |
15 | 25 | 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); | |
17 | 28 | |
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; | |
19 | 31 | |
20 | 32 | redisContext *ctx; |
21 | 33 | std::string hash; |
1 | 1 | #define _DB_SQLITE3_H |
2 | 2 | |
3 | 3 | #include "db.h" |
4 | #include <unordered_map> | |
4 | 5 | #include <sqlite3.h> |
5 | 6 | |
6 | 7 | class DBSQLite3 : public DB { |
7 | 8 | public: |
8 | 9 | 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 | ||
12 | 19 | 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 | ||
13 | 24 | sqlite3 *db; |
14 | 25 | |
15 | 26 | sqlite3_stmt *stmt_get_block_pos; |
27 | sqlite3_stmt *stmt_get_block_pos_z; | |
16 | 28 | 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 | |
17 | 33 | }; |
18 | 34 | |
19 | 35 | #endif // _DB_SQLITE3_H |
4 | 4 | #include <map> |
5 | 5 | #include <list> |
6 | 6 | #include <vector> |
7 | #include <string> | |
8 | 7 | #include <utility> |
9 | 8 | #include "types.h" |
10 | 9 | |
11 | 10 | |
12 | class BlockPos { | |
13 | public: | |
11 | struct BlockPos { | |
14 | 12 | int16_t x; |
15 | 13 | int16_t y; |
16 | 14 | int16_t z; |
17 | 15 | |
18 | 16 | BlockPos() : x(0), y(0), z(0) {} |
19 | 17 | 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) | |
20 | 20 | bool operator < (const BlockPos &p) const |
21 | 21 | { |
22 | 22 | if (z > p.z) |
42 | 42 | |
43 | 43 | class DB { |
44 | 44 | protected: |
45 | // Helpers that implement the hashed positions used by most backends | |
45 | 46 | inline int64_t encodeBlockPos(const BlockPos pos) const; |
46 | 47 | inline BlockPos decodeBlockPos(int64_t hash) const; |
47 | 48 | |
48 | 49 | 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() {} | |
52 | 70 | }; |
53 | 71 | |
54 | 72 |
9 | 9 | { |
10 | 10 | try { |
11 | 11 | return read_setting(name, is); |
12 | } catch(std::runtime_error &e) { | |
12 | } catch(const std::runtime_error &e) { | |
13 | 13 | return def; |
14 | 14 | } |
15 | 15 | } |
0 | #include <cstdio> | |
0 | 1 | #include <cstdlib> |
1 | 2 | #include <cstring> |
2 | 3 | #include <getopt.h> |
3 | 4 | #include <fstream> |
4 | 5 | #include <iostream> |
5 | #include <map> | |
6 | #include <utility> | |
6 | 7 | #include <string> |
7 | 8 | #include <sstream> |
8 | 9 | #include <stdexcept> |
9 | 10 | #include "cmake_config.h" |
10 | 11 | #include "TileGenerator.h" |
11 | 12 | |
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) | |
40 | 59 | { |
41 | 60 | std::ifstream ifs(path.c_str()); |
42 | 61 | return ifs.is_open(); |
43 | 62 | } |
44 | 63 | |
45 | std::string search_colors(const std::string &worldpath) | |
64 | static std::string search_colors(const std::string &worldpath) | |
46 | 65 | { |
47 | 66 | if(file_exists(worldpath + "/colors.txt")) |
48 | 67 | return worldpath + "/colors.txt"; |
56 | 75 | } |
57 | 76 | #endif |
58 | 77 | |
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")) | |
60 | 80 | return SHAREDIR "/colors.txt"; |
61 | 81 | |
62 | 82 | std::cerr << "Warning: Falling back to using colors.txt from current directory." << std::endl; |
65 | 85 | |
66 | 86 | int main(int argc, char *argv[]) |
67 | 87 | { |
68 | static struct option long_options[] = | |
88 | const static struct option long_options[] = | |
69 | 89 | { |
70 | 90 | {"help", no_argument, 0, 'h'}, |
71 | 91 | {"input", required_argument, 0, 'i'}, |
88 | 108 | {"colors", required_argument, 0, 'C'}, |
89 | 109 | {"scales", required_argument, 0, 'f'}, |
90 | 110 | {"noemptyimage", no_argument, 0, 'n'}, |
111 | {"exhaustive", required_argument, 0, 'j'}, | |
91 | 112 | {0, 0, 0, 0} |
92 | 113 | }; |
93 | 114 | |
199 | 220 | case 'n': |
200 | 221 | generator.setDontWriteEmpty(true); |
201 | 222 | 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; | |
202 | 236 | default: |
203 | 237 | exit(1); |
204 | 238 | } |
69 | 69 | |
70 | 70 | .TP |
71 | 71 | .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" | |
73 | 73 | |
74 | 74 | .TP |
75 | 75 | .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" | |
77 | 77 | |
78 | 78 | .TP |
79 | 79 | .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. | |
81 | 81 | |
82 | 82 | .TP |
83 | 83 | .BR \-\-zoom " " \fIfactor\fR |
89 | 89 | |
90 | 90 | .TP |
91 | 91 | .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. | |
93 | 104 | |
94 | 105 | .SH MORE INFORMATION |
95 | 106 | Website: https://github.com/minetest/minetestmapper |
2 | 2 | |
3 | 3 | #include "util.h" |
4 | 4 | |
5 | inline std::string trim(const std::string &s) | |
5 | static inline std::string trim(const std::string &s) | |
6 | 6 | { |
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'; }; | |
14 | 8 | |
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; | |
24 | 15 | |
25 | return s.substr(front, back - front); | |
16 | return s.substr(front, back - front + 1); | |
26 | 17 | } |
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) | |
35 | 18 | |
36 | 19 | std::string read_setting(const std::string &name, std::istream &is) |
37 | 20 | { |
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)); | |
41 | 24 | |
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()); | |
60 | 44 | } |
61 | ||
62 | #undef EOFCHECK |