summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorStanislaw Halik <sthalik@misaki.pl>2024-01-19 15:03:59 +0100
committerStanislaw Halik <sthalik@misaki.pl>2024-01-20 15:37:22 +0100
commitfff6a131ccadee9dcbd7f213e1846a0095f526e1 (patch)
tree199dee817f35460c2a48bc8aecfa403b33a02cd0
parent3264ef85e50add2db3c54291540bde7e8411cb70 (diff)
w
-rw-r--r--compat/int-hash.hpp6
-rw-r--r--doc/saves/quicksave - Copy (47).datbin0 -> 4472 bytes
-rw-r--r--serialize/atlas-type.hpp8
-rw-r--r--serialize/binary-serializer.cpp2
-rw-r--r--serialize/binary-writer.hpp5
-rw-r--r--serialize/binary-writer.inl12
-rw-r--r--serialize/world-impl.hpp51
-rw-r--r--serialize/world-reader.cpp26
-rw-r--r--serialize/world-writer.cpp886
-rw-r--r--src/chunk.cpp2
-rw-r--r--src/chunk.hpp1
-rw-r--r--userconfig-sthalik@Windows-Clang.cmake1
12 files changed, 424 insertions, 576 deletions
diff --git a/compat/int-hash.hpp b/compat/int-hash.hpp
index 752c3597..a5d6b147 100644
--- a/compat/int-hash.hpp
+++ b/compat/int-hash.hpp
@@ -2,11 +2,13 @@
namespace floormat::Hash {
-template<size_t N = sizeof nullptr * 4> struct fnvhash_params;
+template<size_t N = sizeof nullptr * 8> struct fnvhash_params;
template<> struct fnvhash_params<32> { static constexpr uint32_t a = 0x811c9dc5u, b = 0x01000193u; };
template<> struct fnvhash_params<64> { static constexpr uint64_t a = 0xcbf29ce484222325u, b = 0x100000001b3u; };
-size_t fnvhash_buf(const void* __restrict buf, size_t size, size_t seed = fnvhash_params<>::a) noexcept;
+constexpr inline size_t fnvhash_seed = fnvhash_params<>::a;
+
+size_t fnvhash_buf(const void* __restrict buf, size_t size, size_t seed = fnvhash_seed) noexcept;
} // namespace floormat::Hash
diff --git a/doc/saves/quicksave - Copy (47).dat b/doc/saves/quicksave - Copy (47).dat
new file mode 100644
index 00000000..8622340a
--- /dev/null
+++ b/doc/saves/quicksave - Copy (47).dat
Binary files differ
diff --git a/serialize/atlas-type.hpp b/serialize/atlas-type.hpp
new file mode 100644
index 00000000..01b682c3
--- /dev/null
+++ b/serialize/atlas-type.hpp
@@ -0,0 +1,8 @@
+#pragma once
+
+namespace floormat::Serialize {
+ enum class atlas_type : uint8_t
+ {
+ none, ground, wall, object,
+ };
+} // namespace floormat::Serialize
diff --git a/serialize/binary-serializer.cpp b/serialize/binary-serializer.cpp
index ddd8b401..493fcf90 100644
--- a/serialize/binary-serializer.cpp
+++ b/serialize/binary-serializer.cpp
@@ -36,7 +36,7 @@ static_assert(std::is_same_v<test4&, decltype( std::declval<test4&>() << int() )
constexpr bool test5()
{
std::array<char, 4> bytes = {};
- auto w = binary_writer(bytes.begin());
+ auto w = binary_writer(bytes.begin(), bytes.size());
w << (char)0;
w << (char)1;
w << (char)2;
diff --git a/serialize/binary-writer.hpp b/serialize/binary-writer.hpp
index 625bb53c..54e70a5c 100644
--- a/serialize/binary-writer.hpp
+++ b/serialize/binary-writer.hpp
@@ -7,14 +7,15 @@ namespace floormat::Serialize {
template<std::output_iterator<char> It>
struct binary_writer final {
- explicit constexpr binary_writer(It it) noexcept;
+ explicit constexpr binary_writer(It it, size_t allocated_bytes) noexcept;
template<serializable T> constexpr void write(T x) noexcept;
constexpr void write_asciiz_string(StringView str) noexcept;
constexpr size_t bytes_written() const noexcept { return _bytes_written; }
+ constexpr size_t bytes_allocated() const noexcept { return _bytes_allocated; }
private:
It it;
- size_t _bytes_written;
+ size_t _bytes_written, _bytes_allocated;
};
template<std::output_iterator<char> It, serializable T>
diff --git a/serialize/binary-writer.inl b/serialize/binary-writer.inl
index 0833a6f0..424e11e3 100644
--- a/serialize/binary-writer.inl
+++ b/serialize/binary-writer.inl
@@ -8,14 +8,19 @@
namespace floormat::Serialize {
template<std::output_iterator<char> It>
-constexpr binary_writer<It>::binary_writer(It it) noexcept : it{it}, _bytes_written{0} {}
+constexpr binary_writer<It>::binary_writer(It it, size_t bytes_allocated) noexcept :
+ it{it},
+ _bytes_written{0},
+ _bytes_allocated{bytes_allocated}
+{}
template<std::output_iterator<char> It>
template<serializable T>
constexpr void binary_writer<It>::write(T x) noexcept
{
- _bytes_written += sizeof(T);
constexpr size_t N = sizeof(T);
+ _bytes_written += N;
+ fm_assert(_bytes_written <= _bytes_allocated);
const auto buf = std::bit_cast<std::array<char, N>, T>(maybe_byteswap(x));
for (auto i = 0uz; i < N; i++)
*it++ = buf[i];
@@ -32,9 +37,10 @@ template<std::output_iterator<char> It>
constexpr void binary_writer<It>::write_asciiz_string(StringView str) noexcept
{
//fm_debug_assert(str.flags() & StringViewFlag::NullTerminated);
- fm_debug_assert(!str.find('\0'));
+ fm_assert(!str.find('\0'));
const auto sz = str.size();
_bytes_written += sz + 1;
+ fm_assert(_bytes_written <= _bytes_allocated);
for (auto i = 0uz; i < sz; i++)
*it++ = str[i];
*it++ = '\0';
diff --git a/serialize/world-impl.hpp b/serialize/world-impl.hpp
index 07b80514..18aec72a 100644
--- a/serialize/world-impl.hpp
+++ b/serialize/world-impl.hpp
@@ -1,15 +1,7 @@
-#ifndef FM_SERIALIZE_WORLD_IMPL
-#error "not meant to be included directly"
-#endif
-
#pragma once
-#include "src/tile.hpp"
-#include "src/pass-mode.hpp"
-#include "src/rotation.hpp"
-#include "src/object-type.hpp"
-#include <bit>
#include <cstdio>
#include <concepts>
+#include <Corrade/Containers/StringView.h>
/* protocol changelog:
* 1) Initial version.
@@ -30,6 +22,7 @@
* 15) Add light alpha.
* 16) One more bit for light falloff enum.
* 17) Switch critter::offset_frac to unsigned.
+ * 20) Just rewrite the whole thing.
*/
namespace floormat {
@@ -40,8 +33,8 @@ struct object_proto;
namespace floormat::Serialize {
using tilemeta = uint8_t;
-using atlasid = uint16_t;
-using chunksiz = uint16_t;
+using atlasid = uint32_t;
+using chunksiz = uint32_t;
using proto_t = uint16_t;
template<typename T> struct int_traits;
@@ -51,32 +44,14 @@ template<std::signed_integral T> struct int_traits<T> { static constexpr T max =
namespace {
-#define file_magic ".floormat.save"
-
-constexpr inline proto_t proto_version = 19;
-
-constexpr inline size_t atlas_name_max = 128;
-constexpr inline auto null_atlas = (atlasid)-1LL;
-
-constexpr inline size_t critter_name_max = 128;
-constexpr inline size_t string_max = 512;
-
-constexpr inline proto_t min_proto_version = 1;
-constexpr inline auto chunk_magic = (uint16_t)~0xc0d3;
-constexpr inline auto scenery_magic = (uint16_t)~0xb00b;
-
-constexpr inline auto pass_mask = (1 << pass_mode_BITS)-1;
-
-template<typename T, size_t N, size_t off>
-constexpr inline auto highbits = (T(1) << N)-1 << sizeof(T)*8-N-off;
-
-template<size_t N, std::unsigned_integral T = uint8_t>
-constexpr T lowbits = N == sizeof(T)*8 ? (T)-1 : T((T{1} << N)-T{1});
-
-constexpr inline uint8_t meta_short_scenery_bit = highbits<uint8_t, 1, 0>;
-constexpr inline uint8_t meta_rotation_bits = highbits<uint8_t, rotation_BITS, 1>;
-constexpr inline uint8_t scenery_id_flag_mask = meta_short_scenery_bit | meta_rotation_bits;
-constexpr inline uint8_t scenery_id_max = int_traits<uint8_t>::max & ~scenery_id_flag_mask;
+constexpr inline proto_t proto_version = 20;
+constexpr inline proto_t min_proto_version = 20;
+constexpr inline auto file_magic = ".floormat.save"_s;
+constexpr inline auto chunk_magic = (uint16_t)0xdead;
+constexpr inline auto object_magic = (uint16_t)0xb00b;
+constexpr inline auto atlas_magic = (uint16_t)0xbeef;
+constexpr inline auto string_max = 256uz;
+constexpr inline auto null_atlas = (atlasid)-1;
} // namespace
@@ -103,7 +78,7 @@ struct FILE_raii final {
FILE_raii(FILE* s) noexcept : s{s} {}
~FILE_raii() noexcept { close(); }
operator FILE*() noexcept { return s; }
- void close() noexcept { if (s) ::fclose(s); s = nullptr; }
+ void close() noexcept { if (s) std::fclose(s); s = nullptr; }
private:
FILE* s;
};
diff --git a/serialize/world-reader.cpp b/serialize/world-reader.cpp
index 4387acda..ab196f7e 100644
--- a/serialize/world-reader.cpp
+++ b/serialize/world-reader.cpp
@@ -1,7 +1,8 @@
#define FM_SERIALIZE_WORLD_IMPL
#include "world-impl.hpp"
-#include "binary-reader.inl"
#include "src/world.hpp"
+#if 0
+#include "binary-reader.inl"
#include "src/scenery.hpp"
#include "src/critter.hpp"
#include "src/light.hpp"
@@ -78,7 +79,12 @@ bool read_object_flags(binary_reader<T>& s, U& e)
e.pass = pass_mode(flags & pass_mask);
if (e.type != tag)
fm_throw("invalid object type '{}'"_cf, (int)e.type);
- if constexpr(tag == object_type::scenery)
+ if constexpr(tag == object_type::generic_scenery)
+ {
+ e.active = !!(flags & 1 << 2);
+ e.interactive = !!(flags & 1 << 4);
+ }
+ else if constexpr(tag == object_type::door)
{
e.active = !!(flags & 1 << 2);
e.closing = !!(flags & 1 << 3);
@@ -292,6 +298,9 @@ void reader_state::read_chunks(reader_t& s)
SET_CHUNK_SIZE();
switch (type)
{
+ case object_type::door {
+ ...; // todo
+ }
case object_type::critter: {
critter_proto proto;
proto.offset = offset;
@@ -337,7 +346,7 @@ void reader_state::read_chunks(reader_t& s)
(void)e;
break;
}
- case object_type::scenery: {
+ case object_type::generic_scenery: {
atlasid id; id << s;
bool exact;
rotation r;
@@ -548,3 +557,14 @@ world world::deserialize(StringView filename)
}
} // namespace floormat
+
+#endif
+
+namespace floormat {
+
+class world world::deserialize(StringView filename)
+{
+ fm_assert("todo" && false);
+}
+
+} // namespace floormat
diff --git a/serialize/world-writer.cpp b/serialize/world-writer.cpp
index 649c1e9f..528ec332 100644
--- a/serialize/world-writer.cpp
+++ b/serialize/world-writer.cpp
@@ -1,623 +1,445 @@
-#define FM_SERIALIZE_WORLD_IMPL
#include "world-impl.hpp"
-#include "src/ground-atlas.hpp"
-#include "src/wall-atlas.hpp"
#include "binary-writer.inl"
-#include "src/global-coords.hpp"
-#include "src/chunk.hpp"
-#include "src/world.hpp"
+#include "compat/strerror.hpp"
+#include "compat/int-hash.hpp"
#include "loader/loader.hpp"
+#include "src/world.hpp"
+
+#include "atlas-type.hpp"
+#include "src/anim-atlas.hpp"
+#include "src/ground-atlas.hpp"
+#include "src/wall-atlas.hpp"
+
#include "src/scenery.hpp"
#include "src/critter.hpp"
-#include "loader/scenery.hpp"
-#include "src/anim-atlas.hpp"
#include "src/light.hpp"
-#include "compat/strerror.hpp"
-#include <cerrno>
-#include <concepts>
+
+#include <compare>
+#include <memory>
#include <vector>
#include <algorithm>
-#include <string_view>
-#include <tsl/robin_map.h>
-#include <Corrade/Containers/Array.h>
-#include <Corrade/Containers/StringStlHash.h>
#include <Corrade/Utility/Path.h>
+#include <tsl/robin_map.h>
-using namespace floormat;
-using namespace floormat::Serialize;
+#if 1
+#ifdef __CLION_IDE__
+#undef fm_assert
+#define fm_assert(...) (void)(__VA_ARGS__)
+#endif
+#endif
+
+namespace floormat::Serialize {
namespace {
+ struct string_container
+ {
+ StringView str;
+ bool operator==(const string_container&) const = default;
-struct interned_atlas final {
- StringView name;
- atlasid index;
- Vector2ub num_tiles;
-};
+ friend void swap(string_container& a, string_container& b)
+ {
+ auto tmp = a.str;
+ a.str = b.str;
+ b.str = tmp;
+ }
+ };
+}
-struct interned_scenery {
- const serialized_scenery* s;
- atlasid index;
- static_assert(sizeof index >= sizeof scenery::frame);
-};
+} // namespace floormat::Serialize
-struct scenery_pair {
- const scenery_proto* s;
- atlasid index;
- bool exact_match;
-};
+using floormat::Serialize::string_container;
+using floormat::Hash::fnvhash_buf;
-struct writer_state final {
- writer_state(const world& world);
- ArrayView<const char> serialize_world();
- fm_DECLARE_DEFAULT_MOVE_ASSIGNMENT_(writer_state);
- fm_DECLARE_DEPRECATED_COPY_ASSIGNMENT(writer_state);
-
-private:
- using writer_t = binary_writer<decltype(std::vector<char>{}.begin())>;
-
- atlasid intern_atlas(const void* ptr, StringView name, Vector2ub num_tiles);
- scenery_pair intern_scenery(const scenery& sc, bool create);
- uint32_t intern_string(StringView name);
-
- void serialize_scenery(const chunk& c, writer_t& s);
- void serialize_chunk(const chunk& c, chunk_coords_ coord);
- void serialize_atlases();
- void serialize_scenery_names();
- void serialize_strings();
-
- void load_scenery_1(const serialized_scenery& s);
- void load_scenery();
-
- const world* _world;
- std::vector<char> atlas_buf, scenery_buf, chunk_buf, file_buf, string_buf;
- std::vector<std::vector<char>> chunk_bufs;
- tsl::robin_map<const void*, interned_atlas> tile_images;
- std::unordered_map<const void*, std::vector<interned_scenery>> scenery_map;
- tsl::robin_map<StringView, uint32_t> string_map;
- atlasid scenery_map_size = 0;
+template<> struct std::hash<string_container>
+{
+ size_t operator()(const string_container& x) const noexcept
+ {
+ return fnvhash_buf(x.str.data(), x.str.size());
+ }
};
-constexpr auto tile_size = sizeof(tilemeta) + (sizeof(atlasid) + sizeof(variant_t)) * 3;
-constexpr auto chunkbuf_size = sizeof(chunk_magic) + sizeof(chunk_coords_) + tile_size * TILE_COUNT + sizeof(uint32_t);
-constexpr auto object_size = std::max({ sizeof(critter), sizeof(scenery), sizeof(light), });
-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);
- string_map.reserve(64);
-}
+namespace floormat::Serialize {
+
+namespace {
+
+template<typename T> T& non_const(const T& value) { return const_cast<T&>(value); }
+template<typename T> T& non_const(T& value) = delete;
+template<typename T> T& non_const(T&& value) = delete;
+template<typename T> T& non_const(const T&& value) = delete;
-uint32_t writer_state::intern_string(StringView name)
+constexpr size_t vector_initial_size = 128, hash_initial_size = vector_initial_size*2;
+
+template<typename T>
+struct magic
{
- auto [kv, fresh] = string_map.try_emplace(name, (uint32_t)string_map.size());
- return kv->second;
-}
+ using type = T;
+ uint16_t magic;
+};
-atlasid writer_state::intern_atlas(const void* ptr, StringView name, Vector2ub num_tiles)
+struct buffer
{
- fm_debug_assert(ptr != nullptr);
- if (auto it = tile_images.find(ptr); it != tile_images.end())
- return it->second.index;
- else
+ std::unique_ptr<char[]> data;
+ size_t size;
+
+ bool empty() const { return size == 0; }
+ buffer() : data{nullptr}, size{0} {}
+ buffer(size_t len) : // todo use allocator
+ data{std::make_unique<char[]>(len)},
+ size{len}
{
- auto aid = (atlasid)tile_images.size();
- tile_images[ptr] = { name, aid, num_tiles };
- return aid;
+ std::memset(&data[0], 0xfe, size);
}
-}
+};
-void writer_state::load_scenery_1(const serialized_scenery& s)
+struct serialized_atlas
{
- 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
- {
- 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 });
- }
-}
+ buffer buf;
+ const void* atlas;
+ atlas_type type;
+};
-void writer_state::load_scenery()
+struct serialized_chunk
{
- for (const auto& s : loader.sceneries())
- load_scenery_1(s);
-}
+ buffer buf{};
+ const chunk* c;
+};
-scenery_pair writer_state::intern_scenery(const scenery& sc, bool create)
+template<typename Derived>
+struct visitor_
{
- auto s = scenery_proto(sc);
- 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, *ret2 = nullptr;
- for (interned_scenery& x : vec)
+ template<typename T, typename F>
+ CORRADE_ALWAYS_INLINE void do_visit_nonconst(const T& value, F&& fun)
{
- 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 (s == proto)
- {
- if (x.index != null_atlas)
- return { &x.s->proto, x.index, true };
- else
- ret = &x;
- }
- if (x.index != null_atlas)
- ret2 = &x;
+ do_visit(non_const(value), fun);
}
- if (ret)
+ template<typename T, typename F>
+ CORRADE_ALWAYS_INLINE void do_visit(T&& value, F&& fun)
{
- ret->index = scenery_map_size++;
- return { &ret->s->proto, ret->index, true };
+ static_cast<Derived&>(*this).visit(value, fun);
}
- else if (create)
+
+ template<typename T, typename F>
+ requires (std::is_arithmetic_v<T> && std::is_fundamental_v<T>)
+ void visit(T& x, F&& f)
{
- if (ret2)
- return { &ret2->s->proto, ret2->index, false };
- else
- {
- fm_assert(vec[0].index == null_atlas);
- return { &vec[0].s->proto, vec[0].index = scenery_map_size++, false };
- }
+ f(x);
}
- else
- return {};
-}
-template<typename T, object_subtype U>
-void write_object_flags(binary_writer<T>& s, const U& e)
-{
- uint8_t flags = 0;
- auto pass = std::to_underlying(e.pass);
- fm_assert((pass & pass_mask) == pass);
- flags |= pass;
- constexpr auto tag = object_type_<U>::value;
- if (e.type_of() != tag)
- fm_abort("invalid object type '%d'", (int)e.type_of());
- if constexpr(tag == object_type::scenery)
+ template<typename T, size_t N, typename F>
+ void visit(Math::Vector<N, T>& x, F&& f)
{
- flags |= (1 << 2) * e.active;
- flags |= (1 << 3) * e.closing;
- flags |= (1 << 4) * e.interactive;
+ for (uint32_t i = 0; i < N; i++)
+ do_visit(x.data()[i], f);
}
- else if constexpr(tag == object_type::critter)
+
+#if 0
+ template<typename F>
+ void visit(StringView str, F&& f)
{
- flags |= (1 << 2) * e.playable;
+ f(str);
}
- else
- static_assert(tag == object_type::none);
- flags |= (1 << 7) * (e.frame <= 0xff);
- s << flags;
-}
-
-void writer_state::serialize_atlases()
-{
- fm_assert(tile_images.size() < int_traits<atlasid>::max);
- 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_traits<atlasid>::max);
-
- 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;
- });
+#endif
- for (const auto& [name, aid, num_tiles] : atlases)
+ template<typename E, typename F>
+ requires std::is_enum_v<E>
+ void visit(E& x, F&& f)
{
- const auto namesiz = name.size();
- fm_debug_assert(s.bytes_written() + namesiz < atlasbuf_size);
- fm_assert(namesiz < atlas_name_max);
- s << num_tiles[0]; s << num_tiles[1];
- s.write_asciiz_string(name);
+ auto* ptr = const_cast<std::underlying_type_t<E>*>(reinterpret_cast<const std::underlying_type_t<E>*>(&x));
+ do_visit(*ptr, f);
}
- 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(uint8_t) + atlasbuf_size0*int_traits<uint8_t>::max + atlas_name_max;
-
-void writer_state::serialize_scenery_names()
-{
- const size_t sz = scenery_map_size;
- fm_assert(sz == (atlasid)sz);
- 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 a_ = std::string_view{a.s->name.data(), a.s->name.size()},
- b_ = std::string_view{b.s->name.data(), b.s->name.size()};
- auto cmp = a_ <=> b_;
- if (cmp == std::strong_ordering::equal)
- return a.index < b.index;
- else
- return cmp == std::strong_ordering::less;
- });
-
- const auto atlasbuf_size = sizeof(uint16_t) + sizeof(sz) + atlasbuf_size1*sz;
- scenery_buf.resize(atlasbuf_size);
-
- auto s = binary_writer{scenery_buf.begin()};
-
- s << uint16_t{scenery_magic};
- s << (atlasid)sz;
-
- StringView last;
- for (auto i = 0uz; i < sz; i++)
+ template<typename F>
+ void visit(object& obj, F&& f)
{
- fm_debug_assert(s.bytes_written() + atlasbuf_size1 < atlasbuf_size);
- const auto& [sc, idx] = vec[i];
- if (sc->name != last)
+ do_visit(obj.id, f);
+ do_visit(obj.type, f);
+ fm_assert(obj.atlas);
+ do_visit(*obj.atlas, f);
+ //do_visit(*obj.c, f);
+ do_visit(obj.coord.local(), f);
+ do_visit_nonconst(obj.offset, f);
+ do_visit_nonconst(obj.bbox_offset, f);
+ do_visit_nonconst(obj.bbox_size, f);
+ do_visit_nonconst(obj.delta, f);
+ do_visit_nonconst(obj.frame, f);
+ do_visit_nonconst(obj.r, f);
+ do_visit_nonconst(obj.pass, f);
+
+ switch (obj.type)
{
- fm_assert(sc->name.size() < atlas_name_max);
- last = sc->name;
- auto num = 1uz;
- for (auto j = i+1; j < sz && vec[j].s->name == sc->name; j++)
- num++;
- fm_assert(num < int_traits<uint8_t>::max);
- s << (uint8_t)num;
- fm_assert(sc->name.size() < atlas_name_max);
- s.write_asciiz_string(sc->name);
+ case object_type::critter: do_visit(static_cast<critter&>(obj), f); return;
+ case object_type::generic_scenery: do_visit(static_cast<scenery&>(obj), f); return;
+ case object_type::light: do_visit(static_cast<light&>(obj), f); return;
+ case object_type::door: do_visit(static_cast<door&>(obj), f); return;
+ case object_type::COUNT:
+ case object_type::none:
+ break;
}
- s << idx;
- write_object_flags(s, sc->proto);
- if (sc->proto.frame <= 0xff)
- s << (uint8_t)sc->proto.frame;
- else
- s << sc->proto.frame;
+ fm_abort("invalid object type '%d'", (int)obj.type);
}
- scenery_buf.resize(s.bytes_written());
-}
+ template<typename F>
+ void visit(tile_ref c, F&& f)
+ {
+ do_visit(c.ground(), f);
+ do_visit(c.wall_north(), f);
+ do_visit(c.wall_west(), f);
+ }
+};
-void writer_state::serialize_strings()
+struct writer final : visitor_<writer>
{
- static_assert(critter_name_max <= string_max);
- auto len = 0uz;
+ const world& w;
- Array<StringView> sorted_strings{string_map.size()};
- for (auto [s, i] : string_map)
- sorted_strings[i] = s;
+ std::vector<StringView> string_array{};
+ tsl::robin_map<string_container, uint32_t> string_map{hash_initial_size};
- for (const auto& k : sorted_strings)
- {
- fm_assert(k.size()+1 < string_max);
- len += k.size()+1;
- }
- string_buf.resize(sizeof(uint32_t) + len);
- auto s = binary_writer{string_buf.begin()};
- s << (uint32_t)sorted_strings.size();
- for (const auto& k : sorted_strings)
+ std::vector<serialized_atlas> atlas_array{};
+ tsl::robin_map<const void*, uint32_t> atlas_map{hash_initial_size};
+
+ std::vector<serialized_chunk> chunk_array{vector_initial_size};
+
+ buffer header_buf{};
+
+ struct size_counter
{
- fm_assert(k.size() < string_max);
- s.write_asciiz_string(k);
- }
- fm_assert(s.bytes_written() == sizeof(uint32_t) + len);
- fm_assert(s.bytes_written() == string_buf.size());
-}
+ size_t& size;
-void writer_state::serialize_scenery(const chunk& c, writer_t& s)
-{
- constexpr auto def_char_bbox_size = Vector2ub(iTILE_SIZE2); // copied from character_proto
+ template<typename T>
+ requires (std::is_arithmetic_v<T> && std::is_fundamental_v<T>)
+ void operator()(T) { size += sizeof(T); }
+ };
- const auto object_count = (uint32_t)c.objects().size();
- s << object_count;
- fm_assert(object_count == c.objects().size());
- for (const auto& e_ : c.objects())
+ struct byte_writer
{
- const auto& e = *e_;
- fm_assert(s.bytes_written() + object_size <= chunk_buf.size());
- object_id oid = e.id;
- fm_assert((oid & lowbits<collision_data_BITS, object_id>) == e.id);
- const auto type = e.type();
- s << oid;
- s << std::to_underlying(type);
- const auto local = e.coord.local();
- s << local.to_index();
- s << e.offset[0];
- s << e.offset[1];
-
- constexpr auto write_bbox = [](auto& s, const auto& e) {
- s << e.bbox_offset[0];
- s << e.bbox_offset[1];
- s << e.bbox_size[0];
- s << e.bbox_size[1];
- };
- switch (type)
+ binary_writer<char*>& s;
+
+ template<typename T>
+ requires (std::is_fundamental_v<T> && std::is_arithmetic_v<T>)
+ void operator()(T value)
{
- default:
- fm_abort("invalid object type '%d'", (int)type);
- case object_type::critter: {
- const auto& C = static_cast<const critter&>(e);
- uint8_t id = 0;
- const auto sc_exact =
- C.bbox_offset.isZero() &&
- C.bbox_size == def_char_bbox_size;
- id |= meta_short_scenery_bit * sc_exact;
- id |= static_cast<decltype(id)>(C.r) << sizeof(id)*8-1-rotation_BITS;
- s << id;
- write_object_flags(s, C);
- if (C.frame <= 0xff)
- s << (uint8_t)C.frame;
- else
- s << C.frame;
- s << C.offset_frac[0];
- s << C.offset_frac[1];
- fm_assert(C.name.size() < critter_name_max);
- s << intern_string(C.name);
- if (!sc_exact)
- write_bbox(s, C);
- break;
- }
- case object_type::scenery: {
- const auto& sc = static_cast<const scenery&>(e);
- auto [ss, img_s, sc_exact] = intern_scenery(sc, true);
- sc_exact = sc_exact &&
- sc.bbox_offset == ss->bbox_offset && sc.bbox_size == ss->bbox_size &&
- sc.pass == ss->pass && sc.sc_type == ss->sc_type &&
- sc.active == ss->active && sc.closing == ss->closing &&
- sc.interactive == ss->interactive &&
- sc.delta == 0 && sc.frame == ss->frame;
- fm_assert(img_s != null_atlas);
- s << img_s;
- uint8_t bits = 0;
- bits |= meta_short_scenery_bit * sc_exact;
- bits |= std::to_underlying(sc.r) << sizeof(bits)*8-1-rotation_BITS;
- s << bits;
- if (!sc_exact)
- {
- write_object_flags(s, sc);
- fm_assert(sc.active || sc.delta == 0);
- if (sc.frame <= 0xff)
- s << (uint8_t)sc.frame;
- else
- s << sc.frame;
- write_bbox(s, sc);
- if (sc.active)
- s << sc.delta;
- }
- break;
- }
- case object_type::light: {
- const auto& L = static_cast<const light&>(e);
- const auto exact = L.frame == 0 && L.pass == pass_mode::pass &&
- L.bbox_offset.isZero() && L.bbox_size.isZero();
- {
- fm_assert(L.r < rotation_COUNT);
- fm_assert(L.falloff < light_falloff_COUNT);
- uint8_t flags = 0;
- flags |= (uint8_t)exact; // 1 bit
- flags |= ((uint8_t)L.r & lowbits<rotation_BITS>) << 1; // 3 input_bits
- flags |= ((uint8_t)L.falloff & lowbits<light_falloff_BITS>) << 4; // 2 input_bits
- flags |= (uint8_t)!!L.enabled << 7; // 1 bit
- s << flags;
- }
- {
- s << L.max_distance;
- for (auto i = 0uz; i < 4; i++)
- s << L.color[i];
- }
- if (!exact)
- {
- fm_assert(L.frame < (1 << 14));
- fm_assert(L.pass < pass_mode_COUNT);
- uint16_t frame = 0;
- frame |= L.frame;
- frame |= uint16_t(L.pass) << 14;
- s << frame;
- write_bbox(s, L);
- }
- break;
+ s << value;
}
- }
- }
-}
-
-void writer_state::serialize_chunk(const chunk& c, chunk_coords_ coord)
-{
- fm_assert(chunk_buf.empty());
- const auto es_size = sizeof(uint32_t) + object_size*c.objects().size();
- chunk_buf.resize(std::max(chunk_buf.size(), chunkbuf_size + es_size));
-
- auto s = binary_writer{chunk_buf.begin()};
+ };
- s << chunk_magic << coord.x << coord.y;
- fm_assert(coord.z >= chunk_z_min && coord.z <= chunk_z_max);
- s << coord.z;
+ using visitor_<writer>::visit;
- for (auto i = 0uz; i < TILE_COUNT; i++)
+ template<typename F>
+ void intern_atlas_(void* atlas, atlas_type type, F&& f)
{
- const tile_proto x = c[i];
- const auto ground = x.ground();
- const auto wall_north = x.wall_north(), wall_west = x.wall_west();
- //const auto scenery = x.scenery_frame;
-
- fm_debug_assert(s.bytes_written() + tile_size <= chunkbuf_size);
-
- auto img_g = ground.atlas ? intern_atlas(&*ground.atlas, ground.atlas->name(), ground.atlas->num_tiles2()) : null_atlas;
- auto img_n = wall_north ? intern_atlas(&*wall_north.atlas, wall_north.atlas->name(), {0xff, 0xff}) : null_atlas;
- auto img_w = wall_west ? intern_atlas(&*wall_west.atlas, wall_west.atlas->name(), {0xff, 0xff}) : null_atlas;
+ do_visit(atlas_magic, f);
+ do_visit(type, f);
- fm_assert(!ground.atlas || ground.variant < ground.atlas->num_tiles());
+ StringView name;
- if (img_g == null_atlas && img_n == null_atlas && img_w == null_atlas)
+ switch (type)
{
- size_t j, max = std::min(TILE_COUNT, i + 0x80);
- for (j = i+1; j < max; j++)
- {
- auto tile = c[j];
- if (tile.ground_atlas || tile.wall_north_atlas || tile.wall_west_atlas)
- break;
- }
- j -= i + 1;
- fm_assert(j == (j & 0x7fuz));
- i += j;
- tilemeta flags = meta_rle | (tilemeta)j;
- s << flags;
-
- continue;
+ case atlas_type::ground: name = reinterpret_cast<const ground_atlas*>(atlas)->name(); goto ok;
+ case atlas_type::wall: name = reinterpret_cast<const wall_atlas*>(atlas)->name(); goto ok;
+ case atlas_type::object: name = reinterpret_cast<const anim_atlas*>(atlas)->name(); goto ok;
+ case atlas_type::none: break;
}
+ fm_abort("invalid atlas type '%d'", (int)type);
- 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);
- s << flags;
+ok: do_visit(intern_string(name), f);
+ }
- if (img_g != null_atlas)
+ [[nodiscard]] atlasid intern_atlas(void* atlas, atlas_type type)
+ {
+ atlas_array.reserve(vector_initial_size);
+ fm_assert(atlas != nullptr);
+ auto [kv, fresh] = atlas_map.try_emplace(atlas, (uint32_t)-1);
+ if (!fresh)
{
- s << img_g;
- s << ground.variant;
+ fm_debug_assert(kv.value() != (uint32_t)-1);
+ return kv->second;
}
- if (img_n != null_atlas)
+ else
{
- s << img_n;
- s << wall_north.variant;
+ size_t len = 0;
+ intern_atlas_(atlas, type, size_counter{len});
+ fm_assert(len > 0);
+
+ buffer buf{len};
+ binary_writer<char*> s{&buf.data[0], buf.size};
+ intern_atlas_(atlas, type, byte_writer{s});
+ auto id = (uint32_t)atlas_array.size();
+ fm_assert(s.bytes_written() == s.bytes_allocated());
+ atlas_array.emplace_back(std::move(buf), atlas, type);
+ kv.value() = id;
+ fm_assert(id != null_atlas);
+ return atlasid{id};
}
- if (img_w != null_atlas)
+ }
+
+ atlasid maybe_intern_atlas(void* atlas, atlas_type type)
+ {
+ if (!atlas)
+ return null_atlas;
+ else
+ return intern_atlas(atlas, type);
+ }
+
+ atlasid intern_string(StringView str)
+ {
+ string_array.reserve(vector_initial_size);
+ auto [kv, found] = string_map.try_emplace({str}, (uint32_t)-1);
+ if (found)
+ return kv.value();
+ else
{
- s << img_w;
- s << wall_west.variant;
+ auto id = (uint32_t)string_array.size();
+ string_array.emplace_back(str);
+ kv.value() = id;
+ fm_assert(id != null_atlas);
+ return atlasid{id};
}
}
- serialize_scenery(c, s);
+ template<typename F>
+ void serialize_objects_(chunk& c, F&& f)
+ {
+ f((uint32_t)c.objects().size());
- const auto nbytes = s.bytes_written();
- fm_assert(nbytes <= chunkbuf_size);
+ for (const std::shared_ptr<object>& obj : c.objects())
+ {
+ fm_assert(obj != nullptr);
+ do_visit(object_magic, f);
+ do_visit(*obj, f);
+ }
+ }
- chunk_bufs.emplace_back(chunk_buf.cbegin(), chunk_buf.cbegin() + ptrdiff_t(nbytes));
- chunk_buf.clear();
-}
+ template<typename F>
+ void serialize_tile_(tile_ref t, F&& f)
+ {
+ auto g = maybe_intern_atlas(t.ground_atlas().get(), atlas_type::ground),
+ n = maybe_intern_atlas(t.wall_north_atlas().get(), atlas_type::wall),
+ w = maybe_intern_atlas(t.wall_west_atlas().get(), atlas_type::wall);
+ do_visit(g, f);
+ do_visit(n, f);
+ do_visit(w, f);
+ if (g != null_atlas) do_visit(t.ground().variant, f);
+ if (n != null_atlas) do_visit(t.wall_north().variant, f);
+ if (w != null_atlas) do_visit(t.wall_west().variant, f);
+ }
-#ifdef __GNUG__
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
-#elif defined _MSC_VER
-#pragma warning(push)
-#pragma warning(disable : 4996)
-#endif
+ void serialize_chunk_(chunk& c)
+ {
+ size_t len = 0;
+ {
+ auto ctr = size_counter{len};
+ do_visit(chunk_magic, ctr);
+ do_visit(c.coord(), ctr);
+ fm_assert(len > 0);
+ for (uint32_t i = 0; i < TILE_COUNT; i++)
+ serialize_tile_(c[i], ctr);
+ serialize_objects_(c, ctr);
+ }
-ArrayView<const char> writer_state::serialize_world()
-{
- fm_assert(_world != nullptr);
- load_scenery();
+ buffer buf{len};
+ {
+ binary_writer<char*> s{&buf.data[0], buf.size};
+ byte_writer b{s};
+ do_visit(chunk_magic, b);
+ do_visit(c.coord(), b);
+ for (uint32_t i = 0; i < TILE_COUNT; i++)
+ serialize_tile_(c[i], b);
+ serialize_objects_(c, b);
+ fm_assert(s.bytes_written() == s.bytes_allocated());
+ }
+ chunk_array.emplace_back(std::move(buf), &c);
+ }
- for (const auto& [_, c] : _world->chunks())
+ template<typename F>
+ void serialize_header_(F&& f)
{
- for (const auto& e_ : c.objects())
+ fm_assert(header_buf.empty());
+ for (char c : file_magic)
+ f(c);
+ auto nstrings = (uint32_t)string_array.size(),
+ natlases = (uint32_t)atlas_array.size(),
+ nchunks = (uint32_t)chunk_array.size();
+ do_visit(nstrings, f);
+ do_visit(natlases, f);
+ do_visit(nchunks, f);
+ }
+
+ void serialize_world()
+ {
+ fm_assert(string_array.empty());
+ fm_assert(atlas_array.empty());
+ fm_assert(chunk_array.empty());
+ fm_assert(header_buf.empty());
+
+ struct pair { chunk_coords_ coord; chunk* c; };
+ std::vector<pair> chunks;
+ chunks.reserve(w.chunks().size());
+
+ for (auto& [coord, c] : w.chunks())
+ chunks.push_back(pair{coord, &non_const(c)});
+ std::sort(chunks.begin(), chunks.end(), [](const auto& at, const auto& bt) {
+ auto a = at.coord, b = bt.coord;
+ return std::tuple{a.z, a.y, a.x} <=> std::tuple{b.z, b.y, b.x} == std::strong_ordering::less;
+ });
+
+ for (auto [coord, c] : chunks)
+ serialize_chunk_(*c);
+
+ size_t len = 0;
+ {
+ fm_assert(header_buf.empty());
+ serialize_header_(size_counter{len});
+ fm_assert(len > 0);
+ }
+ buffer hdr{len};
{
- const auto& e = *e_;
- switch (e.type())
- {
- case object_type::scenery:
- intern_scenery(static_cast<const scenery&>(e), false);
- break;
- case object_type::critter:
- case object_type::light:
- break;
- default:
- fm_abort("invalid scenery type '%d'", (int)e.type());
- }
+ binary_writer<char*> s{&hdr.data[0], hdr.size};
+ serialize_header_(byte_writer{s});
+ fm_assert(s.bytes_written() == s.bytes_allocated());
}
+ header_buf = std::move(hdr);
}
- for (const auto& [pos, c] : _world->chunks())
+
+ template<typename F>
+ void visit(anim_atlas& a, F&& f)
{
-#ifndef FM_NO_DEBUG
- if (c.empty(true))
- fm_warn("chunk %hd:%hd is empty", pos.x, pos.y);
-#endif
- serialize_chunk(c, pos);
+ atlasid id = intern_atlas(&a, atlas_type::object);
+ do_visit(id, f);
}
- serialize_atlases();
- serialize_scenery_names();
- serialize_strings();
-
- using proto_t = std::decay_t<decltype(proto_version)>;
- fm_assert(_world->size() <= int_traits<chunksiz>::max);
-
- const auto len = fm_begin(
- auto len = 0uz;
- len += std::size(file_magic)-1;
- len += sizeof(proto_t);
- len += atlas_buf.size();
- len += scenery_buf.size();
- len += string_buf.size();
- len += sizeof(object_id);
- len += sizeof(chunksiz);
- for (const auto& buf : chunk_bufs)
- len += buf.size();
- return len;
- );
- file_buf.resize(len);
- auto bytes_written = 0uz;
- auto it = file_buf.begin();
- const auto copy = [&](const auto& in) {
- auto len1 = std::distance(std::cbegin(in), std::cend(in)),
- len2 = std::distance(it, file_buf.end());
- fm_assert(len1 <= len2);
- it = std::copy(std::cbegin(in), std::cend(in), it);
- bytes_written += (size_t)len1;
- };
- 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_int((proto_t)proto_version);
- copy(atlas_buf);
- copy(scenery_buf);
- copy(string_buf);
- copy_int(_world->object_counter());
- copy_int((chunksiz)_world->size());
- for (const auto& buf : chunk_bufs)
- copy(buf);
- fm_assert(file_buf.size() == bytes_written);
- fm_assert(len == bytes_written);
- _world = nullptr;
- return {file_buf.data(), file_buf.size()};
-}
-#ifdef __GNUG__
-#pragma GCC diagnostic pop
-#elif defined _MSC_VER
-#pragma warning(pop)
-#endif
+ template<typename F> void visit(const chunk_coords_& coord, F&& f)
+ {
+ f(coord.x);
+ f(coord.y);
+ f(coord.z);
+ }
+
+ template<typename F> void visit(const local_coords& pt, F&& f)
+ {
+ f(pt.to_index());
+ }
+};
+
+void my_fwrite(FILE_raii& f, const buffer& buf, char(&errbuf)[128])
+{
+ auto len = ::fwrite(&buf.data[0], buf.size, 1, f);
+ int error = errno;
+ if (len != 1)
+ fm_abort("fwrite: %s", get_error_string(errbuf, error).data());
+}
} // namespace
+} // namespace floormat::Serialize
+
namespace floormat {
void world::serialize(StringView filename)
{
+ using namespace floormat::Serialize;
+
collect(true);
char errbuf[128];
fm_assert(filename.flags() & StringViewFlag::NullTerminated);
@@ -629,13 +451,23 @@ void world::serialize(StringView filename)
int error = errno;
fm_abort("fopen(\"%s\", \"w\"): %s", filename.data(), get_error_string(errbuf, error).data());
}
- writer_state s{*this};
- const auto array = s.serialize_world();
- if (auto len = ::fwrite(array.data(), array.size(), 1, file); len != 1)
{
- int error = errno;
- fm_abort("fwrite: %s", get_error_string(errbuf, error).data());
+ struct writer writer{.w = *this};
+ const bool is_empty = chunks().empty();
+ writer.serialize_world();
+ if (!is_empty)
+ {
+ fm_assert(!writer.header_buf.empty());
+ fm_assert(!writer.atlas_array.empty());
+ fm_assert(!writer.atlas_map.empty());
+ fm_assert(!writer.string_array.empty());
+ fm_assert(!writer.string_map.empty());
+ fm_assert(!writer.chunk_array.empty());
+ }
+ my_fwrite(file, writer.header_buf, errbuf);
+
}
+
if (int ret = ::fflush(file); ret != 0)
{
int error = errno;
diff --git a/src/chunk.cpp b/src/chunk.cpp
index 8674eb9f..dcb7d030 100644
--- a/src/chunk.cpp
+++ b/src/chunk.cpp
@@ -45,6 +45,8 @@ tile_proto chunk::operator[](size_t idx) const noexcept { return tile_proto(tile
tile_ref chunk::operator[](local_coords xy) noexcept { return operator[](xy.to_index()); }
tile_proto chunk::operator[](local_coords xy) const noexcept { return operator[](xy.to_index()); }
+chunk_coords_ chunk::coord() const noexcept { return _coord; }
+
tile_ref chunk::at_offset(local_coords pos, Vector2i off)
{
const auto coord = global_coords{_coord, pos};
diff --git a/src/chunk.hpp b/src/chunk.hpp
index 968be089..6833ab03 100644
--- a/src/chunk.hpp
+++ b/src/chunk.hpp
@@ -47,6 +47,7 @@ struct chunk final
tile_ref operator[](local_coords xy) noexcept;
tile_proto operator[](local_coords xy) const noexcept;
+ chunk_coords_ coord() const noexcept;
tile_ref at_offset(local_coords pos, Vector2i off);
tile_ref at_offset(tile_ref r, Vector2i off);
Optional<tile_ref> at_offset_(local_coords pos, Vector2i off);
diff --git a/userconfig-sthalik@Windows-Clang.cmake b/userconfig-sthalik@Windows-Clang.cmake
index 60b69809..71992985 100644
--- a/userconfig-sthalik@Windows-Clang.cmake
+++ b/userconfig-sthalik@Windows-Clang.cmake
@@ -114,6 +114,7 @@ function(fm-userconfig-src)
-Wno-zero-length-array
-Wno-unsafe-buffer-usage
-Wno-bitwise-op-parentheses
+ -Wno-weak-vtables
)
add_compile_options(
-Werror