Add flag to enable verbose output

This commit is contained in:
sfan5 2025-03-05 20:19:36 +01:00
parent e982efe94e
commit 4ba09ec532
12 changed files with 114 additions and 48 deletions

View File

@ -179,6 +179,7 @@ target_sources(minetestmapper PRIVATE
src/Image.cpp src/Image.cpp
src/mapper.cpp src/mapper.cpp
src/util.cpp src/util.cpp
src/log.cpp
src/db-sqlite3.cpp src/db-sqlite3.cpp
$<$<BOOL:${USE_POSTGRESQL}>:src/db-postgresql.cpp> $<$<BOOL:${USE_POSTGRESQL}>:src/db-postgresql.cpp>
$<$<BOOL:${USE_LEVELDB}>:src/db-leveldb.cpp> $<$<BOOL:${USE_LEVELDB}>:src/db-leveldb.cpp>

View File

@ -12,13 +12,15 @@ See additional optional parameters below.
generates an overview image of a Luanti map. This is a port of generates an overview image of a Luanti map. This is a port of
the original minetestmapper.py to C++, that is both faster and the original minetestmapper.py to C++, that is both faster and
provides more functionality than the obsolete Python script. provides more functionality than the obsolete Python script.
.SH MANDATORY PARAMETERS .SH MANDATORY PARAMETERS
.TP .TP
.BR \-i " " \fIworld_path\fR .BR \-i " " \fIworld_path\fR
Input world path. Input world path
.TP .TP
.BR \-o " " \fIoutput_image\fR .BR \-o " " \fIoutput_image\fR
Path to output image. (only PNG supported currently) Path to output image
.SH OPTIONAL PARAMETERS .SH OPTIONAL PARAMETERS
.TP .TP
.BR \-\-bgcolor " " \fIcolor\fR .BR \-\-bgcolor " " \fIcolor\fR
@ -60,6 +62,10 @@ Don't draw shading on nodes
.BR \-\-noemptyimage .BR \-\-noemptyimage
Don't output anything when the image would be empty. Don't output anything when the image would be empty.
.TP
.BR \-\-verbose
Enable verbose log output.
.TP .TP
.BR \-\-min-y " " \fInumber\fR .BR \-\-min-y " " \fInumber\fR
Don't draw nodes below this y value, e.g. "--min-y -25" Don't draw nodes below this y value, e.g. "--min-y -25"
@ -108,7 +114,7 @@ when you don't care about the world below e.g. -60 and above 1000 nodes.
Instead of rendering anything try to load the block at the given position (\fIx,y,z\fR) and print its raw data as hexadecimal. Instead of rendering anything try to load the block at the given position (\fIx,y,z\fR) and print its raw data as hexadecimal.
.SH MORE INFORMATION .SH MORE INFORMATION
Website: https://github.com/minetest/minetestmapper Website: https://github.com/luanti-org/minetestmapper
.SH MAN PAGE AUTHOR .SH MAN PAGE AUTHOR
Daniel Moerner Daniel Moerner

View File

@ -4,6 +4,7 @@
#include "BlockDecoder.h" #include "BlockDecoder.h"
#include "ZlibDecompressor.h" #include "ZlibDecompressor.h"
#include "log.h"
static inline uint16_t readU16(const unsigned char *data) static inline uint16_t readU16(const unsigned char *data)
{ {
@ -161,7 +162,7 @@ const std::string &BlockDecoder::getNode(u8 x, u8 y, u8 z) const
return empty; return empty;
NameMap::const_iterator it = m_nameMap.find(content); NameMap::const_iterator it = m_nameMap.find(content);
if (it == m_nameMap.end()) { if (it == m_nameMap.end()) {
std::cerr << "Skipping node with invalid ID." << std::endl; errorstream << "Skipping node with invalid ID." << std::endl;
return empty; return empty;
} }
return it->second; return it->second;

View File

@ -7,6 +7,7 @@
#include "config.h" #include "config.h"
#include "PlayerAttributes.h" #include "PlayerAttributes.h"
#include "util.h" #include "util.h"
#include "log.h"
#include "db-sqlite3.h" // SQLite3Base #include "db-sqlite3.h" // SQLite3Base
namespace { namespace {
@ -82,7 +83,7 @@ void FilesReader::read(PlayerAttributes::Players &dest)
Player player; Player player;
player.name = name; player.name = name;
if (!parse_pos(position, player)) { if (!parse_pos(position, player)) {
std::cerr << "Failed to parse position " << position << std::endl; errorstream << "Failed to parse position '" << position << "'" << std::endl;
continue; continue;
} }

View File

@ -17,6 +17,7 @@
#include "BlockDecoder.h" #include "BlockDecoder.h"
#include "Image.h" #include "Image.h"
#include "util.h" #include "util.h"
#include "log.h"
#include "db-sqlite3.h" #include "db-sqlite3.h"
#if USE_POSTGRESQL #if USE_POSTGRESQL
@ -346,7 +347,7 @@ void TileGenerator::parseColorsStream(std::istream &in)
unsigned int r, g, b, a = 255, t = 0; unsigned int r, g, b, a = 255, t = 0;
int items = sscanf(line, "%200s %u %u %u %u %u", name, &r, &g, &b, &a, &t); int items = sscanf(line, "%200s %u %u %u %u %u", name, &r, &g, &b, &a, &t);
if (items < 4) { if (items < 4) {
std::cerr << "Failed to parse color entry '" << line << "'" << std::endl; errorstream << "Failed to parse color entry '" << line << "'" << std::endl;
continue; continue;
} }
@ -414,7 +415,7 @@ void TileGenerator::openDb(const std::string &input_path)
} }
if (!read_setting_default("readonly_backend", ifs, "").empty()) { if (!read_setting_default("readonly_backend", ifs, "").empty()) {
std::cerr << "Warning: Map with readonly_backend is not supported. " errorstream << "Warning: Map with readonly_backend is not supported. "
"The result may be incomplete." << std::endl; "The result may be incomplete." << std::endl;
} }
@ -422,11 +423,9 @@ void TileGenerator::openDb(const std::string &input_path)
if (m_exhaustiveSearch == EXH_AUTO) { if (m_exhaustiveSearch == EXH_AUTO) {
size_t y_range = (m_yMax / 16 + 1) - (m_yMin / 16); size_t y_range = (m_yMax / 16 + 1) - (m_yMin / 16);
size_t blocks = sat_mul<size_t>(m_geomX2 - m_geomX, y_range, m_geomY2 - m_geomY); size_t blocks = sat_mul<size_t>(m_geomX2 - m_geomX, y_range, m_geomY2 - m_geomY);
#ifndef NDEBUG verbosestream << "Heuristic parameters:"
std::cerr << "Heuristic parameters:"
<< " preferRangeQueries()=" << m_db->preferRangeQueries() << " preferRangeQueries()=" << m_db->preferRangeQueries()
<< " y_range=" << y_range << " blocks=" << blocks << std::endl; << " y_range=" << y_range << " blocks=" << blocks << std::endl;
#endif
if (m_db->preferRangeQueries()) if (m_db->preferRangeQueries())
m_exhaustiveSearch = EXH_NEVER; m_exhaustiveSearch = EXH_NEVER;
else if (blocks < 200000) else if (blocks < 200000)
@ -437,7 +436,7 @@ void TileGenerator::openDb(const std::string &input_path)
m_exhaustiveSearch = EXH_NEVER; m_exhaustiveSearch = EXH_NEVER;
} else if (m_exhaustiveSearch == EXH_FULL || m_exhaustiveSearch == EXH_Y) { } else if (m_exhaustiveSearch == EXH_FULL || m_exhaustiveSearch == EXH_Y) {
if (m_db->preferRangeQueries()) { if (m_db->preferRangeQueries()) {
std::cerr << "Note: The current database backend supports efficient " errorstream << "Note: The current database backend supports efficient "
"range queries, forcing exhaustive search will generally result " "range queries, forcing exhaustive search will generally result "
"in worse performance." << std::endl; "in worse performance." << std::endl;
} }
@ -486,10 +485,8 @@ void TileGenerator::loadBlocks()
for (const auto &it : m_positions) for (const auto &it : m_positions)
count += it.second.size(); count += it.second.size();
m_progressMax = count; m_progressMax = count;
#ifndef NDEBUG verbosestream << "Loaded " << count
std::cerr << "Loaded " << count
<< " positions (across Z: " << m_positions.size() << ") for rendering" << std::endl; << " positions (across Z: " << m_positions.size() << ") for rendering" << std::endl;
#endif
} }
} }
@ -528,7 +525,7 @@ void TileGenerator::createImage()
image_height += (m_scales & SCALE_BOTTOM) ? scale_d : 0; image_height += (m_scales & SCALE_BOTTOM) ? scale_d : 0;
if(image_width > 4096 || image_height > 4096) { if(image_width > 4096 || image_height > 4096) {
std::cerr << "Warning: The width or height of the image to be created exceeds 4096 pixels!" errorstream << "Warning: The width or height of the image to be created exceeds 4096 pixels!"
<< " (Dimensions: " << image_width << "x" << image_height << ")" << " (Dimensions: " << image_width << "x" << image_height << ")"
<< std::endl; << std::endl;
} }
@ -594,10 +591,8 @@ void TileGenerator::renderMap()
postRenderRow(zPos); postRenderRow(zPos);
} }
} else if (m_exhaustiveSearch == EXH_Y) { } else if (m_exhaustiveSearch == EXH_Y) {
#ifndef NDEBUG verbosestream << "Exhaustively searching height of "
std::cerr << "Exhaustively searching height of "
<< (yMax - yMin) << " blocks" << std::endl; << (yMax - yMin) << " blocks" << std::endl;
#endif
std::vector<BlockPos> positions; std::vector<BlockPos> positions;
positions.reserve(yMax - yMin); positions.reserve(yMax - yMin);
for (auto it = m_positions.rbegin(); it != m_positions.rend(); ++it) { for (auto it = m_positions.rbegin(); it != m_positions.rend(); ++it) {
@ -621,11 +616,9 @@ void TileGenerator::renderMap()
} else if (m_exhaustiveSearch == EXH_FULL) { } else if (m_exhaustiveSearch == EXH_FULL) {
const size_t span_y = yMax - yMin; const size_t span_y = yMax - yMin;
m_progressMax = (m_geomX2 - m_geomX) * span_y * (m_geomY2 - m_geomY); m_progressMax = (m_geomX2 - m_geomX) * span_y * (m_geomY2 - m_geomY);
#ifndef NDEBUG verbosestream << "Exhaustively searching "
std::cerr << "Exhaustively searching "
<< (m_geomX2 - m_geomX) << "x" << span_y << "x" << (m_geomX2 - m_geomX) << "x" << span_y << "x"
<< (m_geomY2 - m_geomY) << " blocks" << std::endl; << (m_geomY2 - m_geomY) << " blocks" << std::endl;
#endif
std::vector<BlockPos> positions; std::vector<BlockPos> positions;
positions.reserve(span_y); positions.reserve(span_y);
@ -879,14 +872,15 @@ void TileGenerator::printUnknown()
{ {
if (m_unknownNodes.empty()) if (m_unknownNodes.empty())
return; return;
std::cerr << "Unknown nodes:" << std::endl; errorstream << "Unknown nodes:\n";
for (const auto &node : m_unknownNodes) for (const auto &node : m_unknownNodes)
std::cerr << "\t" << node << std::endl; errorstream << "\t" << node << '\n';
if (!m_renderedAny) { if (!m_renderedAny) {
std::cerr << "The map was read successfully and not empty, but none of the " errorstream << "The map was read successfully and not empty, but none of the "
"encountered nodes had a color associated.\nCheck that you're using " "encountered nodes had a color associated.\nCheck that you're using "
"the right colors.txt. It should match the game you have installed." << std::endl; "the right colors.txt. It should match the game you have installed.\n";
} }
errorstream << std::flush;
} }
void TileGenerator::reportProgress(size_t count) void TileGenerator::reportProgress(size_t count)

View File

@ -3,12 +3,12 @@
#include <fstream> #include <fstream>
#include <cstdlib> #include <cstdlib>
#include <arpa/inet.h> #include <arpa/inet.h>
#include "db-postgresql.h" #include "db-postgresql.h"
#include "util.h" #include "util.h"
#include "log.h"
#include "types.h" #include "types.h"
#define ARRLEN(x) (sizeof(x) / sizeof((x)[0]))
/* PostgreSQLBase */ /* PostgreSQLBase */
PostgreSQLBase::~PostgreSQLBase() PostgreSQLBase::~PostgreSQLBase()
@ -108,7 +108,7 @@ DBPostgreSQL::~DBPostgreSQL()
try { try {
checkResults(PQexec(db, "COMMIT;")); checkResults(PQexec(db, "COMMIT;"));
} catch (const std::exception& caught) { } catch (const std::exception& caught) {
std::cerr << "could not finalize: " << caught.what() << std::endl; errorstream << "could not finalize: " << caught.what() << std::endl;
} }
} }

View File

@ -3,7 +3,9 @@
#include <iostream> #include <iostream>
#include <algorithm> #include <algorithm>
#include <cassert> #include <cassert>
#include "db-sqlite3.h" #include "db-sqlite3.h"
#include "log.h"
#include "types.h" #include "types.h"
/* SQLite3Base */ /* SQLite3Base */
@ -14,7 +16,7 @@
SQLite3Base::~SQLite3Base() SQLite3Base::~SQLite3Base()
{ {
if (db && sqlite3_close(db) != SQLITE_OK) { if (db && sqlite3_close(db) != SQLITE_OK) {
std::cerr << "Error closing SQLite database: " errorstream << "Error closing SQLite database: "
<< sqlite3_errmsg(db) << std::endl; << sqlite3_errmsg(db) << std::endl;
} }
} }
@ -66,9 +68,7 @@ DBSQLite3::DBSQLite3(const std::string &mapdir)
// error right there. // error right there.
int result = prepare(stmt_get_block_pos, "SELECT x, y, z FROM blocks"); int result = prepare(stmt_get_block_pos, "SELECT x, y, z FROM blocks");
newFormat = result == SQLITE_OK; newFormat = result == SQLITE_OK;
#ifndef NDEBUG verbosestream << "Detected " << (newFormat ? "new" : "old") << " SQLite schema" << std::endl;
std::cerr << "Detected " << (newFormat ? "new" : "old") << " SQLite schema" << std::endl;
#endif
if (newFormat) { if (newFormat) {
SQLOK(prepare(stmt_get_blocks_xz_range, SQLOK(prepare(stmt_get_blocks_xz_range,
@ -228,9 +228,7 @@ void DBSQLite3::getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
/* We have swapped this list before, this is not supposed to happen /* We have swapped this list before, this is not supposed to happen
* because it's bad for performance. But rather than silently breaking * because it's bad for performance. But rather than silently breaking
* do the right thing and load the blocks again. */ * do the right thing and load the blocks again. */
#ifndef NDEBUG verbosestream << "suboptimal access pattern for sqlite3 backend?!" << std::endl;
std::cerr << "Warning: suboptimal access pattern for sqlite3 backend" << std::endl;
#endif
loadBlockCache(z); loadBlockCache(z);
} }
// Swap lists to avoid copying contents // Swap lists to avoid copying contents

16
src/log.cpp Normal file
View File

@ -0,0 +1,16 @@
#include <iostream>
#include "log.h"
StreamProxy errorstream(nullptr);
StreamProxy verbosestream(nullptr);
void configure_log_streams(bool verbose)
{
errorstream << std::flush;
verbosestream << std::flush;
errorstream = std::cerr.good() ? &std::cerr : nullptr;
// std::clog does not automatically flush
verbosestream = (verbose && std::clog.good()) ? &std::clog : nullptr;
}

40
src/log.h Normal file
View File

@ -0,0 +1,40 @@
#pragma once
#include <iostream>
#include <utility>
// Forwards to an ostream, optionally
class StreamProxy {
public:
StreamProxy(std::ostream *os) : m_os(os) {}
template<typename T>
StreamProxy &operator<<(T &&arg)
{
if (m_os)
*m_os << std::forward<T>(arg);
return *this;
}
StreamProxy &operator<<(std::ostream &(*func)(std::ostream&))
{
if (m_os)
*m_os << func;
return *this;
}
private:
std::ostream *m_os;
};
/// Error and warning output, forwards to std::cerr
extern StreamProxy errorstream;
/// Verbose output, might forward to std::cerr
extern StreamProxy verbosestream;
/**
* Configure log streams defined in this file.
* @param verbose enable verbose output
* @note not thread-safe!
*/
void configure_log_streams(bool verbose);

View File

@ -10,6 +10,8 @@
#include <stdexcept> #include <stdexcept>
#include "config.h" #include "config.h"
#include "TileGenerator.h" #include "TileGenerator.h"
#include "util.h"
#include "log.h"
static void usage() static void usage()
{ {
@ -59,8 +61,7 @@ static void usage()
static inline bool file_exists(const std::string &path) static inline bool file_exists(const std::string &path)
{ {
std::ifstream ifs(path); return file_exists(path.c_str());
return ifs.is_open();
} }
static inline int stoi(const char *s) static inline int stoi(const char *s)
@ -78,23 +79,24 @@ static std::string search_colors(const std::string &worldpath)
#ifndef _WIN32 #ifndef _WIN32
char *home = std::getenv("HOME"); char *home = std::getenv("HOME");
if (home) { if (home && home[0]) {
std::string check = std::string(home) + "/.minetest/colors.txt"; std::string check = std::string(home) + "/.minetest/colors.txt";
if (file_exists(check)) if (file_exists(check))
return check; return check;
} }
#endif #endif
constexpr bool sharedir_valid = !(SHAREDIR[0] == '.' || SHAREDIR[0] == '\0'); constexpr bool sharedir_valid = !(SHAREDIR[0] == '.' || !SHAREDIR[0]);
if (sharedir_valid && file_exists(SHAREDIR "/colors.txt")) if (sharedir_valid && file_exists(SHAREDIR "/colors.txt"))
return SHAREDIR "/colors.txt"; return SHAREDIR "/colors.txt";
std::cerr << "Warning: Falling back to using colors.txt from current directory." << std::endl; errorstream << "Warning: Falling back to using colors.txt from current directory." << std::endl;
return "colors.txt"; return "colors.txt";
} }
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
const char *short_options = "hi:o:v";
const static struct option long_options[] = const static struct option long_options[] =
{ {
{"help", no_argument, 0, 'h'}, {"help", no_argument, 0, 'h'},
@ -120,9 +122,12 @@ int main(int argc, char *argv[])
{"noemptyimage", no_argument, 0, 'n'}, {"noemptyimage", no_argument, 0, 'n'},
{"exhaustive", required_argument, 0, 'j'}, {"exhaustive", required_argument, 0, 'j'},
{"dumpblock", required_argument, 0, 'k'}, {"dumpblock", required_argument, 0, 'k'},
{"verbose", no_argument, 0, 'v'},
{0, 0, 0, 0} {0, 0, 0, 0}
}; };
configure_log_streams(false);
std::string input; std::string input;
std::string output; std::string output;
std::string colors; std::string colors;
@ -132,7 +137,7 @@ int main(int argc, char *argv[])
TileGenerator generator; TileGenerator generator;
while (1) { while (1) {
int option_index; int option_index;
int c = getopt_long(argc, argv, "hi:o:", long_options, &option_index); int c = getopt_long(argc, argv, short_options, long_options, &option_index);
if (c == -1) if (c == -1)
break; // done break; // done
@ -192,7 +197,7 @@ int main(int argc, char *argv[])
geometry >> x >> c >> y >> w >> h; geometry >> x >> c >> y >> w >> h;
if (geometry.fail() || c != ':' || w < 1 || h < 1) { if (geometry.fail() || c != ':' || w < 1 || h < 1) {
usage(); usage();
exit(1); return 1;
} }
generator.setGeometry(x, y, w, h); generator.setGeometry(x, y, w, h);
} }
@ -220,7 +225,7 @@ int main(int argc, char *argv[])
generator.setDontWriteEmpty(true); generator.setDontWriteEmpty(true);
break; break;
case 'j': { case 'j': {
int mode = EXH_AUTO;; int mode = EXH_AUTO;
if (!strcmp(optarg, "never")) if (!strcmp(optarg, "never"))
mode = EXH_NEVER; mode = EXH_NEVER;
else if (!strcmp(optarg, "y")) else if (!strcmp(optarg, "y"))
@ -236,12 +241,15 @@ int main(int argc, char *argv[])
iss >> dumpblock.x >> c >> dumpblock.y >> c2 >> dumpblock.z; iss >> dumpblock.x >> c >> dumpblock.y >> c2 >> dumpblock.z;
if (iss.fail() || c != ',' || c2 != ',') { if (iss.fail() || c != ',' || c2 != ',') {
usage(); usage();
exit(1); return 1;
} }
break; break;
} }
case 'v':
configure_log_streams(true);
break;
default: default:
exit(1); return 1;
} }
} }
@ -252,7 +260,6 @@ int main(int argc, char *argv[])
} }
try { try {
if (onlyPrintExtent) { if (onlyPrintExtent) {
generator.printGeometry(input); generator.printGeometry(input);
return 0; return 0;
@ -267,7 +274,7 @@ int main(int argc, char *argv[])
generator.generate(input, output); generator.generate(input, output);
} catch (const std::exception &e) { } catch (const std::exception &e) {
std::cerr << "Exception: " << e.what() << std::endl; errorstream << "Exception: " << e.what() << std::endl;
return 1; return 1;
} }
return 0; return 0;

View File

@ -3,6 +3,8 @@
#include <string> #include <string>
#include <iostream> #include <iostream>
#define ARRLEN(x) (sizeof(x) / sizeof((x)[0]))
template<typename T> template<typename T>
static inline T mymax(T a, T b) static inline T mymax(T a, T b)
{ {

View File

@ -31,7 +31,7 @@ checkmap () {
local c=$1 local c=$1
shift shift
rm -f map.png rm -f map.png
./minetestmapper --noemptyimage -i ./testmap -o map.png "$@" ./minetestmapper --noemptyimage -v -i ./testmap -o map.png "$@"
if [[ $c -eq 1 && ! -f map.png ]]; then if [[ $c -eq 1 && ! -f map.png ]]; then
echo "Output not generated!" echo "Output not generated!"
exit 1 exit 1