Switch MapBlock compression to zstd (#10788)

* Add zstd support.
* Rearrange serialization order
* Compress entire mapblock

Co-authored-by: sfan5 <sfan5@live.de>
This commit is contained in:
lhofhansl 2021-08-31 17:32:31 -07:00 committed by GitHub
parent beac4a2c98
commit d1624a5521
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 494 additions and 152 deletions

@ -227,7 +227,7 @@ jobs:
env: env:
VCPKG_VERSION: 0bf3923f9fab4001c00f0f429682a0853b5749e0 VCPKG_VERSION: 0bf3923f9fab4001c00f0f429682a0853b5749e0
# 2020.11 # 2020.11
vcpkg_packages: irrlicht zlib curl[winssl] openal-soft libvorbis libogg sqlite3 freetype luajit vcpkg_packages: irrlicht zlib zstd curl[winssl] openal-soft libvorbis libogg sqlite3 freetype luajit
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:

@ -34,7 +34,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Install deps - name: Install deps
run: | run: |
pkgs=(cmake freetype gettext gmp hiredis jpeg jsoncpp leveldb libogg libpng libvorbis luajit) pkgs=(cmake freetype gettext gmp hiredis jpeg jsoncpp leveldb libogg libpng libvorbis luajit zstd)
brew update brew update
brew install ${pkgs[@]} brew install ${pkgs[@]}
brew unlink $(brew ls --formula) brew unlink $(brew ls --formula)

@ -17,7 +17,7 @@ variables:
stage: build stage: build
before_script: before_script:
- apt-get update - apt-get update
- apt-get -y install build-essential git cmake libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libleveldb-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev - apt-get -y install build-essential git cmake libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libleveldb-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev
script: script:
- git clone https://github.com/minetest/irrlicht -b $IRRLICHT_TAG lib/irrlichtmt - git clone https://github.com/minetest/irrlicht -b $IRRLICHT_TAG lib/irrlichtmt
- mkdir cmakebuild - mkdir cmakebuild
@ -187,7 +187,7 @@ build:fedora-28:
extends: .build_template extends: .build_template
image: fedora:28 image: fedora:28
before_script: before_script:
- dnf -y install make git gcc gcc-c++ kernel-devel cmake libjpeg-devel libpng-devel libcurl-devel openal-soft-devel libvorbis-devel libXxf86vm-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel spatialindex-devel - dnf -y install make git gcc gcc-c++ kernel-devel cmake libjpeg-devel libpng-devel libcurl-devel openal-soft-devel libvorbis-devel libXxf86vm-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel spatialindex-devel libzstd-devel
## ##
## MinGW for Windows ## MinGW for Windows

@ -20,7 +20,7 @@ COPY textures /usr/src/minetest/textures
WORKDIR /usr/src/minetest WORKDIR /usr/src/minetest
RUN apk add --no-cache git build-base cmake sqlite-dev curl-dev zlib-dev \ RUN apk add --no-cache git build-base cmake sqlite-dev curl-dev zlib-dev zstd-dev \
gmp-dev jsoncpp-dev postgresql-dev ninja luajit-dev ca-certificates && \ gmp-dev jsoncpp-dev postgresql-dev ninja luajit-dev ca-certificates && \
git clone --depth=1 -b ${MINETEST_GAME_VERSION} https://github.com/minetest/minetest_game.git ./games/minetest_game && \ git clone --depth=1 -b ${MINETEST_GAME_VERSION} https://github.com/minetest/minetest_game.git ./games/minetest_game && \
rm -fr ./games/minetest_game/.git rm -fr ./games/minetest_game/.git

@ -57,6 +57,11 @@ LOCAL_MODULE := Vorbis
LOCAL_SRC_FILES := deps/Android/Vorbis/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libvorbis.a LOCAL_SRC_FILES := deps/Android/Vorbis/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libvorbis.a
include $(PREBUILT_STATIC_LIBRARY) include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := Zstd
LOCAL_SRC_FILES := deps/Android/Zstd/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libzstd.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_MODULE := Minetest LOCAL_MODULE := Minetest
@ -101,7 +106,8 @@ LOCAL_C_INCLUDES := \
deps/Android/LuaJIT/src \ deps/Android/LuaJIT/src \
deps/Android/OpenAL-Soft/include \ deps/Android/OpenAL-Soft/include \
deps/Android/sqlite \ deps/Android/sqlite \
deps/Android/Vorbis/include deps/Android/Vorbis/include \
deps/Android/Zstd/include
LOCAL_SRC_FILES := \ LOCAL_SRC_FILES := \
$(wildcard ../../src/client/*.cpp) \ $(wildcard ../../src/client/*.cpp) \
@ -201,7 +207,7 @@ LOCAL_SRC_FILES += \
# SQLite3 # SQLite3
LOCAL_SRC_FILES += deps/Android/sqlite/sqlite3.c LOCAL_SRC_FILES += deps/Android/sqlite/sqlite3.c
LOCAL_STATIC_LIBRARIES += Curl Freetype Irrlicht OpenAL mbedTLS mbedx509 mbedcrypto Vorbis LuaJIT GetText android_native_app_glue $(PROFILER_LIBS) #LevelDB LOCAL_STATIC_LIBRARIES += Curl Freetype Irrlicht OpenAL mbedTLS mbedx509 mbedcrypto Vorbis LuaJIT GetText Zstd android_native_app_glue $(PROFILER_LIBS) #LevelDB
LOCAL_LDLIBS := -lEGL -lGLESv1_CM -lGLESv2 -landroid -lOpenSLES LOCAL_LDLIBS := -lEGL -lGLESv1_CM -lGLESv2 -landroid -lOpenSLES

@ -1100,11 +1100,10 @@ full_block_send_enable_min_time_from_building (Delay in sending blocks after bui
# client number. # client number.
max_packets_per_iteration (Max. packets per iteration) int 1024 max_packets_per_iteration (Max. packets per iteration) int 1024
# ZLib compression level to use when sending mapblocks to the client. # Compression level to use when sending mapblocks to the client.
# -1 - Zlib's default compression level # -1 - use default compression level
# 0 - no compresson, fastest # 0 - least compresson, fastest
# 9 - best compression, slowest # 9 - best compression, slowest
# (levels 1-3 use Zlib's "fast" method, 4-9 use the normal method)
map_compression_level_net (Map Compression Level for Network Transfer) int -1 -1 9 map_compression_level_net (Map Compression Level for Network Transfer) int -1 -1 9
[*Game] [*Game]
@ -1303,12 +1302,11 @@ max_objects_per_block (Maximum objects per block) int 64
# See https://www.sqlite.org/pragma.html#pragma_synchronous # See https://www.sqlite.org/pragma.html#pragma_synchronous
sqlite_synchronous (Synchronous SQLite) enum 2 0,1,2 sqlite_synchronous (Synchronous SQLite) enum 2 0,1,2
# ZLib compression level to use when saving mapblocks to disk. # Compression level to use when saving mapblocks to disk.
# -1 - Zlib's default compression level # -1 - use default compression level
# 0 - no compresson, fastest # 0 - least compresson, fastest
# 9 - best compression, slowest # 9 - best compression, slowest
# (levels 1-3 use Zlib's "fast" method, 4-9 use the normal method) map_compression_level_disk (Map Compression Level for Disk Storage) int -1 -1 9
map_compression_level_disk (Map Compression Level for Disk Storage) int 3 -1 9
# Length of a server tick and the interval at which objects are generally updated over # Length of a server tick and the interval at which objects are generally updated over
# network. # network.

@ -0,0 +1,9 @@
mark_as_advanced(ZSTD_LIBRARY ZSTD_INCLUDE_DIR)
find_path(ZSTD_INCLUDE_DIR NAMES zstd.h)
find_library(ZSTD_LIBRARY NAMES zstd)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Zstd DEFAULT_MSG ZSTD_LIBRARY ZSTD_INCLUDE_DIR)

@ -1,5 +1,5 @@
============================= =============================
Minetest World Format 22...27 Minetest World Format 22...29
============================= =============================
This applies to a world format carrying the block serialization version This applies to a world format carrying the block serialization version
@ -8,6 +8,7 @@ This applies to a world format carrying the block serialization version
- 0.4.0 (23) - 0.4.0 (23)
- 24 was never released as stable and existed for ~2 days - 24 was never released as stable and existed for ~2 days
- 27 was added in 0.4.15-dev - 27 was added in 0.4.15-dev
- 29 was added in 5.5.0-dev
The block serialization version does not fully specify every aspect of this The block serialization version does not fully specify every aspect of this
format; if compliance with this format is to be checked, it needs to be format; if compliance with this format is to be checked, it needs to be
@ -281,6 +282,8 @@ MapBlock serialization format
NOTE: Byte order is MSB first (big-endian). NOTE: Byte order is MSB first (big-endian).
NOTE: Zlib data is in such a format that Python's zlib at least can NOTE: Zlib data is in such a format that Python's zlib at least can
directly decompress. directly decompress.
NOTE: Since version 29 zstd is used instead of zlib. In addition the entire
block is first serialized and then compressed (except the version byte).
u8 version u8 version
- map format version number, see serialisation.h for the latest number - map format version number, see serialisation.h for the latest number
@ -324,6 +327,20 @@ u16 lighting_complete
then Minetest will correct lighting in the day light bank when then Minetest will correct lighting in the day light bank when
the block at (1, 0, 0) is also loaded. the block at (1, 0, 0) is also loaded.
if map format version >= 29:
u32 timestamp
- Timestamp when last saved, as seconds from starting the game.
- 0xffffffff = invalid/unknown timestamp, nothing should be done with the time
difference when loaded
u16 num_name_id_mappings
foreach num_name_id_mappings
u16 id
u16 name_len
u8[name_len] name
if map format version < 29:
-- Nothing right here, timpstamp and node id mappings are serialized later
u8 content_width u8 content_width
- Number of bytes in the content (param0) fields of nodes - Number of bytes in the content (param0) fields of nodes
if map format version <= 23: if map format version <= 23:
@ -335,7 +352,7 @@ u8 params_width
- Number of bytes used for parameters per node - Number of bytes used for parameters per node
- Always 2 - Always 2
zlib-compressed node data: node data (zlib-compressed if version < 29):
if content_width == 1: if content_width == 1:
- content: - content:
u8[4096]: param0 fields u8[4096]: param0 fields
@ -348,31 +365,31 @@ if content_width == 2:
u8[4096]: param2 fields u8[4096]: param2 fields
- The location of a node in each of those arrays is (z*16*16 + y*16 + x). - The location of a node in each of those arrays is (z*16*16 + y*16 + x).
zlib-compressed node metadata list node metadata list (zlib-compressed if version < 29):
- content: - content:
if map format version <= 22: if map format version <= 22:
u16 version (=1) u16 version (=1)
u16 count of metadata u16 count of metadata
foreach count: foreach count:
u16 position (p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X) u16 position (p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X)
u16 type_id u16 type_id
u16 content_size u16 content_size
u8[content_size] content of metadata. Format depends on type_id, see below. u8[content_size] content of metadata. Format depends on type_id, see below.
if map format version >= 23: if map format version >= 23:
u8 version -- Note: type was u16 for map format version <= 22 u8 version -- Note: type was u16 for map format version <= 22
-- = 1 for map format version < 28 -- = 1 for map format version < 28
-- = 2 since map format version 28 -- = 2 since map format version 28
u16 count of metadata u16 count of metadata
foreach count: foreach count:
u16 position (p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X) u16 position (p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X)
u32 num_vars u32 num_vars
foreach num_vars: foreach num_vars:
u16 key_len u16 key_len
u8[key_len] key u8[key_len] key
u32 val_len u32 val_len
u8[val_len] value u8[val_len] value
u8 is_private -- only for version >= 2. 0 = not private, 1 = private u8 is_private -- only for version >= 2. 0 = not private, 1 = private
serialized inventory serialized inventory
- Node timers - Node timers
if map format version == 23: if map format version == 23:
@ -403,20 +420,18 @@ foreach static_object_count:
u16 data_size u16 data_size
u8[data_size] data u8[data_size] data
u32 timestamp if map format version < 29:
- Timestamp when last saved, as seconds from starting the game. u32 timestamp
- 0xffffffff = invalid/unknown timestamp, nothing should be done with the time - Same meaning as the timestamp further up
difference when loaded
u8 name-id-mapping version u8 name-id-mapping version
- Always 0 - Always 0
u16 num_name_id_mappings u16 num_name_id_mappings
foreach num_name_id_mappings
foreach num_name_id_mappings u16 id
u16 id u16 name_len
u16 name_len u8[name_len] name
u8[name_len] name
- Node timers - Node timers
if map format version == 25: if map format version == 25:

@ -3,7 +3,7 @@ Priority: extra
Standards-Version: 3.6.2 Standards-Version: 3.6.2
Package: minetest-staging Package: minetest-staging
Version: 5.4.0-DATEPLACEHOLDER Version: 5.4.0-DATEPLACEHOLDER
Depends: libc6, libcurl3-gnutls, libfreetype6, libgl1, JPEG_PLACEHOLDER, JSONCPP_PLACEHOLDER, LEVELDB_PLACEHOLDER, libopenal1, libpng16-16, libsqlite3-0, libstdc++6, libvorbisfile3, libx11-6, libxxf86vm1, zlib1g Depends: libc6, libcurl3-gnutls, libfreetype6, libgl1, JPEG_PLACEHOLDER, JSONCPP_PLACEHOLDER, LEVELDB_PLACEHOLDER, libopenal1, libpng16-16, libsqlite3-0, libstdc++6, libvorbisfile3, libx11-6, libxxf86vm1, libzstd1, zlib1g
Maintainer: Loic Blot <loic.blot@unix-experience.fr> Maintainer: Loic Blot <loic.blot@unix-experience.fr>
Homepage: https://www.minetest.net/ Homepage: https://www.minetest.net/
Vcs-Git: https://github.com/minetest/minetest.git Vcs-Git: https://github.com/minetest/minetest.git

@ -271,9 +271,13 @@ if(WIN32)
find_path(ZLIB_INCLUDE_DIR "zlib.h" DOC "Zlib include directory") find_path(ZLIB_INCLUDE_DIR "zlib.h" DOC "Zlib include directory")
find_library(ZLIB_LIBRARIES "zlib" DOC "Path to zlib library") find_library(ZLIB_LIBRARIES "zlib" DOC "Path to zlib library")
find_path(ZSTD_INCLUDE_DIR "zstd.h" DOC "Zstd include directory")
find_library(ZSTD_LIBRARY "zstd" DOC "Path to zstd library")
# Dll's are automatically copied to the output directory by vcpkg when VCPKG_APPLOCAL_DEPS=ON # Dll's are automatically copied to the output directory by vcpkg when VCPKG_APPLOCAL_DEPS=ON
if(NOT VCPKG_APPLOCAL_DEPS) if(NOT VCPKG_APPLOCAL_DEPS)
find_file(ZLIB_DLL NAMES "zlib.dll" "zlib1.dll" DOC "Path to zlib.dll for installation (optional)") find_file(ZLIB_DLL NAMES "zlib.dll" "zlib1.dll" DOC "Path to zlib.dll for installation (optional)")
find_file(ZSTD_DLL NAMES "zstd.dll" DOC "Path to zstd.dll for installation (optional)")
if(ENABLE_SOUND) if(ENABLE_SOUND)
set(OPENAL_DLL "" CACHE FILEPATH "Path to OpenAL32.dll for installation (optional)") set(OPENAL_DLL "" CACHE FILEPATH "Path to OpenAL32.dll for installation (optional)")
set(OGG_DLL "" CACHE FILEPATH "Path to libogg.dll for installation (optional)") set(OGG_DLL "" CACHE FILEPATH "Path to libogg.dll for installation (optional)")
@ -296,6 +300,7 @@ else()
endif() endif()
find_package(ZLIB REQUIRED) find_package(ZLIB REQUIRED)
find_package(Zstd REQUIRED)
set(PLATFORM_LIBS -lpthread ${CMAKE_DL_LIBS}) set(PLATFORM_LIBS -lpthread ${CMAKE_DL_LIBS})
if(APPLE) if(APPLE)
set(PLATFORM_LIBS "-framework CoreFoundation" ${PLATFORM_LIBS}) set(PLATFORM_LIBS "-framework CoreFoundation" ${PLATFORM_LIBS})
@ -486,6 +491,7 @@ include_directories(
${PROJECT_BINARY_DIR} ${PROJECT_BINARY_DIR}
${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}
${ZLIB_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR}
${ZSTD_INCLUDE_DIR}
${SOUND_INCLUDE_DIRS} ${SOUND_INCLUDE_DIRS}
${SQLITE3_INCLUDE_DIR} ${SQLITE3_INCLUDE_DIR}
${LUA_INCLUDE_DIR} ${LUA_INCLUDE_DIR}
@ -521,6 +527,7 @@ if(BUILD_CLIENT)
${PROJECT_NAME} ${PROJECT_NAME}
${ZLIB_LIBRARIES} ${ZLIB_LIBRARIES}
IrrlichtMt::IrrlichtMt IrrlichtMt::IrrlichtMt
${ZSTD_LIBRARY}
${X11_LIBRARIES} ${X11_LIBRARIES}
${SOUND_LIBRARIES} ${SOUND_LIBRARIES}
${SQLITE3_LIBRARY} ${SQLITE3_LIBRARY}
@ -605,6 +612,7 @@ if(BUILD_SERVER)
target_link_libraries( target_link_libraries(
${PROJECT_NAME}server ${PROJECT_NAME}server
${ZLIB_LIBRARIES} ${ZLIB_LIBRARIES}
${ZSTD_LIBRARY}
${SQLITE3_LIBRARY} ${SQLITE3_LIBRARY}
${JSON_LIBRARY} ${JSON_LIBRARY}
${LUA_LIBRARY} ${LUA_LIBRARY}
@ -821,6 +829,9 @@ if(WIN32)
if(ZLIB_DLL) if(ZLIB_DLL)
install(FILES ${ZLIB_DLL} DESTINATION ${BINDIR}) install(FILES ${ZLIB_DLL} DESTINATION ${BINDIR})
endif() endif()
if(ZSTD_DLL)
install(FILES ${ZSTD_DLL} DESTINATION ${BINDIR})
endif()
if(BUILD_CLIENT AND FREETYPE_DLL) if(BUILD_CLIENT AND FREETYPE_DLL)
install(FILES ${FREETYPE_DLL} DESTINATION ${BINDIR}) install(FILES ${FREETYPE_DLL} DESTINATION ${BINDIR})
endif() endif()

@ -398,7 +398,7 @@ void set_default_settings()
settings->setDefault("chat_message_limit_per_10sec", "8.0"); settings->setDefault("chat_message_limit_per_10sec", "8.0");
settings->setDefault("chat_message_limit_trigger_kick", "50"); settings->setDefault("chat_message_limit_trigger_kick", "50");
settings->setDefault("sqlite_synchronous", "2"); settings->setDefault("sqlite_synchronous", "2");
settings->setDefault("map_compression_level_disk", "3"); settings->setDefault("map_compression_level_disk", "-1");
settings->setDefault("map_compression_level_net", "-1"); settings->setDefault("map_compression_level_net", "-1");
settings->setDefault("full_block_send_enable_min_time_from_building", "2.0"); settings->setDefault("full_block_send_enable_min_time_from_building", "2.0");
settings->setDefault("dedicated_server_step", "0.09"); settings->setDefault("dedicated_server_step", "0.09");
@ -484,7 +484,7 @@ void set_default_settings()
settings->setDefault("max_objects_per_block", "20"); settings->setDefault("max_objects_per_block", "20");
settings->setDefault("sqlite_synchronous", "1"); settings->setDefault("sqlite_synchronous", "1");
settings->setDefault("map_compression_level_disk", "-1"); settings->setDefault("map_compression_level_disk", "-1");
settings->setDefault("map_compression_level_net", "3"); settings->setDefault("map_compression_level_net", "-1");
settings->setDefault("server_map_save_interval", "15"); settings->setDefault("server_map_save_interval", "15");
settings->setDefault("client_mapblock_limit", "1000"); settings->setDefault("client_mapblock_limit", "1000");
settings->setDefault("active_block_range", "2"); settings->setDefault("active_block_range", "2");

@ -38,6 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "player.h" #include "player.h"
#include "porting.h" #include "porting.h"
#include "network/socket.h" #include "network/socket.h"
#include "mapblock.h"
#if USE_CURSES #if USE_CURSES
#include "terminal_chat_console.h" #include "terminal_chat_console.h"
#endif #endif
@ -111,6 +112,7 @@ static bool determine_subgame(GameParams *game_params);
static bool run_dedicated_server(const GameParams &game_params, const Settings &cmd_args); static bool run_dedicated_server(const GameParams &game_params, const Settings &cmd_args);
static bool migrate_map_database(const GameParams &game_params, const Settings &cmd_args); static bool migrate_map_database(const GameParams &game_params, const Settings &cmd_args);
static bool recompress_map_database(const GameParams &game_params, const Settings &cmd_args, const Address &addr);
/**********************************************************************/ /**********************************************************************/
@ -302,6 +304,8 @@ static void set_allowed_options(OptionList *allowed_options)
_("Migrate from current auth backend to another (Only works when using minetestserver or with --server)")))); _("Migrate from current auth backend to another (Only works when using minetestserver or with --server)"))));
allowed_options->insert(std::make_pair("terminal", ValueSpec(VALUETYPE_FLAG, allowed_options->insert(std::make_pair("terminal", ValueSpec(VALUETYPE_FLAG,
_("Feature an interactive terminal (Only works when using minetestserver or with --server)")))); _("Feature an interactive terminal (Only works when using minetestserver or with --server)"))));
allowed_options->insert(std::make_pair("recompress", ValueSpec(VALUETYPE_FLAG,
_("Recompress the blocks of the given map database."))));
#ifndef SERVER #ifndef SERVER
allowed_options->insert(std::make_pair("speedtests", ValueSpec(VALUETYPE_FLAG, allowed_options->insert(std::make_pair("speedtests", ValueSpec(VALUETYPE_FLAG,
_("Run speed tests")))); _("Run speed tests"))));
@ -875,7 +879,7 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings &
return false; return false;
} }
// Database migration // Database migration/compression
if (cmd_args.exists("migrate")) if (cmd_args.exists("migrate"))
return migrate_map_database(game_params, cmd_args); return migrate_map_database(game_params, cmd_args);
@ -885,6 +889,9 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings &
if (cmd_args.exists("migrate-auth")) if (cmd_args.exists("migrate-auth"))
return ServerEnvironment::migrateAuthDatabase(game_params, cmd_args); return ServerEnvironment::migrateAuthDatabase(game_params, cmd_args);
if (cmd_args.getFlag("recompress"))
return recompress_map_database(game_params, cmd_args, bind_addr);
if (cmd_args.exists("terminal")) { if (cmd_args.exists("terminal")) {
#if USE_CURSES #if USE_CURSES
bool name_ok = true; bool name_ok = true;
@ -1034,3 +1041,67 @@ static bool migrate_map_database(const GameParams &game_params, const Settings &
return true; return true;
} }
static bool recompress_map_database(const GameParams &game_params, const Settings &cmd_args, const Address &addr)
{
Settings world_mt;
const std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
if (!world_mt.readConfigFile(world_mt_path.c_str())) {
errorstream << "Cannot read world.mt at " << world_mt_path << std::endl;
return false;
}
const std::string &backend = world_mt.get("backend");
Server server(game_params.world_path, game_params.game_spec, false, addr, false);
MapDatabase *db = ServerMap::createDatabase(backend, game_params.world_path, world_mt);
u32 count = 0;
u64 last_update_time = 0;
bool &kill = *porting::signal_handler_killstatus();
const u8 serialize_as_ver = SER_FMT_VER_HIGHEST_WRITE;
// This is ok because the server doesn't actually run
std::vector<v3s16> blocks;
db->listAllLoadableBlocks(blocks);
db->beginSave();
std::istringstream iss(std::ios_base::binary);
std::ostringstream oss(std::ios_base::binary);
for (auto it = blocks.begin(); it != blocks.end(); ++it) {
if (kill) return false;
std::string data;
db->loadBlock(*it, &data);
if (data.empty()) {
errorstream << "Failed to load block " << PP(*it) << std::endl;
return false;
}
iss.str(data);
iss.clear();
MapBlock mb(nullptr, v3s16(0,0,0), &server);
u8 ver = readU8(iss);
mb.deSerialize(iss, ver, true);
oss.str("");
oss.clear();
writeU8(oss, serialize_as_ver);
mb.serialize(oss, serialize_as_ver, true, -1);
db->saveBlock(*it, oss.str());
count++;
if (count % 0xFF == 0 && porting::getTimeS() - last_update_time >= 1) {
std::cerr << " Recompressed " << count << " blocks, "
<< (100.0f * count / blocks.size()) << "% completed.\r";
db->endSave();
db->beginSave();
last_update_time = porting::getTimeS();
}
}
std::cerr << std::endl;
db->endSave();
actionstream << "Done, " << count << " blocks were recompressed." << std::endl;
return true;
}

@ -355,7 +355,7 @@ static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
} }
} }
void MapBlock::serialize(std::ostream &os, u8 version, bool disk, int compression_level) void MapBlock::serialize(std::ostream &os_compressed, u8 version, bool disk, int compression_level)
{ {
if(!ser_ver_supported(version)) if(!ser_ver_supported(version))
throw VersionMismatchException("ERROR: MapBlock format not supported"); throw VersionMismatchException("ERROR: MapBlock format not supported");
@ -365,6 +365,9 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk, int compressio
FATAL_ERROR_IF(version < SER_FMT_VER_LOWEST_WRITE, "Serialisation version error"); FATAL_ERROR_IF(version < SER_FMT_VER_LOWEST_WRITE, "Serialisation version error");
std::ostringstream os_raw(std::ios_base::binary);
std::ostream &os = version >= 29 ? os_raw : os_compressed;
// First byte // First byte
u8 flags = 0; u8 flags = 0;
if(is_underground) if(is_underground)
@ -382,37 +385,52 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk, int compressio
Bulk node data Bulk node data
*/ */
NameIdMapping nimap; NameIdMapping nimap;
if(disk) SharedBuffer<u8> buf;
const u8 content_width = 2;
const u8 params_width = 2;
if(disk)
{ {
MapNode *tmp_nodes = new MapNode[nodecount]; MapNode *tmp_nodes = new MapNode[nodecount];
for(u32 i=0; i<nodecount; i++) memcpy(tmp_nodes, data, nodecount * sizeof(MapNode));
tmp_nodes[i] = data[i];
getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef()); getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
u8 content_width = 2; buf = MapNode::serializeBulk(version, tmp_nodes, nodecount,
u8 params_width = 2; content_width, params_width);
writeU8(os, content_width);
writeU8(os, params_width);
MapNode::serializeBulk(os, version, tmp_nodes, nodecount,
content_width, params_width, compression_level);
delete[] tmp_nodes; delete[] tmp_nodes;
// write timestamp and node/id mapping first
if (version >= 29) {
writeU32(os, getTimestamp());
nimap.serialize(os);
}
} }
else else
{ {
u8 content_width = 2; buf = MapNode::serializeBulk(version, data, nodecount,
u8 params_width = 2; content_width, params_width);
writeU8(os, content_width); }
writeU8(os, params_width);
MapNode::serializeBulk(os, version, data, nodecount, writeU8(os, content_width);
content_width, params_width, compression_level); writeU8(os, params_width);
if (version >= 29) {
os.write(reinterpret_cast<char*>(*buf), buf.getSize());
} else {
// prior to 29 node data was compressed individually
compress(buf, os, version, compression_level);
} }
/* /*
Node metadata Node metadata
*/ */
std::ostringstream oss(std::ios_base::binary); if (version >= 29) {
m_node_metadata.serialize(oss, version, disk); m_node_metadata.serialize(os, version, disk);
compressZlib(oss.str(), os, compression_level); } else {
// use os_raw from above to avoid allocating another stream object
m_node_metadata.serialize(os_raw, version, disk);
// prior to 29 node data was compressed individually
compress(os_raw.str(), os, version, compression_level);
}
/* /*
Data that goes to disk, but not the network Data that goes to disk, but not the network
@ -427,17 +445,24 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk, int compressio
// Static objects // Static objects
m_static_objects.serialize(os); m_static_objects.serialize(os);
// Timestamp if(version < 29){
writeU32(os, getTimestamp()); // Timestamp
writeU32(os, getTimestamp());
// Write block-specific node definition id mapping // Write block-specific node definition id mapping
nimap.serialize(os); nimap.serialize(os);
}
if(version >= 25){ if(version >= 25){
// Node timers // Node timers
m_node_timers.serialize(os, version); m_node_timers.serialize(os, version);
} }
} }
if (version >= 29) {
// now compress the whole thing
compress(os_raw.str(), os_compressed, version, compression_level);
}
} }
void MapBlock::serializeNetworkSpecific(std::ostream &os) void MapBlock::serializeNetworkSpecific(std::ostream &os)
@ -449,7 +474,7 @@ void MapBlock::serializeNetworkSpecific(std::ostream &os)
writeU8(os, 2); // version writeU8(os, 2); // version
} }
void MapBlock::deSerialize(std::istream &is, u8 version, bool disk) void MapBlock::deSerialize(std::istream &in_compressed, u8 version, bool disk)
{ {
if(!ser_ver_supported(version)) if(!ser_ver_supported(version))
throw VersionMismatchException("ERROR: MapBlock format not supported"); throw VersionMismatchException("ERROR: MapBlock format not supported");
@ -460,10 +485,16 @@ void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
if(version <= 21) if(version <= 21)
{ {
deSerialize_pre22(is, version, disk); deSerialize_pre22(in_compressed, version, disk);
return; return;
} }
// Decompress the whole block (version >= 29)
std::stringstream in_raw(std::ios_base::binary | std::ios_base::in | std::ios_base::out);
if (version >= 29)
decompress(in_compressed, in_raw, version);
std::istream &is = version >= 29 ? in_raw : in_compressed;
u8 flags = readU8(is); u8 flags = readU8(is);
is_underground = (flags & 0x01) != 0; is_underground = (flags & 0x01) != 0;
m_day_night_differs = (flags & 0x02) != 0; m_day_night_differs = (flags & 0x02) != 0;
@ -473,9 +504,20 @@ void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
m_lighting_complete = readU16(is); m_lighting_complete = readU16(is);
m_generated = (flags & 0x08) == 0; m_generated = (flags & 0x08) == 0;
/* NameIdMapping nimap;
Bulk node data if (disk && version >= 29) {
*/ // Timestamp
TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
<<": Timestamp"<<std::endl);
setTimestampNoChangedFlag(readU32(is));
m_disk_timestamp = m_timestamp;
// Node/id mapping
TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
<<": NameIdMapping"<<std::endl);
nimap.deSerialize(is);
}
TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos()) TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
<<": Bulk node data"<<std::endl); <<": Bulk node data"<<std::endl);
u8 content_width = readU8(is); u8 content_width = readU8(is);
@ -484,29 +526,44 @@ void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
throw SerializationError("MapBlock::deSerialize(): invalid content_width"); throw SerializationError("MapBlock::deSerialize(): invalid content_width");
if(params_width != 2) if(params_width != 2)
throw SerializationError("MapBlock::deSerialize(): invalid params_width"); throw SerializationError("MapBlock::deSerialize(): invalid params_width");
MapNode::deSerializeBulk(is, version, data, nodecount,
/*
Bulk node data
*/
if (version >= 29) {
MapNode::deSerializeBulk(is, version, data, nodecount,
content_width, params_width); content_width, params_width);
} else {
// use in_raw from above to avoid allocating another stream object
decompress(is, in_raw, version);
MapNode::deSerializeBulk(in_raw, version, data, nodecount,
content_width, params_width);
}
/* /*
NodeMetadata NodeMetadata
*/ */
TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos()) TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
<<": Node metadata"<<std::endl); <<": Node metadata"<<std::endl);
// Ignore errors if (version >= 29) {
try { m_node_metadata.deSerialize(is, m_gamedef->idef());
std::ostringstream oss(std::ios_base::binary); } else {
decompressZlib(is, oss); try {
std::istringstream iss(oss.str(), std::ios_base::binary); // reuse in_raw
if (version >= 23) in_raw.str("");
m_node_metadata.deSerialize(iss, m_gamedef->idef()); in_raw.clear();
else decompress(is, in_raw, version);
content_nodemeta_deserialize_legacy(iss, if (version >= 23)
&m_node_metadata, &m_node_timers, m_node_metadata.deSerialize(in_raw, m_gamedef->idef());
m_gamedef->idef()); else
} catch(SerializationError &e) { content_nodemeta_deserialize_legacy(in_raw,
warningstream<<"MapBlock::deSerialize(): Ignoring an error" &m_node_metadata, &m_node_timers,
<<" while deserializing node metadata at (" m_gamedef->idef());
<<PP(getPos())<<": "<<e.what()<<std::endl; } catch(SerializationError &e) {
warningstream<<"MapBlock::deSerialize(): Ignoring an error"
<<" while deserializing node metadata at ("
<<PP(getPos())<<": "<<e.what()<<std::endl;
}
} }
/* /*
@ -530,17 +587,20 @@ void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
<<": Static objects"<<std::endl); <<": Static objects"<<std::endl);
m_static_objects.deSerialize(is); m_static_objects.deSerialize(is);
// Timestamp if(version < 29) {
TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos()) // Timestamp
<<": Timestamp"<<std::endl); TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
setTimestampNoChangedFlag(readU32(is)); <<": Timestamp"<<std::endl);
m_disk_timestamp = m_timestamp; setTimestampNoChangedFlag(readU32(is));
m_disk_timestamp = m_timestamp;
// Node/id mapping
TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
<<": NameIdMapping"<<std::endl);
nimap.deSerialize(is);
}
// Dynamically re-set ids based on node names // Dynamically re-set ids based on node names
TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
<<": NameIdMapping"<<std::endl);
NameIdMapping nimap;
nimap.deSerialize(is);
correctBlockNodeIds(&nimap, data, m_gamedef); correctBlockNodeIds(&nimap, data, m_gamedef);
if(version >= 25){ if(version >= 25){

@ -473,7 +473,7 @@ public:
// These don't write or read version by itself // These don't write or read version by itself
// Set disk to true for on-disk format, false for over-the-network format // Set disk to true for on-disk format, false for over-the-network format
// Precondition: version >= SER_FMT_VER_LOWEST_WRITE // Precondition: version >= SER_FMT_VER_LOWEST_WRITE
void serialize(std::ostream &os, u8 version, bool disk, int compression_level); void serialize(std::ostream &result, u8 version, bool disk, int compression_level);
// If disk == true: In addition to doing other things, will add // If disk == true: In addition to doing other things, will add
// unknown blocks from id-name mapping to wndef // unknown blocks from id-name mapping to wndef
void deSerialize(std::istream &is, u8 version, bool disk); void deSerialize(std::istream &is, u8 version, bool disk);

@ -339,7 +339,9 @@ bool Schematic::deserializeFromMts(std::istream *is)
delete []schemdata; delete []schemdata;
schemdata = new MapNode[nodecount]; schemdata = new MapNode[nodecount];
MapNode::deSerializeBulk(ss, SER_FMT_VER_HIGHEST_READ, schemdata, std::stringstream d_ss(std::ios_base::binary | std::ios_base::in | std::ios_base::out);
decompress(ss, d_ss, MTSCHEM_MAPNODE_SER_FMT_VER);
MapNode::deSerializeBulk(d_ss, MTSCHEM_MAPNODE_SER_FMT_VER, schemdata,
nodecount, 2, 2); nodecount, 2, 2);
// Fix probability values for nodes that were ignore; removed in v2 // Fix probability values for nodes that were ignore; removed in v2
@ -384,8 +386,9 @@ bool Schematic::serializeToMts(std::ostream *os) const
} }
// compressed bulk node data // compressed bulk node data
MapNode::serializeBulk(ss, SER_FMT_VER_HIGHEST_WRITE, SharedBuffer<u8> buf = MapNode::serializeBulk(MTSCHEM_MAPNODE_SER_FMT_VER,
schemdata, size.X * size.Y * size.Z, 2, 2, -1); schemdata, size.X * size.Y * size.Z, 2, 2);
compress(buf, ss, MTSCHEM_MAPNODE_SER_FMT_VER);
return true; return true;
} }

@ -70,6 +70,7 @@ class Server;
#define MTSCHEM_FILE_SIGNATURE 0x4d54534d // 'MTSM' #define MTSCHEM_FILE_SIGNATURE 0x4d54534d // 'MTSM'
#define MTSCHEM_FILE_VER_HIGHEST_READ 4 #define MTSCHEM_FILE_VER_HIGHEST_READ 4
#define MTSCHEM_FILE_VER_HIGHEST_WRITE 4 #define MTSCHEM_FILE_VER_HIGHEST_WRITE 4
#define MTSCHEM_MAPNODE_SER_FMT_VER 28 // Fixed serialization version for schematics since these still need to use Zlib
#define MTSCHEM_PROB_MASK 0x7F #define MTSCHEM_PROB_MASK 0x7F

@ -730,9 +730,10 @@ void MapNode::deSerialize(u8 *source, u8 version)
} }
} }
} }
void MapNode::serializeBulk(std::ostream &os, int version,
SharedBuffer<u8> MapNode::serializeBulk(int version,
const MapNode *nodes, u32 nodecount, const MapNode *nodes, u32 nodecount,
u8 content_width, u8 params_width, int compression_level) u8 content_width, u8 params_width)
{ {
if (!ser_ver_supported(version)) if (!ser_ver_supported(version))
throw VersionMismatchException("ERROR: MapNode format not supported"); throw VersionMismatchException("ERROR: MapNode format not supported");
@ -746,8 +747,7 @@ void MapNode::serializeBulk(std::ostream &os, int version,
throw SerializationError("MapNode::serializeBulk: serialization to " throw SerializationError("MapNode::serializeBulk: serialization to "
"version < 24 not possible"); "version < 24 not possible");
size_t databuf_size = nodecount * (content_width + params_width); SharedBuffer<u8> databuf(nodecount * (content_width + params_width));
u8 *databuf = new u8[databuf_size];
u32 start1 = content_width * nodecount; u32 start1 = content_width * nodecount;
u32 start2 = (content_width + 1) * nodecount; u32 start2 = (content_width + 1) * nodecount;
@ -758,14 +758,7 @@ void MapNode::serializeBulk(std::ostream &os, int version,
writeU8(&databuf[start1 + i], nodes[i].param1); writeU8(&databuf[start1 + i], nodes[i].param1);
writeU8(&databuf[start2 + i], nodes[i].param2); writeU8(&databuf[start2 + i], nodes[i].param2);
} }
return databuf;
/*
Compress data to output stream
*/
compressZlib(databuf, databuf_size, os, compression_level);
delete [] databuf;
} }
// Deserialize bulk node data // Deserialize bulk node data
@ -781,15 +774,10 @@ void MapNode::deSerializeBulk(std::istream &is, int version,
|| params_width != 2) || params_width != 2)
FATAL_ERROR("Deserialize bulk node data error"); FATAL_ERROR("Deserialize bulk node data error");
// Uncompress or read data // read data
u32 len = nodecount * (content_width + params_width); const u32 len = nodecount * (content_width + params_width);
std::ostringstream os(std::ios_base::binary); Buffer<u8> databuf(len);
decompressZlib(is, os); is.read(reinterpret_cast<char*>(*databuf), len);
std::string s = os.str();
if(s.size() != len)
throw SerializationError("deSerializeBulkNodes: "
"decompress resulted in invalid size");
const u8 *databuf = reinterpret_cast<const u8*>(s.c_str());
// Deserialize content // Deserialize content
if(content_width == 1) if(content_width == 1)

