diff options
author | Stanislaw Halik <sthalik@misaki.pl> | 2022-10-28 12:25:44 +0200 |
---|---|---|
committer | Stanislaw Halik <sthalik@misaki.pl> | 2022-10-28 12:25:44 +0200 |
commit | dbaafe5ecea1b2350d526c51059b9f0d123b5669 (patch) | |
tree | 9c0a07fc4558f88168eec4c121eae82a1e428106 /serialize | |
parent | b93ec1eee7d3199ecd1fa8bf2061622ede93a98c (diff) |
serializer work
Diffstat (limited to 'serialize')
-rw-r--r-- | serialize/binary-serializer.cpp | 13 | ||||
-rw-r--r-- | serialize/binary-serializer.hpp | 15 | ||||
-rw-r--r-- | serialize/binary-serializer.inl | 33 | ||||
-rw-r--r-- | serialize/world.cpp | 255 |
4 files changed, 309 insertions, 7 deletions
diff --git a/serialize/binary-serializer.cpp b/serialize/binary-serializer.cpp index 2444b403..05959707 100644 --- a/serialize/binary-serializer.cpp +++ b/serialize/binary-serializer.cpp @@ -80,4 +80,17 @@ static_assert(std::is_same_v<test3&, decltype( std::declval<test3&>() >> std::de using test4 = binary_writer<std::array<char, sizeof(int)>::iterator>; static_assert(std::is_same_v<test4&, decltype( std::declval<test4&>() << int() )>); +[[maybe_unused]] +static constexpr bool test5() +{ + std::array<char, 4> bytes = {}; + auto w = binary_writer(bytes.begin()); + w << (char)0; + w << (char)1; + w << (char)2; + w << (char)3; + return bytes[0] == 0 && bytes[1] == 1 && bytes[2] == 2 && bytes[3] == 3; +} +static_assert(test5()); + } // namespace floormat::Serialize diff --git a/serialize/binary-serializer.hpp b/serialize/binary-serializer.hpp index bab729f4..7ac665be 100644 --- a/serialize/binary-serializer.hpp +++ b/serialize/binary-serializer.hpp @@ -7,6 +7,13 @@ #include <concepts> #include <type_traits> +namespace Corrade::Containers { + +template<typename T> class BasicStringView; +using StringView = BasicStringView<const char>; + +} // namespace Corrade::Containers + namespace floormat::Serialize { static_assert(std::endian::native == std::endian::big || std::endian::native == std::endian::little); @@ -92,14 +99,18 @@ binary_reader(Array&& array) -> binary_reader<std::decay_t<decltype(std::begin(a template<std::output_iterator<char> It> struct binary_writer final { explicit constexpr binary_writer(It it) noexcept; - template<integer T> void write(T x) noexcept; + template<integer T> constexpr void write(T x) noexcept; template<std::floating_point T> 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<std::output_iterator<char> It, serializable T> -binary_writer<It>& operator<<(binary_writer<It>& writer, T x) noexcept; +constexpr binary_writer<It>& operator<<(binary_writer<It>& writer, T x) noexcept; } // namespace floormat::Serialize diff --git a/serialize/binary-serializer.inl b/serialize/binary-serializer.inl index 35b7ddc7..d16504e6 100644 --- a/serialize/binary-serializer.inl +++ b/serialize/binary-serializer.inl @@ -1,6 +1,6 @@ #pragma once - #include "binary-serializer.hpp" +#include "compat/assert.hpp" namespace floormat::Serialize { @@ -70,17 +70,28 @@ constexpr inline T maybe_byteswap(T x) } template<std::output_iterator<char> It> -constexpr binary_writer<It>::binary_writer(It it) noexcept : it{it} {} +constexpr binary_writer<It>::binary_writer(It it) noexcept : it{it}, _bytes_written{0} {} template<std::output_iterator<char> It> template<integer T> -void binary_writer<It>::write(T x) noexcept +constexpr void binary_writer<It>::write(T x) noexcept { union { T datum; char bytes[sizeof(T)]; } buf; - buf.datum = maybe_byteswap(x); + + 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]; } @@ -93,6 +104,7 @@ void binary_writer<It>::write(T x) noexcept 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]; @@ -107,12 +119,23 @@ binary_reader<It>& operator>>(binary_reader<It>& reader, T& x) noexcept } template<std::output_iterator<char> It, serializable T> -binary_writer<It>& operator<<(binary_writer<It>& writer, T x) noexcept +constexpr binary_writer<It>& operator<<(binary_writer<It>& writer, T x) noexcept { writer.template write<T>(x); return writer; } +template<std::output_iterator<char> It> +constexpr void binary_writer<It>::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/world.cpp b/serialize/world.cpp new file mode 100644 index 00000000..e4ccbb67 --- /dev/null +++ b/serialize/world.cpp @@ -0,0 +1,255 @@ +#include "src/world.hpp" +#include "src/tile-defs.hpp" +#include "src/tile-atlas.hpp" +#include "binary-serializer.inl" + +#include <bit> +#include <cerrno> +#include <cstdio> +#include <array> +#include <vector> +#include <limits> +#include <unordered_map> + +#include <Corrade/Containers/ArrayView.h> +#include <Corrade/Utility/Path.h> + +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<typename T> constexpr inline T int_max = std::numeric_limits<T>::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<tile::pass_mode> 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 state final { + 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<const char> serialize_world(); + + fm_DECLARE_DEFAULT_MOVE_ASSIGNMENT_(state); + fm_DECLARE_DEPRECATED_COPY_ASSIGNMENT(state); + +private: + const struct world* world; + std::vector<char> atlas_buf, chunk_buf, file_buf; + std::vector<std::vector<char>> chunk_bufs; + std::unordered_map<const void*, interned_atlas> 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 + +state::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 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 state::maybe_intern_atlas(const tile_image& img) +{ + return img ? intern_atlas(img) : null_atlas; +} + +void 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 state::serialize_atlases() +{ + const std::size_t sz = tile_images.size(); + fm_assert(sz < int_max<atlasid>); + 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<atlasid>); + + 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(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<const char> 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) { it = std::copy(std::cbegin(in), std::cend(in), it); }; + copy(file_magic); + copy(std::initializer_list<char>{ 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 = []<std::size_t N> (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::state s{*this}; + const auto array = s.serialize_world(); + if (auto len = ::fwrite(array.data(), array.size(), 1, file)) + 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 |