diff options
author | Stanislaw Halik <sthalik@misaki.pl> | 2022-12-01 13:21:32 +0100 |
---|---|---|
committer | Stanislaw Halik <sthalik@misaki.pl> | 2022-12-01 13:21:32 +0100 |
commit | 81f68a2c83c0c25259cd526c8bb4839caa361e8f (patch) | |
tree | ff2db492dbd3dddfc341370a4cf4b2a95abdae70 | |
parent | 511d823c2dc2b917afed6a9c50ad940e5c58c5d5 (diff) |
serialize, loader, test: add serializing scenery
-rw-r--r-- | CMakeLists.txt | 16 | ||||
-rw-r--r-- | editor/scenery-json.cpp | 3 | ||||
-rw-r--r-- | loader/impl.cpp | 1 | ||||
-rw-r--r-- | loader/impl.hpp | 5 | ||||
-rw-r--r-- | loader/json.cpp | 33 | ||||
-rw-r--r-- | loader/loader.hpp | 5 | ||||
-rw-r--r-- | loader/scenery.hpp | 13 | ||||
-rw-r--r-- | main/draw.cpp | 2 | ||||
-rw-r--r-- | serialize/binary-reader.hpp | 5 | ||||
-rw-r--r-- | serialize/scenery.cpp | 4 | ||||
-rw-r--r-- | serialize/scenery.hpp | 16 | ||||
-rw-r--r-- | serialize/world-impl.hpp | 22 | ||||
-rw-r--r-- | serialize/world-reader.cpp | 105 | ||||
-rw-r--r-- | serialize/world-writer.cpp | 279 | ||||
-rw-r--r-- | src/scenery.hpp | 4 | ||||
-rw-r--r-- | test/serializer.cpp | 9 |
16 files changed, 439 insertions, 83 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d56aa45..4c92d30f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,6 +107,7 @@ if(MSVC) -wd4456 # warning C4456: declaration of 'x' hides previous local declaration -wd4458 # warning C4458: declaration of 'keys' hides class member -wd4127 # warning C4127: conditional expression is constant + -wd4554 # warning C4554: '<<': check operator precedence for possible error; use parentheses to clarify precedence ) add_definitions(-utf-8) if(CMAKE_SIZEOF_VOID_P GREATER_EQUAL 8) @@ -146,14 +147,6 @@ add_definitions( -DIMGUI_DISABLE_OBSOLETE_KEYIO ) -if(CMAKE_COMPILER_IS_GNUCXX) - if (CMAKE_CXX_COMPILER_ID MATCHES "Clang$") - add_definitions(-D_LIBCPP_REMOVE_TRANSITIVE_INCLUDES) - else() - add_compile_options(-Wno-subobject-linkage -Wno-parentheses) - endif() -endif() - if(NOT APPLE AND NOT WIN32) add_compile_options(-fno-plt) endif() @@ -166,6 +159,13 @@ endif() fm_run_hook(fm-userconfig-src) +if (CMAKE_CXX_COMPILER_ID MATCHES "Clang$") + add_definitions(-D_LIBCPP_REMOVE_TRANSITIVE_INCLUDES) + add_compile_options(-Wno-shift-op-parentheses) +elseif(CMAKE_COMPILER_IS_GNUCXX) + add_compile_options(-Wno-subobject-linkage -Wno-parentheses) +endif() + include_directories(.) include_directories(src) diff --git a/editor/scenery-json.cpp b/editor/scenery-json.cpp index a6756512..c5d7d043 100644 --- a/editor/scenery-json.cpp +++ b/editor/scenery-json.cpp @@ -1,13 +1,14 @@ #include "scenery-editor.hpp" #include "serialize/scenery.hpp" #include "loader/loader.hpp" +#include "loader/scenery.hpp" namespace floormat { void scenery_editor::load_atlases() { _atlases.clear(); - for (auto& s : loader.sceneries()) + for (const auto& s : loader.sceneries()) _atlases[s.name] = scenery_{s.name, s.descr, s.proto}; } diff --git a/loader/impl.cpp b/loader/impl.cpp index 65516e17..45c5b055 100644 --- a/loader/impl.cpp +++ b/loader/impl.cpp @@ -1,5 +1,6 @@ #include "impl.hpp" #include "compat/assert.hpp" +#include "loader/scenery.hpp" #include <cstring> #include <memory> #include <Corrade/Containers/Pair.h> diff --git a/loader/impl.hpp b/loader/impl.hpp index 6a49269f..a6916ceb 100644 --- a/loader/impl.hpp +++ b/loader/impl.hpp @@ -25,11 +25,16 @@ struct loader_impl final : loader_ std::unordered_map<String, std::shared_ptr<struct anim_atlas>> anim_atlas_map; std::vector<String> anim_atlases; + std::vector<serialized_scenery> sceneries_array; + std::unordered_map<StringView, const serialized_scenery*> sceneries_map; + StringView shader(StringView filename) override; Trade::ImageData2D texture(StringView prefix, StringView filename); std::shared_ptr<struct tile_atlas> tile_atlas(StringView filename, Vector2ub size) override; ArrayView<String> anim_atlas_list() override; std::shared_ptr<struct anim_atlas> anim_atlas(StringView name, StringView dir) override; + const std::vector<serialized_scenery>& sceneries() override; + const scenery_proto& scenery(StringView name) override; void get_anim_atlas_list(); diff --git a/loader/json.cpp b/loader/json.cpp index 1a698a38..710792f8 100644 --- a/loader/json.cpp +++ b/loader/json.cpp @@ -1,8 +1,10 @@ #include "impl.hpp" +#include "compat/assert.hpp" #include "serialize/json-helper.hpp" #include "serialize/anim.hpp" #include "serialize/tile-atlas.hpp" #include "serialize/scenery.hpp" +#include "loader/scenery.hpp" #include <Corrade/Utility/Path.h> namespace floormat::loader_detail { @@ -12,6 +14,32 @@ anim_def loader_impl::deserialize_anim(StringView filename) return json_helper::from_json<anim_def>(filename); } +const std::vector<serialized_scenery>& loader_impl::sceneries() +{ + if (!sceneries_array.empty()) + return sceneries_array; + + sceneries_array = json_helper::from_json<std::vector<serialized_scenery>>(Path::join(SCENERY_PATH, "scenery.json")); + sceneries_map.reserve(sceneries_array.size() * 2); + for (const serialized_scenery& s : sceneries_array) + { + if (sceneries_map.contains(s.name)) + fm_abort("duplicate scenery name '%s'", s.name.data()); + sceneries_map[s.name] = &s; + } + return sceneries_array; +} + +const scenery_proto& loader_impl::scenery(StringView name) +{ + if (sceneries_array.empty()) + (void)sceneries(); + auto it = sceneries_map.find(name); + if (it == sceneries_map.end()) + fm_abort("no such scenery: '%s'", name.data()); + return it->second->proto; +} + } // namespace floormat::loader_detail namespace floormat { @@ -21,9 +49,4 @@ std::vector<std::shared_ptr<struct tile_atlas>> loader_::tile_atlases(StringView return json_helper::from_json<std::vector<std::shared_ptr<struct tile_atlas>>>(Path::join(loader_::IMAGE_PATH, filename)); } -std::vector<Serialize::serialized_scenery> loader_::sceneries() -{ - return json_helper::from_json<std::vector<Serialize::serialized_scenery>>(Path::join(SCENERY_PATH, "scenery.json")); -} - } // namespace floormat diff --git a/loader/loader.hpp b/loader/loader.hpp index 93ac5654..458bf34d 100644 --- a/loader/loader.hpp +++ b/loader/loader.hpp @@ -4,7 +4,7 @@ #include <Corrade/Containers/StringView.h> namespace Magnum { using Vector2ub = Math::Vector2<unsigned char>; } -namespace floormat::Serialize { struct serialized_scenery; } +namespace floormat { struct serialized_scenery; } namespace floormat { @@ -21,7 +21,8 @@ struct loader_ static void destroy(); static loader_& default_loader() noexcept; static std::vector<std::shared_ptr<struct tile_atlas>> tile_atlases(StringView filename); - static std::vector<Serialize::serialized_scenery> sceneries(); + virtual const std::vector<serialized_scenery>& sceneries() = 0; + virtual const scenery_proto& scenery(StringView name) = 0; loader_(const loader_&) = delete; loader_& operator=(const loader_&) = delete; diff --git a/loader/scenery.hpp b/loader/scenery.hpp new file mode 100644 index 00000000..55f3f972 --- /dev/null +++ b/loader/scenery.hpp @@ -0,0 +1,13 @@ +#pragma once +#include "src/scenery.hpp" +#include <Corrade/Containers/String.h> + +namespace floormat { + +struct serialized_scenery final +{ + String name, descr; + scenery_proto proto; +}; + +} // namespace floormat diff --git a/main/draw.cpp b/main/draw.cpp index 91000e81..2f9bc737 100644 --- a/main/draw.cpp +++ b/main/draw.cpp @@ -171,7 +171,7 @@ void main_impl::do_update() timeline.nextFrame(); } - dt = std::clamp(dt, 1e-5f, std::fmaxf(1e-1f, dt_expected.value)); + dt = std::clamp(dt, 1e-5f, std::fmaxf(5e-2f, dt_expected.value)); app.update(dt); } diff --git a/serialize/binary-reader.hpp b/serialize/binary-reader.hpp index b95c3713..bf7e578e 100644 --- a/serialize/binary-reader.hpp +++ b/serialize/binary-reader.hpp @@ -33,6 +33,11 @@ struct binary_reader final { constexpr std::size_t bytes_read() const noexcept { return num_bytes_read; } template<std::size_t Max> constexpr auto read_asciiz_string() noexcept; + binary_reader(binary_reader&&) noexcept = default; + binary_reader& operator=(binary_reader&&) noexcept = default; + binary_reader(const binary_reader&) = delete; + binary_reader& operator=(const binary_reader&) = delete; + private: std::size_t num_bytes_read = 0; It it, end; diff --git a/serialize/scenery.cpp b/serialize/scenery.cpp index 94d9ad9d..7b402945 100644 --- a/serialize/scenery.cpp +++ b/serialize/scenery.cpp @@ -3,13 +3,13 @@ #include "compat/assert.hpp" #include "loader/loader.hpp" #include "serialize/corrade-string.hpp" +#include "loader/scenery.hpp" #include <Corrade/Containers/StringStlView.h> #include <nlohmann/json.hpp> namespace { using namespace floormat; -using namespace floormat::Serialize; constexpr struct { scenery_type value = scenery_type::none; @@ -107,8 +107,8 @@ void adl_serializer<scenery_proto>::from_json(const json& j, scenery_proto& val) f = {}; auto type = scenery_type::generic; get("type", type); + auto r = val.atlas->first_rotation(); get("rotation", r); auto frame = f.frame; get("frame", frame); - auto r = f.r; get("rotation", r); bool passable = f.passable; get("passable", passable); bool blocks_view = f.blocks_view; get("blocks-view", blocks_view); bool active = f.active; get("active", active); diff --git a/serialize/scenery.hpp b/serialize/scenery.hpp index e3eb977a..f3ceb586 100644 --- a/serialize/scenery.hpp +++ b/serialize/scenery.hpp @@ -4,15 +4,7 @@ #include <Corrade/Containers/String.h> #include <nlohmann/json_fwd.hpp> -namespace floormat::Serialize { - -struct serialized_scenery final -{ - String name, descr; - scenery_proto proto; -}; - -} // namespace floormat::Serialize +namespace floormat { struct serialized_scenery; } namespace nlohmann { @@ -31,9 +23,9 @@ template<> struct adl_serializer<floormat::scenery_proto> { static void from_json(const json& j, floormat::scenery_proto& val); }; -template<> struct adl_serializer<floormat::Serialize::serialized_scenery> { - static void to_json(json& j, const floormat::Serialize::serialized_scenery& val); - static void from_json(const json& j, floormat::Serialize::serialized_scenery& val); +template<> struct adl_serializer<floormat::serialized_scenery> { + static void to_json(json& j, const floormat::serialized_scenery& val); + static void from_json(const json& j, floormat::serialized_scenery& val); }; } // namespace nlohmann diff --git a/serialize/world-impl.hpp b/serialize/world-impl.hpp index c32c460f..dcd379a9 100644 --- a/serialize/world-impl.hpp +++ b/serialize/world-impl.hpp @@ -5,9 +5,18 @@ #pragma once #include "src/tile.hpp" #include <bit> +#include <cstddef> +#include <cstdint> #include <cstdio> #include <limits> +/* protocol changelog: + * 1) Initial version. + * 2) Tile atlas variant now always a uint8_t. Was uint16_t or uint8_t + * depending on value of the tile flag (1 << 6) which is now removed. + * 3) Serialize scenery. Tile flag (1 << 6) added. + */ + namespace floormat::Serialize { using tilemeta = std::uint8_t; @@ -24,13 +33,23 @@ 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; -constexpr inline proto_t proto_version = 2; +constexpr inline proto_t proto_version = 3; constexpr inline proto_t min_proto_version = 1; constexpr inline auto chunk_magic = (std::uint16_t)~0xc0d3; +constexpr inline auto scenery_magic = (std::uint16_t)~0xb00b; constexpr inline std::underlying_type_t<pass_mode> pass_mask = pass_blocked | pass_shoot_through | pass_ok; constexpr inline auto pass_bits = std::bit_width(pass_mask); +template<typename T> constexpr inline auto highbit = T(1) << sizeof(T)*8-1; +template<typename T, std::size_t N, std::size_t off> +constexpr inline auto highbits = (T(1) << N)-1 << sizeof(T)*8-N-off; + +constexpr inline atlasid meta_long_scenery_bit = highbit<atlasid>; +constexpr inline atlasid meta_rotation_bits = highbits<atlasid, rotation_BITS, 1>; +constexpr inline atlasid scenery_id_flag_mask = meta_long_scenery_bit | meta_rotation_bits; +constexpr inline atlasid scenery_id_max = int_max<atlasid> & ~scenery_id_flag_mask; + } // namespace enum : tilemeta { @@ -38,6 +57,7 @@ enum : tilemeta { meta_wall_n = 1 << (pass_bits + 1), meta_wall_w = 1 << (pass_bits + 2), meta_short_atlasid = 1 << (pass_bits + 3), + meta_scenery = 1 << (pass_bits + 4), }; } // namespace floormat::Serialize diff --git a/serialize/world-reader.cpp b/serialize/world-reader.cpp index 16a3afc1..d61a574a 100644 --- a/serialize/world-reader.cpp +++ b/serialize/world-reader.cpp @@ -3,10 +3,16 @@ #include "binary-reader.inl" #include "src/world.hpp" #include "loader/loader.hpp" +#include "loader/scenery.hpp" #include "src/tile-atlas.hpp" +#include "src/anim-atlas.hpp" #include <cstring> +#include <Corrade/Containers/StringStlHash.h> -namespace floormat::Serialize { +namespace { + +using namespace floormat; +using namespace floormat::Serialize; struct reader_state final { explicit reader_state(world& world) noexcept; @@ -15,16 +21,27 @@ struct reader_state final { private: using reader_t = binary_reader<decltype(ArrayView<const char>{}.cbegin())>; + void load_sceneries(); std::shared_ptr<tile_atlas> lookup_atlas(atlasid id); void read_atlases(reader_t& reader); + void read_sceneries(reader_t& reader); void read_chunks(reader_t& reader); std::unordered_map<atlasid, std::shared_ptr<tile_atlas>> atlases; + std::unordered_map<StringView, const serialized_scenery*> default_sceneries; + std::vector<scenery_proto> sceneries; world* _world; + std::uint16_t PROTO = (std::uint16_t)-1; }; reader_state::reader_state(world& world) noexcept : _world{&world} {} +void reader_state::load_sceneries() +{ + for (const serialized_scenery& s : loader.sceneries()) + default_sceneries[s.name] = &s; +} + void reader_state::read_atlases(reader_t& s) { const auto N = s.read<atlasid>(); @@ -39,6 +56,57 @@ void reader_state::read_atlases(reader_t& s) } } +template<typename T> +bool read_scenery_flags(binary_reader<T>& s, scenery& sc) +{ + std::uint8_t flags; s >> flags; + sc.passable = !!(flags & 1 << 0); + sc.blocks_view = !!(flags & 1 << 1); + sc.active = !!(flags & 1 << 2); + sc.closing = !!(flags & 1 << 3); + sc.interactive = !!(flags & 1 << 4); + return flags & 1 << 7; +} + +void reader_state::read_sceneries(reader_t& s) +{ + std::uint16_t magic; s >> magic; + if (magic != scenery_magic) + fm_abort("bad scenery magic"); + atlasid sz; s >> sz; + fm_assert(sz < scenery_id_max); + sceneries.resize(sz); + + std::size_t i = 0; + while (i < sz) + { + std::uint8_t num; s >> num; + fm_assert(num > 0); + auto str = s.read_asciiz_string<atlas_name_max>(); + auto it = default_sceneries.find(StringView{str.buf, str.len}); + if (it == default_sceneries.end()) + fm_abort("can't find scenery '%s'", str.buf); + for (std::size_t n = 0; n < num; n++) + { + atlasid id; s >> id; + fm_assert(id < sz); + scenery_proto sc = it->second->proto; + bool short_frame = read_scenery_flags(s, sc.frame); + fm_debug_assert(sc.atlas != nullptr); + if (short_frame) + sc.frame.frame = s.read<std::uint8_t>(); + else + s >> sc.frame.frame; + fm_assert(sc.frame.frame < sc.atlas->info().nframes); + sceneries[id] = sc; + } + i += num; + } + fm_assert(i == sz); + for (const scenery_proto& x : sceneries) + fm_assert(x.atlas != nullptr); +} + std::shared_ptr<tile_atlas> reader_state::lookup_atlas(atlasid id) { if (auto it = atlases.find(id); it != atlases.end()) @@ -51,7 +119,7 @@ void reader_state::read_chunks(reader_t& s) { const auto N = s.read<chunksiz>(); - for (std::size_t i = 0; i < N; i++) + for (std::size_t k = 0; k < N; k++) { std::decay_t<decltype(chunk_magic)> magic; s >> magic; @@ -74,12 +142,35 @@ void reader_state::read_chunks(reader_t& s) return { atlas, v }; }; + t.pass_mode() = pass_mode(flags & pass_mask); if (flags & meta_ground) t.ground() = make_atlas(); if (flags & meta_wall_n) t.wall_north() = make_atlas(); if (flags & meta_wall_w) t.wall_west() = make_atlas(); + if (PROTO >= 3) [[likely]] + if (flags & meta_scenery) + { + atlasid id; s >> id; + const bool exact = id & meta_long_scenery_bit; + const auto r = rotation(id >> sizeof(id)*8-1-rotation_BITS & rotation_MASK); + id &= ~scenery_id_flag_mask; + fm_assert(id < sceneries.size()); + auto sc = sceneries[id]; + (void)sc.atlas->group(r); + sc.frame.r = r; + if (!exact) + { + if (read_scenery_flags(s, sc.frame)) + sc.frame.frame = s.read<std::uint8_t>(); + else + s >> sc.frame.frame; + if (sc.frame.active) + s >> sc.frame.delta; + } + t.scenery() = sc; + } switch (auto x = pass_mode(flags & pass_mask)) { @@ -88,7 +179,7 @@ void reader_state::read_chunks(reader_t& s) case pass_ok: t.pass_mode() = x; break; - default: + default: [[unlikely]] fm_abort("bad pass mode '%zu' for tile %zu", i, (std::size_t)x); } } @@ -105,12 +196,16 @@ void reader_state::deserialize_world(ArrayView<const char> buf) if (!(proto >= min_proto_version && proto <= proto_version)) fm_abort("bad proto version '%zu' (should be between '%zu' and '%zu')", (std::size_t)proto, (std::size_t)min_proto_version, (std::size_t)proto_version); + PROTO = proto; + load_sceneries(); read_atlases(s); + if (PROTO >= 3) + read_sceneries(s); read_chunks(s); s.assert_end(); } -} // namespace floormat::Serialize +} // namespace namespace floormat { @@ -159,7 +254,7 @@ world world::deserialize(StringView filename) } world w; - Serialize::reader_state s{w}; + reader_state s{w}; s.deserialize_world({buf_.get(), len}); return w; } 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) { diff --git a/src/scenery.hpp b/src/scenery.hpp index 05a72b43..4ed55d20 100644 --- a/src/scenery.hpp +++ b/src/scenery.hpp @@ -11,7 +11,9 @@ enum class rotation : std::uint8_t { N, NE, E, SE, S, SW, W, NW, }; -constexpr inline rotation rotation_COUNT = rotation{8}; +constexpr inline std::size_t rotation_BITS = 3; +constexpr inline std::size_t rotation_MASK = (1 << rotation_BITS)-1; +constexpr inline rotation rotation_COUNT = rotation{1 << rotation_BITS}; enum class scenery_type : std::uint8_t { none, generic, door, diff --git a/test/serializer.cpp b/test/serializer.cpp index 568d0217..8a22d0e5 100644 --- a/test/serializer.cpp +++ b/test/serializer.cpp @@ -1,6 +1,7 @@ #include "app.hpp" #include "src/world.hpp" #include "loader/loader.hpp" +#include "loader/scenery.hpp" #include "src/tile-atlas.hpp" #include <Corrade/Utility/Path.h> @@ -17,11 +18,19 @@ static chunk make_test_chunk() chunk c; for (auto [x, k, pt] : c) x.ground() = { tiles, variant_t(k % tiles->num_tiles()) }; + auto door = loader.scenery("door1"), + table = loader.scenery("table1"), + control_panel = loader.scenery("control panel (wall) 1"); + control_panel.frame.r = rotation::W; constexpr auto K = N/2; c[{K, K }].wall_north() = { metal1, 0 }; c[{K, K }].wall_west() = { metal2, 0 }; c[{K, K+1}].wall_north() = { metal1, 0 }; c[{K+1, K }].wall_west() = { metal2, 0 }; + c[{K+3, K+1}].scenery() = door; + c[{ 3, 4 }].scenery() = table; + c[{K, K+1}].scenery() = control_panel; + c.mark_modified(); return c; } |