Add ZStandard(zstd) savegame compression
This commit is contained in:
1
Doxyfile
1
Doxyfile
@@ -290,6 +290,7 @@ INCLUDE_FILE_PATTERNS =
|
|||||||
PREDEFINED = WITH_ZLIB \
|
PREDEFINED = WITH_ZLIB \
|
||||||
WITH_LZO \
|
WITH_LZO \
|
||||||
WITH_LIBLZMA \
|
WITH_LIBLZMA \
|
||||||
|
WITH_ZSTD \
|
||||||
WITH_SDL \
|
WITH_SDL \
|
||||||
WITH_PNG \
|
WITH_PNG \
|
||||||
WITH_FONTCONFIG \
|
WITH_FONTCONFIG \
|
||||||
|
|||||||
34
config.lib
34
config.lib
@@ -68,6 +68,7 @@ set_default() {
|
|||||||
with_cocoa="1"
|
with_cocoa="1"
|
||||||
with_zlib="1"
|
with_zlib="1"
|
||||||
with_lzma="1"
|
with_lzma="1"
|
||||||
|
with_zstd="1"
|
||||||
with_lzo2="1"
|
with_lzo2="1"
|
||||||
with_xdg_basedir="1"
|
with_xdg_basedir="1"
|
||||||
with_png="1"
|
with_png="1"
|
||||||
@@ -340,6 +341,10 @@ detect_params() {
|
|||||||
--without-liblzma) with_lzma="0";;
|
--without-liblzma) with_lzma="0";;
|
||||||
--with-liblzma=*) with_lzma="$optarg";;
|
--with-liblzma=*) with_lzma="$optarg";;
|
||||||
|
|
||||||
|
--with-zstd) with_zstd="2";;
|
||||||
|
--without-zstd) with_zstd="0";;
|
||||||
|
--with-zstd=*) with_zstd="$optarg";;
|
||||||
|
|
||||||
--with-lzo2) with_lzo2="2";;
|
--with-lzo2) with_lzo2="2";;
|
||||||
--without-lzo2) with_lzo2="0";;
|
--without-lzo2) with_lzo2="0";;
|
||||||
--with-lzo2=*) with_lzo2="$optarg";;
|
--with-lzo2=*) with_lzo2="$optarg";;
|
||||||
@@ -824,6 +829,20 @@ check_params() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
pre_detect_with_zstd=$with_zstd
|
||||||
|
detect_zstd
|
||||||
|
|
||||||
|
if [ "$with_zstd" = "0" ] || [ -z "$zstd_config" ]; then
|
||||||
|
log 1 "WARNING: zstd was not detected or disabled"
|
||||||
|
if [ "$pre_detect_with_zstd" = "0" ]; then
|
||||||
|
log 1 "WARNING: We strongly suggest you to install zstd."
|
||||||
|
else
|
||||||
|
log 1 "configure: error: no zstd detected"
|
||||||
|
log 1 " If you want to compile without zstd use --without-zstd as parameter"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
pre_detect_with_lzo2=$with_lzo2
|
pre_detect_with_lzo2=$with_lzo2
|
||||||
detect_lzo2
|
detect_lzo2
|
||||||
|
|
||||||
@@ -1690,6 +1709,17 @@ make_cflags_and_ldflags() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -n "$zstd_config" ]; then
|
||||||
|
CFLAGS="$CFLAGS -DWITH_ZSTD"
|
||||||
|
CFLAGS="$CFLAGS `$zstd_config --cflags | tr '\n\r' ' '`"
|
||||||
|
|
||||||
|
if [ "$enable_static" != "0" ]; then
|
||||||
|
LIBS="$LIBS `$zstd_config --libs --static | tr '\n\r' ' '`"
|
||||||
|
else
|
||||||
|
LIBS="$LIBS `$zstd_config --libs | tr '\n\r' ' '`"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$with_lzo2" != "0" ]; then
|
if [ "$with_lzo2" != "0" ]; then
|
||||||
if [ "$enable_static" != "0" ] && [ "$os" != "OSX" ]; then
|
if [ "$enable_static" != "0" ] && [ "$os" != "OSX" ]; then
|
||||||
LIBS="$LIBS $lzo2"
|
LIBS="$LIBS $lzo2"
|
||||||
@@ -2796,6 +2826,10 @@ detect_lzma() {
|
|||||||
detect_pkg_config "$with_lzma" "liblzma" "lzma_config" "5.0"
|
detect_pkg_config "$with_lzma" "liblzma" "lzma_config" "5.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
detect_zstd() {
|
||||||
|
detect_pkg_config "$with_zstd" "libzstd" "zstd_config" "1.4"
|
||||||
|
}
|
||||||
|
|
||||||
detect_xdg_basedir() {
|
detect_xdg_basedir() {
|
||||||
detect_pkg_config "$with_xdg_basedir" "libxdg-basedir" "xdg_basedir_config" "1.2"
|
detect_pkg_config "$with_xdg_basedir" "libxdg-basedir" "xdg_basedir_config" "1.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,9 @@
|
|||||||
#ifdef WITH_LIBLZMA
|
#ifdef WITH_LIBLZMA
|
||||||
# include <lzma.h>
|
# include <lzma.h>
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WITH_ZSTD
|
||||||
|
#include <zstd.h>
|
||||||
|
#endif
|
||||||
#ifdef WITH_LZO
|
#ifdef WITH_LZO
|
||||||
#include <lzo/lzo1x.h>
|
#include <lzo/lzo1x.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -255,6 +258,10 @@ char *CrashLog::LogLibraries(char *buffer, const char *last) const
|
|||||||
buffer += seprintf(buffer, last, " LZMA: %s\n", lzma_version_string());
|
buffer += seprintf(buffer, last, " LZMA: %s\n", lzma_version_string());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef WITH_ZSTD
|
||||||
|
buffer += seprintf(buffer, last, " ZSTD: %s\n", ZSTD_versionString());
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef WITH_LZO
|
#ifdef WITH_LZO
|
||||||
buffer += seprintf(buffer, last, " LZO: %s\n", lzo_version_string());
|
buffer += seprintf(buffer, last, " LZO: %s\n", lzo_version_string());
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -2275,6 +2275,118 @@ struct LZMASaveFilter : SaveFilter {
|
|||||||
|
|
||||||
#endif /* WITH_LIBLZMA */
|
#endif /* WITH_LIBLZMA */
|
||||||
|
|
||||||
|
/********************************************
|
||||||
|
********** START OF ZSTD CODE **************
|
||||||
|
********************************************/
|
||||||
|
|
||||||
|
#if defined(WITH_ZSTD)
|
||||||
|
#include <zstd.h>
|
||||||
|
|
||||||
|
|
||||||
|
/** Filter using ZSTD compression. */
|
||||||
|
struct ZSTDLoadFilter : LoadFilter {
|
||||||
|
ZSTD_DCtx *zstd; ///< ZSTD decompression context
|
||||||
|
byte fread_buf[MEMORY_CHUNK_SIZE]; ///< Buffer for reading from the file
|
||||||
|
ZSTD_inBuffer input; ///< ZSTD input buffer for fread_buf
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise this filter.
|
||||||
|
* @param chain The next filter in this chain.
|
||||||
|
*/
|
||||||
|
ZSTDLoadFilter(LoadFilter *chain) : LoadFilter(chain)
|
||||||
|
{
|
||||||
|
this->zstd = ZSTD_createDCtx();
|
||||||
|
if (!this->zstd) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize compressor");
|
||||||
|
this->input = {this->fread_buf, 0, 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clean everything up. */
|
||||||
|
~ZSTDLoadFilter()
|
||||||
|
{
|
||||||
|
ZSTD_freeDCtx(this->zstd);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Read(byte *buf, size_t size) override
|
||||||
|
{
|
||||||
|
ZSTD_outBuffer output{buf, size, 0};
|
||||||
|
|
||||||
|
do {
|
||||||
|
/* read more bytes from the file? */
|
||||||
|
if (this->input.pos == this->input.size) {
|
||||||
|
this->input.size = this->chain->Read(this->fread_buf, sizeof(this->fread_buf));
|
||||||
|
this->input.pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ret = ZSTD_decompressStream(this->zstd, &output, &this->input);
|
||||||
|
if (ZSTD_isError(ret)) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "libzstd returned error code");
|
||||||
|
if (ret == 0) break;
|
||||||
|
} while (output.pos < output.size);
|
||||||
|
|
||||||
|
return output.pos;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Filter using ZSTD compression. */
|
||||||
|
struct ZSTDSaveFilter : SaveFilter {
|
||||||
|
ZSTD_CCtx *zstd; ///< ZSTD compression context
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise this filter.
|
||||||
|
* @param chain The next filter in this chain.
|
||||||
|
* @param compression_level The requested level of compression.
|
||||||
|
*/
|
||||||
|
ZSTDSaveFilter(SaveFilter *chain, byte compression_level) : SaveFilter(chain)
|
||||||
|
{
|
||||||
|
this->zstd = ZSTD_createCCtx();
|
||||||
|
if (!this->zstd) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize compressor");
|
||||||
|
if (ZSTD_isError(ZSTD_CCtx_setParameter(this->zstd, ZSTD_c_compressionLevel, (int)compression_level - 100))) {
|
||||||
|
ZSTD_freeCCtx(this->zstd);
|
||||||
|
SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "invalid compresison level");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clean up what we allocated. */
|
||||||
|
~ZSTDSaveFilter()
|
||||||
|
{
|
||||||
|
ZSTD_freeCCtx(this->zstd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper loop for writing the data.
|
||||||
|
* @param p The bytes to write.
|
||||||
|
* @param len Amount of bytes to write.
|
||||||
|
* @param mode Mode for ZSTD_compressStream2.
|
||||||
|
*/
|
||||||
|
void WriteLoop(byte *p, size_t len, ZSTD_EndDirective mode)
|
||||||
|
{
|
||||||
|
byte buf[MEMORY_CHUNK_SIZE]; // output buffer
|
||||||
|
ZSTD_inBuffer input{p, len, 0};
|
||||||
|
|
||||||
|
bool finished;
|
||||||
|
do {
|
||||||
|
ZSTD_outBuffer output{buf, sizeof(buf), 0};
|
||||||
|
size_t remaining = ZSTD_compressStream2(this->zstd, &output, &input, mode);
|
||||||
|
if (ZSTD_isError(remaining)) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "libzstd returned error code");
|
||||||
|
|
||||||
|
if (output.pos != 0) this->chain->Write(buf, output.pos);
|
||||||
|
|
||||||
|
finished = (mode == ZSTD_e_end ? (remaining == 0) : (input.pos == input.size));
|
||||||
|
} while (!finished);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Write(byte *buf, size_t size) override
|
||||||
|
{
|
||||||
|
this->WriteLoop(buf, size, ZSTD_e_continue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Finish() override
|
||||||
|
{
|
||||||
|
this->WriteLoop(nullptr, 0, ZSTD_e_end);
|
||||||
|
this->chain->Finish();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif /* WITH_LIBZSTD */
|
||||||
|
|
||||||
/*******************************************
|
/*******************************************
|
||||||
************* END OF CODE *****************
|
************* END OF CODE *****************
|
||||||
*******************************************/
|
*******************************************/
|
||||||
@@ -2310,6 +2422,17 @@ static const SaveLoadFormat _saveload_formats[] = {
|
|||||||
#else
|
#else
|
||||||
{"zlib", TO_BE32X('OTTZ'), nullptr, nullptr, 0, 0, 0},
|
{"zlib", TO_BE32X('OTTZ'), nullptr, nullptr, 0, 0, 0},
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(WITH_ZSTD)
|
||||||
|
/* Zstd provides a decent compression rate at a very high compression/decompression speed. Compared to lzma level 2
|
||||||
|
* zstd saves are about 40% larger (on level 1) but it has about 30x faster compression and 5x decompression making it
|
||||||
|
* a good choice for multiplayer servers. And zstd level 1 seems to be the optimal one for client connection speed
|
||||||
|
* (compress + 10 MB/s download + decompress time), about 3x faster than lzma:2 and 1.5x than zlib:2 and lzo.
|
||||||
|
* As zstd has negative compression levels the values were increased by 100 moving zstd level range -100..22 into
|
||||||
|
* openttd 0..122. Also note that value 100 mathes zstd level 0 which is a special value for default level 3 (openttd 103) */
|
||||||
|
{"zstd", TO_BE32X('OTTS'), CreateLoadFilter<ZSTDLoadFilter>, CreateSaveFilter<ZSTDSaveFilter>, 0, 101, 122},
|
||||||
|
#else
|
||||||
|
{"zstd", TO_BE32X('OTTS'), nullptr, nullptr, 0, 0, 0},
|
||||||
|
#endif
|
||||||
#if defined(WITH_LIBLZMA)
|
#if defined(WITH_LIBLZMA)
|
||||||
/* Level 2 compression is speed wise as fast as zlib level 6 compression (old default), but results in ~10% smaller saves.
|
/* Level 2 compression is speed wise as fast as zlib level 6 compression (old default), but results in ~10% smaller saves.
|
||||||
* Higher compression levels are possible, and might improve savegame size by up to 25%, but are also up to 10 times slower.
|
* Higher compression levels are possible, and might improve savegame size by up to 25%, but are also up to 10 times slower.
|
||||||
|
|||||||
Reference in New Issue
Block a user