@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "irrlichttypes_bloated.h" #include "irrlichttypes_bloated.h"
#include "light.h" #include "light.h"
#include "util/pointer.h"
#include <string> #include <string>
#include <vector> #include <vector>
@ -293,9 +294,9 @@ struct MapNode
// content_width = the number of bytes of content per node // content_width = the number of bytes of content per node
// params_width = the number of bytes of params per node // params_width = the number of bytes of params per node
// compressed = true to zlib-compress output // compressed = true to zlib-compress output
static void serializeBulk(std::ostream &os, int version, static SharedBuffer<u8> serializeBulk(int version,
const MapNode *nodes, u32 nodecount, const MapNode *nodes, u32 nodecount,
u8 content_width, u8 params_width, int compression_level); u8 content_width, u8 params_width);
static void deSerializeBulk(std::istream &is, int version, static void deSerializeBulk(std::istream &is, int version,
MapNode *nodes, u32 nodecount, MapNode *nodes, u32 nodecount,
u8 content_width, u8 params_width); u8 content_width, u8 params_width);

@ -21,7 +21,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/serialize.h" #include "util/serialize.h"
#include "zlib.h" #include <zlib.h>
#include <zstd.h>
/* report a zlib or i/o error */ /* report a zlib or i/o error */
void zerr(int ret) void zerr(int ret)
@ -197,27 +198,133 @@ void decompressZlib(std::istream &is, std::ostream &os, size_t limit)
inflateEnd(&z); inflateEnd(&z);
} }
void compress(const SharedBuffer<u8> &data, std::ostream &os, u8 version) struct ZSTD_Deleter {
void operator() (ZSTD_CStream* cstream) {
ZSTD_freeCStream(cstream);
}
void operator() (ZSTD_DStream* dstream) {
ZSTD_freeDStream(dstream);
}
};
void compressZstd(const u8 *data, size_t data_size, std::ostream &os, int level)
{ {
if(version >= 11) // reusing the context is recommended for performance
// it will destroyed when the thread ends
thread_local std::unique_ptr<ZSTD_CStream, ZSTD_Deleter> stream(ZSTD_createCStream());
ZSTD_initCStream(stream.get(), level);
const size_t bufsize = 16384;
char output_buffer[bufsize];
ZSTD_inBuffer input = { data, data_size, 0 };
ZSTD_outBuffer output = { output_buffer, bufsize, 0 };
while (input.pos < input.size) {
size_t ret = ZSTD_compressStream(stream.get(), &output, &input);
if (ZSTD_isError(ret)) {
dstream << ZSTD_getErrorName(ret) << std::endl;
throw SerializationError("compressZstd: failed");
}
if (output.pos) {
os.write(output_buffer, output.pos);
output.pos = 0;
}
}
size_t ret;
do {
ret = ZSTD_endStream(stream.get(), &output);
if (ZSTD_isError(ret)) {
dstream << ZSTD_getErrorName(ret) << std::endl;
throw SerializationError("compressZstd: failed");
}
if (output.pos) {
os.write(output_buffer, output.pos);
output.pos = 0;
}
} while (ret != 0);
}
void compressZstd(const std::string &data, std::ostream &os, int level)
{
compressZstd((u8*)data.c_str(), data.size(), os, level);
}
void decompressZstd(std::istream &is, std::ostream &os)
{
// reusing the context is recommended for performance
// it will destroyed when the thread ends
thread_local std::unique_ptr<ZSTD_DStream, ZSTD_Deleter> stream(ZSTD_createDStream());
ZSTD_initDStream(stream.get());
const size_t bufsize = 16384;
char output_buffer[bufsize];
char input_buffer[bufsize];
ZSTD_outBuffer output = { output_buffer, bufsize, 0 };
ZSTD_inBuffer input = { input_buffer, 0, 0 };
size_t ret;
do
{ {
compressZlib(*data ,data.getSize(), os); if (input.size == input.pos) {
is.read(input_buffer, bufsize);
input.size = is.gcount();
input.pos = 0;
}
ret = ZSTD_decompressStream(stream.get(), &output, &input);
if (ZSTD_isError(ret)) {
dstream << ZSTD_getErrorName(ret) << std::endl;
throw SerializationError("decompressZstd: failed");
}
if (output.pos) {
os.write(output_buffer, output.pos);
output.pos = 0;
}
} while (ret != 0);
// Unget all the data that ZSTD_decompressStream didn't take
is.clear(); // Just in case EOF is set
for (u32 i = 0; i < input.size - input.pos; i++) {
is.unget();
if (is.fail() || is.bad())
throw SerializationError("decompressZstd: unget failed");
}
}
void compress(u8 *data, u32 size, std::ostream &os, u8 version, int level)
{
if(version >= 29)
{
// map the zlib levels [0,9] to [1,10]. -1 becomes 0 which indicates the default (currently 3)
compressZstd(data, size, os, level + 1);
return; return;
} }
if(data.getSize() == 0) if(version >= 11)
{
compressZlib(data, size, os, level);
return;
}
if(size == 0)
return; return;
// Write length (u32) // Write length (u32)
u8 tmp[4]; u8 tmp[4];
writeU32(tmp, data.getSize()); writeU32(tmp, size);
os.write((char*)tmp, 4); os.write((char*)tmp, 4);
// We will be writing 8-bit pairs of more_count and byte // We will be writing 8-bit pairs of more_count and byte
u8 more_count = 0; u8 more_count = 0;
u8 current_byte = data[0]; u8 current_byte = data[0];
for(u32 i=1; i<data.getSize(); i++) for(u32 i=1; i<size; i++)
{ {
if( if(
data[i] != current_byte data[i] != current_byte
@ -240,8 +347,24 @@ void compress(const SharedBuffer<u8> &data, std::ostream &os, u8 version)
os.write((char*)&current_byte, 1); os.write((char*)&current_byte, 1);
} }
void compress(const SharedBuffer<u8> &data, std::ostream &os, u8 version, int level)
{
compress(*data, data.getSize(), os, version, level);
}
void compress(const std::string &data, std::ostream &os, u8 version, int level)
{
compress((u8*)data.c_str(), data.size(), os, version, level);
}
void decompress(std::istream &is, std::ostream &os, u8 version) void decompress(std::istream &is, std::ostream &os, u8 version)
{ {
if(version >= 29)
{
decompressZstd(is, os);
return;
}
if(version >= 11) if(version >= 11)
{ {
decompressZlib(is, os); decompressZlib(is, os);

@ -63,13 +63,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
26: Never written; read the same as 25 26: Never written; read the same as 25
27: Added light spreading flags to blocks 27: Added light spreading flags to blocks
28: Added "private" flag to NodeMetadata 28: Added "private" flag to NodeMetadata
29: Switched compression to zstd, a bit of reorganization
*/ */
// This represents an uninitialized or invalid format // This represents an uninitialized or invalid format
#define SER_FMT_VER_INVALID 255 #define SER_FMT_VER_INVALID 255
// Highest supported serialization version // Highest supported serialization version
#define SER_FMT_VER_HIGHEST_READ 28 #define SER_FMT_VER_HIGHEST_READ 29
// Saved on disk version // Saved on disk version
#define SER_FMT_VER_HIGHEST_WRITE 28 #define SER_FMT_VER_HIGHEST_WRITE 29
// Lowest supported serialization version // Lowest supported serialization version
#define SER_FMT_VER_LOWEST_READ 0 #define SER_FMT_VER_LOWEST_READ 0
// Lowest serialization version for writing // Lowest serialization version for writing
@ -89,7 +90,12 @@ void compressZlib(const u8 *data, size_t data_size, std::ostream &os, int level
void compressZlib(const std::string &data, std::ostream &os, int level = -1); void compressZlib(const std::string &data, std::ostream &os, int level = -1);
void decompressZlib(std::istream &is, std::ostream &os, size_t limit = 0); void decompressZlib(std::istream &is, std::ostream &os, size_t limit = 0);
void compressZstd(const u8 *data, size_t data_size, std::ostream &os, int level = 0);
void compressZstd(const std::string &data, std::ostream &os, int level = 0);
void decompressZstd(std::istream &is, std::ostream &os);
// These choose between zlib and a self-made one according to version // These choose between zlib and a self-made one according to version
void compress(const SharedBuffer<u8> &data, std::ostream &os, u8 version); void compress(const SharedBuffer<u8> &data, std::ostream &os, u8 version, int level = -1);
//void compress(const std::string &data, std::ostream &os, u8 version); void compress(const std::string &data, std::ostream &os, u8 version, int level = -1);
void compress(u8 *data, u32 size, std::ostream &os, u8 version, int level = -1);
void decompress(std::istream &is, std::ostream &os, u8 version); void decompress(std::istream &is, std::ostream &os, u8 version);

@ -37,6 +37,7 @@ public:
void testRLECompression(); void testRLECompression();
void testZlibCompression(); void testZlibCompression();
void testZlibLargeData(); void testZlibLargeData();
void testZstdLargeData();
void testZlibLimit(); void testZlibLimit();
void _testZlibLimit(u32 size, u32 limit); void _testZlibLimit(u32 size, u32 limit);
}; };
@ -48,6 +49,7 @@ void TestCompression::runTests(IGameDef *gamedef)
TEST(testRLECompression); TEST(testRLECompression);
TEST(testZlibCompression); TEST(testZlibCompression);
TEST(testZlibLargeData); TEST(testZlibLargeData);
TEST(testZstdLargeData);
TEST(testZlibLimit); TEST(testZlibLimit);
} }
@ -111,7 +113,7 @@ void TestCompression::testZlibCompression()
fromdata[3]=1; fromdata[3]=1;
std::ostringstream os(std::ios_base::binary); std::ostringstream os(std::ios_base::binary);
compress(fromdata, os, SER_FMT_VER_HIGHEST_READ); compressZlib(*fromdata, fromdata.getSize(), os);
std::string str_out = os.str(); std::string str_out = os.str();
@ -124,7 +126,7 @@ void TestCompression::testZlibCompression()
std::istringstream is(str_out, std::ios_base::binary); std::istringstream is(str_out, std::ios_base::binary);
std::ostringstream os2(std::ios_base::binary); std::ostringstream os2(std::ios_base::binary);
decompress(is, os2, SER_FMT_VER_HIGHEST_READ); decompressZlib(is, os2);
std::string str_out2 = os2.str(); std::string str_out2 = os2.str();
infostream << "decompress: "; infostream << "decompress: ";
@ -174,6 +176,42 @@ void TestCompression::testZlibLargeData()
} }
} }
void TestCompression::testZstdLargeData()
{
infostream << "Test: Testing zstd wrappers with a large amount "
"of pseudorandom data" << std::endl;
u32 size = 500000;
infostream << "Test: Input size of large compressZstd is "
<< size << std::endl;
std::string data_in;
data_in.resize(size);
PseudoRandom pseudorandom(9420);
for (u32 i = 0; i < size; i++)
data_in[i] = pseudorandom.range(0, 255);
std::ostringstream os_compressed(std::ios::binary);
compressZstd(data_in, os_compressed, 0);
infostream << "Test: Output size of large compressZstd is "
<< os_compressed.str().size()<<std::endl;
std::istringstream is_compressed(os_compressed.str(), std::ios::binary);
std::ostringstream os_decompressed(std::ios::binary);
decompressZstd(is_compressed, os_decompressed);
infostream << "Test: Output size of large decompressZstd is "
<< os_decompressed.str().size() << std::endl;
std::string str_decompressed = os_decompressed.str();
UASSERTEQ(size_t, str_decompressed.size(), data_in.size());
for (u32 i = 0; i < size && i < str_decompressed.size(); i++) {
UTEST(str_decompressed[i] == data_in[i],
"index out[%i]=%i differs from in[%i]=%i",
i, str_decompressed[i], i, data_in[i]);
}
}
void TestCompression::testZlibLimit() void TestCompression::testZlibLimit()
{ {
// edge cases // edge cases

@ -40,6 +40,7 @@ sqlite3_version=3.35.5
luajit_version=2.1.0-beta3 luajit_version=2.1.0-beta3
leveldb_version=1.23 leveldb_version=1.23
zlib_version=1.2.11 zlib_version=1.2.11
zstd_version=1.4.9
mkdir -p $libdir mkdir -p $libdir
@ -66,6 +67,7 @@ download () {
cd $libdir cd $libdir
download "https://github.com/minetest/irrlicht/releases/download/$irrlicht_version/win32.zip" irrlicht-$irrlicht_version.zip download "https://github.com/minetest/irrlicht/releases/download/$irrlicht_version/win32.zip" irrlicht-$irrlicht_version.zip
download "http://minetest.kitsunemimi.pw/zlib-$zlib_version-win32.zip" download "http://minetest.kitsunemimi.pw/zlib-$zlib_version-win32.zip"
download "http://minetest.kitsunemimi.pw/zstd-$zstd_version-win32.zip"
download "http://minetest.kitsunemimi.pw/libogg-$ogg_version-win32.zip" download "http://minetest.kitsunemimi.pw/libogg-$ogg_version-win32.zip"
download "http://minetest.kitsunemimi.pw/libvorbis-$vorbis_version-win32.zip" download "http://minetest.kitsunemimi.pw/libvorbis-$vorbis_version-win32.zip"
download "http://minetest.kitsunemimi.pw/curl-$curl_version-win32.zip" download "http://minetest.kitsunemimi.pw/curl-$curl_version-win32.zip"
@ -120,6 +122,10 @@ cmake -S $sourcedir -B . \
-DZLIB_LIBRARIES=$libdir/zlib/lib/libz.dll.a \ -DZLIB_LIBRARIES=$libdir/zlib/lib/libz.dll.a \
-DZLIB_DLL=$libdir/zlib/bin/zlib1.dll \ -DZLIB_DLL=$libdir/zlib/bin/zlib1.dll \
\ \
-DZSTD_INCLUDE_DIR=$libdir/zstd/include \
-DZSTD_LIBRARY=$libdir/zstd/lib/libzstd.dll.a \
-DZSTD_DLL=$libdir/zstd/bin/libzstd.dll \
\
-DLUA_INCLUDE_DIR=$libdir/luajit/include \ -DLUA_INCLUDE_DIR=$libdir/luajit/include \
-DLUA_LIBRARY=$libdir/luajit/libluajit.a \ -DLUA_LIBRARY=$libdir/luajit/libluajit.a \
\ \

@ -40,6 +40,7 @@ sqlite3_version=3.35.5
luajit_version=2.1.0-beta3 luajit_version=2.1.0-beta3
leveldb_version=1.23 leveldb_version=1.23
zlib_version=1.2.11 zlib_version=1.2.11
zstd_version=1.4.9
mkdir -p $libdir mkdir -p $libdir
@ -66,6 +67,7 @@ download () {
cd $libdir cd $libdir
download "https://github.com/minetest/irrlicht/releases/download/$irrlicht_version/win64.zip" irrlicht-$irrlicht_version.zip download "https://github.com/minetest/irrlicht/releases/download/$irrlicht_version/win64.zip" irrlicht-$irrlicht_version.zip
download "http://minetest.kitsunemimi.pw/zlib-$zlib_version-win64.zip" download "http://minetest.kitsunemimi.pw/zlib-$zlib_version-win64.zip"
download "http://minetest.kitsunemimi.pw/zstd-$zstd_version-win64.zip"
download "http://minetest.kitsunemimi.pw/libogg-$ogg_version-win64.zip" download "http://minetest.kitsunemimi.pw/libogg-$ogg_version-win64.zip"
download "http://minetest.kitsunemimi.pw/libvorbis-$vorbis_version-win64.zip" download "http://minetest.kitsunemimi.pw/libvorbis-$vorbis_version-win64.zip"
download "http://minetest.kitsunemimi.pw/curl-$curl_version-win64.zip" download "http://minetest.kitsunemimi.pw/curl-$curl_version-win64.zip"
@ -120,6 +122,10 @@ cmake -S $sourcedir -B . \
-DZLIB_LIBRARIES=$libdir/zlib/lib/libz.dll.a \ -DZLIB_LIBRARIES=$libdir/zlib/lib/libz.dll.a \
-DZLIB_DLL=$libdir/zlib/bin/zlib1.dll \ -DZLIB_DLL=$libdir/zlib/bin/zlib1.dll \
\ \
-DZSTD_INCLUDE_DIR=$libdir/zstd/include \
-DZSTD_LIBRARY=$libdir/zstd/lib/libzstd.dll.a \
-DZSTD_DLL=$libdir/zstd/bin/libzstd.dll \
\
-DLUA_INCLUDE_DIR=$libdir/luajit/include \ -DLUA_INCLUDE_DIR=$libdir/luajit/include \
-DLUA_LIBRARY=$libdir/luajit/libluajit.a \ -DLUA_LIBRARY=$libdir/luajit/libluajit.a \
\ \

@ -5,7 +5,7 @@ install_linux_deps() {
local pkgs=(cmake libpng-dev \ local pkgs=(cmake libpng-dev \
libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev \ libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev \
libhiredis-dev libogg-dev libgmp-dev libvorbis-dev libopenal-dev \ libhiredis-dev libogg-dev libgmp-dev libvorbis-dev libopenal-dev \
gettext libpq-dev libleveldb-dev libcurl4-openssl-dev) gettext libpq-dev libleveldb-dev libcurl4-openssl-dev libzstd-dev)
if [[ "$1" == "--old-irr" ]]; then if [[ "$1" == "--old-irr" ]]; then
shift shift