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