summaryrefslogtreecommitdiffhomepage
path: root/serialize
diff options
context:
space:
mode:
authorStanislaw Halik <sthalik@misaki.pl>2022-10-28 12:25:44 +0200
committerStanislaw Halik <sthalik@misaki.pl>2022-10-28 12:25:44 +0200
commitdbaafe5ecea1b2350d526c51059b9f0d123b5669 (patch)
tree9c0a07fc4558f88168eec4c121eae82a1e428106 /serialize
parentb93ec1eee7d3199ecd1fa8bf2061622ede93a98c (diff)
serializer work
Diffstat (limited to 'serialize')
-rw-r--r--serialize/binary-serializer.cpp13
-rw-r--r--serialize/binary-serializer.hpp15
-rw-r--r--serialize/binary-serializer.inl33
-rw-r--r--serialize/world.cpp255
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