summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorStanislaw Halik <sthalik@misaki.pl>2022-12-01 13:21:32 +0100
committerStanislaw Halik <sthalik@misaki.pl>2022-12-01 13:21:32 +0100
commit81f68a2c83c0c25259cd526c8bb4839caa361e8f (patch)
treeff2db492dbd3dddfc341370a4cf4b2a95abdae70
parent511d823c2dc2b917afed6a9c50ad940e5c58c5d5 (diff)
serialize, loader, test: add serializing scenery
-rw-r--r--CMakeLists.txt16
-rw-r--r--editor/scenery-json.cpp3
-rw-r--r--loader/impl.cpp1
-rw-r--r--loader/impl.hpp5
-rw-r--r--loader/json.cpp33
-rw-r--r--loader/loader.hpp5
-rw-r--r--loader/scenery.hpp13
-rw-r--r--main/draw.cpp2
-rw-r--r--serialize/binary-reader.hpp5
-rw-r--r--serialize/scenery.cpp4
-rw-r--r--serialize/scenery.hpp16
-rw-r--r--serialize/world-impl.hpp22
-rw-r--r--serialize/world-reader.cpp105
-rw-r--r--serialize/world-writer.cpp279
-rw-r--r--src/scenery.hpp4
-rw-r--r--test/serializer.cpp9
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;
}