From 9813d9263e0b45f46b90579ea217117da823dad7 Mon Sep 17 00:00:00 2001 From: Stanislaw Halik Date: Sat, 29 Oct 2022 16:51:36 +0200 Subject: convert line endings --- .gitattributes | 4 + serialize/binary-reader.hpp | 98 ++++---- serialize/binary-reader.inl | 166 ++++++------- serialize/binary-writer.hpp | 46 ++-- serialize/world-impl.hpp | 120 +++++----- serialize/world-reader.cpp | 302 ++++++++++++------------ serialize/world-writer.cpp | 556 ++++++++++++++++++++++---------------------- src/tile-image.cpp | 22 +- src/tile-image.hpp | 40 ++-- test/serializer.cpp | 114 ++++----- 10 files changed, 736 insertions(+), 732 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..89aed297 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +*.[ch]pp text eol=lf +*.[ch] text eol=lf +*.inl text eol=lf +*.rc text eol=lf diff --git a/serialize/binary-reader.hpp b/serialize/binary-reader.hpp index 2d09d51b..881ed88f 100644 --- a/serialize/binary-reader.hpp +++ b/serialize/binary-reader.hpp @@ -1,49 +1,49 @@ -#pragma once -#include "binary-serializer.hpp" -#include -#include - -namespace floormat::Serialize { - -template -concept char_sequence = requires(T& x, const T& cx) { - requires std::same_as; - requires std::same_as; - requires std::forward_iterator; - requires std::forward_iterator; - requires std::same_as>; - requires std::same_as>; -}; - -template -concept string_input_iterator = requires(It it) { - requires std::forward_iterator; - requires std::is_same_v>; -}; - -template -struct binary_reader final { - template explicit constexpr binary_reader(const Seq& seq) noexcept; - constexpr binary_reader(It begin, It end) noexcept; - constexpr void assert_end() noexcept; - - template constexpr T read() noexcept; - template constexpr std::array read() noexcept; - constexpr std::size_t bytes_read() const noexcept { return num_bytes_read; } - template constexpr auto read_asciiz_string() noexcept; - -private: - std::size_t num_bytes_read = 0; - It it, end; -}; - -template -binary_reader& operator>>(binary_reader& reader, T& x) noexcept; - -template binary_reader(It&& begin, It&& end) -> binary_reader>; - -template -binary_reader(Array&& array) -> binary_reader>; - -} // namespace floormat::Serialize - +#pragma once +#include "binary-serializer.hpp" +#include +#include + +namespace floormat::Serialize { + +template +concept char_sequence = requires(T& x, const T& cx) { + requires std::same_as; + requires std::same_as; + requires std::forward_iterator; + requires std::forward_iterator; + requires std::same_as>; + requires std::same_as>; +}; + +template +concept string_input_iterator = requires(It it) { + requires std::forward_iterator; + requires std::is_same_v>; +}; + +template +struct binary_reader final { + template explicit constexpr binary_reader(const Seq& seq) noexcept; + constexpr binary_reader(It begin, It end) noexcept; + constexpr void assert_end() noexcept; + + template constexpr T read() noexcept; + template constexpr std::array read() noexcept; + constexpr std::size_t bytes_read() const noexcept { return num_bytes_read; } + template constexpr auto read_asciiz_string() noexcept; + +private: + std::size_t num_bytes_read = 0; + It it, end; +}; + +template +binary_reader& operator>>(binary_reader& reader, T& x) noexcept; + +template binary_reader(It&& begin, It&& end) -> binary_reader>; + +template +binary_reader(Array&& array) -> binary_reader>; + +} // namespace floormat::Serialize + diff --git a/serialize/binary-reader.inl b/serialize/binary-reader.inl index b190f463..2230dff6 100644 --- a/serialize/binary-reader.inl +++ b/serialize/binary-reader.inl @@ -1,83 +1,83 @@ -#pragma once -#include "binary-reader.hpp" -#include "compat/assert.hpp" - -namespace floormat::Serialize { - -template -template -constexpr binary_reader::binary_reader(const Seq& seq) noexcept - : it{std::begin(seq)}, end{std::end(seq)} -{} - -template -constexpr binary_reader::binary_reader(It begin, It end) noexcept : - it{begin}, end{end} -{} - -template -template -constexpr T binary_reader::read() noexcept -{ - constexpr std::size_t N = sizeof(T); - fm_assert((std::ptrdiff_t)N <= std::distance(it, end)); - num_bytes_read += N; - char buf[N]; - for (std::size_t i = 0; i < N; i++) - buf[i] = *it++; - return maybe_byteswap(std::bit_cast(buf)); -} - -template -template -constexpr std::array binary_reader::read() noexcept -{ - std::array array; - if (std::is_constant_evaluated()) - array = {}; - fm_assert(N <= (std::size_t)std::distance(it, end)); - num_bytes_read += N; - for (std::size_t i = 0; i < N; i++) - array[i] = *it++; - return array; -} - -template -constexpr void binary_reader::assert_end() noexcept -{ - fm_assert(it == end); -} - -template -binary_reader& operator>>(binary_reader& reader, T& x) noexcept -{ - x = reader.template read(); - return reader; -} - -template -template -constexpr auto binary_reader::read_asciiz_string() noexcept -{ - static_assert(MAX > 0); - - struct fixed_string final { - char buf[MAX]; - std::size_t len; - }; - - fixed_string ret; - for (std::size_t i = 0; i < MAX && it != end; i++) - { - const char c = *it++; - ret.buf[i] = c; - if (c == '\0') - { - ret.len = i; - return ret; - } - } - fm_abort("can't find string terminator"); -} - -} // namespace floormat::Serialize +#pragma once +#include "binary-reader.hpp" +#include "compat/assert.hpp" + +namespace floormat::Serialize { + +template +template +constexpr binary_reader::binary_reader(const Seq& seq) noexcept + : it{std::begin(seq)}, end{std::end(seq)} +{} + +template +constexpr binary_reader::binary_reader(It begin, It end) noexcept : + it{begin}, end{end} +{} + +template +template +constexpr T binary_reader::read() noexcept +{ + constexpr std::size_t N = sizeof(T); + fm_assert((std::ptrdiff_t)N <= std::distance(it, end)); + num_bytes_read += N; + char buf[N]; + for (std::size_t i = 0; i < N; i++) + buf[i] = *it++; + return maybe_byteswap(std::bit_cast(buf)); +} + +template +template +constexpr std::array binary_reader::read() noexcept +{ + std::array array; + if (std::is_constant_evaluated()) + array = {}; + fm_assert(N <= (std::size_t)std::distance(it, end)); + num_bytes_read += N; + for (std::size_t i = 0; i < N; i++) + array[i] = *it++; + return array; +} + +template +constexpr void binary_reader::assert_end() noexcept +{ + fm_assert(it == end); +} + +template +binary_reader& operator>>(binary_reader& reader, T& x) noexcept +{ + x = reader.template read(); + return reader; +} + +template +template +constexpr auto binary_reader::read_asciiz_string() noexcept +{ + static_assert(MAX > 0); + + struct fixed_string final { + char buf[MAX]; + std::size_t len; + }; + + fixed_string ret; + for (std::size_t i = 0; i < MAX && it != end; i++) + { + const char c = *it++; + ret.buf[i] = c; + if (c == '\0') + { + ret.len = i; + return ret; + } + } + fm_abort("can't find string terminator"); +} + +} // namespace floormat::Serialize diff --git a/serialize/binary-writer.hpp b/serialize/binary-writer.hpp index 13580eb0..098f9aa2 100644 --- a/serialize/binary-writer.hpp +++ b/serialize/binary-writer.hpp @@ -1,23 +1,23 @@ -#pragma once -#include "binary-serializer.hpp" -#include -#include - -namespace floormat::Serialize { - -template It> -struct binary_writer final { - explicit constexpr binary_writer(It it) noexcept; - template constexpr void write(T x) noexcept; - constexpr void write_asciiz_string(StringView str) noexcept; - constexpr std::size_t bytes_written() const noexcept { return _bytes_written; } - -private: - It it; - std::size_t _bytes_written; -}; - -template It, serializable T> -constexpr binary_writer& operator<<(binary_writer& writer, T x) noexcept; - -} // namespace floormat::Serialize +#pragma once +#include "binary-serializer.hpp" +#include +#include + +namespace floormat::Serialize { + +template It> +struct binary_writer final { + explicit constexpr binary_writer(It it) noexcept; + template constexpr void write(T x) noexcept; + constexpr void write_asciiz_string(StringView str) noexcept; + constexpr std::size_t bytes_written() const noexcept { return _bytes_written; } + +private: + It it; + std::size_t _bytes_written; +}; + +template It, serializable T> +constexpr binary_writer& operator<<(binary_writer& writer, T x) noexcept; + +} // namespace floormat::Serialize diff --git a/serialize/world-impl.hpp b/serialize/world-impl.hpp index 54e4622c..a0414606 100644 --- a/serialize/world-impl.hpp +++ b/serialize/world-impl.hpp @@ -1,60 +1,60 @@ -#ifndef FM_SERIALIZE_WORLD_IMPL -#error "not meant to be included directly" -#endif - -#pragma once -#include "src/tile.hpp" -#include -#include - -namespace floormat::Serialize { - -namespace { - -using tilemeta = std::uint8_t; -using varid = decltype(tile_image::variant); -using atlasid = std::uint16_t; -using chunksiz = std::uint16_t; -using enum tile::pass_mode; - -template constexpr inline T int_max = std::numeric_limits::max(); - -#define file_magic ".floormat.save" - -constexpr inline std::size_t atlas_name_max = 128; -constexpr inline auto null_atlas = (atlasid)-1LL; - -constexpr inline std::uint16_t proto_version = 1; -constexpr inline auto chunk_magic = (std::uint16_t)~0xc0d3; - -constexpr inline std::underlying_type_t pass_mask = pass_blocked | pass_shoot_through | pass_ok; -constexpr inline auto pass_bits = std::bit_width(pass_mask); - -enum : tilemeta { - meta_ground = 1 << (pass_bits + 0), - meta_wall_n = 1 << (pass_bits + 1), - meta_wall_w = 1 << (pass_bits + 2), - meta_short_atlasid = 1 << (pass_bits + 3), - meta_short_variant = 1 << (pass_bits + 4), -}; - -} // namespace - -} // namespace floormat::Serialize - -namespace floormat { - -namespace { - -struct FILE_raii final { - FILE_raii(FILE* s) noexcept : s{s} {} - ~FILE_raii() noexcept { close(); } - operator FILE*() noexcept { return s; } - void close() noexcept { if (s) ::fclose(s); s = nullptr; } -private: - FILE* s; -}; - -} // namespace - -} // namespace floormat +#ifndef FM_SERIALIZE_WORLD_IMPL +#error "not meant to be included directly" +#endif + +#pragma once +#include "src/tile.hpp" +#include +#include + +namespace floormat::Serialize { + +namespace { + +using tilemeta = std::uint8_t; +using varid = decltype(tile_image::variant); +using atlasid = std::uint16_t; +using chunksiz = std::uint16_t; +using enum tile::pass_mode; + +template constexpr inline T int_max = std::numeric_limits::max(); + +#define file_magic ".floormat.save" + +constexpr inline std::size_t atlas_name_max = 128; +constexpr inline auto null_atlas = (atlasid)-1LL; + +constexpr inline std::uint16_t proto_version = 1; +constexpr inline auto chunk_magic = (std::uint16_t)~0xc0d3; + +constexpr inline std::underlying_type_t pass_mask = pass_blocked | pass_shoot_through | pass_ok; +constexpr inline auto pass_bits = std::bit_width(pass_mask); + +enum : tilemeta { + meta_ground = 1 << (pass_bits + 0), + meta_wall_n = 1 << (pass_bits + 1), + meta_wall_w = 1 << (pass_bits + 2), + meta_short_atlasid = 1 << (pass_bits + 3), + meta_short_variant = 1 << (pass_bits + 4), +}; + +} // namespace + +} // namespace floormat::Serialize + +namespace floormat { + +namespace { + +struct FILE_raii final { + FILE_raii(FILE* s) noexcept : s{s} {} + ~FILE_raii() noexcept { close(); } + operator FILE*() noexcept { return s; } + void close() noexcept { if (s) ::fclose(s); s = nullptr; } +private: + FILE* s; +}; + +} // namespace + +} // namespace floormat diff --git a/serialize/world-reader.cpp b/serialize/world-reader.cpp index 6172703b..385d6e39 100644 --- a/serialize/world-reader.cpp +++ b/serialize/world-reader.cpp @@ -1,151 +1,151 @@ -#define FM_SERIALIZE_WORLD_IMPL -#include "world-impl.hpp" -#include "binary-reader.inl" -#include "src/world.hpp" -#include "src/loader.hpp" -#include "src/tile-atlas.hpp" - -namespace floormat::Serialize { - -namespace { - -struct reader_state final { - explicit reader_state(world& world) noexcept; - void deserialize_world(ArrayView buf); - -private: - using reader_t = binary_reader{}.cbegin())>; - - std::shared_ptr lookup_atlas(atlasid id); - void read_atlases(reader_t& reader); - void read_chunks(reader_t& reader); - - std::unordered_map> atlases; - struct world* world; -}; - -reader_state::reader_state(struct world& world) noexcept : world{&world} {} - -void reader_state::read_atlases(reader_t& s) -{ - const auto N = s.read(); - atlases.reserve(N * 2); - for (atlasid i = 0; i < N; i++) - { - Vector2ub size; - s >> size[0]; - s >> size[1]; - const auto& [buf, len] = s.read_asciiz_string(); - atlases[i] = loader.tile_atlas({buf, len}, size); - } -} - -std::shared_ptr reader_state::lookup_atlas(atlasid id) -{ - if (auto it = atlases.find(id); it != atlases.end()) - return it->second; - else - fm_abort("not such atlas: '%zu'", (std::size_t)id); -} - -void reader_state::read_chunks(reader_t& s) -{ - const auto N = s.read(); - - for (std::size_t i = 0; i < N; i++) - { - std::decay_t magic; - s >> magic; - if (magic != chunk_magic) - fm_abort("bad chunk magic"); - chunk_coords coord; - s >> coord.x; - s >> coord.y; - auto& chunk = (*world)[coord]; - for (std::size_t i = 0; i < TILE_COUNT; i++) - { - const tilemeta flags = s.read(); - tile& t = chunk[i]; - using uchar = std::uint8_t; - const auto make_atlas = [&]() -> tile_image { - auto id = flags & meta_short_atlasid ? (atlasid)(s.read()) : s.read(); - auto v = flags & meta_short_variant ? (varid) (s.read()) : s.read(); - auto atlas = lookup_atlas(id); - fm_assert(v < atlas->num_tiles()); - return { atlas, v }; - }; - - if (flags & meta_ground) - t.ground = make_atlas(); - if (flags & meta_wall_n) - t.wall_north = make_atlas(); - if (flags & meta_wall_w) - t.wall_west = make_atlas(); - - switch (auto x = flags & pass_mask) - { - case tile::pass_shoot_through: - case tile::pass_blocked: - case tile::pass_ok: - t.passability = (tile::pass_mode)x; - break; - default: - fm_abort("bad pass mode '%zu' for tile %zu", i, (std::size_t)x); - } - } - } -} - -void reader_state::deserialize_world(ArrayView buf) -{ - auto s = binary_reader{buf}; - if (!!::memcmp(s.read().data(), file_magic, std::size(file_magic)-1)) - fm_abort("bad magic"); - std::decay_t proto; - s >> proto; - if (proto != proto_version) - fm_abort("bad proto version '%zu' (should be '%zu')", - (std::size_t)proto, (std::size_t)proto_version); - read_atlases(s); - read_chunks(s); - s.assert_end(); -} - -} // namespace - -} // namespace floormat::Serialize - -namespace floormat { - -world world::deserialize(StringView filename) -{ - char errbuf[128]; - constexpr auto strerror = [] (char (&buf)[N]) -> const char* { - ::strerror_s(buf, std::size(buf), errno); - return buf; - }; - fm_assert(filename.flags() & StringViewFlag::NullTerminated); - FILE_raii f = ::fopen(filename.data(), "rb"); - if (!f) - fm_abort("fopen(\"%s\", \"r\"): %s", filename.data(), strerror(errbuf)); - if (int ret = ::fseek(f, 0, SEEK_END); ret != 0) - fm_abort("fseek(SEEK_END): %s", strerror(errbuf)); - std::size_t len; - if (auto len_ = ::ftell(f); len_ >= 0) - len = (std::size_t)len_; - else - fm_abort("ftell: %s", strerror(errbuf)); - if (int ret = ::fseek(f, 0, SEEK_SET); ret != 0) - fm_abort("fseek(SEEK_SET): %s", strerror(errbuf)); - auto buf_ = std::make_unique(len); - - if (auto ret = ::fread(&buf_[0], 1, len, f); ret != len) - fm_abort("fread short read: %s", strerror(errbuf)); - - world w; - Serialize::reader_state s{w}; - s.deserialize_world({buf_.get(), len}); - return w; -} - -} // namespace floormat +#define FM_SERIALIZE_WORLD_IMPL +#include "world-impl.hpp" +#include "binary-reader.inl" +#include "src/world.hpp" +#include "src/loader.hpp" +#include "src/tile-atlas.hpp" + +namespace floormat::Serialize { + +namespace { + +struct reader_state final { + explicit reader_state(world& world) noexcept; + void deserialize_world(ArrayView buf); + +private: + using reader_t = binary_reader{}.cbegin())>; + + std::shared_ptr lookup_atlas(atlasid id); + void read_atlases(reader_t& reader); + void read_chunks(reader_t& reader); + + std::unordered_map> atlases; + struct world* world; +}; + +reader_state::reader_state(struct world& world) noexcept : world{&world} {} + +void reader_state::read_atlases(reader_t& s) +{ + const auto N = s.read(); + atlases.reserve(N * 2); + for (atlasid i = 0; i < N; i++) + { + Vector2ub size; + s >> size[0]; + s >> size[1]; + const auto& [buf, len] = s.read_asciiz_string(); + atlases[i] = loader.tile_atlas({buf, len}, size); + } +} + +std::shared_ptr reader_state::lookup_atlas(atlasid id) +{ + if (auto it = atlases.find(id); it != atlases.end()) + return it->second; + else + fm_abort("not such atlas: '%zu'", (std::size_t)id); +} + +void reader_state::read_chunks(reader_t& s) +{ + const auto N = s.read(); + + for (std::size_t i = 0; i < N; i++) + { + std::decay_t magic; + s >> magic; + if (magic != chunk_magic) + fm_abort("bad chunk magic"); + chunk_coords coord; + s >> coord.x; + s >> coord.y; + auto& chunk = (*world)[coord]; + for (std::size_t i = 0; i < TILE_COUNT; i++) + { + const tilemeta flags = s.read(); + tile& t = chunk[i]; + using uchar = std::uint8_t; + const auto make_atlas = [&]() -> tile_image { + auto id = flags & meta_short_atlasid ? (atlasid)(s.read()) : s.read(); + auto v = flags & meta_short_variant ? (varid) (s.read()) : s.read(); + auto atlas = lookup_atlas(id); + fm_assert(v < atlas->num_tiles()); + return { atlas, v }; + }; + + if (flags & meta_ground) + t.ground = make_atlas(); + if (flags & meta_wall_n) + t.wall_north = make_atlas(); + if (flags & meta_wall_w) + t.wall_west = make_atlas(); + + switch (auto x = flags & pass_mask) + { + case tile::pass_shoot_through: + case tile::pass_blocked: + case tile::pass_ok: + t.passability = (tile::pass_mode)x; + break; + default: + fm_abort("bad pass mode '%zu' for tile %zu", i, (std::size_t)x); + } + } + } +} + +void reader_state::deserialize_world(ArrayView buf) +{ + auto s = binary_reader{buf}; + if (!!::memcmp(s.read().data(), file_magic, std::size(file_magic)-1)) + fm_abort("bad magic"); + std::decay_t proto; + s >> proto; + if (proto != proto_version) + fm_abort("bad proto version '%zu' (should be '%zu')", + (std::size_t)proto, (std::size_t)proto_version); + read_atlases(s); + read_chunks(s); + s.assert_end(); +} + +} // namespace + +} // namespace floormat::Serialize + +namespace floormat { + +world world::deserialize(StringView filename) +{ + char errbuf[128]; + constexpr auto strerror = [] (char (&buf)[N]) -> const char* { + ::strerror_s(buf, std::size(buf), errno); + return buf; + }; + fm_assert(filename.flags() & StringViewFlag::NullTerminated); + FILE_raii f = ::fopen(filename.data(), "rb"); + if (!f) + fm_abort("fopen(\"%s\", \"r\"): %s", filename.data(), strerror(errbuf)); + if (int ret = ::fseek(f, 0, SEEK_END); ret != 0) + fm_abort("fseek(SEEK_END): %s", strerror(errbuf)); + std::size_t len; + if (auto len_ = ::ftell(f); len_ >= 0) + len = (std::size_t)len_; + else + fm_abort("ftell: %s", strerror(errbuf)); + if (int ret = ::fseek(f, 0, SEEK_SET); ret != 0) + fm_abort("fseek(SEEK_SET): %s", strerror(errbuf)); + auto buf_ = std::make_unique(len); + + if (auto ret = ::fread(&buf_[0], 1, len, f); ret != len) + fm_abort("fread short read: %s", strerror(errbuf)); + + world w; + Serialize::reader_state s{w}; + s.deserialize_world({buf_.get(), len}); + return w; +} + +} // namespace floormat diff --git a/serialize/world-writer.cpp b/serialize/world-writer.cpp index 84d894bd..a55e6eb1 100644 --- a/serialize/world-writer.cpp +++ b/serialize/world-writer.cpp @@ -1,278 +1,278 @@ -#define FM_SERIALIZE_WORLD_IMPL -#include "world-impl.hpp" - -#include "src/tile-atlas.hpp" -#include "binary-writer.inl" -#include "src/global-coords.hpp" -#include "src/chunk.hpp" -#include "src/world.hpp" -#include -#include -#include -#include - -namespace Path = Corrade::Utility::Path; - -namespace floormat::Serialize { - -namespace { - -struct interned_atlas final { - const tile_atlas* img; - atlasid index; -}; - -struct writer_state final { - writer_state(const world& world); - ArrayView serialize_world(); - fm_DECLARE_DEFAULT_MOVE_ASSIGNMENT_(writer_state); - fm_DECLARE_DEPRECATED_COPY_ASSIGNMENT(writer_state); - -private: - atlasid intern_atlas(const tile_image& img); - atlasid maybe_intern_atlas(const tile_image& img); - void serialize_chunk(const chunk& c, chunk_coords coord); - void serialize_atlases(); - - const struct world* world; - std::vector atlas_buf, chunk_buf, file_buf; - std::vector> chunk_bufs; - std::unordered_map tile_images; -}; - -constexpr auto tile_size = sizeof(tilemeta) + (sizeof(atlasid) + sizeof(varid)) * 3; - -constexpr auto chunkbuf_size = - sizeof(chunk_magic) + sizeof(chunk_coords) + tile_size * TILE_COUNT; - -#ifdef __GNUG__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#elif defined _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4996) -#endif - -writer_state::writer_state(const struct world& world) : world{&world} -{ - chunk_buf.reserve(chunkbuf_size); - chunk_bufs.reserve(world.chunks().size()); - atlas_buf.reserve(atlas_name_max * 64); -} - -#ifdef __GNUG__ -#pragma GCC diagnostic pop -#elif defined _MSC_VER -#pragma warning(pop) -#endif - -atlasid writer_state::intern_atlas(const tile_image& img) -{ - const void* const ptr = img.atlas.get(); - fm_debug_assert(ptr != nullptr); - if (auto it = tile_images.find(ptr); it != tile_images.end()) - return it->second.index; - else - return (tile_images[ptr] = { &*img.atlas, (atlasid)tile_images.size() }).index; -} - -atlasid writer_state::maybe_intern_atlas(const tile_image& img) -{ - return img ? intern_atlas(img) : null_atlas; -} - -void writer_state::serialize_chunk(const chunk& c, chunk_coords coord) -{ - fm_assert(chunk_buf.empty()); - chunk_buf.resize(chunkbuf_size); - - auto s = binary_writer{chunk_buf.begin()}; - - s << chunk_magic << coord.x << coord.y; - - for (std::size_t i = 0; i < TILE_COUNT; i++) - { - const tile& x = c[i]; - - fm_debug_assert(s.bytes_written() + tile_size <= chunkbuf_size); - - auto img_g = maybe_intern_atlas(x.ground); - auto img_n = maybe_intern_atlas(x.wall_north); - auto img_w = maybe_intern_atlas(x.wall_west); - - tilemeta flags = {}; - flags |= meta_ground * (img_g != null_atlas); - flags |= meta_wall_n * (img_n != null_atlas); - flags |= meta_wall_w * (img_w != null_atlas); - - using uchar = std::uint8_t; - - constexpr auto ashortp = [](atlasid id) { - return id == null_atlas || id == (uchar)id; - }; - constexpr auto vshortp = [](const tile_image& img) { - return !img.atlas || img.variant == (uchar)img.variant; - }; - - if (flags != 0 && ashortp(img_g) && ashortp(img_n) && ashortp(img_w)) - flags |= meta_short_atlasid; - if (flags != 0 && vshortp(x.ground) && vshortp(x.wall_north) && vshortp(x.wall_west)) - flags |= meta_short_variant; - - fm_debug_assert((x.passability & pass_mask) == x.passability); - flags |= x.passability; - - s << flags; - -#ifndef FM_NO_DEBUG - constexpr auto check_atlas = [](const tile_image& x) { - if (x.atlas) - fm_assert(x.variant < x.atlas->num_tiles()); - }; - check_atlas(x.ground); - check_atlas(x.wall_north); - check_atlas(x.wall_west); -#endif - - const auto write = [&](atlasid x, varid v) { - flags & meta_short_atlasid ? s << (uchar) x : s << x; - flags & meta_short_variant ? s << (uchar) v : s << v; - }; - - if (img_g != null_atlas) - write(img_g, x.ground.variant); - if (img_n != null_atlas) - write(img_n, x.wall_north.variant); - if (img_w != null_atlas) - write(img_w, x.wall_west.variant); - } - - const auto nbytes = s.bytes_written(); - fm_assert(nbytes <= chunkbuf_size); - - chunk_buf.resize(nbytes); - chunk_bufs.push_back(chunk_buf); - chunk_buf.clear(); -} - -void writer_state::serialize_atlases() -{ - fm_assert(tile_images.size() < int_max); - const auto sz = (atlasid)tile_images.size(); - const auto atlasbuf_size = sizeof(sz) + atlas_name_max*sz; - atlas_buf.resize(atlasbuf_size); - auto s = binary_writer{atlas_buf.begin()}; - fm_assert(sz <= int_max); - - s << sz; - - std::vector atlases; - atlases.reserve(tile_images.size()); - - for (const auto& [_, t] : tile_images) - atlases.push_back(t); - std::sort(atlases.begin(), atlases.end(), [](const auto& a, const auto& b) { - return a.index < b.index; - }); - - for (const auto& [atlas, _] : atlases) - { - const auto name = atlas->name(); - const auto namesiz = name.size(); - fm_debug_assert(s.bytes_written() + namesiz + 1 <= atlasbuf_size); - fm_assert(namesiz <= atlas_name_max - 1); // null terminated - fm_debug_assert(name.find('\0') == name.cend()); - const auto sz2 = atlas->num_tiles2(); - s << sz2[0]; s << sz2[1]; - s.write_asciiz_string(name); - } - atlas_buf.resize(s.bytes_written()); - fm_assert(s.bytes_written() <= atlasbuf_size); -} - -#ifdef __GNUG__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#elif defined _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4996) -#endif - -ArrayView writer_state::serialize_world() -{ - for (const auto& [pos, c] : world->chunks()) - { -#ifndef FM_NO_DEBUG - if (c.empty(true)) - fm_warn("chunk %hd:%hd is empty", pos.x, pos.y); -#endif - serialize_chunk(c, pos); - } - serialize_atlases(); - - using proto_t = std::decay_t; - union { chunksiz x; char bytes[sizeof x]; } c = {.x = maybe_byteswap((chunksiz)world->size())}; - union { proto_t x; char bytes[sizeof x]; } p = {.x = maybe_byteswap(proto_version)}; - fm_assert(world->size() <= int_max); - - std::size_t len = 0; - len += std::size(file_magic)-1; - len += sizeof(p.x); - len += sizeof(c.x); - for (const auto& buf : chunk_bufs) - len += buf.size(); - len += atlas_buf.size(); - file_buf.resize(len); - auto it = file_buf.begin(); - const auto copy = [&](const auto& in) { -#ifndef FM_NO_DEBUG - auto len1 = std::distance(std::cbegin(in), std::cend(in)), - len2 = std::distance(it, file_buf.end()); - fm_assert(len1 <= len2); -#endif - it = std::copy(std::cbegin(in), std::cend(in), it); - }; - copy(Containers::StringView{file_magic, std::size(file_magic)-1}); - copy(p.bytes); - copy(atlas_buf); - copy(c.bytes); - for (const auto& buf : chunk_bufs) - copy(buf); - return {file_buf.data(), file_buf.size()}; -} - -#ifdef __GNUG__ -#pragma GCC diagnostic pop -#elif defined _MSC_VER -#pragma warning(pop) -#endif - -} // namespace - -} // namespace floormat::Serialize - -namespace floormat { - -void world::serialize(StringView filename) -{ - collect(true); - char errbuf[128]; - constexpr auto strerror = [] (char (&buf)[N]) -> const char* { - ::strerror_s(buf, std::size(buf), errno); - return buf; - }; - fm_assert(filename.flags() & StringViewFlag::NullTerminated); - if (Path::exists(filename)) - Path::remove(filename); - FILE_raii file = ::fopen(filename.data(), "wb"); - if (!file) - fm_abort("fopen(\"%s\", \"w\"): %s", filename.data(), strerror(errbuf)); - Serialize::writer_state s{*this}; - const auto array = s.serialize_world(); - if (auto len = ::fwrite(array.data(), array.size(), 1, file); len != 1) - fm_abort("fwrite: %s", strerror(errbuf)); - if (int ret = ::fflush(file); ret != 0) - fm_abort("fflush: %s", strerror(errbuf)); -} - -} // namespace floormat +#define FM_SERIALIZE_WORLD_IMPL +#include "world-impl.hpp" + +#include "src/tile-atlas.hpp" +#include "binary-writer.inl" +#include "src/global-coords.hpp" +#include "src/chunk.hpp" +#include "src/world.hpp" +#include +#include +#include +#include + +namespace Path = Corrade::Utility::Path; + +namespace floormat::Serialize { + +namespace { + +struct interned_atlas final { + const tile_atlas* img; + atlasid index; +}; + +struct writer_state final { + writer_state(const world& world); + ArrayView serialize_world(); + fm_DECLARE_DEFAULT_MOVE_ASSIGNMENT_(writer_state); + fm_DECLARE_DEPRECATED_COPY_ASSIGNMENT(writer_state); + +private: + atlasid intern_atlas(const tile_image& img); + atlasid maybe_intern_atlas(const tile_image& img); + void serialize_chunk(const chunk& c, chunk_coords coord); + void serialize_atlases(); + + const struct world* world; + std::vector atlas_buf, chunk_buf, file_buf; + std::vector> chunk_bufs; + std::unordered_map tile_images; +}; + +constexpr auto tile_size = sizeof(tilemeta) + (sizeof(atlasid) + sizeof(varid)) * 3; + +constexpr auto chunkbuf_size = + sizeof(chunk_magic) + sizeof(chunk_coords) + tile_size * TILE_COUNT; + +#ifdef __GNUG__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4996) +#endif + +writer_state::writer_state(const struct world& world) : world{&world} +{ + chunk_buf.reserve(chunkbuf_size); + chunk_bufs.reserve(world.chunks().size()); + atlas_buf.reserve(atlas_name_max * 64); +} + +#ifdef __GNUG__ +#pragma GCC diagnostic pop +#elif defined _MSC_VER +#pragma warning(pop) +#endif + +atlasid writer_state::intern_atlas(const tile_image& img) +{ + const void* const ptr = img.atlas.get(); + fm_debug_assert(ptr != nullptr); + if (auto it = tile_images.find(ptr); it != tile_images.end()) + return it->second.index; + else + return (tile_images[ptr] = { &*img.atlas, (atlasid)tile_images.size() }).index; +} + +atlasid writer_state::maybe_intern_atlas(const tile_image& img) +{ + return img ? intern_atlas(img) : null_atlas; +} + +void writer_state::serialize_chunk(const chunk& c, chunk_coords coord) +{ + fm_assert(chunk_buf.empty()); + chunk_buf.resize(chunkbuf_size); + + auto s = binary_writer{chunk_buf.begin()}; + + s << chunk_magic << coord.x << coord.y; + + for (std::size_t i = 0; i < TILE_COUNT; i++) + { + const tile& x = c[i]; + + fm_debug_assert(s.bytes_written() + tile_size <= chunkbuf_size); + + auto img_g = maybe_intern_atlas(x.ground); + auto img_n = maybe_intern_atlas(x.wall_north); + auto img_w = maybe_intern_atlas(x.wall_west); + + tilemeta flags = {}; + flags |= meta_ground * (img_g != null_atlas); + flags |= meta_wall_n * (img_n != null_atlas); + flags |= meta_wall_w * (img_w != null_atlas); + + using uchar = std::uint8_t; + + constexpr auto ashortp = [](atlasid id) { + return id == null_atlas || id == (uchar)id; + }; + constexpr auto vshortp = [](const tile_image& img) { + return !img.atlas || img.variant == (uchar)img.variant; + }; + + if (flags != 0 && ashortp(img_g) && ashortp(img_n) && ashortp(img_w)) + flags |= meta_short_atlasid; + if (flags != 0 && vshortp(x.ground) && vshortp(x.wall_north) && vshortp(x.wall_west)) + flags |= meta_short_variant; + + fm_debug_assert((x.passability & pass_mask) == x.passability); + flags |= x.passability; + + s << flags; + +#ifndef FM_NO_DEBUG + constexpr auto check_atlas = [](const tile_image& x) { + if (x.atlas) + fm_assert(x.variant < x.atlas->num_tiles()); + }; + check_atlas(x.ground); + check_atlas(x.wall_north); + check_atlas(x.wall_west); +#endif + + const auto write = [&](atlasid x, varid v) { + flags & meta_short_atlasid ? s << (uchar) x : s << x; + flags & meta_short_variant ? s << (uchar) v : s << v; + }; + + if (img_g != null_atlas) + write(img_g, x.ground.variant); + if (img_n != null_atlas) + write(img_n, x.wall_north.variant); + if (img_w != null_atlas) + write(img_w, x.wall_west.variant); + } + + const auto nbytes = s.bytes_written(); + fm_assert(nbytes <= chunkbuf_size); + + chunk_buf.resize(nbytes); + chunk_bufs.push_back(chunk_buf); + chunk_buf.clear(); +} + +void writer_state::serialize_atlases() +{ + fm_assert(tile_images.size() < int_max); + const auto sz = (atlasid)tile_images.size(); + const auto atlasbuf_size = sizeof(sz) + atlas_name_max*sz; + atlas_buf.resize(atlasbuf_size); + auto s = binary_writer{atlas_buf.begin()}; + fm_assert(sz <= int_max); + + s << sz; + + std::vector atlases; + atlases.reserve(tile_images.size()); + + for (const auto& [_, t] : tile_images) + atlases.push_back(t); + std::sort(atlases.begin(), atlases.end(), [](const auto& a, const auto& b) { + return a.index < b.index; + }); + + for (const auto& [atlas, _] : atlases) + { + const auto name = atlas->name(); + const auto namesiz = name.size(); + fm_debug_assert(s.bytes_written() + namesiz + 1 <= atlasbuf_size); + fm_assert(namesiz <= atlas_name_max - 1); // null terminated + fm_debug_assert(name.find('\0') == name.cend()); + const auto sz2 = atlas->num_tiles2(); + s << sz2[0]; s << sz2[1]; + s.write_asciiz_string(name); + } + atlas_buf.resize(s.bytes_written()); + fm_assert(s.bytes_written() <= atlasbuf_size); +} + +#ifdef __GNUG__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4996) +#endif + +ArrayView writer_state::serialize_world() +{ + for (const auto& [pos, c] : world->chunks()) + { +#ifndef FM_NO_DEBUG + if (c.empty(true)) + fm_warn("chunk %hd:%hd is empty", pos.x, pos.y); +#endif + serialize_chunk(c, pos); + } + serialize_atlases(); + + using proto_t = std::decay_t; + union { chunksiz x; char bytes[sizeof x]; } c = {.x = maybe_byteswap((chunksiz)world->size())}; + union { proto_t x; char bytes[sizeof x]; } p = {.x = maybe_byteswap(proto_version)}; + fm_assert(world->size() <= int_max); + + std::size_t len = 0; + len += std::size(file_magic)-1; + len += sizeof(p.x); + len += sizeof(c.x); + for (const auto& buf : chunk_bufs) + len += buf.size(); + len += atlas_buf.size(); + file_buf.resize(len); + auto it = file_buf.begin(); + const auto copy = [&](const auto& in) { +#ifndef FM_NO_DEBUG + auto len1 = std::distance(std::cbegin(in), std::cend(in)), + len2 = std::distance(it, file_buf.end()); + fm_assert(len1 <= len2); +#endif + it = std::copy(std::cbegin(in), std::cend(in), it); + }; + copy(Containers::StringView{file_magic, std::size(file_magic)-1}); + copy(p.bytes); + copy(atlas_buf); + copy(c.bytes); + for (const auto& buf : chunk_bufs) + copy(buf); + return {file_buf.data(), file_buf.size()}; +} + +#ifdef __GNUG__ +#pragma GCC diagnostic pop +#elif defined _MSC_VER +#pragma warning(pop) +#endif + +} // namespace + +} // namespace floormat::Serialize + +namespace floormat { + +void world::serialize(StringView filename) +{ + collect(true); + char errbuf[128]; + constexpr auto strerror = [] (char (&buf)[N]) -> const char* { + ::strerror_s(buf, std::size(buf), errno); + return buf; + }; + fm_assert(filename.flags() & StringViewFlag::NullTerminated); + if (Path::exists(filename)) + Path::remove(filename); + FILE_raii file = ::fopen(filename.data(), "wb"); + if (!file) + fm_abort("fopen(\"%s\", \"w\"): %s", filename.data(), strerror(errbuf)); + Serialize::writer_state s{*this}; + const auto array = s.serialize_world(); + if (auto len = ::fwrite(array.data(), array.size(), 1, file); len != 1) + fm_abort("fwrite: %s", strerror(errbuf)); + if (int ret = ::fflush(file); ret != 0) + fm_abort("fflush: %s", strerror(errbuf)); +} + +} // namespace floormat diff --git a/src/tile-image.cpp b/src/tile-image.cpp index 4210db94..784bd4f5 100644 --- a/src/tile-image.cpp +++ b/src/tile-image.cpp @@ -1,11 +1,11 @@ -#include "tile-image.hpp" - -namespace floormat { - -bool operator==(const tile_image& a, const tile_image& b) noexcept -{ - return a.atlas == b.atlas && a.variant == b.variant; -} - -} // namespace floormat - +#include "tile-image.hpp" + +namespace floormat { + +bool operator==(const tile_image& a, const tile_image& b) noexcept +{ + return a.atlas == b.atlas && a.variant == b.variant; +} + +} // namespace floormat + diff --git a/src/tile-image.hpp b/src/tile-image.hpp index 6c90a2d5..79a0bacc 100644 --- a/src/tile-image.hpp +++ b/src/tile-image.hpp @@ -1,20 +1,20 @@ -#pragma once -#include "compat/integer-types.hpp" -#include -#include - -namespace floormat { - -struct tile_atlas; - -struct tile_image final -{ - std::shared_ptr atlas; - std::uint16_t variant = (std::uint16_t)-1; - - explicit operator bool() const noexcept { return !!atlas; } -}; - -bool operator==(const tile_image& a, const tile_image& b) noexcept; - -} // namespace floormat +#pragma once +#include "compat/integer-types.hpp" +#include +#include + +namespace floormat { + +struct tile_atlas; + +struct tile_image final +{ + std::shared_ptr atlas; + std::uint16_t variant = (std::uint16_t)-1; + + explicit operator bool() const noexcept { return !!atlas; } +}; + +bool operator==(const tile_image& a, const tile_image& b) noexcept; + +} // namespace floormat diff --git a/test/serializer.cpp b/test/serializer.cpp index 7970bdac..db08a9f8 100644 --- a/test/serializer.cpp +++ b/test/serializer.cpp @@ -1,57 +1,57 @@ -#include "app.hpp" -#include "src/world.hpp" -#include "src/loader.hpp" -#include "src/tile-atlas.hpp" -#include - -namespace floormat { - -namespace Path = Corrade::Utility::Path; - -static chunk make_test_chunk() -{ - auto metal1 = loader.tile_atlas("metal1", {2, 2}), - metal2 = loader.tile_atlas("metal2", {2, 2}), - tiles = loader.tile_atlas("tiles", {8, 5}); - constexpr auto N = TILE_MAX_DIM; - chunk c; - for (auto& [x, k, pt] : c) { - x.ground = { tiles, decltype(tile_image::variant)(k % tiles->num_tiles()) }; - } - constexpr auto K = N/2; - c[{K, K }].wall_north = { metal1, 0 }; - c[{K, K }].wall_west = { metal2, 0 }; - c[{K, K+1}].wall_north = { metal1, 0 }; - c[{K+1, K }].wall_west = { metal2, 0 }; - return c; -} - -static bool chunks_equal(const chunk& a, const chunk& b) -{ - for (std::size_t i = 0; i < TILE_COUNT; i++) - if (a[i] != b[i]) - return false; - return true; -} - -static bool test_serializer1() -{ - constexpr auto filename = "../test/test-serializer1.dat"; - if (Path::exists(filename)) - Path::remove(filename); - world w; - const chunk_coords coord{1, 1}; - w[coord] = make_test_chunk(); - w.serialize(filename); - auto w2 = world::deserialize(filename); - return chunks_equal(w[coord], w2[coord]); -} - -bool floormat::test_serializer() -{ - bool ret = true; - ret &= test_serializer1(); - return ret; -} - -} // namespace floormat +#include "app.hpp" +#include "src/world.hpp" +#include "src/loader.hpp" +#include "src/tile-atlas.hpp" +#include + +namespace floormat { + +namespace Path = Corrade::Utility::Path; + +static chunk make_test_chunk() +{ + auto metal1 = loader.tile_atlas("metal1", {2, 2}), + metal2 = loader.tile_atlas("metal2", {2, 2}), + tiles = loader.tile_atlas("tiles", {8, 5}); + constexpr auto N = TILE_MAX_DIM; + chunk c; + for (auto& [x, k, pt] : c) { + x.ground = { tiles, decltype(tile_image::variant)(k % tiles->num_tiles()) }; + } + constexpr auto K = N/2; + c[{K, K }].wall_north = { metal1, 0 }; + c[{K, K }].wall_west = { metal2, 0 }; + c[{K, K+1}].wall_north = { metal1, 0 }; + c[{K+1, K }].wall_west = { metal2, 0 }; + return c; +} + +static bool chunks_equal(const chunk& a, const chunk& b) +{ + for (std::size_t i = 0; i < TILE_COUNT; i++) + if (a[i] != b[i]) + return false; + return true; +} + +static bool test_serializer1() +{ + constexpr auto filename = "../test/test-serializer1.dat"; + if (Path::exists(filename)) + Path::remove(filename); + world w; + const chunk_coords coord{1, 1}; + w[coord] = make_test_chunk(); + w.serialize(filename); + auto w2 = world::deserialize(filename); + return chunks_equal(w[coord], w2[coord]); +} + +bool floormat::test_serializer() +{ + bool ret = true; + ret &= test_serializer1(); + return ret; +} + +} // namespace floormat -- cgit v1.2.3