From 4ba09ec5325300d7eac931aeab2a8209bf19482f Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 5 Mar 2025 20:19:36 +0100 Subject: [PATCH] Add flag to enable verbose output --- CMakeLists.txt | 1 + minetestmapper.6 | 12 +++++++++--- src/BlockDecoder.cpp | 3 ++- src/PlayerAttributes.cpp | 3 ++- src/TileGenerator.cpp | 34 ++++++++++++++-------------------- src/db-postgresql.cpp | 6 +++--- src/db-sqlite3.cpp | 12 +++++------- src/log.cpp | 16 ++++++++++++++++ src/log.h | 40 ++++++++++++++++++++++++++++++++++++++++ src/mapper.cpp | 31 +++++++++++++++++++------------ src/util.h | 2 ++ util/ci/test.sh | 2 +- 12 files changed, 114 insertions(+), 48 deletions(-) create mode 100644 src/log.cpp create mode 100644 src/log.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 04b06ac..a3e9d99 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -179,6 +179,7 @@ target_sources(minetestmapper PRIVATE src/Image.cpp src/mapper.cpp src/util.cpp + src/log.cpp src/db-sqlite3.cpp $<$:src/db-postgresql.cpp> $<$:src/db-leveldb.cpp> diff --git a/minetestmapper.6 b/minetestmapper.6 index 4e20ff5..0da5fb4 100644 --- a/minetestmapper.6 +++ b/minetestmapper.6 @@ -12,13 +12,15 @@ See additional optional parameters below. generates an overview image of a Luanti map. This is a port of the original minetestmapper.py to C++, that is both faster and provides more functionality than the obsolete Python script. + .SH MANDATORY PARAMETERS .TP .BR \-i " " \fIworld_path\fR -Input world path. +Input world path .TP .BR \-o " " \fIoutput_image\fR -Path to output image. (only PNG supported currently) +Path to output image + .SH OPTIONAL PARAMETERS .TP .BR \-\-bgcolor " " \fIcolor\fR @@ -60,6 +62,10 @@ Don't draw shading on nodes .BR \-\-noemptyimage Don't output anything when the image would be empty. +.TP +.BR \-\-verbose +Enable verbose log output. + .TP .BR \-\-min-y " " \fInumber\fR 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. .SH MORE INFORMATION -Website: https://github.com/minetest/minetestmapper +Website: https://github.com/luanti-org/minetestmapper .SH MAN PAGE AUTHOR Daniel Moerner diff --git a/src/BlockDecoder.cpp b/src/BlockDecoder.cpp index b0fb41e..240af17 100644 --- a/src/BlockDecoder.cpp +++ b/src/BlockDecoder.cpp @@ -4,6 +4,7 @@ #include "BlockDecoder.h" #include "ZlibDecompressor.h" +#include "log.h" 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; NameMap::const_iterator it = m_nameMap.find(content); 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 it->second; diff --git a/src/PlayerAttributes.cpp b/src/PlayerAttributes.cpp index 43b5efd..0ce641e 100644 --- a/src/PlayerAttributes.cpp +++ b/src/PlayerAttributes.cpp @@ -7,6 +7,7 @@ #include "config.h" #include "PlayerAttributes.h" #include "util.h" +#include "log.h" #include "db-sqlite3.h" // SQLite3Base namespace { @@ -82,7 +83,7 @@ void FilesReader::read(PlayerAttributes::Players &dest) Player player; player.name = name; if (!parse_pos(position, player)) { - std::cerr << "Failed to parse position " << position << std::endl; + errorstream << "Failed to parse position '" << position << "'" << std::endl; continue; } diff --git a/src/TileGenerator.cpp b/src/TileGenerator.cpp index 0f814bc..d2fa610 100644 --- a/src/TileGenerator.cpp +++ b/src/TileGenerator.cpp @@ -17,6 +17,7 @@ #include "BlockDecoder.h" #include "Image.h" #include "util.h" +#include "log.h" #include "db-sqlite3.h" #if USE_POSTGRESQL @@ -346,7 +347,7 @@ void TileGenerator::parseColorsStream(std::istream &in) 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); if (items < 4) { - std::cerr << "Failed to parse color entry '" << line << "'" << std::endl; + errorstream << "Failed to parse color entry '" << line << "'" << std::endl; continue; } @@ -414,7 +415,7 @@ void TileGenerator::openDb(const std::string &input_path) } 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; } @@ -422,11 +423,9 @@ void TileGenerator::openDb(const std::string &input_path) if (m_exhaustiveSearch == EXH_AUTO) { size_t y_range = (m_yMax / 16 + 1) - (m_yMin / 16); size_t blocks = sat_mul(m_geomX2 - m_geomX, y_range, m_geomY2 - m_geomY); -#ifndef NDEBUG - std::cerr << "Heuristic parameters:" + verbosestream << "Heuristic parameters:" << " preferRangeQueries()=" << m_db->preferRangeQueries() << " y_range=" << y_range << " blocks=" << blocks << std::endl; -#endif if (m_db->preferRangeQueries()) m_exhaustiveSearch = EXH_NEVER; else if (blocks < 200000) @@ -437,7 +436,7 @@ void TileGenerator::openDb(const std::string &input_path) m_exhaustiveSearch = EXH_NEVER; } else if (m_exhaustiveSearch == EXH_FULL || m_exhaustiveSearch == EXH_Y) { 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 " "in worse performance." << std::endl; } @@ -486,10 +485,8 @@ void TileGenerator::loadBlocks() for (const auto &it : m_positions) count += it.second.size(); m_progressMax = count; -#ifndef NDEBUG - std::cerr << "Loaded " << count + verbosestream << "Loaded " << count << " 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; 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 << ")" << std::endl; } @@ -594,10 +591,8 @@ void TileGenerator::renderMap() postRenderRow(zPos); } } else if (m_exhaustiveSearch == EXH_Y) { -#ifndef NDEBUG - std::cerr << "Exhaustively searching height of " + verbosestream << "Exhaustively searching height of " << (yMax - yMin) << " blocks" << std::endl; -#endif std::vector positions; positions.reserve(yMax - yMin); for (auto it = m_positions.rbegin(); it != m_positions.rend(); ++it) { @@ -621,11 +616,9 @@ void TileGenerator::renderMap() } else if (m_exhaustiveSearch == EXH_FULL) { const size_t span_y = yMax - yMin; m_progressMax = (m_geomX2 - m_geomX) * span_y * (m_geomY2 - m_geomY); -#ifndef NDEBUG - std::cerr << "Exhaustively searching " + verbosestream << "Exhaustively searching " << (m_geomX2 - m_geomX) << "x" << span_y << "x" << (m_geomY2 - m_geomY) << " blocks" << std::endl; -#endif std::vector positions; positions.reserve(span_y); @@ -879,14 +872,15 @@ void TileGenerator::printUnknown() { if (m_unknownNodes.empty()) return; - std::cerr << "Unknown nodes:" << std::endl; + errorstream << "Unknown nodes:\n"; for (const auto &node : m_unknownNodes) - std::cerr << "\t" << node << std::endl; + errorstream << "\t" << node << '\n'; 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 " - "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) diff --git a/src/db-postgresql.cpp b/src/db-postgresql.cpp index 1836585..4f9c9d7 100644 --- a/src/db-postgresql.cpp +++ b/src/db-postgresql.cpp @@ -3,12 +3,12 @@ #include #include #include + #include "db-postgresql.h" #include "util.h" +#include "log.h" #include "types.h" -#define ARRLEN(x) (sizeof(x) / sizeof((x)[0])) - /* PostgreSQLBase */ PostgreSQLBase::~PostgreSQLBase() @@ -108,7 +108,7 @@ DBPostgreSQL::~DBPostgreSQL() try { checkResults(PQexec(db, "COMMIT;")); } catch (const std::exception& caught) { - std::cerr << "could not finalize: " << caught.what() << std::endl; + errorstream << "could not finalize: " << caught.what() << std::endl; } } diff --git a/src/db-sqlite3.cpp b/src/db-sqlite3.cpp index e824e29..18fa31e 100644 --- a/src/db-sqlite3.cpp +++ b/src/db-sqlite3.cpp @@ -3,7 +3,9 @@ #include #include #include + #include "db-sqlite3.h" +#include "log.h" #include "types.h" /* SQLite3Base */ @@ -14,7 +16,7 @@ SQLite3Base::~SQLite3Base() { if (db && sqlite3_close(db) != SQLITE_OK) { - std::cerr << "Error closing SQLite database: " + errorstream << "Error closing SQLite database: " << sqlite3_errmsg(db) << std::endl; } } @@ -66,9 +68,7 @@ DBSQLite3::DBSQLite3(const std::string &mapdir) // error right there. int result = prepare(stmt_get_block_pos, "SELECT x, y, z FROM blocks"); newFormat = result == SQLITE_OK; -#ifndef NDEBUG - std::cerr << "Detected " << (newFormat ? "new" : "old") << " SQLite schema" << std::endl; -#endif + verbosestream << "Detected " << (newFormat ? "new" : "old") << " SQLite schema" << std::endl; if (newFormat) { 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 * because it's bad for performance. But rather than silently breaking * do the right thing and load the blocks again. */ -#ifndef NDEBUG - std::cerr << "Warning: suboptimal access pattern for sqlite3 backend" << std::endl; -#endif + verbosestream << "suboptimal access pattern for sqlite3 backend?!" << std::endl; loadBlockCache(z); } // Swap lists to avoid copying contents diff --git a/src/log.cpp b/src/log.cpp new file mode 100644 index 0000000..aa4e585 --- /dev/null +++ b/src/log.cpp @@ -0,0 +1,16 @@ +#include + +#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; +} diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..bef8ffe --- /dev/null +++ b/src/log.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +// Forwards to an ostream, optionally +class StreamProxy { +public: + StreamProxy(std::ostream *os) : m_os(os) {} + + template + StreamProxy &operator<<(T &&arg) + { + if (m_os) + *m_os << std::forward(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); diff --git a/src/mapper.cpp b/src/mapper.cpp index 79fc2cd..877254a 100644 --- a/src/mapper.cpp +++ b/src/mapper.cpp @@ -10,6 +10,8 @@ #include #include "config.h" #include "TileGenerator.h" +#include "util.h" +#include "log.h" static void usage() { @@ -59,8 +61,7 @@ static void usage() static inline bool file_exists(const std::string &path) { - std::ifstream ifs(path); - return ifs.is_open(); + return file_exists(path.c_str()); } static inline int stoi(const char *s) @@ -78,23 +79,24 @@ static std::string search_colors(const std::string &worldpath) #ifndef _WIN32 char *home = std::getenv("HOME"); - if (home) { + if (home && home[0]) { std::string check = std::string(home) + "/.minetest/colors.txt"; if (file_exists(check)) return check; } #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")) 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"; } int main(int argc, char *argv[]) { + const char *short_options = "hi:o:v"; const static struct option long_options[] = { {"help", no_argument, 0, 'h'}, @@ -120,9 +122,12 @@ int main(int argc, char *argv[]) {"noemptyimage", no_argument, 0, 'n'}, {"exhaustive", required_argument, 0, 'j'}, {"dumpblock", required_argument, 0, 'k'}, + {"verbose", no_argument, 0, 'v'}, {0, 0, 0, 0} }; + configure_log_streams(false); + std::string input; std::string output; std::string colors; @@ -132,7 +137,7 @@ int main(int argc, char *argv[]) TileGenerator generator; while (1) { 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) break; // done @@ -192,7 +197,7 @@ int main(int argc, char *argv[]) geometry >> x >> c >> y >> w >> h; if (geometry.fail() || c != ':' || w < 1 || h < 1) { usage(); - exit(1); + return 1; } generator.setGeometry(x, y, w, h); } @@ -220,7 +225,7 @@ int main(int argc, char *argv[]) generator.setDontWriteEmpty(true); break; case 'j': { - int mode = EXH_AUTO;; + int mode = EXH_AUTO; if (!strcmp(optarg, "never")) mode = EXH_NEVER; else if (!strcmp(optarg, "y")) @@ -236,12 +241,15 @@ int main(int argc, char *argv[]) iss >> dumpblock.x >> c >> dumpblock.y >> c2 >> dumpblock.z; if (iss.fail() || c != ',' || c2 != ',') { usage(); - exit(1); + return 1; } break; } + case 'v': + configure_log_streams(true); + break; default: - exit(1); + return 1; } } @@ -252,7 +260,6 @@ int main(int argc, char *argv[]) } try { - if (onlyPrintExtent) { generator.printGeometry(input); return 0; @@ -267,7 +274,7 @@ int main(int argc, char *argv[]) generator.generate(input, output); } catch (const std::exception &e) { - std::cerr << "Exception: " << e.what() << std::endl; + errorstream << "Exception: " << e.what() << std::endl; return 1; } return 0; diff --git a/src/util.h b/src/util.h index 76d429e..50173ef 100644 --- a/src/util.h +++ b/src/util.h @@ -3,6 +3,8 @@ #include #include +#define ARRLEN(x) (sizeof(x) / sizeof((x)[0])) + template static inline T mymax(T a, T b) { diff --git a/util/ci/test.sh b/util/ci/test.sh index 1ab290f..0fbc820 100755 --- a/util/ci/test.sh +++ b/util/ci/test.sh @@ -31,7 +31,7 @@ checkmap () { local c=$1 shift 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 echo "Output not generated!" exit 1