From 7c02d3b8f1e6ec4ee82bdf1220da431b1cd1231e Mon Sep 17 00:00:00 2001 From: Stanislaw Halik Date: Fri, 28 Oct 2022 18:08:13 +0200 Subject: serializer work --- serialize/binary-reader.hpp | 62 ++++++++++ serialize/binary-reader.inl | 87 ++++++++++++++ serialize/binary-serializer.cpp | 26 +--- serialize/binary-serializer.hpp | 76 ++---------- serialize/binary-serializer.inl | 141 ---------------------- serialize/binary-writer.hpp | 25 ++++ serialize/binary-writer.inl | 69 +++++++++++ serialize/world-impl.hpp | 59 +++++++++ serialize/world-reader.cpp | 68 +++++++++++ serialize/world-writer.cpp | 221 ++++++++++++++++++++++++++++++++++ serialize/world.cpp | 257 +--------------------------------------- 11 files changed, 605 insertions(+), 486 deletions(-) create mode 100644 serialize/binary-reader.hpp create mode 100644 serialize/binary-reader.inl delete mode 100644 serialize/binary-serializer.inl create mode 100644 serialize/binary-writer.hpp create mode 100644 serialize/binary-writer.inl create mode 100644 serialize/world-impl.hpp create mode 100644 serialize/world-reader.cpp create mode 100644 serialize/world-writer.cpp (limited to 'serialize') diff --git a/serialize/binary-reader.hpp b/serialize/binary-reader.hpp new file mode 100644 index 00000000..3b6eae9a --- /dev/null +++ b/serialize/binary-reader.hpp @@ -0,0 +1,62 @@ +#pragma once +#include "binary-serializer.hpp" +#include + +namespace floormat::Serialize { + +union value_u { + alignas(alignof(double)) char bytes[8]; + unsigned char uc; + std::uint8_t u8; + std::uint16_t u16; + std::uint32_t u32; + std::uint64_t u64; + float f32; + double f64; +}; + +static_assert(sizeof(value_u) == 8); + +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 value_u read_u() noexcept; + template constexpr value_u read_u() noexcept; + template T read() noexcept; + template constexpr std::array read() noexcept; + constexpr std::size_t bytes_read() const noexcept { return num_bytes_read; } + +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 new file mode 100644 index 00000000..cf935a76 --- /dev/null +++ b/serialize/binary-reader.inl @@ -0,0 +1,87 @@ +#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 value_u binary_reader::read_u() noexcept +{ + value_u buf; + static_assert(sizeof(T) <= sizeof(buf)); + fm_assert(std::distance(it, end) >= sizeof(T)); + num_bytes_read += sizeof(T); + for (int i = 0; i < sizeof(T); i++) + buf.bytes[i] = *it++; + return buf; +} + +template +template +T binary_reader::read() noexcept +{ + value_u buf = read_u(); + return *reinterpret_cast(buf.bytes); +} + +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 +template +constexpr value_u binary_reader::read_u() noexcept +{ + value_u buf; + if (std::is_constant_evaluated()) + for (std::size_t i = 0; i < std::size(buf.bytes); i++) + buf.bytes[i] = 0; + static_assert(sizeof(T) <= sizeof(buf)); + fm_assert((std::ptrdiff_t)sizeof(T) <= std::distance(it, end)); + num_bytes_read += sizeof(T); + if constexpr(std::endian::native == std::endian::big) + for (int i = sizeof(T) - 1; i >= 0; i--) + buf.bytes[i] = *it++; + else + for (std::size_t i = 0; i < sizeof(T); i++) + buf.bytes[i] = *it++; + return buf; +} + +template +binary_reader& operator>>(binary_reader& reader, T& x) noexcept +{ + value_u u = reader.template read(); + x = *reinterpret_cast(&u.bytes[0]); + return reader; +} + +} // namespace floormat::Serialize diff --git a/serialize/binary-serializer.cpp b/serialize/binary-serializer.cpp index 05959707..d632febd 100644 --- a/serialize/binary-serializer.cpp +++ b/serialize/binary-serializer.cpp @@ -1,4 +1,5 @@ -#include "binary-serializer.inl" +#include "binary-reader.inl" +#include "binary-writer.inl" #include namespace floormat::Serialize { @@ -30,28 +31,6 @@ private: template struct byte_array_iterator; #endif -struct value_buf final { - value_u value; - std::int8_t len; - - explicit constexpr value_buf(value_u value, std::int8_t len) : value{value}, len{len} {} - constexpr bool operator==(const value_buf& o) const noexcept; - - fm_DECLARE_DEFAULT_MOVE_ASSIGNMENT_(value_buf); - fm_DECLARE_DEFAULT_COPY_ASSIGNMENT(value_buf); -}; - -constexpr bool value_buf::operator==(const value_buf& o) const noexcept -{ - const auto N = len; - if (N != o.len) - return false; - for (std::int8_t i = 0; i < N; i++) - if (value.bytes[i] != o.value.bytes[i]) - return false; - return true; -} - [[maybe_unused]] static constexpr bool test1() { @@ -70,6 +49,7 @@ static constexpr bool test2() constexpr std::array bytes = { 1, 0, 1, 0 }; auto r = binary_reader(bytes.cbegin(), bytes.cend()); const auto x = r.read_u(); + r.assert_end(); return x.bytes[0] == 1 && x.bytes[1] == 0 && x.bytes[2] == 1 && x.bytes[3] == 0; } static_assert(test2()); diff --git a/serialize/binary-serializer.hpp b/serialize/binary-serializer.hpp index 7ac665be..43fffea8 100644 --- a/serialize/binary-serializer.hpp +++ b/serialize/binary-serializer.hpp @@ -3,7 +3,6 @@ #include "compat/defs.hpp" #include -#include #include #include @@ -40,77 +39,18 @@ concept integer = requires(T x) { requires sizeof(T) == sizeof(make_integer_t); }; -union value_u { - alignas(alignof(double)) char bytes[8]; - unsigned char uc; - std::uint8_t u8; - std::uint16_t u16; - std::uint32_t u32; - std::uint64_t u64; - float f32; - double f64; -}; - -static_assert(sizeof(value_u) == 8); - -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 concept serializable = requires(T x) { requires std::floating_point || integer; }; -template -struct binary_reader final { - template explicit constexpr binary_reader(const Seq& seq) noexcept; - constexpr binary_reader(It begin, It end) noexcept; - constexpr ~binary_reader() noexcept; - - template constexpr value_u read_u() noexcept; - template constexpr value_u read_u() noexcept; - template T read() noexcept; - -private: - 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>; - -template It> -struct binary_writer final { - explicit constexpr binary_writer(It it) noexcept; - template constexpr void write(T x) noexcept; - template 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; +template +constexpr inline T maybe_byteswap(T x) +{ + if constexpr(std::endian::native == std::endian::big) + return std::byteswap(x); + else + return x; +} } // namespace floormat::Serialize diff --git a/serialize/binary-serializer.inl b/serialize/binary-serializer.inl deleted file mode 100644 index d16504e6..00000000 --- a/serialize/binary-serializer.inl +++ /dev/null @@ -1,141 +0,0 @@ -#pragma once -#include "binary-serializer.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 value_u binary_reader::read_u() noexcept -{ - value_u buf; - static_assert(sizeof(T) <= sizeof(buf)); - fm_assert(std::distance(it, end) >= sizeof(T)); - for (int i = 0; i < sizeof(T); i++) - buf.bytes[i] = *it++; - return buf; -} - -template -template -T binary_reader::read() noexcept -{ - value_u buf = read_u(); - return *reinterpret_cast(buf.bytes); -} - -template -constexpr binary_reader::~binary_reader() noexcept -{ - fm_assert(it == end); -} - -template -template -constexpr value_u binary_reader::read_u() noexcept -{ - value_u buf; - if (std::is_constant_evaluated()) - for (std::size_t i = 0; i < std::size(buf.bytes); i++) - buf.bytes[i] = 0; - static_assert(sizeof(T) <= sizeof(buf)); - fm_assert(std::distance(it, end) >= (std::ptrdiff_t) sizeof(T)); - if constexpr(std::endian::native == std::endian::big) - for (int i = sizeof(T) - 1; i >= 0; i--) - buf.bytes[i] = *it++; - else - for (std::size_t i = 0; i < sizeof(T); i++) - buf.bytes[i] = *it++; - return buf; -} - -template -constexpr inline T maybe_byteswap(T x) -{ - if constexpr(std::endian::native == std::endian::big) - return std::byteswap(x); - else - return x; -} - -template It> -constexpr binary_writer::binary_writer(It it) noexcept : it{it}, _bytes_written{0} {} - -template It> -template -constexpr void binary_writer::write(T x) noexcept -{ - union { - T datum; - char bytes[sizeof(T)]; - } buf; - - if (std::is_constant_evaluated()) - for (std::size_t i = 0; i < std::size(buf.bytes); i++) - buf.bytes[i] = 0; - _bytes_written += sizeof(T); - if constexpr(sizeof(T) == 1) - buf.bytes[0] = (char)x; - else if (!std::is_constant_evaluated()) - buf.datum = maybe_byteswap(x); - else - for (std::size_t i = 0; i < sizeof(T); x >>= 8, i++) - buf.bytes[i] = (char)(unsigned char)x; - for (std::size_t i = 0; i < sizeof(T); i++) - *it++ = buf.bytes[i]; -} - -template It> -template -void binary_writer::write(T x) noexcept -{ - union { - T datum; - char bytes[sizeof(T)]; - } buf; - _bytes_written += sizeof(T); - buf.datum = maybe_byteswap(x); - for (std::size_t i = 0; i < sizeof(T); i++) - *it++ = buf.bytes[i]; -} - -template -binary_reader& operator>>(binary_reader& reader, T& x) noexcept -{ - value_u u = reader.template read(); - x = *reinterpret_cast(&u.bytes[0]); - return reader; -} - -template It, serializable T> -constexpr binary_writer& operator<<(binary_writer& writer, T x) noexcept -{ - writer.template write(x); - return writer; -} - -template It> -constexpr void binary_writer::write_asciiz_string(StringView str) noexcept -{ - fm_assert_debug(str.flags() & StringViewFlag::NullTerminated); - const auto sz = str.size(); - _bytes_written += sz + 1; - for (std::size_t i = 0; i < sz; i++) - *it++ = str[i]; - *it++ = '\0'; -} - -} // namespace floormat::Serialize - - diff --git a/serialize/binary-writer.hpp b/serialize/binary-writer.hpp new file mode 100644 index 00000000..ead0824d --- /dev/null +++ b/serialize/binary-writer.hpp @@ -0,0 +1,25 @@ +#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; + template 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/binary-writer.inl b/serialize/binary-writer.inl new file mode 100644 index 00000000..5fb4db6d --- /dev/null +++ b/serialize/binary-writer.inl @@ -0,0 +1,69 @@ +#pragma once +#include "binary-writer.hpp" +#include "binary-serializer.hpp" +#include "compat/assert.hpp" +#include +#include + +namespace floormat::Serialize { + +template It> +constexpr binary_writer::binary_writer(It it) noexcept : it{it}, _bytes_written{0} {} + +template It> +template +constexpr void binary_writer::write(T x) noexcept +{ + union { + T datum; + char bytes[sizeof(T)]; + } buf; + + if (std::is_constant_evaluated()) + for (std::size_t i = 0; i < std::size(buf.bytes); i++) + buf.bytes[i] = 0; + _bytes_written += sizeof(T); + if constexpr(sizeof(T) == 1) + buf.bytes[0] = (char)x; + else if (!std::is_constant_evaluated()) + buf.datum = maybe_byteswap(x); + else + for (std::size_t i = 0; i < sizeof(T); x >>= 8, i++) + buf.bytes[i] = (char)(unsigned char)x; + for (std::size_t i = 0; i < sizeof(T); i++) + *it++ = buf.bytes[i]; +} + +template It> +template +void binary_writer::write(T x) noexcept +{ + union { + T datum; + char bytes[sizeof(T)]; + } buf; + _bytes_written += sizeof(T); + buf.datum = maybe_byteswap(x); + for (std::size_t i = 0; i < sizeof(T); i++) + *it++ = buf.bytes[i]; +} + +template It, serializable T> +constexpr binary_writer& operator<<(binary_writer& writer, T x) noexcept +{ + writer.template write(x); + return writer; +} + +template It> +constexpr void binary_writer::write_asciiz_string(StringView str) noexcept +{ + fm_debug_assert(str.flags() & StringViewFlag::NullTerminated); + const auto sz = str.size(); + _bytes_written += sz + 1; + for (std::size_t i = 0; i < sz; i++) + *it++ = str[i]; + *it++ = '\0'; +} + +} // namespace floormat::Serialize diff --git a/serialize/world-impl.hpp b/serialize/world-impl.hpp new file mode 100644 index 00000000..b2d74db3 --- /dev/null +++ b/serialize/world-impl.hpp @@ -0,0 +1,59 @@ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-macros" +#ifndef FM_SERIALIZE_WORLD_IMPL +#error "not meant to be included directly" +#endif + +#pragma once +#include "src/tile.hpp" + +namespace floormat::Serialize { + +namespace { + +using tilemeta = std::uint8_t; +using imgvar = std::uint8_t; +using atlasid = 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), +}; + +} // namespace + +} // namespace floormat::Serialize + +namespace floormat { + +namespace { + +struct FILE_raii final { + FILE_raii(FILE* s) noexcept : s{s} {} + ~FILE_raii() noexcept { if (s) ::fclose(s); } + operator FILE*() noexcept { return s; } + void close() noexcept { if (s) ::fclose(s); s = nullptr; } +private: + FILE* s; +}; + +} // namespace + +} // namespace floormat + +#pragma clang diagnostic pop diff --git a/serialize/world-reader.cpp b/serialize/world-reader.cpp new file mode 100644 index 00000000..86b07834 --- /dev/null +++ b/serialize/world-reader.cpp @@ -0,0 +1,68 @@ +#define FM_SERIALIZE_WORLD_IMPL +#include "world-impl.hpp" +#include "binary-reader.inl" +#include "src/world.hpp" + +namespace floormat::Serialize { + +namespace { + +struct reader_state final { + explicit reader_state(world& world) noexcept; + void deserialize_world(ArrayView buf); + +private: + std::shared_ptr lookup_atlas(atlasid id); + void read_atlases(); + void read_chunks(); + + std::unordered_map> atlases; + struct world* world; +}; + +reader_state::reader_state(struct world& world) noexcept : world{&world} {} + +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"); +} + +} // 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(), "r"); + 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(\"%s\", 0, SEEK_END): %s", filename.data(), strerror(errbuf)); + std::size_t len; + if (auto len_ = ::ftell(f); len_ >= 0) + len = (std::size_t)len_; + else + fm_abort("ftell(\"%s\"): %s", filename.data(), strerror(errbuf)); + if (int ret = ::fseek(f, 0, SEEK_SET); ret != 0) + fm_abort("fseek(\"%s\", 0, SEEK_SET): %s", filename.data(), strerror(errbuf)); + auto buf_ = std::make_unique(len); + if (auto ret = ::fread(&buf_[0], len, 1, f); ret != 1) + fm_abort("fread(\"%s\", %zu): %s", filename.data(), len, 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 new file mode 100644 index 00000000..b28c113b --- /dev/null +++ b/serialize/world-writer.cpp @@ -0,0 +1,221 @@ +#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 + +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)*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" +#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 +#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]; + + [[maybe_unused]] constexpr auto tile_size = sizeof(atlasid)*3 + sizeof(tilemeta); + fm_debug_assert(s.bytes_written() + tile_size <= chunkbuf_size); + + auto img_g = maybe_intern_atlas(x.ground_image); + auto img_n = maybe_intern_atlas(x.wall_north); + auto img_w = maybe_intern_atlas(x.wall_north); + + 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); + + fm_debug_assert((x.passability & pass_mask) == x.passability); + flags |= x.passability; + + s << flags; + + if (img_g != null_atlas) + s << img_g; + if (img_n != null_atlas) + s << img_n; + if (img_w != null_atlas) + s << img_w; + } + + 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() +{ + const std::size_t sz = tile_images.size(); + fm_assert(sz < int_max); + 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; + + for (const auto& [p, t] : tile_images) + { + const auto& [atlas, index] = t; + 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()); + s.write_asciiz_string(name); + } + fm_assert(s.bytes_written() <= atlasbuf_size); +} + +#ifdef __GNUG__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#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(); + + std::size_t len = 0; + len += std::size(file_magic)-1; + len += sizeof(proto_version); + 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(std::initializer_list{ char(proto_version & 0xff), char((proto_version >> 8) & 0xff) }); + copy(atlas_buf); + for (const auto& buf : chunk_bufs) + copy(buf); + return {file_buf.data(), file_buf.size()}; +} + +#ifdef __GNUG__ +#pragma GCC diagnostic 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(), "w"); + 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/serialize/world.cpp b/serialize/world.cpp index 0c6f6ddc..70d4d0c9 100644 --- a/serialize/world.cpp +++ b/serialize/world.cpp @@ -1,263 +1,12 @@ -#include "src/world.hpp" -#include "src/tile-defs.hpp" -#include "src/tile-atlas.hpp" -#include "binary-serializer.inl" - -#include -#include -#include -#include -#include -#include -#include - -#include +#define FM_SERIALIZE_WORLD_IMPL +#include "world-impl.hpp" +#include "binary-reader.inl" #include -namespace floormat::Serialize { - -namespace { - -using tilemeta = std::uint8_t; -using imgvar = std::uint8_t; -using atlasid = std::uint16_t; -using enum tile::pass_mode; - -template constexpr inline T int_max = std::numeric_limits::max(); - -constexpr inline std::size_t atlas_name_max = 128; -constexpr inline auto null_atlas = (atlasid)-1LL; - -#define file_magic ".floormat.save" -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), -}; - -struct interned_atlas final { - const tile_atlas* img; - atlasid index; -}; - -struct writer_state final { - writer_state(const world& world); - 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(); - ArrayView serialize_world(); - - fm_DECLARE_DEFAULT_MOVE_ASSIGNMENT_(writer_state); - fm_DECLARE_DEPRECATED_COPY_ASSIGNMENT(writer_state); - -private: - 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)*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" -#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 -#endif - -atlasid writer_state::intern_atlas(const tile_image& img) -{ - const void* const ptr = img.atlas.get(); - fm_assert_debug(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]; - - [[maybe_unused]] constexpr auto tile_size = sizeof(atlasid)*3 + sizeof(tilemeta); - fm_assert_debug(s.bytes_written() + tile_size <= chunkbuf_size); - - auto img_g = maybe_intern_atlas(x.ground_image); - auto img_n = maybe_intern_atlas(x.wall_north); - auto img_w = maybe_intern_atlas(x.wall_north); - - 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); - - fm_assert_debug((x.passability & pass_mask) == x.passability); - flags |= x.passability; - - s << flags; - - if (img_g != null_atlas) - s << img_g; - if (img_n != null_atlas) - s << img_n; - if (img_w != null_atlas) - s << img_w; - } - - 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() -{ - const std::size_t sz = tile_images.size(); - fm_assert(sz < int_max); - 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; - - for (const auto& [p, t] : tile_images) - { - const auto& [atlas, index] = t; - const auto name = atlas->name(); - const auto namesiz = name.size(); - fm_assert_debug(s.bytes_written() + namesiz + 1 <= atlasbuf_size); - fm_assert(namesiz <= atlas_name_max - 1); // null terminated - fm_assert_debug(name.find('\0') == name.cend()); - s.write_asciiz_string(name); - } - fm_assert(s.bytes_written() <= atlasbuf_size); -} - -#ifdef __GNUG__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#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(); - - std::size_t len = 0; - len += std::size(file_magic)-1; - len += sizeof(proto_version); - 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(std::initializer_list{ char(proto_version & 0xff), char((proto_version >> 8) & 0xff) }); - copy(atlas_buf); - for (const auto& buf : chunk_bufs) - copy(buf); - return {file_buf.data(), file_buf.size()}; -} - -#ifdef __GNUG__ -#pragma GCC diagnostic pop -#endif - -} // namespace - -} // namespace floormat::Serialize - namespace floormat { namespace Path = Corrade::Utility::Path; -struct FILE_raii final { - FILE_raii(FILE* s) noexcept : s{s} {} - ~FILE_raii() noexcept { if (s) ::fclose(s); } - operator FILE*() noexcept { return s; } - void close() noexcept { if (s) ::fclose(s); s = nullptr; } -private: - FILE* s; -}; - -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(), "w"); - 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)); -} -world world::deserialize(StringView filename) -{ - (void)filename; - return world(); -} } // namespace floormat -- cgit v1.2.3