diff options
Diffstat (limited to 'serialize/world-writer.cpp')
-rw-r--r-- | serialize/world-writer.cpp | 279 |
1 files changed, 234 insertions, 45 deletions
diff --git a/serialize/world-writer.cpp b/serialize/world-writer.cpp index ed62fcf5..2754f26f 100644 --- a/serialize/world-writer.cpp +++ b/serialize/world-writer.cpp @@ -7,19 +7,37 @@ #include "src/chunk.hpp" #include "src/world.hpp" #include "src/emplacer.hpp" +#include "loader/loader.hpp" +#include "src/scenery.hpp" +#include "loader/scenery.hpp" #include <vector> #include <algorithm> #include <cstring> #include <Corrade/Containers/StringView.h> #include <Corrade/Utility/Path.h> -namespace floormat::Serialize { +namespace { + +using namespace floormat; +using namespace floormat::Serialize; struct interned_atlas final { const tile_atlas* img; atlasid index; }; +struct interned_scenery { + const serialized_scenery* s; + atlasid index; + static_assert(sizeof index >= sizeof scenery::frame); +}; + +struct scenery_pair { + const serialized_scenery* s; + atlasid index; + bool exact_match; +}; + struct writer_state final { writer_state(const world& world); ArrayView<const char> serialize_world(); @@ -27,15 +45,26 @@ struct writer_state final { fm_DECLARE_DEPRECATED_COPY_ASSIGNMENT(writer_state); private: + static constexpr inline scenery_pair null_scenery = { nullptr, null_atlas, true }; + atlasid intern_atlas(const tile_image_proto& img); atlasid maybe_intern_atlas(const tile_image_proto& img); + scenery_pair intern_scenery(scenery_proto s, bool create); + scenery_pair maybe_intern_scenery(const scenery_proto& s, bool create); + void serialize_chunk(const chunk& c, chunk_coords coord); void serialize_atlases(); + void serialize_scenery(); + + void load_scenery_1(const serialized_scenery& s); + void load_scenery(); const world* _world; - std::vector<char> atlas_buf, chunk_buf, file_buf; + std::vector<char> atlas_buf, scenery_buf, chunk_buf, file_buf; std::vector<std::vector<char>> chunk_bufs; std::unordered_map<const void*, interned_atlas> tile_images; + std::unordered_map<const void*, std::vector<interned_scenery>> scenery_map; + atlasid scenery_map_size = 0; }; constexpr auto tile_size = sizeof(tilemeta) + (sizeof(atlasid) + sizeof(variant_t)) * 3; @@ -77,6 +106,173 @@ atlasid writer_state::maybe_intern_atlas(const tile_image_proto& img) return img ? intern_atlas(img) : null_atlas; } +void writer_state::load_scenery_1(const serialized_scenery& s) +{ + const void* const ptr = s.proto.atlas.get(); + fm_debug_assert(ptr != nullptr); + if (auto it = scenery_map.find(ptr); it == scenery_map.end()) + scenery_map[ptr] = { { &s, null_atlas } }; + else + { + fm_assert(s.proto.frame.delta == 0.f); + auto& vec = scenery_map[ptr]; + for (const auto& x : vec) + if (s.proto.frame == x.s->proto.frame) + return; + vec.push_back({ &s, null_atlas }); + } +} + +void writer_state::load_scenery() +{ + for (const auto& s : loader.sceneries()) + load_scenery_1(s); +} + +scenery_pair writer_state::intern_scenery(scenery_proto s, bool create) +{ + const void* const ptr = s.atlas.get(); + fm_debug_assert(ptr != nullptr); + auto it = scenery_map.find(ptr); + fm_assert(it != scenery_map.end() && !it->second.empty()); + auto& vec = it->second; + interned_scenery* ret = nullptr; + for (interned_scenery& x : vec) + { + fm_debug_assert(s.frame.type == x.s->proto.frame.type); + s.frame.r = x.s->proto.frame.r; + if (x.s->proto.frame == s.frame) + { + if (x.index != null_atlas) + return { x.s, x.index, true }; + else + ret = &x; + } + } + + if (ret) + { + ret->index = scenery_map_size++; + return { ret->s, ret->index, true }; + } + else if (create) + return { vec[0].s, vec[0].index = scenery_map_size++, false }; + else + return {}; +} + +scenery_pair writer_state::maybe_intern_scenery(const scenery_proto& s, bool create) +{ + return s ? intern_scenery(s, create) : null_scenery; +} + +template<typename T> +void write_scenery_flags(binary_writer<T>& s, const scenery& proto) +{ + std::uint8_t flags = 0; + flags |= (1 << 0) * proto.passable; + flags |= (1 << 1) * proto.blocks_view; + flags |= (1 << 2) * proto.active; + flags |= (1 << 3) * proto.closing; + flags |= (1 << 4) * proto.interactive; + flags |= (1 << 7) * (proto.frame <= 0xff); + s << flags; +} + +void writer_state::serialize_atlases() +{ + fm_assert(tile_images.size() < int_max<atlasid>); + const auto sz = (atlasid)tile_images.size(); + 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; + + std::vector<interned_atlas> atlases; + atlases.reserve(tile_images.size()); + + for (const auto& [_, t] : tile_images) + atlases.push_back(t); + std::sort(atlases.begin(), atlases.end(), [](const auto& a, const auto& b) { + return a.index < b.index; + }); + + for (const auto& [atlas, _] : atlases) + { + 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()); + const auto sz2 = atlas->num_tiles2(); + s << sz2[0]; s << sz2[1]; + s.write_asciiz_string(name); + } + atlas_buf.resize(s.bytes_written()); + fm_assert(s.bytes_written() <= atlasbuf_size); +} + +constexpr auto atlasbuf_size0 = sizeof(atlasid) + sizeof(scenery); +constexpr auto atlasbuf_size1 = sizeof(std::uint8_t) + atlasbuf_size0*int_max<std::uint8_t> + atlas_name_max; + +void writer_state::serialize_scenery() +{ + fm_assert(scenery_map_size < scenery_id_max); + const std::size_t sz = scenery_map_size; + std::vector<interned_scenery> vec; vec.reserve(scenery_map_size); + for (const auto& x : scenery_map) + for (const auto& s : x.second) + if (s.index != null_atlas) + vec.push_back(s); + fm_assert(sz == vec.size()); + + std::sort(vec.begin(), vec.end(), [](const interned_scenery& a, const interned_scenery& b) { + auto cmp = a.s->name <=> b.s->name; + if (cmp == std::strong_ordering::equal) + return a.index < b.index; + else + return cmp == std::strong_ordering::less; + }); + + const auto atlasbuf_size = sizeof(std::uint16_t) + sizeof(sz) + atlasbuf_size1*sz; + scenery_buf.resize(atlasbuf_size); + + auto s = binary_writer{scenery_buf.begin()}; + + s << std::uint16_t{scenery_magic}; + fm_assert(sz < scenery_id_max); + s << (atlasid)sz; + + StringView last; + for (std::size_t i = 0; i < sz; i++) + { + fm_debug_assert(s.bytes_written() + atlasbuf_size1 < atlasbuf_size); + const auto& [sc, idx] = vec[i]; + if (sc->name != last) + { + fm_assert(sc->name.size() < atlas_name_max); + last = sc->name; + std::size_t num = 1; + for (std::size_t j = i+1; j < sz && vec[j].s->name == sc->name; j++) + num++; + fm_assert(num < int_max<std::uint8_t>); + s << (std::uint8_t)num; + s.write_asciiz_string(sc->name); + } + s << idx; + const auto& fr = sc->proto.frame; + write_scenery_flags(s, sc->proto.frame); + if (sc->proto.frame.frame <= 0xff) + s << (std::uint8_t)fr.frame; + else + s << fr.frame; + } + + scenery_buf.resize(s.bytes_written()); +} + void writer_state::serialize_chunk(const chunk& c, chunk_coords coord) { fm_assert(chunk_buf.empty()); @@ -90,17 +286,20 @@ void writer_state::serialize_chunk(const chunk& c, chunk_coords coord) { const tile_proto x = c[i]; const auto ground = x.ground_image(), wall_north = x.wall_north_image(), wall_west = x.wall_west_image(); + const auto scenery = x.scenery_frame; fm_debug_assert(s.bytes_written() + tile_size <= chunkbuf_size); auto img_g = maybe_intern_atlas(ground); auto img_n = maybe_intern_atlas(wall_north); auto img_w = maybe_intern_atlas(wall_west); + auto [sc, img_s, sc_exact] = maybe_intern_scenery(x.scenery_image(), true); 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); + flags |= meta_ground * (img_g != null_atlas); + flags |= meta_wall_n * (img_n != null_atlas); + flags |= meta_wall_w * (img_w != null_atlas); + flags |= meta_scenery * (img_s != null_atlas); using uchar = std::uint8_t; @@ -137,51 +336,34 @@ void writer_state::serialize_chunk(const chunk& c, chunk_coords coord) write(img_n, wall_north.variant); if (img_w != null_atlas) write(img_w, wall_west.variant); + if (img_s != null_atlas) + { + atlasid id = img_s; + id |= meta_long_scenery_bit * sc_exact; + id |= atlasid(scenery.r) << sizeof(atlasid)*8-1-rotation_BITS; + s << id; + if (!sc_exact) + { + fm_assert(scenery.active || scenery.delta == 0.0f); + write_scenery_flags(s, scenery); + if (scenery.frame <= 0xff) + s << (std::uint8_t)scenery.frame; + else + s << scenery.frame; + if (scenery.active) + s << scenery.delta; + } + } } const auto nbytes = s.bytes_written(); fm_assert(nbytes <= chunkbuf_size); chunk_buf.resize(nbytes); - chunk_bufs.push_back(chunk_buf); + chunk_bufs.push_back(std::move(chunk_buf)); chunk_buf.clear(); } -void writer_state::serialize_atlases() -{ - fm_assert(tile_images.size() < int_max<atlasid>); - const auto sz = (atlasid)tile_images.size(); - 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; - - std::vector<interned_atlas> atlases; - atlases.reserve(tile_images.size()); - - for (const auto& [_, t] : tile_images) - atlases.push_back(t); - std::sort(atlases.begin(), atlases.end(), [](const auto& a, const auto& b) { - return a.index < b.index; - }); - - for (const auto& [atlas, _] : atlases) - { - 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()); - const auto sz2 = atlas->num_tiles2(); - s << sz2[0]; s << sz2[1]; - s.write_asciiz_string(name); - } - atlas_buf.resize(s.bytes_written()); - fm_assert(s.bytes_written() <= atlasbuf_size); -} - #ifdef __GNUG__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" @@ -192,6 +374,12 @@ void writer_state::serialize_atlases() ArrayView<const char> writer_state::serialize_world() { + load_scenery(); + + for (const auto& [_, c] : _world->chunks()) + for (auto [x, _k, _pt] : c) + maybe_intern_scenery(x.scenery_image(), false); + for (const auto& [pos, c] : _world->chunks()) { #ifndef FM_NO_DEBUG @@ -201,6 +389,7 @@ ArrayView<const char> writer_state::serialize_world() serialize_chunk(c, pos); } serialize_atlases(); + serialize_scenery(); using proto_t = std::decay_t<decltype(proto_version)>; union { chunksiz x; char bytes[sizeof x]; } c = {.x = maybe_byteswap((chunksiz)_world->size())}; @@ -214,19 +403,19 @@ ArrayView<const char> writer_state::serialize_world() for (const auto& buf : chunk_bufs) len += buf.size(); len += atlas_buf.size(); + len += scenery_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(p.bytes); copy(atlas_buf); + copy(scenery_buf); copy(c.bytes); for (const auto& buf : chunk_bufs) copy(buf); @@ -239,7 +428,7 @@ ArrayView<const char> writer_state::serialize_world() #pragma warning(pop) #endif -} // namespace floormat::Serialize +} // namespace namespace floormat { @@ -264,7 +453,7 @@ void world::serialize(StringView filename) get_error_string(errbuf); fm_abort("fopen(\"%s\", \"w\"): %s", filename.data(), errbuf); } - Serialize::writer_state s{*this}; + writer_state s{*this}; const auto array = s.serialize_world(); if (auto len = ::fwrite(array.data(), array.size(), 1, file); len != 1) { |