3#include <tess/core/shape.h>
4#include <tess/storage/residency.h>
5#include <tess/storage/sparse_world.h>
6#include <tess/storage/world.h>
7#include <tess/version.h>
24template <
typename Tag, std::u
int64_t Id, std::u
int32_t Version = 1>
27 static constexpr std::uint64_t
id = Id;
28 static constexpr std::uint32_t version = Version;
33template <
typename... Fields>
34consteval bool unique_persisted_field_ids() {
35 constexpr std::array ids{Fields::id...};
36 for (std::size_t i = 0; i < ids.size(); ++i) {
37 for (std::size_t j = i + 1; j < ids.size(); ++j) {
38 if (ids[i] == ids[j]) {
46template <
typename... Fields>
47consteval bool unique_persisted_field_tags() {
48 return is_valid_field_schema_v<Field<typename Fields::tag_type, Fields>...>;
54template <std::uint64_t Id, std::uint32_t Version,
typename... Fields>
56 static_assert(detail::unique_persisted_field_ids<Fields...>(),
57 "PersistenceSchema field IDs must be unique.");
58 static_assert(detail::unique_persisted_field_tags<Fields...>(),
59 "PersistenceSchema field tags must be unique.");
61 static constexpr std::uint64_t
id = Id;
62 static constexpr std::uint32_t version = Version;
63 static constexpr std::size_t field_count =
sizeof...(Fields);
64 using fields = std::tuple<Fields...>;
68enum class WorldArchiveResidency : std::uint8_t {
74enum class WorldArchiveStatus : std::uint8_t {
87 ResidencyCapacityExceeded,
93 std::uint32_t format_version = 0;
96 lattice::Identity lattice_identity = lattice::Identity::Orthogonal;
97 std::uint32_t lattice_version = 0;
98 std::uint32_t key_layout_version = 0;
99 std::uint64_t schema_id = 0;
100 std::uint32_t schema_version = 0;
101 std::uint32_t library_major = 0;
102 std::uint32_t library_minor = 0;
103 std::uint32_t library_patch = 0;
104 WorldArchiveResidency residency = WorldArchiveResidency::AlwaysResident;
105 std::uint32_t field_count = 0;
106 std::uint64_t chunk_count = 0;
111 WorldArchiveStatus status = WorldArchiveStatus::Ok;
113 std::size_t bytes_processed = 0;
135 std::size_t bytes_written = 0;
140inline constexpr std::array<std::byte, 8> world_archive_magic{
141 std::byte{
'T'}, std::byte{
'E'}, std::byte{
'S'}, std::byte{
'S'},
142 std::byte{
'W'}, std::byte{
'L'}, std::byte{
'D'}, std::byte{0},
144inline constexpr std::uint32_t world_archive_format_version = 2;
145inline constexpr std::uint32_t world_archive_key_layout_version = 1;
147inline constexpr std::size_t world_archive_header_size = 121;
148inline constexpr std::size_t world_archive_checksum_offset = 20;
149inline constexpr std::size_t world_archive_checksum_size =
150 sizeof(std::uint32_t);
151inline constexpr std::size_t world_archive_field_desc_size = 17;
152inline constexpr std::size_t world_archive_chunk_prefix_size = 16;
153inline constexpr std::uint32_t world_archive_max_fields = 1024;
162template <
typename Shape>
163constexpr void assert_lattice_version_is_representable() noexcept {
165 static_cast<std::uint64_t
>(ShapeTraits<Shape>::lattice_version) <=
166 std::numeric_limits<std::uint32_t>::max(),
167 "lattice_version exceeds the 32-bit archive header field; a world "
168 "using this lattice cannot be persisted in archive format v2");
171enum class ArchiveScalarKind : std::uint8_t {
178template <
typename T,
bool IsEnum = std::is_enum_v<T>>
179struct ArchiveScalarBase {
184struct ArchiveScalarBase<T, true> {
185 using type = std::underlying_type_t<T>;
189using ArchiveScalarBaseT =
typename ArchiveScalarBase<T>::type;
195template <
typename T,
bool IsEnum = std::is_enum_v<T>>
196struct ArchiveEnumSupported : std::true_type {};
199struct ArchiveEnumSupported<T, true>
200 : std::bool_constant<!std::is_convertible_v<T, std::underlying_type_t<T>>> {
204inline constexpr bool archive_scalar_supported_v =
205 ArchiveEnumSupported<T>::value &&
206 (std::is_integral_v<ArchiveScalarBaseT<T>> ||
207 std::is_floating_point_v<ArchiveScalarBaseT<T>>) &&
208 (
sizeof(ArchiveScalarBaseT<T>) == 1 ||
sizeof(ArchiveScalarBaseT<T>) == 2 ||
209 sizeof(ArchiveScalarBaseT<T>) == 4 ||
sizeof(ArchiveScalarBaseT<T>) == 8);
212consteval auto archive_scalar_kind() -> ArchiveScalarKind {
213 using Base = ArchiveScalarBaseT<T>;
214 static_assert(archive_scalar_supported_v<T>,
215 "Persisted field values must be bool, integral, scoped enum, "
216 "float, or double scalar types of 1, 2, 4, or 8 bytes.");
217 if constexpr (std::is_same_v<Base, bool>) {
218 return ArchiveScalarKind::Boolean;
219 }
else if constexpr (std::is_floating_point_v<Base>) {
220 return ArchiveScalarKind::Floating;
221 }
else if constexpr (std::is_signed_v<Base>) {
222 return ArchiveScalarKind::Signed;
224 return ArchiveScalarKind::Unsigned;
228template <
typename UInt>
229void append_unsigned_le(std::vector<std::byte>& out, UInt value) {
230 static_assert(std::is_unsigned_v<UInt>);
231 for (std::size_t i = 0; i <
sizeof(UInt); ++i) {
233 static_cast<std::byte
>((value >> (i * 8U)) &
static_cast<UInt
>(0xff)));
238void append_scalar(std::vector<std::byte>& out, T value) {
239 using Base = ArchiveScalarBaseT<T>;
240 static_assert(archive_scalar_supported_v<T>);
241 const auto base =
static_cast<Base
>(value);
242 if constexpr (std::is_same_v<Base, bool>) {
243 out.push_back(base ? std::byte{1} : std::byte{0});
244 }
else if constexpr (std::is_floating_point_v<Base>) {
246 std::conditional_t<
sizeof(Base) == 4, std::uint32_t, std::uint64_t>;
247 append_unsigned_le(out, std::bit_cast<UInt>(base));
249 using UInt = std::make_unsigned_t<Base>;
250 append_unsigned_le(out, std::bit_cast<UInt>(base));
256 explicit ArchiveCursor(std::span<const std::byte> bytes) : bytes_(bytes) {}
258 template <
typename UInt>
259 bool read_unsigned_le(UInt& value) {
260 static_assert(std::is_unsigned_v<UInt>);
261 if (remaining() <
sizeof(UInt)) {
265 for (std::size_t i = 0; i <
sizeof(UInt); ++i) {
266 value =
static_cast<UInt
>(
268 (
static_cast<UInt
>(std::to_integer<unsigned int>(bytes_[at_ + i]))
275 bool read_byte(std::uint8_t& value) {
276 if (remaining() == 0) {
280 static_cast<std::uint8_t
>(std::to_integer<unsigned int>(bytes_[at_++]));
284 bool skip(std::size_t count) {
285 if (remaining() < count) {
292 [[nodiscard]] std::size_t position() const noexcept {
return at_; }
293 [[nodiscard]] std::size_t remaining() const noexcept {
294 return bytes_.size() - at_;
298 std::span<const std::byte> bytes_;
303bool read_scalar(ArchiveCursor& cursor, T& value) {
304 using Base = ArchiveScalarBaseT<T>;
305 static_assert(archive_scalar_supported_v<T>);
307 if constexpr (std::is_same_v<Base, bool>) {
308 std::uint8_t
byte = 0;
309 if (!cursor.read_byte(
byte) ||
byte > 1) {
313 }
else if constexpr (std::is_floating_point_v<Base>) {
315 std::conditional_t<
sizeof(Base) == 4, std::uint32_t, std::uint64_t>;
317 if (!cursor.read_unsigned_le(bits)) {
320 base = std::bit_cast<Base>(bits);
322 using UInt = std::make_unsigned_t<Base>;
324 if (!cursor.read_unsigned_le(bits)) {
327 base = std::bit_cast<Base>(bits);
329 value =
static_cast<T
>(base);
333inline void update_crc32(std::uint32_t& crc,
const std::byte* bytes,
334 std::size_t size)
noexcept {
335 for (std::size_t i = 0; i < size; ++i) {
336 crc ^= std::to_integer<std::uint8_t>(bytes[i]);
337 for (
int bit = 0; bit < 8; ++bit) {
339 static_cast<std::uint32_t
>(-
static_cast<std::int32_t
>(crc & 1U));
340 crc = (crc >> 1U) ^ (0xedb88320U & mask);
345inline auto archive_crc32(std::span<const std::byte> bytes)
noexcept
347 constexpr auto kSuffixOffset =
348 world_archive_checksum_offset + world_archive_checksum_size;
349 if (bytes.size() < kSuffixOffset) {
352 auto crc = std::uint32_t{0xffffffffU};
356 update_crc32(crc, bytes.data(), world_archive_checksum_offset);
357 update_crc32(crc, bytes.data() + kSuffixOffset, bytes.size() - kSuffixOffset);
361struct ArchiveFieldDesc {
362 std::uint64_t
id = 0;
363 std::uint32_t version = 0;
364 ArchiveScalarKind kind = ArchiveScalarKind::Unsigned;
365 std::uint32_t width = 0;
368struct ParsedArchive {
369 WorldArchiveResult result{};
370 std::span<const std::byte> body;
371 std::vector<ArchiveFieldDesc> fields;
372 std::size_t chunks_offset = 0;
373 std::size_t chunk_record_size = 0;
376inline bool checked_add(std::size_t lhs, std::size_t rhs,
377 std::size_t& result)
noexcept {
378 if (lhs > std::numeric_limits<std::size_t>::max() - rhs) {
385inline bool checked_multiply(std::size_t lhs, std::size_t rhs,
386 std::size_t& result)
noexcept {
387 if (lhs != 0 && rhs > std::numeric_limits<std::size_t>::max() / lhs) {
394inline auto parse_world_archive(std::span<const std::byte> bytes)
396 ParsedArchive parsed;
397 auto& info = parsed.result.info;
398 auto fail = [&](WorldArchiveStatus status) {
399 parsed.result.status = status;
402 if (bytes.size() < world_archive_magic.size()) {
403 return fail(WorldArchiveStatus::Truncated);
405 if (!std::equal(world_archive_magic.begin(), world_archive_magic.end(),
407 return fail(WorldArchiveStatus::InvalidMagic);
410 ArchiveCursor cursor(bytes.subspan(world_archive_magic.size()));
411 if (!cursor.read_unsigned_le(info.format_version)) {
412 return fail(WorldArchiveStatus::Truncated);
417 if (info.format_version != world_archive_format_version) {
418 return fail(WorldArchiveStatus::UnsupportedFormat);
420 if (bytes.size() < world_archive_header_size) {
421 return fail(WorldArchiveStatus::Truncated);
424 auto body_size = std::uint64_t{};
425 auto checksum = std::uint32_t{};
426 auto lattice_id = std::uint32_t{};
427 auto residency = std::uint8_t{};
428 if (!cursor.read_unsigned_le(body_size) ||
429 !cursor.read_unsigned_le(checksum) ||
430 !cursor.read_unsigned_le(info.size.x) ||
431 !cursor.read_unsigned_le(info.size.y) ||
432 !cursor.read_unsigned_le(info.size.z) ||
433 !cursor.read_unsigned_le(info.chunk.x) ||
434 !cursor.read_unsigned_le(info.chunk.y) ||
435 !cursor.read_unsigned_le(info.chunk.z) ||
436 !cursor.read_unsigned_le(lattice_id) ||
437 !cursor.read_unsigned_le(info.lattice_version) ||
438 !cursor.read_unsigned_le(info.key_layout_version) ||
439 !cursor.read_unsigned_le(info.schema_id) ||
440 !cursor.read_unsigned_le(info.schema_version) ||
441 !cursor.read_unsigned_le(info.library_major) ||
442 !cursor.read_unsigned_le(info.library_minor) ||
443 !cursor.read_unsigned_le(info.library_patch) ||
444 !cursor.read_byte(residency) ||
445 !cursor.read_unsigned_le(info.field_count) ||
446 !cursor.read_unsigned_le(info.chunk_count)) {
447 return fail(WorldArchiveStatus::Truncated);
449 info.lattice_identity =
static_cast<lattice::Identity
>(lattice_id);
450 info.residency =
static_cast<WorldArchiveResidency
>(residency);
452 if (body_size > std::numeric_limits<std::size_t>::max()) {
453 return fail(WorldArchiveStatus::Corrupt);
455 auto expected_size = std::size_t{};
456 if (!checked_add(world_archive_header_size,
457 static_cast<std::size_t
>(body_size), expected_size)) {
458 return fail(WorldArchiveStatus::Corrupt);
460 if (bytes.size() < expected_size) {
461 return fail(WorldArchiveStatus::Truncated);
463 if (bytes.size() != expected_size) {
464 return fail(WorldArchiveStatus::Corrupt);
466 parsed.body = bytes.subspan(world_archive_header_size);
467 if (archive_crc32(bytes) != checksum) {
468 return fail(WorldArchiveStatus::Corrupt);
470 if (info.field_count > world_archive_max_fields || info.size.x == 0 ||
471 info.size.y == 0 || info.size.z == 0 || info.chunk.x == 0 ||
472 info.chunk.y == 0 || info.chunk.z == 0 ||
473 info.size.x % info.chunk.x != 0 || info.size.y % info.chunk.y != 0 ||
474 info.size.z % info.chunk.z != 0 ||
475 (info.residency != WorldArchiveResidency::AlwaysResident &&
476 info.residency != WorldArchiveResidency::SparseResident)) {
477 return fail(WorldArchiveStatus::Corrupt);
480 ArchiveCursor body_cursor(parsed.body);
481 parsed.fields.reserve(info.field_count);
482 auto bytes_per_tile = std::size_t{};
483 for (std::uint32_t i = 0; i < info.field_count; ++i) {
484 ArchiveFieldDesc field;
485 auto kind = std::uint8_t{};
486 if (!body_cursor.read_unsigned_le(field.id) ||
487 !body_cursor.read_unsigned_le(field.version) ||
488 !body_cursor.read_byte(kind) ||
489 !body_cursor.read_unsigned_le(field.width)) {
493 return fail(WorldArchiveStatus::Corrupt);
495 field.kind =
static_cast<ArchiveScalarKind
>(kind);
496 const auto valid_width = field.width == 1 || field.width == 2 ||
497 field.width == 4 || field.width == 8;
498 const auto valid_kind = field.kind == ArchiveScalarKind::Unsigned ||
499 field.kind == ArchiveScalarKind::Signed ||
500 field.kind == ArchiveScalarKind::Floating ||
501 field.kind == ArchiveScalarKind::Boolean;
502 if (!valid_width || !valid_kind ||
503 (field.kind == ArchiveScalarKind::Boolean && field.width != 1) ||
504 (field.kind == ArchiveScalarKind::Floating && field.width != 4 &&
506 !checked_add(bytes_per_tile, field.width, bytes_per_tile)) {
507 return fail(WorldArchiveStatus::Corrupt);
512 if (std::any_of(parsed.fields.begin(), parsed.fields.end(),
513 [&](
const ArchiveFieldDesc& existing) {
514 return existing.id == field.id;
516 return fail(WorldArchiveStatus::Corrupt);
518 parsed.fields.push_back(field);
520 parsed.chunks_offset = body_cursor.position();
522 std::size_t local_xy = 0;
523 std::size_t local_tiles = 0;
524 if (info.chunk.x > std::numeric_limits<std::size_t>::max() ||
525 info.chunk.y > std::numeric_limits<std::size_t>::max() ||
526 info.chunk.z > std::numeric_limits<std::size_t>::max() ||
527 !checked_multiply(
static_cast<std::size_t
>(info.chunk.x),
528 static_cast<std::size_t
>(info.chunk.y), local_xy) ||
529 !checked_multiply(local_xy,
static_cast<std::size_t
>(info.chunk.z),
531 return fail(WorldArchiveStatus::Corrupt);
533 std::size_t field_bytes = 0;
534 if (!checked_multiply(local_tiles, bytes_per_tile, field_bytes) ||
535 !checked_add(world_archive_chunk_prefix_size, field_bytes,
536 parsed.chunk_record_size)) {
537 return fail(WorldArchiveStatus::Corrupt);
539 std::size_t all_chunk_bytes = 0;
540 std::size_t expected_body_size = 0;
541 if (info.chunk_count > std::numeric_limits<std::size_t>::max() ||
542 !checked_multiply(
static_cast<std::size_t
>(info.chunk_count),
543 parsed.chunk_record_size, all_chunk_bytes) ||
544 !checked_add(parsed.chunks_offset, all_chunk_bytes, expected_body_size) ||
545 expected_body_size != parsed.body.size()) {
546 return fail(WorldArchiveStatus::Corrupt);
549 const auto chunks_x = info.size.x / info.chunk.x;
550 const auto chunks_y = info.size.y / info.chunk.y;
551 const auto chunks_z = info.size.z / info.chunk.z;
552 if (chunks_x > std::numeric_limits<std::uint64_t>::max() / chunks_y ||
553 chunks_x * chunks_y >
554 std::numeric_limits<std::uint64_t>::max() / chunks_z) {
555 return fail(WorldArchiveStatus::Corrupt);
557 const auto logical_chunks = chunks_x * chunks_y * chunks_z;
562 if (info.residency == WorldArchiveResidency::AlwaysResident &&
563 info.chunk_count != logical_chunks) {
564 return fail(WorldArchiveStatus::InvalidChunk);
566 auto previous_key = std::uint64_t{};
567 for (std::uint64_t i = 0; i < info.chunk_count; ++i) {
568 auto key = std::uint64_t{};
569 auto active = std::uint32_t{};
570 auto entities = std::uint32_t{};
571 if (!body_cursor.read_unsigned_le(key) ||
572 !body_cursor.read_unsigned_le(active) ||
573 !body_cursor.read_unsigned_le(entities) || key >= logical_chunks ||
574 (i != 0 && key <= previous_key) || !body_cursor.skip(field_bytes)) {
575 return fail(WorldArchiveStatus::InvalidChunk);
579 if (body_cursor.remaining() != 0) {
580 return fail(WorldArchiveStatus::Corrupt);
582 parsed.result.status = WorldArchiveStatus::Ok;
583 parsed.result.bytes_processed = bytes.size();
587template <
typename World>
588consteval auto world_archive_residency() -> WorldArchiveResidency {
589 if constexpr (std::is_same_v<
typename World::residency_type,
591 return WorldArchiveResidency::AlwaysResident;
594 std::is_same_v<typename World::residency_type, SparseResident>);
595 return WorldArchiveResidency::SparseResident;
599template <
typename Archive,
typename World>
600consteval bool archive_fields_supported() {
601 auto supported =
true;
603 [&]<
typename... Fields>(Fields...) {
604 supported = ((World::schema_type::template contains<
605 typename Fields::tag_type> &&
606 archive_scalar_supported_v<
607 typename World::schema_type::template value_type<
608 typename Fields::tag_type>>) &&
611 typename Archive::fields{});
615template <
typename Archive,
typename World>
616auto expected_field_descs() {
617 std::array<ArchiveFieldDesc, Archive::field_count> fields;
618 auto index = std::size_t{};
620 [&]<
typename... Fields>(Fields...) {
623 Fields::id, Fields::version,
625 typename World::schema_type::template value_type<
626 typename Fields::tag_type>>(),
627 sizeof(typename World::schema_type::template value_type<
628 typename Fields::tag_type>)}),
631 typename Archive::fields{});
635template <
typename Archive,
typename World>
637 std::vector<std::byte>& body) {
639 [&]<
typename... Fields>(Fields...) {
643 world.template field_span<typename Fields::tag_type>(key);
644 for (const auto value : values) {
645 append_scalar(body, value);
650 typename Archive::fields{});
653template <
typename Archive,
typename World>
654bool read_chunk_fields(World& world, ChunkKey key, ArchiveCursor& cursor) {
656 [&]<
typename... Fields>(Fields...) {
657 const auto read_field = [&]<typename Field>(Field) {
659 world.template field_span<typename Field::tag_type>(key);
660 for (auto& value : values) {
661 if (!read_scalar(cursor, value)) {
671 return (read_field(Fields{}) && ...);
673 typename Archive::fields{});
676template <
typename Field,
typename WorldType>
677bool validate_chunk_field(ArchiveCursor& cursor) {
678 using Value =
typename WorldType::schema_type::template value_type<
679 typename Field::tag_type>;
680 for (std::uint64_t i = 0; i < WorldType::local_tile_count; ++i) {
684 auto scalar = ArchiveScalarBaseT<Value>{};
685 if (!read_scalar(cursor, scalar)) {
692template <
typename Archive,
typename WorldType, std::size_t... Indices>
693bool validate_chunk_fields_impl(ArchiveCursor& cursor,
694 std::index_sequence<Indices...>) {
696 validate_chunk_field<
697 std::tuple_element_t<Indices, typename Archive::fields>, WorldType>(
702template <
typename Archive,
typename WorldType>
703bool validate_chunk_fields(ArchiveCursor& cursor) {
704 return validate_chunk_fields_impl<Archive, WorldType>(
705 cursor, std::make_index_sequence<Archive::field_count>{});
708template <
typename Archive,
typename World>
709consteval std::size_t archive_field_bytes_per_chunk() {
710 auto bytes = std::size_t{};
712 [&]<
typename... Fields>(Fields...) {
713 ((bytes += sizeof(typename World::schema_type::template value_type<
714 typename Fields::tag_type>) *
715 static_cast<std::size_t>(World::local_tile_count)),
718 typename Archive::fields{});
722template <
typename World>
723auto archive_chunk_keys(
const World& world) -> std::vector<ChunkKey> {
724 std::vector<ChunkKey> keys;
725 if constexpr (std::is_same_v<
typename World::residency_type,
727 keys.reserve(
static_cast<std::size_t
>(World::chunk_count));
728 for (std::uint64_t key = 0; key < World::chunk_count; ++key) {
729 keys.push_back(ChunkKey{key});
732 const auto resident = world.resident_chunk_keys();
733 keys.assign(resident.begin(), resident.end());
734 std::sort(keys.begin(), keys.end(),
735 [](ChunkKey lhs, ChunkKey rhs) { return lhs.value < rhs.value; });
740template <
typename World>
741void prepare_world_for_load(World& world,
742 std::span<const ChunkKey> archive_keys) {
743 if constexpr (std::is_same_v<
typename World::residency_type,
748 const auto current_span = world.resident_chunk_keys();
749 const std::vector current(current_span.begin(), current_span.end());
750 for (
const auto key : current) {
751 static_cast<void>(world.evict(key));
753 for (
const auto key : archive_keys) {
754 static_cast<void>(world.ensure_resident(key));
759template <
typename World>
760void restore_chunk_metadata(World& world, ChunkKey key, ActiveMask active_mask,
761 std::uint32_t entity_count,
762 DirtyMask invalidation_mask) {
763 world.clear_dirty(key, DirtyMask{std::numeric_limits<std::uint32_t>::max()});
764 world.clear_active(key,
765 ActiveMask{std::numeric_limits<std::uint32_t>::max()});
766 auto& meta = world.meta(key);
767 const auto old_content_version = meta.content_version;
768 const auto old_topology_version = meta.topology_version;
770 meta.content_version = old_content_version;
771 meta.topology_version = old_topology_version;
772 meta.entity_count = entity_count;
773 world.mark_active(key, active_mask);
775 Box3{coord<typename World::shape_type>(
776 chunk_coord<typename World::shape_type>(key), LocalTileId{}),
777 World::shape_type::chunk};
778 if (invalidation_mask) {
779 world.mark_topology_dirty(key, invalidation_mask, bounds);
781 ++meta.content_version;
782 world.mark_topology_rebuilt(key);
789[[nodiscard]]
inline auto inspect_world_archive(
790 std::span<const std::byte> bytes) -> WorldArchiveResult {
791 return detail::parse_world_archive(bytes).result;
800template <
typename Archive,
typename World>
801[[nodiscard]]
auto save_world_archive(
const World& world,
802 std::vector<std::byte>& out)
803 -> WorldArchiveSaveResult {
804 static_assert(detail::archive_fields_supported<Archive, World>(),
805 "Archive fields must exist in the world and use supported "
806 "scalar value types.");
807 static_assert(Archive::field_count <= detail::world_archive_max_fields);
809 const auto keys = detail::archive_chunk_keys(world);
810 auto body = std::vector<std::byte>{};
811 body.reserve(Archive::field_count * detail::world_archive_field_desc_size +
813 (detail::world_archive_chunk_prefix_size +
814 detail::archive_field_bytes_per_chunk<Archive, World>()));
815 const auto fields = detail::expected_field_descs<Archive, World>();
816 for (
const auto field : fields) {
817 detail::append_unsigned_le(body, field.id);
818 detail::append_unsigned_le(body, field.version);
819 body.push_back(
static_cast<std::byte
>(field.kind));
820 detail::append_unsigned_le(body, field.width);
822 for (
const auto key : keys) {
823 detail::append_unsigned_le(body, key.value);
824 detail::append_unsigned_le(body, world.active_mask(key).value);
825 detail::append_unsigned_le(body, world.meta(key).entity_count);
826 detail::append_chunk_fields<Archive>(world, key, body);
830 out.reserve(detail::world_archive_header_size + body.size());
831 out.insert(out.end(), detail::world_archive_magic.begin(),
832 detail::world_archive_magic.end());
833 detail::append_unsigned_le(out, detail::world_archive_format_version);
834 detail::append_unsigned_le(out,
static_cast<std::uint64_t
>(body.size()));
835 detail::append_unsigned_le(out, std::uint32_t{0});
836 detail::append_unsigned_le(out, World::shape_type::size.x);
837 detail::append_unsigned_le(out, World::shape_type::size.y);
838 detail::append_unsigned_le(out, World::shape_type::size.z);
839 detail::append_unsigned_le(out, World::shape_type::chunk.x);
840 detail::append_unsigned_le(out, World::shape_type::chunk.y);
841 detail::append_unsigned_le(out, World::shape_type::chunk.z);
842 detail::append_unsigned_le(
843 out,
static_cast<std::uint32_t
>(
844 ShapeTraits<typename World::shape_type>::lattice_identity));
852 detail::assert_lattice_version_is_representable<typename World::shape_type>();
853 detail::append_unsigned_le(
854 out,
static_cast<std::uint32_t
>(
855 ShapeTraits<typename World::shape_type>::lattice_version));
856 detail::append_unsigned_le(out, detail::world_archive_key_layout_version);
857 detail::append_unsigned_le(out, Archive::id);
858 detail::append_unsigned_le(out, Archive::version);
859 detail::append_unsigned_le(out,
860 static_cast<std::uint32_t
>(library_version.major));
861 detail::append_unsigned_le(out,
862 static_cast<std::uint32_t
>(library_version.minor));
863 detail::append_unsigned_le(out,
864 static_cast<std::uint32_t
>(library_version.patch));
866 static_cast<std::byte
>(detail::world_archive_residency<World>()));
867 detail::append_unsigned_le(out,
868 static_cast<std::uint32_t
>(Archive::field_count));
869 detail::append_unsigned_le(out,
static_cast<std::uint64_t
>(keys.size()));
870 out.insert(out.end(), body.begin(), body.end());
871 const auto checksum = detail::archive_crc32(out);
872 for (std::size_t i = 0; i < detail::world_archive_checksum_size; ++i) {
873 out[detail::world_archive_checksum_offset + i] =
874 static_cast<std::byte
>((checksum >> (i * 8U)) & std::uint32_t{0xff});
876 WorldArchiveSaveResult result;
877 result.info.format_version = detail::world_archive_format_version;
878 result.info.size = World::shape_type::size;
879 result.info.chunk = World::shape_type::chunk;
880 result.info.lattice_identity =
881 ShapeTraits<typename World::shape_type>::lattice_identity;
882 result.info.lattice_version =
static_cast<std::uint32_t
>(
883 ShapeTraits<typename World::shape_type>::lattice_version);
884 result.info.key_layout_version = detail::world_archive_key_layout_version;
885 result.info.schema_id = Archive::id;
886 result.info.schema_version = Archive::version;
887 result.info.library_major =
static_cast<std::uint32_t
>(library_version.major);
888 result.info.library_minor =
static_cast<std::uint32_t
>(library_version.minor);
889 result.info.library_patch =
static_cast<std::uint32_t
>(library_version.patch);
890 result.info.residency = detail::world_archive_residency<World>();
891 result.info.field_count =
static_cast<std::uint32_t
>(Archive::field_count);
892 result.info.chunk_count =
static_cast<std::uint64_t
>(keys.size());
893 result.bytes_written = out.size();
906template <
typename Archive,
typename World>
907[[nodiscard]]
auto load_world_archive(
908 World& world, std::span<const std::byte> bytes,
909 DirtyMask invalidation_mask = DirtyMask{
910 std::numeric_limits<std::uint32_t>::max()}) -> WorldArchiveResult {
911 static_assert(detail::archive_fields_supported<Archive, World>(),
912 "Archive fields must exist in the world and use supported "
913 "scalar value types.");
914 static_assert(Archive::field_count <= detail::world_archive_max_fields);
915 auto parsed = detail::parse_world_archive(bytes);
916 if (parsed.result.status != WorldArchiveStatus::Ok) {
917 return parsed.result;
919 auto fail = [&](WorldArchiveStatus status) {
920 parsed.result.status = status;
921 parsed.result.bytes_processed = 0;
922 return parsed.result;
924 const auto& info = parsed.result.info;
925 if (info.size != World::shape_type::size ||
926 info.chunk != World::shape_type::chunk) {
927 return fail(WorldArchiveStatus::ShapeMismatch);
929 if (info.lattice_identity !=
930 ShapeTraits<typename World::shape_type>::lattice_identity ||
931 info.lattice_version !=
932 static_cast<std::uint32_t
>(
933 ShapeTraits<typename World::shape_type>::lattice_version)) {
934 return fail(WorldArchiveStatus::LatticeMismatch);
936 if (info.key_layout_version != detail::world_archive_key_layout_version) {
937 return fail(WorldArchiveStatus::KeyLayoutMismatch);
939 if (info.residency != detail::world_archive_residency<World>()) {
940 return fail(WorldArchiveStatus::ResidencyMismatch);
942 if (info.schema_id != Archive::id) {
943 return fail(WorldArchiveStatus::SchemaMismatch);
945 if (info.schema_version != Archive::version) {
946 return fail(WorldArchiveStatus::MigrationRequired);
948 const auto expected = detail::expected_field_descs<Archive, World>();
949 if (parsed.fields.size() != expected.size() ||
950 !std::equal(parsed.fields.begin(), parsed.fields.end(), expected.begin(),
951 [](
const auto& lhs,
const auto& rhs) {
952 return lhs.id == rhs.id && lhs.version == rhs.version &&
953 lhs.kind == rhs.kind && lhs.width == rhs.width;
955 return fail(WorldArchiveStatus::FieldMismatch);
957 if constexpr (std::is_same_v<
typename World::residency_type,
959 if (info.chunk_count > world.capacity()) {
960 return fail(WorldArchiveStatus::ResidencyCapacityExceeded);
962 }
else if (info.chunk_count != World::chunk_count) {
963 return fail(WorldArchiveStatus::InvalidChunk);
966 std::vector<ChunkKey> keys;
967 keys.reserve(
static_cast<std::size_t
>(info.chunk_count));
968 detail::ArchiveCursor key_cursor(parsed.body.subspan(parsed.chunks_offset));
969 for (std::uint64_t i = 0; i < info.chunk_count; ++i) {
970 auto key = std::uint64_t{};
971 static_cast<void>(key_cursor.read_unsigned_le(key));
972 keys.push_back(ChunkKey{key});
973 static_cast<void>(key_cursor.skip(
sizeof(std::uint32_t) * 2));
978 if (!detail::validate_chunk_fields<Archive, World>(key_cursor)) {
979 return fail(WorldArchiveStatus::Corrupt);
982 detail::prepare_world_for_load(world, keys);
984 detail::ArchiveCursor cursor(parsed.body.subspan(parsed.chunks_offset));
985 for (
const auto key : keys) {
986 auto encoded_key = std::uint64_t{};
987 auto active_mask = std::uint32_t{};
988 auto entity_count = std::uint32_t{};
989 static_cast<void>(cursor.read_unsigned_le(encoded_key));
990 static_cast<void>(cursor.read_unsigned_le(active_mask));
991 static_cast<void>(cursor.read_unsigned_le(entity_count));
992 if (!detail::read_chunk_fields<Archive>(world, key, cursor)) {
993 return fail(WorldArchiveStatus::Corrupt);
995 detail::restore_chunk_metadata(world, key, ActiveMask{active_mask},
996 entity_count, invalidation_mask);
998 parsed.result.bytes_processed = bytes.size();
999 return parsed.result;
Defines the persistent application schema carried by one world archive.
Definition archive.h:55
Definition residency.h:18
Parsed compatibility metadata from the fixed world-archive envelope.
Definition archive.h:92
Result shared by archive inspection and loading, both of which can fail.
Definition archive.h:110