From ad9cd66554fc2b57b56ca07f33b87fd16c4cfdab Mon Sep 17 00:00:00 2001 From: Stanislaw Halik Date: Sat, 20 Jan 2024 18:30:39 +0100 Subject: w --- serialize/savegame.cpp | 568 +++++++++++++++++++++++++++++++++++++++++++++ serialize/world-impl.hpp | 34 --- serialize/world-reader.cpp | 5 +- serialize/world-writer.cpp | 564 -------------------------------------------- 4 files changed, 571 insertions(+), 600 deletions(-) create mode 100644 serialize/savegame.cpp delete mode 100644 serialize/world-impl.hpp delete mode 100644 serialize/world-writer.cpp (limited to 'serialize') diff --git a/serialize/savegame.cpp b/serialize/savegame.cpp new file mode 100644 index 00000000..829459b7 --- /dev/null +++ b/serialize/savegame.cpp @@ -0,0 +1,568 @@ +#include "binary-writer.inl" +#include "compat/defs.hpp" +#include "compat/strerror.hpp" +#include "compat/int-hash.hpp" +#include "loader/loader.hpp" +#include "src/world.hpp" + +#include "atlas-type.hpp" +#include "src/anim-atlas.hpp" +#include "src/ground-atlas.hpp" +#include "src/wall-atlas.hpp" + +#include "src/scenery.hpp" +#include "src/critter.hpp" +#include "src/light.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#if 1 +#ifdef __CLION_IDE__ +#undef fm_assert +#define fm_assert(...) (void)(__VA_ARGS__) +#endif +#endif + +namespace floormat::Serialize { + +namespace { + +using tilemeta = uint8_t; +using atlasid = uint32_t; +using chunksiz = uint32_t; +using proto_t = uint32_t; + +constexpr inline proto_t proto_version = 20; +constexpr inline auto file_magic = ".floormat.save"_s; +constexpr inline auto chunk_magic = (uint16_t)0xdead; +constexpr inline auto object_magic = (uint16_t)0xb00b; +constexpr inline auto atlas_magic = (uint16_t)0xbeef; +constexpr inline auto null_atlas = (atlasid)-1; + +struct string_container +{ + StringView str; + bool operator==(const string_container&) const = default; + + friend void swap(string_container& a, string_container& b) + { + auto tmp = a.str; + a.str = b.str; + b.str = tmp; + } +}; + +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) std::fclose(s); s = nullptr; } +private: + FILE* s; +}; + +} // namespace + +} // namespace floormat::Serialize + +using floormat::Serialize::string_container; +using floormat::Hash::fnvhash_buf; + +template<> struct std::hash +{ + size_t operator()(const string_container& x) const noexcept + { + return fnvhash_buf(x.str.data(), x.str.size()); + } +}; + + +namespace floormat::Serialize { + +namespace { + +template T& non_const(const T& value) { return const_cast(value); } +template T& non_const(T& value) = delete; +template T& non_const(T&& value) = delete; +template T& non_const(const T&& value) = delete; + +constexpr size_t vector_initial_size = 128, hash_initial_size = vector_initial_size*2; + +template +struct magic +{ + using type = T; + uint16_t magic; +}; + +struct buffer +{ + std::unique_ptr data; + size_t size; + + bool empty() const { return size == 0; } + buffer() : data{nullptr}, size{0} {} + buffer(size_t len) : // todo use allocator + data{std::make_unique(len)}, + size{len} + { +#if !fm_ASAN + std::memset(&data[0], 0xfe, size); +#endif + } +}; + +struct serialized_atlas +{ + buffer buf; + const void* atlas; + atlas_type type; +}; + +struct serialized_chunk +{ + buffer buf{}; + chunk* c; +}; + +template +struct visitor_ +{ + template + CORRADE_ALWAYS_INLINE void do_visit_nonconst(const T& value, F&& fun) + { + do_visit(non_const(value), fun); + } + + template + CORRADE_ALWAYS_INLINE void do_visit(T&& value, F&& fun) + { + static_cast(*this).visit(value, fun); + } + + template + requires (std::is_arithmetic_v && std::is_fundamental_v) + void visit(T& x, F&& f) + { + f(x); + } + + template + void visit(Math::Vector& x, F&& f) + { + for (uint32_t i = 0; i < N; i++) + do_visit(x.data()[i], f); + } + +#if 0 + template + void visit(StringView str, F&& f) + { + f(str); + } +#endif + + template + requires std::is_enum_v + void visit(E& x, F&& f) + { + auto* ptr = const_cast*>(reinterpret_cast*>(&x)); + do_visit(*ptr, f); + } + + template + void visit(object& obj, F&& f) + { + auto type = obj.type(); + + do_visit(obj.id, f); + do_visit(type, f); + fm_assert(obj.atlas); + do_visit(*obj.atlas, f); + //do_visit(*obj.c, f); + do_visit(obj.coord.local(), f); + do_visit_nonconst(obj.offset, f); + do_visit_nonconst(obj.bbox_offset, f); + do_visit_nonconst(obj.bbox_size, f); + do_visit_nonconst(obj.delta, f); + do_visit_nonconst(obj.frame, f); + do_visit_nonconst(obj.r, f); + do_visit_nonconst(obj.pass, f); + + switch (type) + { + case object_type::critter: do_visit(static_cast(obj), f); return; + case object_type::scenery: do_visit(static_cast(obj), f); return; + case object_type::light: do_visit(static_cast(obj), f); return; + //case object_type::door: do_visit(static_cast(obj), f); return; + case object_type::COUNT: + case object_type::none: + break; + } + fm_abort("invalid object type '%d'", (int)obj.type()); + } + + template + void visit(tile_ref c, F&& f) + { + do_visit(c.ground(), f); + do_visit(c.wall_north(), f); + do_visit(c.wall_west(), f); + } +}; + +struct writer final : visitor_ +{ + const world& w; + + std::vector string_array{}; + tsl::robin_map string_map{hash_initial_size}; + + std::vector atlas_array{}; + tsl::robin_map atlas_map{hash_initial_size}; + + std::vector chunk_array{}; + + buffer header_buf{}, string_buf{}; + + writer(const world& w) : w{w} { } // added to avoid spurious warning until GCC 14: warning: missing initializer for member writer:: + + struct size_counter + { + size_t& size; + + template + requires (std::is_arithmetic_v && std::is_fundamental_v) + void operator()(T) { size += sizeof(T); } + }; + + struct byte_writer + { + binary_writer& s; + + template + requires (std::is_fundamental_v && std::is_arithmetic_v) + void operator()(T value) + { + s << value; + } + }; + + using visitor_::visit; + + template + void visit(critter& obj, F&& f) + { + uint8_t flags = 0; + flags |= (1 << 0) * obj.playable; + do_visit(flags, f); + do_visit(obj.name, f); + do_visit(obj.offset_frac, f); + } + + template + void visit(scenery& obj, F&& f) + { + auto sc_type = obj.sc_type; + do_visit(sc_type, f); + uint8_t flags = 0; + flags |= obj.active * (1 << 0); + flags |= obj.closing * (1 << 1); + flags |= obj.interactive * (1 << 2); + do_visit(flags, f); + } + + template + void visit(light& obj, F&& f) + { + do_visit(obj.max_distance, f); + do_visit(obj.color, f); + auto falloff = obj.falloff; + do_visit(falloff, f); + uint8_t flags = 0; + flags |= obj.enabled * (1 << 0); + do_visit(flags, f); + } + + template + void intern_atlas_(void* atlas, atlas_type type, F&& f) + { + do_visit(atlas_magic, f); + do_visit(type, f); + + StringView name; + + switch (type) + { + case atlas_type::ground: name = reinterpret_cast(atlas)->name(); goto ok; + case atlas_type::wall: name = reinterpret_cast(atlas)->name(); goto ok; + case atlas_type::object: name = reinterpret_cast(atlas)->name(); goto ok; + case atlas_type::none: break; + } + fm_abort("invalid atlas type '%d'", (int)type); + +ok: do_visit(intern_string(name), f); + } + + [[nodiscard]] atlasid intern_atlas(void* atlas, atlas_type type) + { + atlas_array.reserve(vector_initial_size); + fm_assert(atlas != nullptr); + auto [kv, fresh] = atlas_map.try_emplace(atlas, (uint32_t)-1); + if (!fresh) + { + fm_debug_assert(kv.value() != (uint32_t)-1); + return kv->second; + } + else + { + size_t len = 0; + intern_atlas_(atlas, type, size_counter{len}); + fm_assert(len > 0); + + buffer buf{len}; + binary_writer s{&buf.data[0], buf.size}; + intern_atlas_(atlas, type, byte_writer{s}); + auto id = (uint32_t)atlas_array.size(); + fm_assert(s.bytes_written() == s.bytes_allocated()); + atlas_array.emplace_back(std::move(buf), atlas, type); + kv.value() = id; + fm_assert(id != null_atlas); + return atlasid{id}; + } + } + + atlasid maybe_intern_atlas(void* atlas, atlas_type type) + { + if (!atlas) + return null_atlas; + else + return intern_atlas(atlas, type); + } + + atlasid intern_string(StringView str) + { + string_array.reserve(vector_initial_size); + auto [kv, found] = string_map.try_emplace({str}, (uint32_t)-1); + if (found) + return kv.value(); + else + { + auto id = (uint32_t)string_array.size(); + string_array.emplace_back(str); + kv.value() = id; + fm_assert(id != null_atlas); + return atlasid{id}; + } + } + + template + void serialize_objects_(chunk& c, F&& f) + { + f((uint32_t)c.objects().size()); + + for (const std::shared_ptr& obj : c.objects()) + { + fm_assert(obj != nullptr); + do_visit(object_magic, f); + do_visit(*obj, f); + } + } + + template + void serialize_tile_(tile_ref t, F&& f) + { + auto g = maybe_intern_atlas(t.ground_atlas().get(), atlas_type::ground), + n = maybe_intern_atlas(t.wall_north_atlas().get(), atlas_type::wall), + w = maybe_intern_atlas(t.wall_west_atlas().get(), atlas_type::wall); + do_visit(g, f); + do_visit(n, f); + do_visit(w, f); + if (g != null_atlas) do_visit(t.ground().variant, f); + if (n != null_atlas) do_visit(t.wall_north().variant, f); + if (w != null_atlas) do_visit(t.wall_west().variant, f); + } + + void serialize_chunk_(chunk& c, buffer& buf) + { + size_t len = 0; + auto ctr = size_counter{len}; + do_visit(chunk_magic, ctr); + do_visit(c.coord(), ctr); + fm_assert(len > 0); + for (uint32_t i = 0; i < TILE_COUNT; i++) + serialize_tile_(c[i], ctr); + serialize_objects_(c, ctr); + + buf = buffer{len}; + binary_writer s{&buf.data[0], buf.size}; + byte_writer b{s}; + do_visit(chunk_magic, b); + do_visit(c.coord(), b); + for (uint32_t i = 0; i < TILE_COUNT; i++) + serialize_tile_(c[i], b); + serialize_objects_(c, b); + fm_assert(s.bytes_written() == s.bytes_allocated()); + } + + template void serialize_header_(F&& f) + { + fm_assert(header_buf.empty()); + for (char c : file_magic) + f(c); + f(proto_version); + auto nstrings = (uint32_t)string_array.size(), + natlases = (uint32_t)atlas_array.size(), + nchunks = (uint32_t)chunk_array.size(); + do_visit(nstrings, f); + do_visit(natlases, f); + do_visit(nchunks, f); + } + + void serialize_strings_() + { + fm_assert(string_buf.empty()); + size_t len = 0; + len += sizeof uint32_t{}; + for (const auto& s : string_array) + len += s.size() + 1; + buffer buf{len}; + binary_writer b{&buf.data[0], buf.size}; + b << (uint32_t)string_array.size(); + for (const auto& s : string_array) + b.write_asciiz_string(s); + fm_assert(b.bytes_written() == b.bytes_allocated()); + string_buf = std::move(buf); + } + + void serialize_world() + { + fm_assert(string_array.empty()); + fm_assert(atlas_array.empty()); + fm_assert(chunk_array.empty()); + fm_assert(header_buf.empty()); + fm_assert(string_buf.empty()); + + for (auto& [coord, c] : non_const(w.chunks())) + chunk_array.push_back({.c = &c }); + + std::sort(chunk_array.begin(), chunk_array.end(), [](const auto& c1, const auto& c2) { + auto a = c1.c->coord(), b = c2.c->coord(); + return std::tuple{a.z, a.y, a.x} <=> std::tuple{b.z, b.y, b.x} == std::strong_ordering::less; + }); + + for (uint32_t i = 0; auto& [coord, c] : chunk_array) + serialize_chunk_(*c, chunk_array[i++].buf); + + { + size_t len = 0; + serialize_header_(size_counter{len}); + fm_assert(len > 0); + + buffer hdr{len}; + binary_writer s{&hdr.data[0], hdr.size}; + serialize_header_(byte_writer{s}); + fm_assert(s.bytes_written() == s.bytes_allocated()); + header_buf = std::move(hdr); + } + + serialize_strings_(); + } + + template + void visit(anim_atlas& a, F&& f) + { + atlasid id = intern_atlas(&a, atlas_type::object); + do_visit(id, f); + } + + template void visit(const chunk_coords_& coord, F&& f) + { + f(coord.x); + f(coord.y); + f(coord.z); + } + + template void visit(const local_coords& pt, F&& f) + { + f(pt.to_index()); + } + + template void visit(StringView name, F&& f) + { + f(intern_string(name)); + } +}; + +void my_fwrite(FILE_raii& f, const buffer& buf, char(&errbuf)[128]) +{ + auto len = ::fwrite(&buf.data[0], buf.size, 1, f); + int error = errno; + if (len != 1) + fm_abort("fwrite: %s", get_error_string(errbuf, error).data()); +} + +} // namespace + +} // namespace floormat::Serialize + +namespace floormat { + +void world::serialize(StringView filename) +{ + using namespace floormat::Serialize; + + collect(true); + char errbuf[128]; + fm_assert(filename.flags() & StringViewFlag::NullTerminated); + if (Path::exists(filename)) + Path::remove(filename); + FILE_raii file = ::fopen(filename.data(), "wb"); + if (!file) + { + int error = errno; + fm_abort("fopen(\"%s\", \"w\"): %s", filename.data(), get_error_string(errbuf, error).data()); + } + { + struct writer writer{*this}; + const bool is_empty = chunks().empty(); + writer.serialize_world(); + fm_assert(!writer.header_buf.empty()); + if (!is_empty) + { + fm_assert(!writer.string_buf.empty()); + fm_assert(!writer.string_array.empty()); + fm_assert(!writer.string_map.empty()); + fm_assert(!writer.atlas_array.empty()); + fm_assert(!writer.atlas_map.empty()); + fm_assert(!writer.chunk_array.empty()); + } + my_fwrite(file, writer.header_buf, errbuf); + my_fwrite(file, writer.string_buf, errbuf); + for (const auto& x : writer.atlas_array) + { + fm_assert(!x.buf.empty()); + my_fwrite(file, x.buf, errbuf); + } + for (const auto& x : writer.chunk_array) + { + fm_assert(!x.buf.empty()); + my_fwrite(file, x.buf, errbuf); + } + } + + if (int ret = ::fflush(file); ret != 0) + { + int error = errno; + fm_abort("fflush: %s", get_error_string(errbuf, error).data()); + } +} + +} // namespace floormat diff --git a/serialize/world-impl.hpp b/serialize/world-impl.hpp deleted file mode 100644 index 7eb6c7c9..00000000 --- a/serialize/world-impl.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once -#include -#include -#include - -namespace floormat::Serialize { - -using tilemeta = uint8_t; -using atlasid = uint32_t; -using chunksiz = uint32_t; -using proto_t = uint32_t; - -template struct int_traits; - -template struct int_traits { static constexpr T max = T(-1); }; -template struct int_traits { static constexpr T max = T(-1)&~(T(1) << sizeof(T)*8-1); }; - -namespace { - - - -} // namespace - -} // namespace floormat::Serialize - -namespace floormat { - -namespace { - - - -} // namespace - -} // namespace floormat diff --git a/serialize/world-reader.cpp b/serialize/world-reader.cpp index ab196f7e..a4f33b94 100644 --- a/serialize/world-reader.cpp +++ b/serialize/world-reader.cpp @@ -1,6 +1,6 @@ -#define FM_SERIALIZE_WORLD_IMPL -#include "world-impl.hpp" +#include "compat/assert.hpp" #include "src/world.hpp" +#include #if 0 #include "binary-reader.inl" #include "src/scenery.hpp" @@ -564,6 +564,7 @@ namespace floormat { class world world::deserialize(StringView filename) { + (void)filename; fm_assert("todo" && false); } diff --git a/serialize/world-writer.cpp b/serialize/world-writer.cpp deleted file mode 100644 index 4396b1fd..00000000 --- a/serialize/world-writer.cpp +++ /dev/null @@ -1,564 +0,0 @@ -#include "world-impl.hpp" -#include "binary-writer.inl" -#include "compat/defs.hpp" -#include "compat/strerror.hpp" -#include "compat/int-hash.hpp" -#include "loader/loader.hpp" -#include "src/world.hpp" - -#include "atlas-type.hpp" -#include "src/anim-atlas.hpp" -#include "src/ground-atlas.hpp" -#include "src/wall-atlas.hpp" - -#include "src/scenery.hpp" -#include "src/critter.hpp" -#include "src/light.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#if 1 -#ifdef __CLION_IDE__ -#undef fm_assert -#define fm_assert(...) (void)(__VA_ARGS__) -#endif -#endif - -namespace floormat::Serialize { - -namespace { - -constexpr inline proto_t proto_version = 20; -constexpr inline auto file_magic = ".floormat.save"_s; -constexpr inline auto chunk_magic = (uint16_t)0xdead; -constexpr inline auto object_magic = (uint16_t)0xb00b; -constexpr inline auto atlas_magic = (uint16_t)0xbeef; -constexpr inline auto null_atlas = (atlasid)-1; - -struct string_container -{ - StringView str; - bool operator==(const string_container&) const = default; - - friend void swap(string_container& a, string_container& b) - { - auto tmp = a.str; - a.str = b.str; - b.str = tmp; - } -}; - -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) std::fclose(s); s = nullptr; } -private: - FILE* s; -}; - -} // namespace - -} // namespace floormat::Serialize - -using floormat::Serialize::string_container; -using floormat::Hash::fnvhash_buf; - -template<> struct std::hash -{ - size_t operator()(const string_container& x) const noexcept - { - return fnvhash_buf(x.str.data(), x.str.size()); - } -}; - - -namespace floormat::Serialize { - -namespace { - -template T& non_const(const T& value) { return const_cast(value); } -template T& non_const(T& value) = delete; -template T& non_const(T&& value) = delete; -template T& non_const(const T&& value) = delete; - -constexpr size_t vector_initial_size = 128, hash_initial_size = vector_initial_size*2; - -template -struct magic -{ - using type = T; - uint16_t magic; -}; - -struct buffer -{ - std::unique_ptr data; - size_t size; - - bool empty() const { return size == 0; } - buffer() : data{nullptr}, size{0} {} - buffer(size_t len) : // todo use allocator - data{std::make_unique(len)}, - size{len} - { -#if !fm_ASAN - std::memset(&data[0], 0xfe, size); -#endif - } -}; - -struct serialized_atlas -{ - buffer buf; - const void* atlas; - atlas_type type; -}; - -struct serialized_chunk -{ - buffer buf{}; - chunk* c; -}; - -template -struct visitor_ -{ - template - CORRADE_ALWAYS_INLINE void do_visit_nonconst(const T& value, F&& fun) - { - do_visit(non_const(value), fun); - } - - template - CORRADE_ALWAYS_INLINE void do_visit(T&& value, F&& fun) - { - static_cast(*this).visit(value, fun); - } - - template - requires (std::is_arithmetic_v && std::is_fundamental_v) - void visit(T& x, F&& f) - { - f(x); - } - - template - void visit(Math::Vector& x, F&& f) - { - for (uint32_t i = 0; i < N; i++) - do_visit(x.data()[i], f); - } - -#if 0 - template - void visit(StringView str, F&& f) - { - f(str); - } -#endif - - template - requires std::is_enum_v - void visit(E& x, F&& f) - { - auto* ptr = const_cast*>(reinterpret_cast*>(&x)); - do_visit(*ptr, f); - } - - template - void visit(object& obj, F&& f) - { - auto type = obj.type(); - - do_visit(obj.id, f); - do_visit(type, f); - fm_assert(obj.atlas); - do_visit(*obj.atlas, f); - //do_visit(*obj.c, f); - do_visit(obj.coord.local(), f); - do_visit_nonconst(obj.offset, f); - do_visit_nonconst(obj.bbox_offset, f); - do_visit_nonconst(obj.bbox_size, f); - do_visit_nonconst(obj.delta, f); - do_visit_nonconst(obj.frame, f); - do_visit_nonconst(obj.r, f); - do_visit_nonconst(obj.pass, f); - - switch (type) - { - case object_type::critter: do_visit(static_cast(obj), f); return; - case object_type::scenery: do_visit(static_cast(obj), f); return; - case object_type::light: do_visit(static_cast(obj), f); return; - //case object_type::door: do_visit(static_cast(obj), f); return; - case object_type::COUNT: - case object_type::none: - break; - } - fm_abort("invalid object type '%d'", (int)obj.type()); - } - - template - void visit(tile_ref c, F&& f) - { - do_visit(c.ground(), f); - do_visit(c.wall_north(), f); - do_visit(c.wall_west(), f); - } -}; - -struct writer final : visitor_ -{ - const world& w; - - std::vector string_array{}; - tsl::robin_map string_map{hash_initial_size}; - - std::vector atlas_array{}; - tsl::robin_map atlas_map{hash_initial_size}; - - std::vector chunk_array{}; - - buffer header_buf{}, string_buf{}; - - writer(const world& w) : w{w} { } // added to avoid spurious warning until GCC 14: warning: missing initializer for member writer:: - - struct size_counter - { - size_t& size; - - template - requires (std::is_arithmetic_v && std::is_fundamental_v) - void operator()(T) { size += sizeof(T); } - }; - - struct byte_writer - { - binary_writer& s; - - template - requires (std::is_fundamental_v && std::is_arithmetic_v) - void operator()(T value) - { - s << value; - } - }; - - using visitor_::visit; - - template - void visit(critter& obj, F&& f) - { - uint8_t flags = 0; - flags |= (1 << 0) * obj.playable; - do_visit(flags, f); - do_visit(obj.name, f); - do_visit(obj.offset_frac, f); - } - - template - void visit(scenery& obj, F&& f) - { - auto sc_type = obj.sc_type; - do_visit(sc_type, f); - uint8_t flags = 0; - flags |= obj.active * (1 << 0); - flags |= obj.closing * (1 << 1); - flags |= obj.interactive * (1 << 2); - do_visit(flags, f); - } - - template - void visit(light& obj, F&& f) - { - do_visit(obj.max_distance, f); - do_visit(obj.color, f); - auto falloff = obj.falloff; - do_visit(falloff, f); - uint8_t flags = 0; - flags |= obj.enabled * (1 << 0); - do_visit(flags, f); - } - - template - void intern_atlas_(void* atlas, atlas_type type, F&& f) - { - do_visit(atlas_magic, f); - do_visit(type, f); - - StringView name; - - switch (type) - { - case atlas_type::ground: name = reinterpret_cast(atlas)->name(); goto ok; - case atlas_type::wall: name = reinterpret_cast(atlas)->name(); goto ok; - case atlas_type::object: name = reinterpret_cast(atlas)->name(); goto ok; - case atlas_type::none: break; - } - fm_abort("invalid atlas type '%d'", (int)type); - -ok: do_visit(intern_string(name), f); - } - - [[nodiscard]] atlasid intern_atlas(void* atlas, atlas_type type) - { - atlas_array.reserve(vector_initial_size); - fm_assert(atlas != nullptr); - auto [kv, fresh] = atlas_map.try_emplace(atlas, (uint32_t)-1); - if (!fresh) - { - fm_debug_assert(kv.value() != (uint32_t)-1); - return kv->second; - } - else - { - size_t len = 0; - intern_atlas_(atlas, type, size_counter{len}); - fm_assert(len > 0); - - buffer buf{len}; - binary_writer s{&buf.data[0], buf.size}; - intern_atlas_(atlas, type, byte_writer{s}); - auto id = (uint32_t)atlas_array.size(); - fm_assert(s.bytes_written() == s.bytes_allocated()); - atlas_array.emplace_back(std::move(buf), atlas, type); - kv.value() = id; - fm_assert(id != null_atlas); - return atlasid{id}; - } - } - - atlasid maybe_intern_atlas(void* atlas, atlas_type type) - { - if (!atlas) - return null_atlas; - else - return intern_atlas(atlas, type); - } - - atlasid intern_string(StringView str) - { - string_array.reserve(vector_initial_size); - auto [kv, found] = string_map.try_emplace({str}, (uint32_t)-1); - if (found) - return kv.value(); - else - { - auto id = (uint32_t)string_array.size(); - string_array.emplace_back(str); - kv.value() = id; - fm_assert(id != null_atlas); - return atlasid{id}; - } - } - - template - void serialize_objects_(chunk& c, F&& f) - { - f((uint32_t)c.objects().size()); - - for (const std::shared_ptr& obj : c.objects()) - { - fm_assert(obj != nullptr); - do_visit(object_magic, f); - do_visit(*obj, f); - } - } - - template - void serialize_tile_(tile_ref t, F&& f) - { - auto g = maybe_intern_atlas(t.ground_atlas().get(), atlas_type::ground), - n = maybe_intern_atlas(t.wall_north_atlas().get(), atlas_type::wall), - w = maybe_intern_atlas(t.wall_west_atlas().get(), atlas_type::wall); - do_visit(g, f); - do_visit(n, f); - do_visit(w, f); - if (g != null_atlas) do_visit(t.ground().variant, f); - if (n != null_atlas) do_visit(t.wall_north().variant, f); - if (w != null_atlas) do_visit(t.wall_west().variant, f); - } - - void serialize_chunk_(chunk& c, buffer& buf) - { - size_t len = 0; - auto ctr = size_counter{len}; - do_visit(chunk_magic, ctr); - do_visit(c.coord(), ctr); - fm_assert(len > 0); - for (uint32_t i = 0; i < TILE_COUNT; i++) - serialize_tile_(c[i], ctr); - serialize_objects_(c, ctr); - - buf = buffer{len}; - binary_writer s{&buf.data[0], buf.size}; - byte_writer b{s}; - do_visit(chunk_magic, b); - do_visit(c.coord(), b); - for (uint32_t i = 0; i < TILE_COUNT; i++) - serialize_tile_(c[i], b); - serialize_objects_(c, b); - fm_assert(s.bytes_written() == s.bytes_allocated()); - } - - template void serialize_header_(F&& f) - { - fm_assert(header_buf.empty()); - for (char c : file_magic) - f(c); - f(proto_version); - auto nstrings = (uint32_t)string_array.size(), - natlases = (uint32_t)atlas_array.size(), - nchunks = (uint32_t)chunk_array.size(); - do_visit(nstrings, f); - do_visit(natlases, f); - do_visit(nchunks, f); - } - - void serialize_strings_() - { - fm_assert(string_buf.empty()); - size_t len = 0; - len += sizeof uint32_t{}; - for (const auto& s : string_array) - len += s.size() + 1; - buffer buf{len}; - binary_writer b{&buf.data[0], buf.size}; - b << (uint32_t)string_array.size(); - for (const auto& s : string_array) - b.write_asciiz_string(s); - fm_assert(b.bytes_written() == b.bytes_allocated()); - string_buf = std::move(buf); - } - - void serialize_world() - { - fm_assert(string_array.empty()); - fm_assert(atlas_array.empty()); - fm_assert(chunk_array.empty()); - fm_assert(header_buf.empty()); - fm_assert(string_buf.empty()); - - for (auto& [coord, c] : non_const(w.chunks())) - chunk_array.push_back({.c = &c }); - - std::sort(chunk_array.begin(), chunk_array.end(), [](const auto& c1, const auto& c2) { - auto a = c1.c->coord(), b = c2.c->coord(); - return std::tuple{a.z, a.y, a.x} <=> std::tuple{b.z, b.y, b.x} == std::strong_ordering::less; - }); - - for (uint32_t i = 0; auto& [coord, c] : chunk_array) - serialize_chunk_(*c, chunk_array[i++].buf); - - { - size_t len = 0; - serialize_header_(size_counter{len}); - fm_assert(len > 0); - - buffer hdr{len}; - binary_writer s{&hdr.data[0], hdr.size}; - serialize_header_(byte_writer{s}); - fm_assert(s.bytes_written() == s.bytes_allocated()); - header_buf = std::move(hdr); - } - - serialize_strings_(); - } - - template - void visit(anim_atlas& a, F&& f) - { - atlasid id = intern_atlas(&a, atlas_type::object); - do_visit(id, f); - } - - template void visit(const chunk_coords_& coord, F&& f) - { - f(coord.x); - f(coord.y); - f(coord.z); - } - - template void visit(const local_coords& pt, F&& f) - { - f(pt.to_index()); - } - - template void visit(StringView name, F&& f) - { - f(intern_string(name)); - } -}; - -void my_fwrite(FILE_raii& f, const buffer& buf, char(&errbuf)[128]) -{ - auto len = ::fwrite(&buf.data[0], buf.size, 1, f); - int error = errno; - if (len != 1) - fm_abort("fwrite: %s", get_error_string(errbuf, error).data()); -} - -} // namespace - -} // namespace floormat::Serialize - -namespace floormat { - -void world::serialize(StringView filename) -{ - using namespace floormat::Serialize; - - collect(true); - char errbuf[128]; - fm_assert(filename.flags() & StringViewFlag::NullTerminated); - if (Path::exists(filename)) - Path::remove(filename); - FILE_raii file = ::fopen(filename.data(), "wb"); - if (!file) - { - int error = errno; - fm_abort("fopen(\"%s\", \"w\"): %s", filename.data(), get_error_string(errbuf, error).data()); - } - { - struct writer writer{*this}; - const bool is_empty = chunks().empty(); - writer.serialize_world(); - fm_assert(!writer.header_buf.empty()); - if (!is_empty) - { - fm_assert(!writer.string_buf.empty()); - fm_assert(!writer.string_array.empty()); - fm_assert(!writer.string_map.empty()); - fm_assert(!writer.atlas_array.empty()); - fm_assert(!writer.atlas_map.empty()); - fm_assert(!writer.chunk_array.empty()); - } - my_fwrite(file, writer.header_buf, errbuf); - my_fwrite(file, writer.string_buf, errbuf); - for (const auto& x : writer.atlas_array) - { - fm_assert(!x.buf.empty()); - my_fwrite(file, x.buf, errbuf); - } - for (const auto& x : writer.chunk_array) - { - fm_assert(!x.buf.empty()); - my_fwrite(file, x.buf, errbuf); - } - } - - if (int ret = ::fflush(file); ret != 0) - { - int error = errno; - fm_abort("fflush: %s", get_error_string(errbuf, error).data()); - } -} - -} // namespace floormat -- cgit v1.2.3