Skip to content

Commit

Permalink
Merge pull request #121 from fallahn/zstd
Browse files Browse the repository at this point in the history
Implement optional Zstd support
  • Loading branch information
fallahn authored Mar 31, 2023
2 parents 87c3b46 + 425e0e4 commit 2755e96
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 15 deletions.
6 changes: 6 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ if get_option('use_extlibs')
add_project_arguments('-DUSE_EXTLIBS', language: ['cpp', 'c'])
zdep = dependency('zlib', version : '>=1.2.8', required: true)
pugidep = dependency('pugixml', required: true)
zstddep = dependency('zstd', required: true)
else
if get_option('use_zstd')
add_project_arguments('-DUSE_ZSTD', language: ['cpp', 'c'])
zstddep = dependency('zstd', required: true)
endif
endif

subdir('tmxlite')
Expand Down
3 changes: 2 additions & 1 deletion meson_options.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
option('use_rtti', type: 'boolean', value: true, description: 'Use run time type information?', yield: true)
option('project_static_runtime', type: 'boolean', value: false, description: 'Use statically linked standard/runtime libraries?', yield: true)
option('use_extlibs', type: 'boolean', value: false, description: 'Use external pugixml and zlib libraries instead of the included source?', yield: true)
option('use_extlibs', type: 'boolean', value: false, description: 'Use external pugixml, zstd and zlib libraries instead of the included source?', yield: true)
option('use_zstd', type: 'boolean', value: false, description: 'Use zstd compression library (automatically enabled when use_extlibs is true)?', yield: true)
option('build_examples', type: 'boolean', value: false)
option('build_tests', type: 'boolean', value: false)
option('pause_test', type: 'boolean', value: true, description: 'Wait for user input after tests have finished running')
19 changes: 16 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ tmxlite
[![Github Actions](https://github.com/fallahn/tmxlite/actions/workflows/cmake.yml/badge.svg)](https://github.com/fallahn/tmxlite/actions)

#### Description
A lightweight C++14 parsing library for tmx map files created with the Tiled map editor. Requires no external linking, all dependencies are included. Fully supports tmx maps up to 1.0 (see [here](https://doc.mapeditor.org/en/stable/reference/tmx-changelog/#tiled-1-0)) with CSV, zlib and base64 compression. Also supports some features of newer map versions (see below). The parser is renderer agnostic, and is cross platform on Windows, linux and macOS. It has also been successfully built for Android too.
A lightweight C++14 parsing library for tmx map files created with the Tiled map editor. Requires no external linking, all dependencies are included. Optionally Zstd support can be enabled by linking the required external library. Fully supports tmx maps up to 1.0 (see [here](https://doc.mapeditor.org/en/stable/reference/tmx-changelog/#tiled-1-0)) with CSV, zlib and base64 compression. Also supports some features of newer map versions (see below). The parser is renderer agnostic, and is cross platform on Windows, linux and macOS. It has also been successfully built for Android too.

As the library contains no specific rendering functions some example projects are included, along with the relevant CMake files. These are meant mostly for guidance and are not 100% optimised, but should get you off on the right foot when using libraries such as SFML or SDL2/OpenGL. Examples for any specific rendering library are welcome via a pull request.

Expand All @@ -16,8 +16,21 @@ As well as full support for maps up to version 1.0, tmxlite also supports these
* Parallax layers - the parallax offset property of layers is parsed, as well as each map's parallax origin, if they exist
* Layer tint colours

By default tmxlite supports zlib compressed maps, however gzip and zstd compression can be enabled at compile time, by linking the relevant external libraries:

###### Zstd compression
Tmxlite supports maps using Zstd compressed tile layers, however Zstd needs to be linked externally. To configure CMake or meson to use Zstd add the definition `USE_ZSTD` and set it to `true`. You may also configure your project manually, and add `-DUSE_ZSTD` to the compiler options. If you are using the `USE_EXTLIBS` option (see below) this is automatically configured for you.

#### Building
Either use the included Visual Studio project file if you are on Windows or the CMake file to generate project files for your compiler of choice. tmxlite can be built as both static or shared libraries, or simply include the source files in your own project.
Either use the included Visual Studio project file if you are on Windows or the CMake file to generate project files for your compiler of choice. tmxlite can be built as both static or shared libraries, or simply include the source files in your own project. The following options are available for CMake configuration:

* `TMXLITE_STATIC_LIB` - Set this to true to build a static library, default false
* `PROJECT_STATIC_RUNTIME` - Statically link the cstd libraries, default false
* `USE_RTTI` - Enable runtime type information, default true
* `USE_EXTLIBS` - Use externally linked pugixml, zlib and Zstd libraries, default false
* `USE_ZSTD` - Use externally linked Zstd library, required for Zstd compressed maps. Default is false, but is overridden if `USE_EXTLIBS` is true

Configuring with meson is also possible, see `meson_options.txt` for details.

#### Quick Start
There is a getting started page on the Github wiki [here](https://github.com/fallahn/tmxlite/wiki/Quick-Start).
Expand All @@ -32,7 +45,7 @@ Doxygen generated API documentation can be found online [here](https://codedocs.
using the doxy file in the tmxlite/documentation/ directory.

#### Important information
tmxlite uses [pugixml](https://pugixml.org/) and [miniz](https://github.com/richgel999/miniz) which are included in the repository, although external zlib and pugixml libraries can be used. Add `-DUSE_EXTLIBS` to your compiler's definitions or when configuring CMake set `USE_EXTLIBS` to TRUE.
tmxlite uses [pugixml](https://pugixml.org/) and [miniz](https://github.com/richgel999/miniz) which are included in the repository, although external zlib and pugixml libraries can be used. Add `-DUSE_EXTLIBS` to your compiler's definitions or when configuring CMake set `USE_EXTLIBS` to TRUE. This will also automatically include Zstd.

***

Expand Down
22 changes: 19 additions & 3 deletions tmxlite/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ SET(PROJECT_STATIC_RUNTIME FALSE CACHE BOOL "Use statically linked standard/runt

SET(USE_RTTI TRUE CACHE BOOL "Use run time type information?")

SET(USE_EXTLIBS FALSE CACHE BOOL "Use external zlib and pugixml libraries instead of the included source?")
SET(USE_EXTLIBS FALSE CACHE BOOL "Use external zlib, zstd and pugixml libraries instead of the included source?")
SET(USE_ZSTD FALSE CACHE BOOL "Enable zstd compression? (Already set to true if USE_EXTLIBS is true)")

if(USE_RTTI)
if(CMAKE_COMPILER_IS_GNUCXX OR APPLE)
Expand Down Expand Up @@ -62,17 +63,28 @@ include(${PROJECT_DIR}/CMakeLists.txt)
#if we want external zip and xml libs find them and tell the compiler
if(USE_EXTLIBS)
add_definitions(-DUSE_EXTLIBS)
add_definitions(-DUSE_ZSTD)

SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/modules/")

find_package(ZLIB REQUIRED)
find_package(PUGIXML REQUIRED)
find_package(Zstd REQUIRED)

include_directories(${ZLIB_INCLUDE_DIRS} ${PUGIXML_INCLUDE_DIR})
include_directories(${ZLIB_INCLUDE_DIRS} ${PUGIXML_INCLUDE_DIR} ${ZSTD_INCLUDE_DIR})

else()
#add miniz and pugixml from source
SET(PROJECT_SRC ${PROJECT_SRC} ${LIB_SRC})

if(USE_ZSTD)
add_definitions(-DUSE_ZSTD)

SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/modules/")
find_package(Zstd REQUIRED)
include_directories(${ZSTD_INCLUDE_DIR})
endif()

endif()

if(WIN32)
Expand All @@ -90,7 +102,11 @@ else()
endif()

if(USE_EXTLIBS)
target_link_libraries(${PROJECT_NAME} ${ZLIB_LIBRARIES} ${PUGIXML_LIBRARY})
target_link_libraries(${PROJECT_NAME} ${ZLIB_LIBRARIES} ${PUGIXML_LIBRARY} ${ZSTD_LIBRARY})
else()
if(USE_ZSTD)
target_link_libraries(${PROJECT_NAME} ${ZSTD_LIBRARY})
endif()
endif()

configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tmxlite.pc.in ${CMAKE_CURRENT_BINARY_DIR}/tmxlite.pc
Expand Down
41 changes: 41 additions & 0 deletions tmxlite/cmake/modules/FindZstd.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

#
# - Try to find Facebook zstd library
# This will define
# ZSTD_FOUND
# ZSTD_INCLUDE_DIR
# ZSTD_LIBRARY
#

find_path(ZSTD_INCLUDE_DIR NAMES zstd.h)

find_library(ZSTD_LIBRARY_DEBUG NAMES zstdd zstd_staticd)
find_library(ZSTD_LIBRARY_RELEASE NAMES zstd zstd_static)

include(SelectLibraryConfigurations)
SELECT_LIBRARY_CONFIGURATIONS(ZSTD)

include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(
ZSTD DEFAULT_MSG
ZSTD_LIBRARY ZSTD_INCLUDE_DIR
)

if (ZSTD_FOUND)
message(STATUS "Found Zstd: ${ZSTD_LIBRARY}")
endif()

mark_as_advanced(ZSTD_INCLUDE_DIR ZSTD_LIBRARY)
71 changes: 64 additions & 7 deletions tmxlite/src/TileLayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,15 @@ source distribution.

#ifdef USE_EXTLIBS
#include <pugixml.hpp>
#include <zstd.h>
#else
#include "detail/pugixml.hpp"
#endif

#ifdef USE_ZSTD
#include <zstd.h>
#endif

#include <tmxlite/FreeFuncs.hpp>
#include <tmxlite/TileLayer.hpp>
#include <tmxlite/detail/Log.hpp>
Expand All @@ -38,6 +44,17 @@ source distribution.

using namespace tmx;

namespace
{
struct CompressionType final
{
enum
{
Zlib, GZip, Zstd, None
};
};
}

TileLayer::TileLayer(std::size_t tileCount)
: m_tileCount (tileCount)
{
Expand Down Expand Up @@ -100,7 +117,7 @@ void TileLayer::parse(const pugi::xml_node& node, Map*)
//private
void TileLayer::parseBase64(const pugi::xml_node& node)
{
auto processDataString = [](std::string dataString, std::size_t tileCount, bool compressed)->std::vector<std::uint32_t>
auto processDataString = [](std::string dataString, std::size_t tileCount, std::int32_t compressionType)->std::vector<std::uint32_t>
{
std::stringstream ss;
ss << dataString;
Expand All @@ -111,19 +128,45 @@ void TileLayer::parseBase64(const pugi::xml_node& node)
std::vector<unsigned char> byteData;
byteData.reserve(expectedSize);

if (compressed)
switch (compressionType)
{
default:
byteData.insert(byteData.end(), dataString.begin(), dataString.end());
break;
case CompressionType::Zstd:
#if defined USE_ZSTD || defined USE_EXTLIBS
{
std::size_t dataSize = dataString.length() * sizeof(unsigned char);
std::size_t result = ZSTD_decompress(byteData.data(), expectedSize, &dataString[0], dataSize);

if (ZSTD_isError(result))
{
std::string err = ZSTD_getErrorName(result);
LOG("Failed to decompress layer data, node skipped.\nError: " + err, Logger::Type::Error);
}
}
#else
Logger::log("Library must be built with USE_EXTLIBS or USE_ZSTD for Zstd compression", Logger::Type::Error);
return {};
#endif
case CompressionType::GZip:
#ifndef USE_EXTLIBS
Logger::log("Library must be built with USE_EXTLIBS for GZip compression", Logger::Type::Error);
return {};
#endif
//[[fallthrough]];
case CompressionType::Zlib:
{
//unzip
std::size_t dataSize = dataString.length() * sizeof(unsigned char);

if (!decompress(dataString.c_str(), byteData, dataSize, expectedSize))
{
LOG("Failed to decompress layer data, node skipped.", Logger::Type::Error);
return {};
}
}
else
{
byteData.insert(byteData.end(), dataString.begin(), dataString.end());
break;
}

//data stream is in bytes so we need to OR into 32 bit values
Expand All @@ -138,6 +181,20 @@ void TileLayer::parseBase64(const pugi::xml_node& node)
return IDs;
};

std::int32_t compressionType = CompressionType::None;
std::string compression = node.attribute("compression").as_string();
if (compression == "gzip")
{
compressionType = CompressionType::GZip;
}
else if (compression == "zlib")
{
compressionType = CompressionType::Zlib;
}
else if (compression == "zstd")
{
compressionType = CompressionType::Zstd;
}

std::string data = node.text().as_string();
if (data.empty())
Expand All @@ -159,7 +216,7 @@ void TileLayer::parseBase64(const pugi::xml_node& node)
chunk.size.x = childNode.attribute("width").as_int();
chunk.size.y = childNode.attribute("height").as_int();

auto IDs = processDataString(dataString, (chunk.size.x * chunk.size.y), node.attribute("compression"));
auto IDs = processDataString(dataString, (chunk.size.x * chunk.size.y), compressionType);

if (!IDs.empty())
{
Expand All @@ -179,7 +236,7 @@ void TileLayer::parseBase64(const pugi::xml_node& node)
}
else
{
auto IDs = processDataString(data, m_tileCount, node.attribute("compression"));
auto IDs = processDataString(data, m_tileCount, compressionType);
createTiles(IDs, m_tiles);
}
}
Expand Down
24 changes: 23 additions & 1 deletion tmxlite/src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,30 @@ if get_option('use_extlibs')
'Tileset.cpp',
install: true,
include_directories: incdir,
dependencies: [zdep, pugidep]
dependencies: [zdep, pugidep, zstddep]
)
else

if get_option('use_zstd')

tmxlite_lib = library(meson.project_name() + binary_postfix,
'detail/pugixml.cpp',
'FreeFuncs.cpp',
'ImageLayer.cpp',
'Map.cpp',
'miniz.c',
'Object.cpp',
'ObjectGroup.cpp',
'Property.cpp',
'TileLayer.cpp',
'LayerGroup.cpp',
'Tileset.cpp',
install: true,
include_directories: incdir,
dependencies: zstddep
)
else

tmxlite_lib = library(meson.project_name() + binary_postfix,
'detail/pugixml.cpp',
'FreeFuncs.cpp',
Expand All @@ -29,6 +50,7 @@ else
install: true,
include_directories: incdir,
)
endif
endif

tmxlite_dep = declare_dependency(
Expand Down

0 comments on commit 2755e96

Please sign in to comment.