diff options
author | Stanislaw Halik <sthalik@misaki.pl> | 2023-03-17 15:31:57 +0100 |
---|---|---|
committer | Stanislaw Halik <sthalik@misaki.pl> | 2023-03-17 23:23:12 +0100 |
commit | 72782ef1298deabbae0598d0d50159210ed64b27 (patch) | |
tree | 6107108a7733b9beda77a36dea0209fe1367d8b9 | |
parent | 90742e5c5abd4fb996f548e0cff6661a950057c1 (diff) |
buffer flush (wip)
-rw-r--r-- | editor/app.cpp | 7 | ||||
-rw-r--r-- | serialize/world-impl.hpp | 22 | ||||
-rw-r--r-- | serialize/world-reader.cpp | 65 | ||||
-rw-r--r-- | serialize/world-writer.cpp | 251 | ||||
-rw-r--r-- | src/character.cpp | 4 | ||||
-rw-r--r-- | src/character.hpp | 31 | ||||
-rw-r--r-- | src/entity-type.hpp | 1 | ||||
-rw-r--r-- | src/entity.hpp | 2 | ||||
-rw-r--r-- | src/scenery.hpp | 5 | ||||
-rw-r--r-- | src/world.cpp | 13 | ||||
-rw-r--r-- | src/world.hpp | 18 | ||||
-rw-r--r-- | test/serializer.cpp | 10 |
12 files changed, 281 insertions, 148 deletions
diff --git a/editor/app.cpp b/editor/app.cpp index aa615137..49d8e96c 100644 --- a/editor/app.cpp +++ b/editor/app.cpp @@ -51,7 +51,12 @@ void app::reset_world(struct world&& w) return; auto& w2 = M->reset_world(std::move(w)); w2.collect(true); - _character_id = w2.make_entity<character>(global_coords{}, true)->id; + { + character_proto cproto; + cproto.name = "Player"_s; + cproto.playable = true; + _character_id = w2.make_entity<character>(global_coords{}, cproto)->id; + } } int app::exec() diff --git a/serialize/world-impl.hpp b/serialize/world-impl.hpp index c8282141..6d3f73cf 100644 --- a/serialize/world-impl.hpp +++ b/serialize/world-impl.hpp @@ -6,6 +6,7 @@ #include "src/tile.hpp" #include "src/pass-mode.hpp" #include "src/rotation.hpp" +#include "src/entity-type.hpp" #include <bit> #include <cstddef> #include <cstdint> @@ -21,8 +22,14 @@ * 5) Serialize scenery pixel offset. * 6) Serialize scenery bboxes. * 7) Serialize scenery bbox_size offset. + * 8) Scenery replaced with entities. */ +namespace floormat { +struct entity; +struct entity_proto; +} // namespace floormat + namespace floormat::Serialize { using tilemeta = std::uint8_t; @@ -39,14 +46,17 @@ 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 = 7; +constexpr inline std::size_t character_name_max = 128; + +constexpr inline proto_t proto_version = 8; 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; -using pass_mode_ = std::underlying_type_t<pass_mode>; -constexpr inline pass_mode_ pass_mask = pass_mode_COUNT - 1; +using pass_mode_i = std::underlying_type_t<pass_mode>; +constexpr inline pass_mode_i pass_mask = pass_mode_COUNT - 1; constexpr inline auto pass_bits = std::bit_width(pass_mask); +using entity_type_i = std::underlying_type_t<entity_type>; template<typename T> constexpr inline auto highbit = T(1) << sizeof(T)*8-1; template<typename T, std::size_t N, std::size_t off> @@ -59,13 +69,15 @@ constexpr inline atlasid scenery_id_max = int_max<atlasid> & ~scenery_id_flag_ma } // namespace +template<typename T> concept entity_subtype = std::is_base_of_v<entity, T> || std::is_base_of_v<entity_proto, T>; + enum : tilemeta { meta_ground = 1 << 2, meta_wall_n = 1 << 3, meta_wall_w = 1 << 4, - meta_short_atlasid = 1 << 5, + meta_short_atlasid_ = 1 << 5, meta_short_variant_ = 1 << 6, - meta_scenery = 1 << 7, + meta_scenery_ = 1 << 7, }; } // namespace floormat::Serialize diff --git a/serialize/world-reader.cpp b/serialize/world-reader.cpp index 07dc1bbf..da39fe17 100644 --- a/serialize/world-reader.cpp +++ b/serialize/world-reader.cpp @@ -3,6 +3,7 @@ #include "binary-reader.inl" #include "src/world.hpp" #include "src/scenery.hpp" +#include "src/character.hpp" #include "loader/loader.hpp" #include "loader/scenery.hpp" #include "src/tile-atlas.hpp" @@ -52,14 +53,27 @@ void reader_state::read_atlases(reader_t& s) } } -template<typename T> -bool read_scenery_flags(binary_reader<T>& s, scenery_proto& sc) +template<typename T, entity_subtype U> +bool read_scenery_flags(binary_reader<T>& s, U& e) { + constexpr auto tag = entity_type_<U>::value; + static_assert(tag != entity_type::none); std::uint8_t flags; flags << s; - sc.pass = pass_mode(flags & pass_mask); - sc.active = !!(flags & 1 << 2); - sc.closing = !!(flags & 1 << 3); - sc.interactive = !!(flags & 1 << 4); + e.pass = pass_mode(flags & pass_mask); + if (e.type != tag) + fm_abort("invalid entity type '%d'", (int)e.type); + if constexpr(tag == entity_type::scenery) + { + e.active = !!(flags & 1 << 2); + e.closing = !!(flags & 1 << 3); + e.interactive = !!(flags & 1 << 4); + } + else if constexpr(tag == entity_type::character) + { + e.playable = !!(flags & 1 << 2); + } + else + static_assert(tag == entity_type::none); return flags & 1 << 7; } @@ -130,13 +144,18 @@ void reader_state::read_chunks(reader_t& s) ch.x << s; ch.y << s; auto& c = (*_world)[ch]; + c.mark_modified(); for (auto i = 0_uz; i < TILE_COUNT; i++) { const tilemeta flags = s.read<tilemeta>(); tile_ref t = c[i]; using uchar = std::uint8_t; const auto make_atlas = [&]() -> tile_image_proto { - auto id = flags & meta_short_atlasid ? atlasid{s.read<uchar>()} : s.read<atlasid>(); + atlasid id; + if (PROTO < 8) [[unlikely]] + id = flags & meta_short_atlasid_ ? atlasid{s.read<uchar>()} : s.read<atlasid>(); + else + id << s; variant_t v; if (PROTO >= 2) [[likely]] v << s; @@ -156,8 +175,8 @@ void reader_state::read_chunks(reader_t& s) t.wall_north() = make_atlas(); if (flags & meta_wall_w) t.wall_west() = make_atlas(); - if (PROTO >= 3) [[likely]] - if (flags & meta_scenery) + if (PROTO >= 3 && PROTO < 8) [[unlikely]] + if (flags & meta_scenery_) { atlasid id; id << s; const bool exact = id & meta_long_scenery_bit; @@ -215,6 +234,8 @@ void reader_state::deserialize_world(ArrayView<const char> buf) fm_throw("bad proto version '{}' (should be between '{}' and '{}')"_cf, (std::size_t)proto, (std::size_t)min_proto_version, (std::size_t)proto_version); PROTO = proto; + if (PROTO >= 8) + _world->set_entity_counter(s.read<std::uint64_t>()); read_atlases(s); if (PROTO >= 3) read_sceneries(s); @@ -229,46 +250,34 @@ namespace floormat { world world::deserialize(StringView filename) { char errbuf[128]; - constexpr auto get_error_string = []<std::size_t N> (char (&buf)[N]) { + constexpr auto get_error_string = []<std::size_t N> (char (&buf)[N]) -> const char* { buf[0] = '\0'; #ifndef _WIN32 (void)::strerror_r(errno, buf, std::size(buf)); #else (void)::strerror_s(buf, std::size(buf), errno); #endif + return buf; }; fm_soft_assert(filename.flags() & StringViewFlag::NullTerminated); FILE_raii f = ::fopen(filename.data(), "rb"); if (!f) { - get_error_string(errbuf); - fm_throw("fopen(\"{}\", \"r\"): {}"_cf, filename.data(), errbuf); + fm_throw("fopen(\"{}\", \"r\"): {}"_cf, filename.data(), get_error_string(errbuf)); } if (int ret = ::fseek(f, 0, SEEK_END); ret != 0) - { - get_error_string(errbuf); - fm_throw("fseek(SEEK_END): {}"_cf, errbuf); - } + fm_throw("fseek(SEEK_END): {}"_cf, get_error_string(errbuf)); std::size_t len; if (auto len_ = ::ftell(f); len_ >= 0) len = (std::size_t)len_; else - { - get_error_string(errbuf); - fm_throw("ftell: {}"_cf, errbuf); - } + fm_throw("ftell: {}"_cf, get_error_string(errbuf)); if (int ret = ::fseek(f, 0, SEEK_SET); ret != 0) - { - get_error_string(errbuf); - fm_throw("fseek(SEEK_SET): {}"_cf, errbuf); - } + fm_throw("fseek(SEEK_SET): {}"_cf, get_error_string(errbuf)); auto buf_ = std::make_unique<char[]>(len); if (auto ret = ::fread(&buf_[0], 1, len, f); ret != len) - { - get_error_string(errbuf); - fm_throw("fread short read: {}"_cf, errbuf); - } + fm_throw("fread short read: {}"_cf, get_error_string(errbuf)); world w; reader_state s{w}; diff --git a/serialize/world-writer.cpp b/serialize/world-writer.cpp index 413a9566..9351856f 100644 --- a/serialize/world-writer.cpp +++ b/serialize/world-writer.cpp @@ -9,11 +9,13 @@ #include "src/emplacer.hpp" #include "loader/loader.hpp" #include "src/scenery.hpp" +#include "src/character.hpp" #include "loader/scenery.hpp" #include "src/anim-atlas.hpp" +#include <concepts> +#include <cstring> #include <vector> #include <algorithm> -#include <cstring> #include <Corrade/Containers/StringView.h> #include <Corrade/Utility/Path.h> @@ -46,12 +48,9 @@ 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); + scenery_pair intern_scenery(const scenery& sc, bool create); void serialize_chunk(const chunk& c, chunk_coords coord); void serialize_atlases(); @@ -86,6 +85,7 @@ writer_state::writer_state(const world& world) : _world{&world} chunk_buf.reserve(chunkbuf_size); chunk_bufs.reserve(world.chunks().size()); atlas_buf.reserve(atlas_name_max * 64); + scenery_map.reserve(64); } #ifdef __GNUG__ @@ -129,8 +129,9 @@ void writer_state::load_scenery() load_scenery_1(s); } -scenery_pair writer_state::intern_scenery(scenery_proto s, bool create) +scenery_pair writer_state::intern_scenery(const scenery& sc, bool create) { + auto s = scenery_proto(sc); const void* const ptr = s.atlas.get(); fm_debug_assert(ptr != nullptr); auto it = scenery_map.find(ptr); @@ -139,8 +140,14 @@ scenery_pair writer_state::intern_scenery(scenery_proto s, bool create) interned_scenery *ret = nullptr, *ret2 = nullptr; for (interned_scenery& x : vec) { - fm_debug_assert(s.type == x.s->proto.type); - s.r = x.s->proto.r; + const auto& proto = x.s->proto; + fm_assert(s.type == proto.type); + fm_assert(s.sc_type == proto.sc_type); + s.r = proto.r; + s.interactive = proto.interactive; + s.active = proto.active; + s.closing = proto.closing; + s.pass = proto.pass; if (x.s->proto.frame == s.frame) { if (x.index != null_atlas) @@ -171,20 +178,30 @@ scenery_pair writer_state::intern_scenery(scenery_proto s, bool create) 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& proto) +template<typename T, entity_subtype U> +void write_entity_flags(binary_writer<T>& s, const U& e) { std::uint8_t flags = 0; - flags |= pass_mode_(proto.pass) & pass_mask; - flags |= (1 << 2) * proto.active; - flags |= (1 << 3) * proto.closing; - flags |= (1 << 4) * proto.interactive; - flags |= (1 << 7) * (proto.frame <= 0xff); + auto pass = (pass_mode_i)e.pass; + fm_assert((pass & pass_mask) == pass); + flags |= pass; + constexpr auto tag = entity_type_<U>::value; + static_assert(tag != entity_type::none); + if (e.type != tag) + fm_abort("invalid entity type '%d'", (int)e.type); + if constexpr(tag == entity_type::scenery) + { + flags |= (1 << 2) * e.active; + flags |= (1 << 3) * e.closing; + flags |= (1 << 4) * e.interactive; + } + else if constexpr(tag == entity_type::character) + { + flags |= (1 << 2) * e.playable; + } + else + static_assert(tag == entity_type::none); + flags |= (1 << 7) * (e.frame <= 0xff); s << flags; } @@ -271,7 +288,7 @@ void writer_state::serialize_scenery() s.write_asciiz_string(sc->name); } s << idx; - write_scenery_flags(s, sc->proto); + write_entity_flags(s, sc->proto); if (sc->proto.frame <= 0xff) s << (std::uint8_t)sc->proto.frame; else @@ -284,7 +301,8 @@ void writer_state::serialize_scenery() void writer_state::serialize_chunk(const chunk& c, chunk_coords coord) { fm_assert(chunk_buf.empty()); - chunk_buf.resize(chunkbuf_size); + constexpr std::size_t entity_size = std::max(sizeof(character), sizeof(scenery)); + chunk_buf.resize(chunkbuf_size + sizeof(std::uint32_t) + entity_size*c.entities().size()); auto s = binary_writer{chunk_buf.begin()}; @@ -301,16 +319,7 @@ void writer_state::serialize_chunk(const chunk& c, chunk_coords coord) 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(), true); - -#if 0 - if (sc_exact && sc) - { - sc_exact = scenery.offset == sc->proto.frame.offset && - scenery.bbox_size == sc->proto.frame.bbox_size && - scenery.bbox_offset == sc->proto.frame.bbox_offset; - } -#endif + //auto [sc, img_s, sc_exact] = maybe_intern_scenery(scenery, true); tilemeta flags = {}; flags |= meta_ground * (img_g != null_atlas); @@ -318,16 +327,6 @@ void writer_state::serialize_chunk(const chunk& c, chunk_coords coord) flags |= meta_wall_w * (img_w != null_atlas); //flags |= meta_scenery * (img_s != null_atlas); - using uchar = std::uint8_t; - - constexpr auto ashortp = [](atlasid id) { - return id == null_atlas || id == (uchar)id; - }; - - if (flags != 0 && ashortp(img_g) && ashortp(img_n) && ashortp(img_w)) - flags |= meta_short_atlasid; - - //fm_debug_assert((pass_mode_(x.passability) & pass_mask) == pass_mode_(x.passability)); //flags |= pass_mode_(x.passability); s << flags; @@ -342,48 +341,107 @@ void writer_state::serialize_chunk(const chunk& c, chunk_coords coord) check_atlas(wall_west); #endif - const auto write = [&](atlasid x, variant_t v) { - flags & meta_short_atlasid ? s << (uchar) x : s << x; - s << v; - }; - if (img_g != null_atlas) - write(img_g, ground.variant); + s << img_g, s << ground.variant; if (img_n != null_atlas) - write(img_n, wall_north.variant); + s << img_n, s << wall_north.variant; if (img_w != null_atlas) - write(img_w, wall_west.variant); -#if 0 - if (img_s != null_atlas) + s << img_w, s << wall_west.variant; + } + + s << (std::uint32_t)c.entities().size(); + fm_assert((std::uint32_t)c.entities().size() == c.entities().size()); + for (const auto& e_ : c.entities()) + { + const auto& e = *e_; + std::uint64_t oid = e.id; + fm_assert((oid & (1ULL << 60)-1) == oid); + static_assert(entity_type_BITS == 3); + fm_assert(((entity_type_i)e.type & (1 << entity_type_BITS)-1) == (entity_type_i)e.type); + oid |= (std::uint64_t)e.type << 64 - entity_type_BITS; + s << oid; + const auto local = e.coord.local(); + s << local.x; + s << local.y; + s << e.offset[0]; + s << e.offset[1]; + s << e.bbox_size[0]; + s << e.bbox_size[1]; + s << e.bbox_offset[0]; + s << e.bbox_offset[1]; + switch (e.type) { + default: + fm_abort("invalid entity type '%d'", (int)e.type); + case entity_type::character: { + const auto& C = static_cast<const character&>(e); + write_entity_flags(s, C); + if (C.frame <= 0xff) + s << (std::uint8_t)C.frame; + else + s << C.frame; + s << C.offset_frac[0]; + s << C.offset_frac[1]; + fm_assert(C.name.size() < character_name_max); + s.write_asciiz_string(C.name); + break; + } + case entity_type::scenery: { + const auto& sc = static_cast<const scenery&>(e); + auto [ss, img_s, sc_exact] = intern_scenery(sc, true); + fm_assert(img_s != null_atlas); atlasid id = img_s; - fm_assert(!(id & ~((1 << 16-3-1)-1))); + static_assert(rotation_BITS == 3); + fm_assert((id & (1 << 16-3-1)-1) == id); id |= meta_long_scenery_bit * sc_exact; - id |= atlasid(scenery.r) << sizeof(atlasid)*8-1-rotation_BITS; + id |= atlasid(sc.r) << sizeof(atlasid)*8-1-rotation_BITS; s << id; if (!sc_exact) { - fm_assert(scenery.active || scenery.delta == 0); - write_scenery_flags(s, scenery); - if (scenery.frame <= 0xff) - s << (std::uint8_t)scenery.frame; + fm_assert(sc.active || sc.delta == 0); + write_entity_flags(s, sc); + if (sc.frame <= 0xff) + s << (std::uint8_t)sc.frame; else - s << scenery.frame; - s << scenery.offset[0]; - s << scenery.offset[1]; + s << sc.frame; + if (sc.active) + s << sc.delta; + } + break; + } + } + } + +#if 0 + if (img_s != null_atlas) + { + atlasid id = img_s; + fm_assert(!(id & ~((1 << 16-3-1)-1))); + 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); + write_entity_flags(s, scenery); + if (scenery.frame <= 0xff) + s << (std::uint8_t)scenery.frame; + else + s << scenery.frame; + s << scenery.offset[0]; + s << scenery.offset[1]; - s << scenery.bbox_size[0]; - s << scenery.bbox_size[1]; + s << scenery.bbox_size[0]; + s << scenery.bbox_size[1]; - s << scenery.bbox_offset[0]; - s << scenery.bbox_offset[1]; + s << scenery.bbox_offset[0]; + s << scenery.bbox_offset[1]; - if (scenery.active) - s << scenery.delta; - } + if (scenery.active) + s << scenery.delta; } -#endif } +#endif const auto nbytes = s.bytes_written(); fm_assert(nbytes <= chunkbuf_size); @@ -405,12 +463,23 @@ ArrayView<const char> writer_state::serialize_world() { load_scenery(); -#if 0 for (const auto& [_, c] : _world->chunks()) - for (auto [x, _k, _pt] : c) - maybe_intern_scenery(x.scenery(), false); -#endif - + { + for (const auto& e_ : c.entities()) + { + const auto& e = *e_; + switch (e.type) + { + case entity_type::scenery: + intern_scenery(static_cast<const scenery&>(e), false); + break; + case entity_type::character: + break; + default: + fm_abort("invalid scenery type '%d'", (int)e.type); + } + } + } for (const auto& [pos, c] : _world->chunks()) { #ifndef FM_NO_DEBUG @@ -423,14 +492,13 @@ ArrayView<const char> writer_state::serialize_world() 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())}; - union { proto_t x; char bytes[sizeof x]; } p = {.x = maybe_byteswap(proto_version)}; fm_assert(_world->size() <= int_max<chunksiz>); std::size_t len = 0; len += std::size(file_magic)-1; - len += sizeof(p.x); - len += sizeof(c.x); + len += sizeof(proto_t); + len += sizeof(std::uint64_t); + len += sizeof(chunksiz); for (const auto& buf : chunk_bufs) len += buf.size(); len += atlas_buf.size(); @@ -443,11 +511,16 @@ ArrayView<const char> writer_state::serialize_world() fm_assert(len1 <= len2); it = std::copy(std::cbegin(in), std::cend(in), it); }; + const auto copy_int = [&]<typename T>(const T& value) { + union { T x; char bytes[sizeof x]; } c = {.x = maybe_byteswap(value)}; + copy(c.bytes); + }; copy(Containers::StringView{file_magic, std::size(file_magic)-1}); - copy(p.bytes); + copy_int((proto_t)proto_version); + copy_int((std::uint64_t)_world->entity_counter()); copy(atlas_buf); copy(scenery_buf); - copy(c.bytes); + copy_int((chunksiz)_world->size()); for (const auto& buf : chunk_bufs) copy(buf); return {file_buf.data(), file_buf.size()}; @@ -467,35 +540,27 @@ void world::serialize(StringView filename) { collect(true); char errbuf[128]; - constexpr auto get_error_string = []<std::size_t N> (char (&buf)[N]) { + constexpr auto get_error_string = []<std::size_t N> (char (&buf)[N]) -> const char* { buf[0] = '\0'; #ifndef _WIN32 (void)::strerror_r(errno, buf, std::size(buf)); #else (void)::strerror_s(buf, std::size(buf), errno); #endif + return buf; }; fm_assert(filename.flags() & StringViewFlag::NullTerminated); if (Path::exists(filename)) Path::remove(filename); FILE_raii file = ::fopen(filename.data(), "wb"); if (!file) - { - get_error_string(errbuf); - fm_abort("fopen(\"%s\", \"w\"): %s", filename.data(), errbuf); - } + fm_abort("fopen(\"%s\", \"w\"): %s", filename.data(), get_error_string(errbuf)); writer_state s{*this}; const auto array = s.serialize_world(); if (auto len = ::fwrite(array.data(), array.size(), 1, file); len != 1) - { - get_error_string(errbuf); - fm_abort("fwrite: %s", errbuf); - } + fm_abort("fwrite: %s", get_error_string(errbuf)); if (int ret = ::fflush(file); ret != 0) - { - get_error_string(errbuf); - fm_abort("fflush: %s", errbuf); - } + fm_abort("fflush: %s", get_error_string(errbuf)); } } // namespace floormat diff --git a/src/character.cpp b/src/character.cpp index e0645ddb..c16fce35 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -51,7 +51,9 @@ constexpr auto arrows_to_dir(bool L, bool R, bool U, bool D) } // namespace -character::character(std::uint64_t id, struct chunk& c, entity_type type, bool playable) : entity{id, c, type}, playable{playable} +character::character(std::uint64_t id, struct chunk& c, entity_type type, const character_proto& proto) : + entity{id, c, type}, + playable{proto.playable} { atlas = loader.anim_atlas("npc-walk", loader.ANIM_PATH); bbox_size = Vector2ub(iTILE_SIZE2/2); diff --git a/src/character.hpp b/src/character.hpp index 9e2617ae..9481c4ac 100644 --- a/src/character.hpp +++ b/src/character.hpp @@ -2,32 +2,49 @@ #include "src/global-coords.hpp" #include "src/rotation.hpp" #include "src/entity.hpp" -#include <memory> +#include <Corrade/Containers/String.h> namespace floormat { struct anim_atlas; struct world; +struct character_proto : entity_proto +{ + String name; + bool playable : 1 = false; + + character_proto(); + character_proto(const character_proto&); + ~character_proto() noexcept override; + character_proto& operator=(const character_proto&); + bool operator==(const entity_proto& proto) const override; + operator bool() const; +}; + struct character final : entity { ~character() override; + explicit operator character_proto() const; + void set_keys(bool L, bool R, bool U, bool D); bool update(std::size_t i, float dt) override; - bool is_playable() const { return playable; } + void update_bbox(Vector2b bbox_offset, Vector2ub bbox_size) override; + + String name; + Vector2s offset_frac; + bool b_L : 1 = false, b_R : 1 = false, b_U : 1 = false, b_D : 1 = false; + bool playable : 1 = false; private: int allocate_frame_time(float dt); static Vector2 move_vec(int left_right, int top_bottom); friend struct world; - character(std::uint64_t id, struct chunk& c, entity_type type, bool playable); - - Vector2s offset_frac; - bool b_L : 1 = false, b_R : 1 = false, b_U : 1 = false, b_D : 1 = false; - bool playable : 1 = false; + character(std::uint64_t id, struct chunk& c, entity_type type, const character_proto& proto); }; template<> struct entity_type_<struct character> : std::integral_constant<entity_type, entity_type::character> {}; +template<> struct entity_type_<struct character_proto> : std::integral_constant<entity_type, entity_type::character> {}; } // namespace floormat diff --git a/src/entity-type.hpp b/src/entity-type.hpp index 625b53c0..e1ed8fcc 100644 --- a/src/entity-type.hpp +++ b/src/entity-type.hpp @@ -5,5 +5,6 @@ namespace floormat { enum class entity_type : unsigned char { none, character, scenery, }; +constexpr inline std::size_t entity_type_BITS = 3; } // namespace floormat diff --git a/src/entity.hpp b/src/entity.hpp index 396cb881..893c30e9 100644 --- a/src/entity.hpp +++ b/src/entity.hpp @@ -66,7 +66,7 @@ struct entity static Pair<global_coords, Vector2b> normalize_coords(global_coords coord, Vector2b cur_offset, Vector2i delta); [[nodiscard]] virtual bool can_move_to(Vector2i delta); std::size_t move(std::size_t i, Vector2i delta); - void update_bbox(Vector2b bbox_offset, Vector2ub bbox_size); // todo + virtual void update_bbox(Vector2b bbox_offset, Vector2ub bbox_size) = 0; bool is_dynamic() const; friend struct world; diff --git a/src/scenery.hpp b/src/scenery.hpp index 61024b55..55a13bd0 100644 --- a/src/scenery.hpp +++ b/src/scenery.hpp @@ -18,10 +18,11 @@ struct world; enum class scenery_type : unsigned char { none, generic, door, }; +constexpr inline std::size_t scenery_type_BITS = 3; struct scenery_proto : entity_proto { - scenery_type sc_type : 3 = scenery_type::none; + scenery_type sc_type : scenery_type_BITS = scenery_type::none; unsigned char active : 1 = false; unsigned char closing : 1 = false; unsigned char interactive : 1 = false; @@ -44,6 +45,7 @@ struct scenery final : entity bool can_activate(std::size_t i) const override; bool activate(std::size_t i) override; bool update(std::size_t i, float dt) override; + void update_bbox(Vector2b bbox_offset, Vector2ub bbox_size) override; explicit operator scenery_proto() const; private: @@ -52,5 +54,6 @@ private: }; template<> struct entity_type_<scenery> : std::integral_constant<entity_type, entity_type::scenery> {}; +template<> struct entity_type_<scenery_proto> : std::integral_constant<entity_type, entity_type::scenery> {}; } // namespace floormat diff --git a/src/world.cpp b/src/world.cpp index b1f2e434..ee1ef459 100644 --- a/src/world.cpp +++ b/src/world.cpp @@ -106,12 +106,9 @@ void world::collect(bool force) fm_debug("world: collected %zu/%zu chunks", len, len0); } -static constexpr std::uint64_t min_id = 1u << 16; -std::uint64_t world::entity_counter = min_id; - void world::do_make_entity(const std::shared_ptr<entity>& e, global_coords pos) { - fm_debug_assert(e->id > min_id); + fm_debug_assert(e->id > 0); fm_debug_assert(e->c->world()._unique_id == _unique_id); fm_assert(Vector2ui(e->bbox_size).product() > 0); fm_assert(e->type != entity_type::none); @@ -122,7 +119,7 @@ void world::do_make_entity(const std::shared_ptr<entity>& e, global_coords pos) void world::do_kill_entity(std::uint64_t id) { - fm_debug_assert(id > min_id); + fm_debug_assert(id > 0); auto cnt = _entities.erase(id); fm_debug_assert(cnt > 0); } @@ -135,4 +132,10 @@ std::shared_ptr<entity> world::find_entity_(std::uint64_t id) return ret; } +void world::set_entity_counter(std::uint64_t value) +{ + fm_assert(value >= _entity_counter); + _entity_counter = value; +} + } // namespace floormat diff --git a/src/world.hpp b/src/world.hpp index 89a1624c..08e4d384 100644 --- a/src/world.hpp +++ b/src/world.hpp @@ -32,11 +32,9 @@ private: std::size_t _last_collection = 0; std::size_t _collect_every = 64; std::shared_ptr<char> _unique_id = std::make_shared<char>('A'); - + std::uint64_t _entity_counter = 0; bool _teardown : 1 = false; - static std::uint64_t entity_counter; - explicit world(std::size_t capacity); void do_make_entity(const std::shared_ptr<entity>& e, global_coords pos); @@ -71,16 +69,26 @@ public: template<typename T, typename... Xs> requires requires(chunk& c) { T{std::uint64_t(), c, entity_type(), std::declval<Xs>()...}; } - std::shared_ptr<T> make_entity(global_coords pos, Xs&&... xs) + std::shared_ptr<T> make_entity(std::uint64_t id, global_coords pos, Xs&&... xs) { static_assert(std::is_base_of_v<entity, T>); - auto ret = std::shared_ptr<T>(new T{++entity_counter, operator[](pos.chunk()), entity_type_<T>::value, std::forward<Xs>(xs)...}); + auto ret = std::shared_ptr<T>(new T{id, operator[](pos.chunk()), entity_type_<T>::value, std::forward<Xs>(xs)...}); do_make_entity(std::static_pointer_cast<entity>(ret), pos); return ret; } + template<typename T, typename... Xs> + requires requires(chunk& c) { T{std::uint64_t(), c, entity_type(), std::declval<Xs>()...}; } + inline std::shared_ptr<T> make_entity(global_coords pos, Xs&&... xs) + { + return make_entity<T, Xs...>(++_entity_counter, pos, std::forward<Xs>(xs)...); + } + template<typename T = entity, typename U = entity> std::shared_ptr<T> find_entity(std::uint64_t id); + bool is_teardown() const { return _teardown; } + std::uint64_t entity_counter() const { return _entity_counter; } + void set_entity_counter(std::uint64_t value); world& operator=(world&& w) noexcept; world(world&& w) noexcept; diff --git a/test/serializer.cpp b/test/serializer.cpp index c74f4ddf..d1c78df2 100644 --- a/test/serializer.cpp +++ b/test/serializer.cpp @@ -3,6 +3,7 @@ #include "loader/loader.hpp" #include "loader/scenery.hpp" #include "src/tile-atlas.hpp" +#include "src/anim-atlas.hpp" #include <Corrade/Utility/Path.h> namespace floormat { @@ -28,9 +29,16 @@ chunk& test_app::make_test_chunk(chunk_coords ch) c[{K, K }].wall_west() = { metal2, 0 }; c[{K, K+1}].wall_north() = { metal1, 0 }; c[{K+1, K }].wall_west() = { metal2, 0 }; - w.make_entity<scenery>({ch, {K+3, K+1}}, door); w.make_entity<scenery>({ch, {3, 4}}, table); w.make_entity<scenery>({ch, {K, K+1}}, control_panel); + { + auto& e = *w.make_entity<scenery>({ch, {K+3, K+1}}, door); + auto i = e.index(); + e.activate(i); + e.update(i, 1.f/60); + fm_assert(e.active); + fm_assert(e.frame != 0 && e.frame != e.atlas->info().nframes - 1); + } return c; } |