diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d28961..d23851b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,26 @@ if(ENABLE_LEVELDB) endif(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR) endif(ENABLE_LEVELDB) +# Find redis +set(USE_REDIS 0) + +OPTION(ENABLE_REDIS "Enable redis backend") + +if(ENABLE_REDIS) + find_library(REDIS_LIBRARY hiredis) + find_path(REDIS_INCLUDE_DIR hiredis.h PATH_SUFFIXES hiredis) + message (STATUS "redis library: ${REDIS_LIBRARY}") + message (STATUS "redis headers: ${REDIS_INCLUDE_DIR}") + if(REDIS_LIBRARY AND REDIS_INCLUDE_DIR) + set(USE_REDIS 1) + message(STATUS "redis backend enabled") + include_directories(${REDIS_INCLUDE_DIR}) + else(REDIS_LIBRARY AND REDIS_INCLUDE_DIR) + set(USE_REDIS 0) + message(STATUS "redis not found!") + endif(REDIS_LIBRARY AND REDIS_INCLUDE_DIR) +endif(ENABLE_REDIS) + include_directories( "${PROJECT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" @@ -88,6 +108,10 @@ if(USE_LEVELDB) set(mapper_SRCS ${mapper_SRCS} db-leveldb.cpp) endif(USE_LEVELDB) +if(USE_REDIS) + set(mapper_SRCS ${mapper_SRCS} db-redis.cpp) +endif(USE_REDIS) + add_executable(minetestmapper ${mapper_SRCS} ) @@ -96,6 +120,7 @@ target_link_libraries( minetestmapper ${SQLITE3_LIBRARY} ${LEVELDB_LIBRARY} + ${REDIS_LIBRARY} ${LIBGD_LIBRARY} ${ZLIB_LIBRARY} ) diff --git a/README.rst b/README.rst index dab6687..963d834 100644 --- a/README.rst +++ b/README.rst @@ -9,6 +9,7 @@ Requirements * libgd * sqlite3 * leveldb (optional, set ENABLE_LEVELDB=1 in CMake to enable leveldb support) +* hiredis (optional, set ENABLE_REDIS=1 in CMake to enable redis support) Compilation ----------- @@ -66,7 +67,7 @@ max-y: Don't draw nodes above this y value, `--max-y 75` backend: - Use specific map backend, supported: sqlite3, leveldb, `--backend leveldb` + Use specific map backend, supported: sqlite3, leveldb, redis, `--backend leveldb` geometry: Limit area to specific geometry, `--geometry -800:-800+1600+1600` diff --git a/TileGenerator.cpp b/TileGenerator.cpp index d346a81..27dc8c9 100644 --- a/TileGenerator.cpp +++ b/TileGenerator.cpp @@ -17,6 +17,9 @@ #if USE_LEVELDB #include "db-leveldb.h" #endif +#if USE_REDIS +#include "db-redis.h" +#endif using namespace std; @@ -298,6 +301,10 @@ void TileGenerator::openDb(const std::string &input) #if USE_LEVELDB else if(m_backend == "leveldb") m_db = new DBLevelDB(input); +#endif +#if USE_REDIS + else if(m_backend == "redis") + m_db = new DBRedis(input); #endif else throw std::runtime_error(((std::string) "Unknown map backend: ") + m_backend); diff --git a/cmake_config.h.in b/cmake_config.h.in index 6d1699e..fbe6387 100644 --- a/cmake_config.h.in +++ b/cmake_config.h.in @@ -4,6 +4,7 @@ #define CMAKE_CONFIG_H #define USE_LEVELDB @USE_LEVELDB@ +#define USE_REDIS @USE_REDIS@ #endif diff --git a/db-redis.cpp b/db-redis.cpp new file mode 100644 index 0000000..5923fd2 --- /dev/null +++ b/db-redis.cpp @@ -0,0 +1,165 @@ +#include +#include +#include +#include "db-redis.h" +#include "types.h" + +static inline int64_t stoi64(const std::string &s) +{ + std::stringstream tmp(s); + int64_t t; + tmp >> t; + return t; +} + + +static inline std::string i64tos(int64_t i) +{ + std::ostringstream os; + os << i; + return os.str(); +} + +inline std::string trim(const std::string &s) +{ + size_t front = 0; + while(s[front] == ' ' || + s[front] == '\t' || + s[front] == '\r' || + s[front] == '\n' + ) + ++front; + + size_t back = s.size(); + while(back > front && + (s[back-1] == ' ' || + s[back-1] == '\t' || + s[back-1] == '\r' || + s[back-1] == '\n' + ) + ) + --back; + + return s.substr(front, back - front); +} + +#define EOFCHECK() \ + if(is.eof()) \ + throw std::runtime_error("setting not found"); + +std::string get_setting(std::string name, std::istream &is) +{ + char c; + char s[256]; + std::string nm, value; + + next: + while((c = is.get()) == ' ' || c == '\t' || c == '\r' || c == '\n') + ; + EOFCHECK(); + if(c == '#') // Ignore comments + is.ignore(0xffff, '\n'); + EOFCHECK(); + s[0] = c; // The current char belongs to the name too + is.get(&s[1], 255, '='); + is.ignore(1); // Jump over the = + EOFCHECK(); + nm = trim(std::string(s)); + is.get(s, 256, '\n'); + value = trim(std::string(s)); + if(name == nm) + return value; + else + goto next; +} + +#undef EOFCHECK + +std::string get_setting_default(std::string name, std::istream &is, const std::string def) +{ + try { + return get_setting(name, is); + } catch(std::runtime_error e) { + return def; + } +} + +DBRedis::DBRedis(const std::string &mapdir) +{ + std::ifstream ifs((mapdir + "/world.mt").c_str()); + if(!ifs.good()) + throw std::runtime_error("Failed to read world.mt"); + std::string tmp; + try { + tmp = get_setting("redis_address", ifs); + ifs.seekg(0); + hash = get_setting("redis_hash", ifs); + ifs.seekg(0); + } catch(std::runtime_error e) { + throw std::runtime_error("Set redis_address and redis_hash in world.mt to use the redis backend"); + } + const char *addr = tmp.c_str(); + int port = stoi64(get_setting_default("redis_port", ifs, "6379")); + ctx = redisConnect(addr, port); + if(!ctx) + throw std::runtime_error("Cannot allocate redis context"); + else if(ctx->err) { + std::string err = std::string("Connection error: ") + ctx->errstr; + redisFree(ctx); + throw std::runtime_error(err); + } + + loadPosCache(); +} + + +DBRedis::~DBRedis() +{ + redisFree(ctx); +} + + +std::vector DBRedis::getBlockPos() +{ + return posCache; +} + + +void DBRedis::loadPosCache() +{ + redisReply *reply; + reply = (redisReply*) redisCommand(ctx, "HKEYS %s", hash.c_str()); + if(!reply) + throw std::runtime_error(std::string("redis command 'HKEYS %s' failed: ") + ctx->errstr); + if(reply->type != REDIS_REPLY_ARRAY) + throw std::runtime_error("Failed to get keys from database"); + for(size_t i = 0; i < reply->elements; i++) { + if(!reply->element[i]->type == REDIS_REPLY_STRING) + throw std::runtime_error("Got errornous response to 'HKEYS %s' command"); + posCache.push_back(decodeBlockPos(stoi64(reply->element[i]->str))); + } + freeReplyObject(reply); +} + + +void DBRedis::getBlocksOnZ(std::map &blocks, int16_t zPos) +{ + redisReply *reply; + std::string tmp; + + for (std::vector::iterator it = posCache.begin(); it != posCache.end(); ++it) { + if (it->z != zPos) { + continue; + } + tmp = i64tos(encodeBlockPos(*it)); + reply = (redisReply*) redisCommand(ctx, "HGET %s %s", hash.c_str(), tmp.c_str()); + if(!reply) + throw std::runtime_error(std::string("redis command 'HGET %s %s' failed: ") + ctx->errstr); + if (reply->type == REDIS_REPLY_STRING && reply->len != 0) { + Block b(*it, ustring((const unsigned char *) reply->str, reply->len)); + blocks[b.first.x].push_back(b); + } + freeReplyObject(reply); + } +} + diff --git a/db-redis.h b/db-redis.h new file mode 100644 index 0000000..2663a9c --- /dev/null +++ b/db-redis.h @@ -0,0 +1,22 @@ +#ifndef DB_REDIS_HEADER +#define DB_REDIS_HEADER + +#include "db.h" +#include + +class DBRedis : public DB { +public: + DBRedis(const std::string &mapdir); + virtual std::vector getBlockPos(); + virtual void getBlocksOnZ(std::map &blocks, int16_t zPos); + ~DBRedis(); +private: + void loadPosCache(); + + std::vector posCache; + + redisContext *ctx; + std::string hash; +}; + +#endif // DB_REDIS_HEADER diff --git a/mapper.cpp b/mapper.cpp index 0382ff4..a661511 100644 --- a/mapper.cpp +++ b/mapper.cpp @@ -34,7 +34,7 @@ void usage() " --noshading\n" " --min-y \n" " --max-y \n" - " --backend \n" + " --backend \n" " --geometry x:y+w+h\n" "Color format: '#000000'\n"; std::cout << usage_text;