summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorStanislaw Halik <sthalik@misaki.pl>2023-03-17 15:31:57 +0100
committerStanislaw Halik <sthalik@misaki.pl>2023-03-17 23:23:12 +0100
commit72782ef1298deabbae0598d0d50159210ed64b27 (patch)
tree6107108a7733b9beda77a36dea0209fe1367d8b9
parent90742e5c5abd4fb996f548e0cff6661a950057c1 (diff)
buffer flush (wip)
-rw-r--r--editor/app.cpp7
-rw-r--r--serialize/world-impl.hpp22
-rw-r--r--serialize/world-reader.cpp65
-rw-r--r--serialize/world-writer.cpp251
-rw-r--r--src/character.cpp4
-rw-r--r--src/character.hpp31
-rw-r--r--src/entity-type.hpp1
-rw-r--r--src/entity.hpp2
-rw-r--r--src/scenery.hpp5
-rw-r--r--src/world.cpp13
-rw-r--r--src/world.hpp18
-rw-r--r--test/serializer.cpp10
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;
}