summaryrefslogtreecommitdiffhomepage
path: root/serialize
diff options
context:
space:
mode:
authorStanislaw Halik <sthalik@misaki.pl>2022-10-28 18:08:13 +0200
committerStanislaw Halik <sthalik@misaki.pl>2022-10-28 18:08:13 +0200
commit7c02d3b8f1e6ec4ee82bdf1220da431b1cd1231e (patch)
treeeea6effd759fdc9cd81f33b732979854eb226e39 /serialize
parentfa07c1fd19e7fbd3b2757583708c4691c79025ed (diff)
serializer work
Diffstat (limited to 'serialize')
-rw-r--r--serialize/binary-reader.hpp62
-rw-r--r--serialize/binary-reader.inl87
-rw-r--r--serialize/binary-serializer.cpp26
-rw-r--r--serialize/binary-serializer.hpp76
-rw-r--r--serialize/binary-serializer.inl141
-rw-r--r--serialize/binary-writer.hpp25
-rw-r--r--serialize/binary-writer.inl69
-rw-r--r--serialize/world-impl.hpp59
-rw-r--r--serialize/world-reader.cpp68
-rw-r--r--serialize/world-writer.cpp221
-rw-r--r--serialize/world.cpp257
11 files changed, 605 insertions, 486 deletions
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 <iterator>
+
+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<typename T>
+concept char_sequence = requires(T& x, const T& cx) {
+ requires std::same_as<decltype(std::begin(x)), decltype(std::end(x))>;
+ requires std::same_as<decltype(std::cbegin(cx)), decltype(std::cend(cx))>;
+ requires std::forward_iterator<decltype(std::begin(x))>;
+ requires std::forward_iterator<decltype(std::cbegin(cx))>;
+ requires std::same_as<char, std::decay_t<decltype(*std::begin(x))>>;
+ requires std::same_as<char, std::decay_t<decltype(*std::cbegin(x))>>;
+};
+
+template<typename It>
+concept string_input_iterator = requires(It it) {
+ requires std::forward_iterator<It>;
+ requires std::is_same_v<char, std::decay_t<decltype(*it)>>;
+};
+
+template<string_input_iterator It>
+struct binary_reader final {
+ template<char_sequence Seq> explicit constexpr binary_reader(const Seq& seq) noexcept;
+ constexpr binary_reader(It begin, It end) noexcept;
+ constexpr void assert_end() noexcept;
+
+ template<integer T> constexpr value_u read_u() noexcept;
+ template<std::floating_point T> constexpr value_u read_u() noexcept;
+ template<typename T> T read() noexcept;
+ template<std::size_t N> constexpr std::array<char, N> 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<string_input_iterator It, serializable T>
+binary_reader<It>& operator>>(binary_reader<It>& reader, T& x) noexcept;
+
+template<string_input_iterator It> binary_reader(It&& begin, It&& end) -> binary_reader<std::decay_t<It>>;
+
+template<typename Array>
+binary_reader(Array&& array) -> binary_reader<std::decay_t<decltype(std::begin(array))>>;
+
+} // 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<string_input_iterator It>
+template<char_sequence Seq>
+constexpr binary_reader<It>::binary_reader(const Seq& seq) noexcept
+ : it{std::begin(seq)}, end{std::end(seq)}
+{}
+
+template<string_input_iterator It>
+constexpr binary_reader<It>::binary_reader(It begin, It end) noexcept :
+ it{begin}, end{end}
+{}
+
+template<string_input_iterator It>
+template<std::floating_point T>
+constexpr value_u binary_reader<It>::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<string_input_iterator It>
+template<typename T>
+T binary_reader<It>::read() noexcept
+{
+ value_u buf = read_u<T>();
+ return *reinterpret_cast<T>(buf.bytes);
+}
+
+template<string_input_iterator It>
+template<std::size_t N>
+constexpr std::array<char, N> binary_reader<It>::read() noexcept
+{
+ std::array<char, N> 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<string_input_iterator It>
+constexpr void binary_reader<It>::assert_end() noexcept
+{
+ fm_assert(it == end);
+}
+
+template<string_input_iterator It>
+template<integer T>
+constexpr value_u binary_reader<It>::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<string_input_iterator It, serializable T>
+binary_reader<It>& operator>>(binary_reader<It>& reader, T& x) noexcept
+{
+ value_u u = reader.template read<T>();
+ x = *reinterpret_cast<T*>(&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 <array>
namespace floormat::Serialize {
@@ -30,28 +31,6 @@ private:
template struct byte_array_iterator<sizeof(double)>;
#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<char, 4> bytes = { 1, 0, 1, 0 };
auto r = binary_reader(bytes.cbegin(), bytes.cend());
const auto x = r.read_u<int>();
+ 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 <bit>
-#include <iterator>
#include <concepts>
#include <type_traits>
@@ -40,77 +39,18 @@ concept integer = requires(T x) {
requires sizeof(T) == sizeof(make_integer_t<sizeof(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<typename T>
-concept char_sequence = requires(T& x, const T& cx) {
- requires std::same_as<decltype(std::begin(x)), decltype(std::end(x))>;
- requires std::same_as<decltype(std::cbegin(cx)), decltype(std::cend(cx))>;
- requires std::forward_iterator<decltype(std::begin(x))>;
- requires std::forward_iterator<decltype(std::cbegin(cx))>;
- requires std::same_as<char, std::decay_t<decltype(*std::begin(x))>>;
- requires std::same_as<char, std::decay_t<decltype(*std::cbegin(x))>>;
-};
-
-template<typename It>
-concept string_input_iterator = requires(It it) {
- requires std::forward_iterator<It>;
- requires std::is_same_v<char, std::decay_t<decltype(*it)>>;
-};
-
template<typename T>
concept serializable = requires(T x) {
requires std::floating_point<T> || integer<T>;
};
-template<string_input_iterator It>
-struct binary_reader final {
- template<char_sequence Seq> explicit constexpr binary_reader(const Seq& seq) noexcept;
- constexpr binary_reader(It begin, It end) noexcept;
- constexpr ~binary_reader() noexcept;
-
- template<integer T> constexpr value_u read_u() noexcept;
- template<std::floating_point T> constexpr value_u read_u() noexcept;
- template<typename T> T read() noexcept;
-
-private:
- It it, end;
-};
-
-template<string_input_iterator It, serializable T>
-binary_reader<It>& operator>>(binary_reader<It>& reader, T& x) noexcept;
-
-template<string_input_iterator It> binary_reader(It&& begin, It&& end) -> binary_reader<std::decay_t<It>>;
-
-template<typename Array>
-binary_reader(Array&& array) -> binary_reader<std::decay_t<decltype(std::begin(array))>>;
-
-template<std::output_iterator<char> It>
-struct binary_writer final {
- explicit constexpr binary_writer(It it) 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>
-constexpr binary_writer<It>& operator<<(binary_writer<It>& writer, T x) noexcept;
+template<integer T>
+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<string_input_iterator It>
-template<char_sequence Seq>
-constexpr binary_reader<It>::binary_reader(const Seq& seq) noexcept
- : it{std::begin(seq)}, end{std::end(seq)}
-{}
-
-template<string_input_iterator It>
-constexpr binary_reader<It>::binary_reader(It begin, It end) noexcept :
- it{begin}, end{end}
-{}
-
-template<string_input_iterator It>
-template<std::floating_point T>
-constexpr value_u binary_reader<It>::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<string_input_iterator It>
-template<typename T>
-T binary_reader<It>::read() noexcept
-{
- value_u buf = read_u<T>();
- return *reinterpret_cast<T>(buf.bytes);
-}
-
-template<string_input_iterator It>
-constexpr binary_reader<It>::~binary_reader() noexcept
-{
- fm_assert(it == end);
-}
-
-template<string_input_iterator It>
-template<integer T>
-constexpr value_u binary_reader<It>::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<integer T>
-constexpr inline T maybe_byteswap(T x)
-{
- if constexpr(std::endian::native == std::endian::big)
- return std::byteswap(x);
- else
- return x;
-}
-
-template<std::output_iterator<char> It>
-constexpr binary_writer<It>::binary_writer(It it) noexcept : it{it}, _bytes_written{0} {}
-
-template<std::output_iterator<char> It>
-template<integer T>
-constexpr void binary_writer<It>::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<std::output_iterator<char> It>
-template<std::floating_point T>
-void binary_writer<It>::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<string_input_iterator It, serializable T>
-binary_reader<It>& operator>>(binary_reader<It>& reader, T& x) noexcept
-{
- value_u u = reader.template read<T>();
- x = *reinterpret_cast<T*>(&u.bytes[0]);
- return reader;
-}
-
-template<std::output_iterator<char> It, serializable T>
-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/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 <iterator>
+#include <Corrade/Containers/StringView.h>
+
+namespace floormat::Serialize {
+
+template<std::output_iterator<char> It>
+struct binary_writer final {
+ explicit constexpr binary_writer(It it) 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>
+constexpr binary_writer<It>& operator<<(binary_writer<It>& 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 <type_traits>
+#include <Corrade/Containers/StringView.h>
+
+namespace floormat::Serialize {
+
+template<std::output_iterator<char> It>
+constexpr binary_writer<It>::binary_writer(It it) noexcept : it{it}, _bytes_written{0} {}
+
+template<std::output_iterator<char> It>
+template<integer T>
+constexpr void binary_writer<It>::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<std::output_iterator<char> It>
+template<std::floating_point T>
+void binary_writer<It>::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<std::output_iterator<char> It, serializable T>
+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_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<typename T> constexpr inline T int_max = std::numeric_limits<T>::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<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),
+};
+
+} // 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<const char> buf);
+
+private:
+ std::shared_ptr<tile_atlas> lookup_atlas(atlasid id);
+ void read_atlases();
+ void read_chunks();
+
+ std::unordered_map<atlasid, std::shared_ptr<tile_atlas>> atlases;
+ struct world* world;
+};
+
+reader_state::reader_state(struct world& world) noexcept : world{&world} {}
+
+void reader_state::deserialize_world(ArrayView<const char> buf)
+{
+ auto s = binary_reader{buf};
+ if (!!::memcmp(s.read<std::size(file_magic)-1>().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 = []<std::size_t N> (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<char[]>(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 <vector>
+#include <Corrade/Containers/StringView.h>
+#include <Corrade/Utility/Path.h>
+
+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<const char> 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<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
+
+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<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_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<const char> 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>{ 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 = []<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::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 <bit>
-#include <cerrno>
-#include <cstdio>
-#include <array>
-#include <vector>
-#include <limits>
-#include <unordered_map>
-
-#include <Corrade/Containers/ArrayView.h>
+#define FM_SERIALIZE_WORLD_IMPL
+#include "world-impl.hpp"
+#include "binary-reader.inl"
#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 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<const char> serialize_world();
-
- fm_DECLARE_DEFAULT_MOVE_ASSIGNMENT_(writer_state);
- fm_DECLARE_DEPRECATED_COPY_ASSIGNMENT(writer_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
-
-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<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_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<const char> 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>{ 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::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