tess 0.4.0
Performance-first tile and path simulation substrate
Loading...
Searching...
No Matches
sparse_world.h
1#pragma once
2
3#include <tess/core/assert.h>
4#include <tess/core/shape.h>
5#include <tess/storage/chunk_meta.h>
6#include <tess/storage/chunk_page.h>
7#include <tess/storage/residency.h>
8#include <tess/storage/world.h>
9
10#include <cstddef>
11#include <cstdint>
12#include <optional>
13#include <span>
14#include <vector>
15
16namespace tess {
17
18namespace detail {
19
20// Fixed-capacity open-addressing map from ChunkKey to a resident slot index.
21// The table is sized to twice the residency capacity (rounded up to a power
22// of two) and never rehashes, so lookups, inserts, and erases allocate
23// nothing after construction. Deletion uses backward-shift compaction so no
24// tombstones accumulate over long-lived evict/reload churn.
25class ChunkDirectory {
26 public:
27 static constexpr std::size_t npos = static_cast<std::size_t>(-1);
28
29 void reset(std::size_t capacity) {
30 std::size_t table = 2;
31 while (table < capacity * 2) {
32 table <<= 1u;
33 }
34 buckets_.assign(table, Bucket{});
35 mask_ = table - 1;
36 }
37
38 [[nodiscard]] std::size_t find(ChunkKey key) const noexcept {
39 std::size_t i = home(key);
40 while (buckets_[i].occupied) {
41 if (buckets_[i].key == key) {
42 return buckets_[i].slot;
43 }
44 i = (i + 1) & mask_;
45 }
46 return npos;
47 }
48
49 // Precondition: key is not already present and the table has a free bucket
50 // (guaranteed while the number of resident chunks stays <= capacity).
51 void insert(ChunkKey key, std::size_t slot) noexcept {
52 std::size_t i = home(key);
53 while (buckets_[i].occupied) {
54 i = (i + 1) & mask_;
55 }
56 buckets_[i] = Bucket{key, slot, true};
57 }
58
59 bool erase(ChunkKey key) noexcept {
60 std::size_t i = home(key);
61 while (buckets_[i].occupied && buckets_[i].key != key) {
62 i = (i + 1) & mask_;
63 }
64 if (!buckets_[i].occupied) {
65 return false;
66 }
67 std::size_t j = i;
68 while (true) {
69 j = (j + 1) & mask_;
70 if (!buckets_[j].occupied) {
71 break;
72 }
73 const std::size_t k = home(buckets_[j].key);
74 // Move j back into the hole at i only when j's home slot is not
75 // cyclically inside (i, j] — otherwise moving it would break its
76 // probe chain.
77 if (!in_cyclic_range(i, k, j)) {
78 buckets_[i] = buckets_[j];
79 i = j;
80 }
81 }
82 buckets_[i] = Bucket{};
83 return true;
84 }
85
86 private:
87 struct Bucket {
88 ChunkKey key{};
89 std::size_t slot = 0;
90 bool occupied = false;
91 };
92
93 [[nodiscard]] std::size_t home(ChunkKey key) const noexcept {
94 return static_cast<std::size_t>(mix(key.value)) & mask_;
95 }
96
97 static std::uint64_t mix(std::uint64_t x) noexcept {
98 x += 0x9e3779b97f4a7c15ull;
99 x = (x ^ (x >> 30u)) * 0xbf58476d1ce4e5b9ull;
100 x = (x ^ (x >> 27u)) * 0x94d049bb133111ebull;
101 return x ^ (x >> 31u);
102 }
103
104 static bool in_cyclic_range(std::size_t lo, std::size_t pos,
105 std::size_t hi) noexcept {
106 if (lo <= hi) {
107 return lo < pos && pos <= hi;
108 }
109 return lo < pos || pos <= hi;
110 }
111
112 std::vector<Bucket> buckets_;
113 std::size_t mask_ = 0;
114};
115
116} // namespace detail
117
133template <typename Shape, typename Schema>
134class World<Shape, Schema, SparseResident> {
135 public:
136 using shape_type = Shape;
137 using schema_type = Schema;
138 using residency_type = SparseResident;
139 using page_type = ChunkPage<Shape, Schema>;
140
141 static constexpr std::uint64_t chunk_count = ShapeTraits<Shape>::chunk_count;
142 static constexpr std::uint64_t local_tile_count =
143 ShapeTraits<Shape>::local_tile_count;
144 static constexpr std::size_t field_count = Schema::field_count;
145 static constexpr std::size_t page_byte_size = page_type::byte_size;
146
153 explicit World(ResidencyConfig config)
154 : byte_budget_(config.byte_budget),
155 capacity_(clamp_capacity(config.byte_budget)) {
156 pages_.reserve(capacity_);
157 for (std::size_t slot = 0; slot < capacity_; ++slot) {
158 pages_.emplace_back(ChunkKey{0}, ChunkCoord3{});
159 }
160 metadata_.assign(capacity_, ChunkMeta{});
161 dirty_flags_.assign(capacity_, 0u);
162 active_flags_.assign(capacity_, 0u);
163 dirty_bounds_.assign(capacity_, Box3{});
164 slot_key_.assign(capacity_, ChunkKey{});
165 slot_generation_.assign(capacity_, 0);
166 lru_prev_.assign(capacity_, npos_slot);
167 lru_next_.assign(capacity_, npos_slot);
168 slot_position_.assign(capacity_, 0);
169
170 resident_keys_.reserve(capacity_);
171 resident_slots_.reserve(capacity_);
172 free_slots_.reserve(capacity_);
173 for (std::size_t slot = capacity_; slot-- > 0;) {
174 free_slots_.push_back(slot);
175 }
176 directory_.reset(capacity_);
177 }
178
180 [[nodiscard]] std::size_t capacity() const noexcept { return capacity_; }
181
183 [[nodiscard]] std::size_t byte_budget() const noexcept {
184 return byte_budget_;
185 }
186
187 [[nodiscard]] std::size_t resident_count() const noexcept {
188 return resident_keys_.size();
189 }
190
191 [[nodiscard]] std::size_t resident_byte_size() const noexcept {
192 return resident_keys_.size() * page_byte_size;
193 }
194
196 static constexpr std::size_t npos_slot = detail::ChunkDirectory::npos;
197
199 [[nodiscard]] static constexpr bool contains(ChunkKey key) noexcept {
200 return key.value < chunk_count;
201 }
202
204 [[nodiscard]] bool is_resident(ChunkKey key) const noexcept {
205 return directory_.find(key) != detail::ChunkDirectory::npos;
206 }
207
215 [[nodiscard]] std::size_t resident_slot(ChunkKey key) const noexcept {
216 return directory_.find(key);
217 }
218
221 std::size_t slot = npos_slot;
222 std::uint64_t generation = 0;
223 const ChunkMeta* meta = nullptr;
224 };
225
233 [[nodiscard]] auto resident_ref(ChunkKey key) const noexcept
235 const auto slot = directory_.find(key);
236 if (slot == detail::ChunkDirectory::npos) {
237 return ResidentChunkRef{};
238 }
239 return ResidentChunkRef{slot, slot_generation_[slot], &metadata_[slot]};
240 }
241
245 [[nodiscard]] std::uint64_t residency_generation(
246 ChunkKey key) const noexcept {
247 const auto slot = directory_.find(key);
248 if (slot == detail::ChunkDirectory::npos) {
249 return 0;
250 }
251 return slot_generation_[slot];
252 }
253
255 [[nodiscard]] bool valid(ResidencyHandle handle) const noexcept {
256 return handle.generation != 0 &&
257 residency_generation(handle.key) == handle.generation;
258 }
259
266 [[nodiscard]] std::span<const ChunkKey> resident_chunk_keys() const noexcept {
267 return {resident_keys_.data(), resident_keys_.size()};
268 }
269
279 [[nodiscard]] std::uint64_t residency_fingerprint() const noexcept {
280 const auto mix = [](std::uint64_t x) noexcept -> std::uint64_t {
281 x = (x ^ (x >> 30u)) * 0xbf58476d1ce4e5b9ull;
282 x = (x ^ (x >> 27u)) * 0x94d049bb133111ebull;
283 return x ^ (x >> 31u);
284 };
285 // Slot-direct iteration: resident_slots_ pairs with resident_keys_, so
286 // every term is a direct array read -- the by-key accessors would pay
287 // three directory probes per chunk for the same data (audit
288 // 2026-07-11 M2).
289 auto acc = std::uint64_t{0};
290 for (const auto slot : resident_slots_) {
291 auto h = mix(slot_key_[slot].value);
292 h ^= mix(h + static_cast<std::uint64_t>(slot));
293 h ^= mix(h + slot_generation_[slot]);
294 h ^= mix(h + static_cast<std::uint64_t>(metadata_[slot].version));
295 acc += h;
296 }
297 return mix(acc + static_cast<std::uint64_t>(resident_count()) +
298 0x9e3779b97f4a7c15ull);
299 }
300
313 TESS_ASSERT(contains(key));
314 auto slot = directory_.find(key);
315 if (slot != detail::ChunkDirectory::npos) {
316 lru_move_to_mru(slot);
317 return ResidencyHandle{key, slot_generation_[slot]};
318 }
319 slot = acquire_slot();
320 pages_[slot].reset(key, chunk_coord<Shape>(key));
321 metadata_[slot] = ChunkMeta{};
322 dirty_flags_[slot] = 0u;
323 active_flags_[slot] = 0u;
324 dirty_bounds_[slot] = Box3{};
325 slot_key_[slot] = key;
326 slot_generation_[slot] = ++generation_clock_;
327 lru_push_mru(slot);
328 slot_position_[slot] = resident_keys_.size();
329 resident_keys_.push_back(key);
330 resident_slots_.push_back(slot);
331 directory_.insert(key, slot);
332 return ResidencyHandle{key, slot_generation_[slot]};
333 }
334
336 bool touch(ChunkKey key) noexcept {
337 const auto slot = directory_.find(key);
338 if (slot == detail::ChunkDirectory::npos) {
339 return false;
340 }
341 lru_move_to_mru(slot);
342 return true;
343 }
344
351 bool evict(ChunkKey key) {
352 const auto slot = directory_.find(key);
353 if (slot == detail::ChunkDirectory::npos) {
354 return false;
355 }
356 release_slot(slot);
357 free_slots_.push_back(slot);
358 return true;
359 }
360
365 [[nodiscard]] auto chunk(ChunkKey key) noexcept -> page_type& {
366 const auto slot = directory_.find(key);
367 TESS_ASSERT(slot != detail::ChunkDirectory::npos);
368 return pages_[slot];
369 }
370
372 [[nodiscard]] auto chunk(ChunkKey key) const noexcept -> const page_type& {
373 const auto slot = directory_.find(key);
374 TESS_ASSERT(slot != detail::ChunkDirectory::npos);
375 return pages_[slot];
376 }
377
379 [[nodiscard]] auto try_chunk(ChunkKey key) noexcept -> page_type* {
380 const auto slot = directory_.find(key);
381 if (slot == detail::ChunkDirectory::npos) {
382 return nullptr;
383 }
384 return &pages_[slot];
385 }
386
388 [[nodiscard]] auto try_chunk(ChunkKey key) const noexcept
389 -> const page_type* {
390 const auto slot = directory_.find(key);
391 if (slot == detail::ChunkDirectory::npos) {
392 return nullptr;
393 }
394 return &pages_[slot];
395 }
396
401 [[nodiscard]] auto meta(ChunkKey key) noexcept -> ChunkMeta& {
402 const auto slot = directory_.find(key);
403 TESS_ASSERT(slot != detail::ChunkDirectory::npos);
404 return metadata_[slot];
405 }
406
407 [[nodiscard]] auto meta(ChunkKey key) const noexcept -> const ChunkMeta& {
408 const auto slot = directory_.find(key);
409 TESS_ASSERT(slot != detail::ChunkDirectory::npos);
410 return metadata_[slot];
411 }
412
414 [[nodiscard]] auto try_meta(ChunkKey key) noexcept -> ChunkMeta* {
415 const auto slot = directory_.find(key);
416 if (slot == detail::ChunkDirectory::npos) {
417 return nullptr;
418 }
419 return &metadata_[slot];
420 }
421
422 [[nodiscard]] auto try_meta(ChunkKey key) const noexcept -> const ChunkMeta* {
423 const auto slot = directory_.find(key);
424 if (slot == detail::ChunkDirectory::npos) {
425 return nullptr;
426 }
427 return &metadata_[slot];
428 }
429
430 [[nodiscard]] auto chunk_state(ChunkKey key) const noexcept -> ChunkState {
431 return meta(key).state;
432 }
433
434 void set_chunk_state(ChunkKey key, ChunkState state) noexcept {
435 meta(key).state = state;
436 }
437
438 // Hot-scan SoA columns split out of ChunkMeta (audit 2026-07-11 M5);
439 // read-only -- mutate through mark_/clear_/observe_ as before. The chunk
440 // must be resident (same contract as meta()).
441 [[nodiscard]] auto dirty_flags(ChunkKey key) const noexcept -> std::uint32_t {
442 return dirty_flags_[resident_slot_checked(key)];
443 }
444
445 [[nodiscard]] auto active_flags(ChunkKey key) const noexcept
446 -> std::uint32_t {
447 return active_flags_[resident_slot_checked(key)];
448 }
449
450 [[nodiscard]] auto dirty_bounds(ChunkKey key) const noexcept -> Box3 {
451 return dirty_bounds_[resident_slot_checked(key)];
452 }
453
454 void mark_dirty(ChunkKey key, std::uint32_t flags, Box3 bounds) noexcept {
455 const auto slot = resident_slot_checked(key);
456 detail::meta_mark_dirty(dirty_flags_[slot], dirty_bounds_[slot],
457 metadata_[slot], flags, bounds);
458 }
459
460 void mark_topology_dirty(ChunkKey key, std::uint32_t flags,
461 Box3 bounds) noexcept {
462 if (flags == 0) {
463 return;
464 }
465 const auto slot = resident_slot_checked(key);
466 detail::meta_mark_dirty(dirty_flags_[slot], dirty_bounds_[slot],
467 metadata_[slot], flags, bounds);
468 ++metadata_[slot].topology_version;
469 }
470
471 void mark_topology_rebuilt(ChunkKey key) noexcept {
472 ++meta(key).topology_version;
473 }
474
475 void clear_dirty(ChunkKey key, std::uint32_t flags) noexcept {
476 const auto slot = resident_slot_checked(key);
477 detail::meta_clear_dirty(dirty_flags_[slot], dirty_bounds_[slot], flags);
478 }
479
480 [[nodiscard]] auto observe_dirty(ChunkKey key,
481 std::uint32_t flags) const noexcept
482 -> DirtyObservation {
483 const auto slot = resident_slot_checked(key);
484 return detail::meta_observe_dirty(dirty_flags_[slot], dirty_bounds_[slot],
485 metadata_[slot], flags);
486 }
487
488 bool clear_dirty_observed(ChunkKey key, DirtyObservation observed) noexcept {
489 const auto slot = resident_slot_checked(key);
490 return detail::meta_clear_dirty_observed(
491 dirty_flags_[slot], dirty_bounds_[slot], metadata_[slot], observed);
492 }
493
494 void mark_active(ChunkKey key, std::uint32_t flags) noexcept {
495 const auto slot = resident_slot_checked(key);
496 detail::meta_mark_active(active_flags_[slot], metadata_[slot], flags);
497 }
498
499 void clear_active(ChunkKey key, std::uint32_t flags) noexcept {
500 const auto slot = resident_slot_checked(key);
501 detail::meta_clear_active(active_flags_[slot], metadata_[slot], flags);
502 }
503
509 void collect_dirty_chunks(std::uint32_t flags,
510 std::vector<ChunkKey>& out) const {
511 collect_matching_chunks(flags, dirty_flags_, out);
512 }
513
519 void collect_active_chunks(std::uint32_t flags,
520 std::vector<ChunkKey>& out) const {
521 collect_matching_chunks(flags, active_flags_, out);
522 }
523
525 [[nodiscard]] auto dirty_chunks(std::uint32_t flags) const
526 -> std::vector<ChunkKey> {
527 std::vector<ChunkKey> chunks;
528 collect_dirty_chunks(flags, chunks);
529 return chunks;
530 }
531
533 [[nodiscard]] auto active_chunks(std::uint32_t flags) const
534 -> std::vector<ChunkKey> {
535 std::vector<ChunkKey> chunks;
536 collect_active_chunks(flags, chunks);
537 return chunks;
538 }
539
544 [[nodiscard]] auto resolve(Coord3 coord) const noexcept
546 TESS_ASSERT(tess::contains<Shape>(coord));
547 return ResolvedTile<Shape>{
548 chunk_key<Shape>(chunk_coord<Shape>(coord)),
549 local_tile_id<Shape>(local_coord<Shape>(coord)),
550 };
551 }
552
558 [[nodiscard]] auto try_resolve(Coord3 coord) const noexcept
559 -> std::optional<ResolvedTile<Shape>> {
560 if (!tess::contains<Shape>(coord)) {
561 return std::nullopt;
562 }
563 return resolve(coord);
564 }
565
570 template <typename Tag>
571 [[nodiscard]] auto field(Coord3 coord) noexcept
572 -> Schema::template value_type<Tag>& {
573 const auto resolved = resolve(coord);
574 return chunk(resolved.chunk_key)
575 .template field<Tag>(resolved.local_tile_id);
576 }
577
579 template <typename Tag>
580 [[nodiscard]] auto field(Coord3 coord) const noexcept
581 -> const Schema::template value_type<Tag>& {
582 const auto resolved = resolve(coord);
583 return chunk(resolved.chunk_key)
584 .template field<Tag>(resolved.local_tile_id);
585 }
586
591 template <typename Tag>
592 [[nodiscard]] auto try_field(Coord3 coord) noexcept
593 -> Schema::template value_type<Tag>* {
594 const auto resolved = try_resolve(coord);
595 if (!resolved.has_value()) {
596 return nullptr;
597 }
598 auto* page = try_chunk(resolved->chunk_key);
599 if (page == nullptr) {
600 return nullptr;
601 }
602 return &page->template field<Tag>(resolved->local_tile_id);
603 }
604
606 template <typename Tag>
607 [[nodiscard]] auto try_field(Coord3 coord) const noexcept
608 -> const Schema::template value_type<Tag>* {
609 const auto resolved = try_resolve(coord);
610 if (!resolved.has_value()) {
611 return nullptr;
612 }
613 const auto* page = try_chunk(resolved->chunk_key);
614 if (page == nullptr) {
615 return nullptr;
616 }
617 return &page->template field<Tag>(resolved->local_tile_id);
618 }
619
624 template <typename Tag>
625 [[nodiscard]] auto field_span(ChunkKey key) noexcept {
626 return chunk(key).template field_span<Tag>();
627 }
628
630 template <typename Tag>
631 [[nodiscard]] auto field_span(ChunkKey key) const noexcept {
632 return chunk(key).template field_span<Tag>();
633 }
634
635 private:
636 std::size_t acquire_slot() {
637 if (!free_slots_.empty()) {
638 const auto slot = free_slots_.back();
639 free_slots_.pop_back();
640 return slot;
641 }
642 return evict_least_recently_used();
643 }
644
645 // Pops the head of the intrusive LRU list -- O(1) per eviction (audit
646 // 2026-07-11 M11b; was an O(resident_count) timestamp scan). The victim
647 // is handed straight back for reuse, so it is deliberately not returned
648 // to the free list.
649 std::size_t evict_least_recently_used() {
650 TESS_ASSERT(!resident_slots_.empty());
651 TESS_ASSERT(lru_head_ != npos_slot);
652 const auto victim = lru_head_;
653 release_slot(victim);
654 return victim;
655 }
656
657 // Intrusive doubly-linked LRU over slot indices: lru_head_ is the
658 // least-recently-used end (the eviction victim), lru_tail_ the
659 // most-recently-used. All operations are O(1).
660 void lru_unlink(std::size_t slot) noexcept {
661 const auto prev = lru_prev_[slot];
662 const auto next = lru_next_[slot];
663 if (prev != npos_slot) {
664 lru_next_[prev] = next;
665 } else {
666 lru_head_ = next;
667 }
668 if (next != npos_slot) {
669 lru_prev_[next] = prev;
670 } else {
671 lru_tail_ = prev;
672 }
673 lru_prev_[slot] = npos_slot;
674 lru_next_[slot] = npos_slot;
675 }
676
677 void lru_push_mru(std::size_t slot) noexcept {
678 lru_prev_[slot] = lru_tail_;
679 lru_next_[slot] = npos_slot;
680 if (lru_tail_ != npos_slot) {
681 lru_next_[lru_tail_] = slot;
682 } else {
683 lru_head_ = slot;
684 }
685 lru_tail_ = slot;
686 }
687
688 void lru_move_to_mru(std::size_t slot) noexcept {
689 if (lru_tail_ == slot) {
690 return;
691 }
692 lru_unlink(slot);
693 lru_push_mru(slot);
694 }
695
696 // Removes a resident chunk from the directory and resident set. The caller
697 // decides the slot's fate: explicit evict returns it to the free list;
698 // eviction under budget pressure reuses it immediately.
699 void release_slot(std::size_t slot) {
700 lru_unlink(slot);
701 directory_.erase(slot_key_[slot]);
702 const auto position = slot_position_[slot];
703 const auto last = resident_keys_.size() - 1;
704 resident_keys_[position] = resident_keys_[last];
705 resident_slots_[position] = resident_slots_[last];
706 slot_position_[resident_slots_[position]] = position;
707 resident_keys_.pop_back();
708 resident_slots_.pop_back();
709 }
710
711 [[nodiscard]] static constexpr std::size_t clamp_capacity(
712 std::size_t byte_budget) noexcept {
713 if (page_byte_size == 0) {
714 return 1;
715 }
716 const auto count = byte_budget / page_byte_size;
717 return count < 1 ? 1 : count;
718 }
719
720 // Reads a dense 4-byte flag column by resident slot instead of streaming
721 // ChunkMeta structs (audit 2026-07-11 M5).
722 void collect_matching_chunks(std::uint32_t flags,
723 const std::vector<std::uint32_t>& column,
724 std::vector<ChunkKey>& out) const {
725 for (const auto slot : resident_slots_) {
726 if ((column[slot] & flags) != 0) {
727 out.push_back(slot_key_[slot]);
728 }
729 }
730 }
731
732 // Directory probe + the meta() residency contract, shared by the
733 // mutation/accessor paths that index the SoA columns by slot.
734 [[nodiscard]] std::size_t resident_slot_checked(ChunkKey key) const noexcept {
735 const auto slot = directory_.find(key);
736 TESS_ASSERT(slot != detail::ChunkDirectory::npos);
737 return slot;
738 }
739
740 std::size_t byte_budget_;
741 std::size_t capacity_;
742
743 std::uint64_t generation_clock_ = 0;
744
745 std::vector<page_type> pages_;
746 std::vector<ChunkMeta> metadata_;
747 std::vector<std::uint32_t> dirty_flags_;
748 std::vector<std::uint32_t> active_flags_;
749 std::vector<Box3> dirty_bounds_;
750 std::vector<ChunkKey> slot_key_;
751 std::vector<std::uint64_t> slot_generation_;
752 std::vector<std::size_t> lru_prev_;
753 std::vector<std::size_t> lru_next_;
754 std::size_t lru_head_ = npos_slot;
755 std::size_t lru_tail_ = npos_slot;
756 std::vector<std::size_t> slot_position_;
757
758 std::vector<ChunkKey> resident_keys_;
759 std::vector<std::size_t> resident_slots_;
760 std::vector<std::size_t> free_slots_;
761 detail::ChunkDirectory directory_;
762};
763
765template <typename Shape, typename Schema>
766using SparseResidentWorld = World<Shape, Schema, SparseResident>;
767
768} // namespace tess
Definition chunk_page.h:108
bool evict(ChunkKey key)
Definition sparse_world.h:351
static constexpr std::size_t npos_slot
Definition sparse_world.h:196
auto meta(ChunkKey key) noexcept -> ChunkMeta &
Definition sparse_world.h:401
std::size_t resident_count() const noexcept
Definition sparse_world.h:187
bool valid(ResidencyHandle handle) const noexcept
Definition sparse_world.h:255
auto active_chunks(std::uint32_t flags) const -> std::vector< ChunkKey >
Definition sparse_world.h:533
auto field_span(ChunkKey key) noexcept
Definition sparse_world.h:625
std::size_t capacity() const noexcept
Definition sparse_world.h:180
bool is_resident(ChunkKey key) const noexcept
Definition sparse_world.h:204
std::uint64_t residency_generation(ChunkKey key) const noexcept
Definition sparse_world.h:245
auto try_field(Coord3 coord) noexcept -> Schema::template value_type< Tag > *
Definition sparse_world.h:592
auto try_field(Coord3 coord) const noexcept -> const Schema::template value_type< Tag > *
Definition sparse_world.h:607
std::size_t byte_budget() const noexcept
Definition sparse_world.h:183
auto field(Coord3 coord) noexcept -> Schema::template value_type< Tag > &
Definition sparse_world.h:571
ResidencyHandle ensure_resident(ChunkKey key)
Definition sparse_world.h:312
std::size_t resident_byte_size() const noexcept
Definition sparse_world.h:191
void collect_active_chunks(std::uint32_t flags, std::vector< ChunkKey > &out) const
Definition sparse_world.h:519
static constexpr bool contains(ChunkKey key) noexcept
Definition sparse_world.h:199
auto try_meta(ChunkKey key) noexcept -> ChunkMeta *
Definition sparse_world.h:414
auto field_span(ChunkKey key) const noexcept
Definition sparse_world.h:631
void collect_dirty_chunks(std::uint32_t flags, std::vector< ChunkKey > &out) const
Definition sparse_world.h:509
auto chunk(ChunkKey key) const noexcept -> const page_type &
Definition sparse_world.h:372
auto chunk(ChunkKey key) noexcept -> page_type &
Definition sparse_world.h:365
World(ResidencyConfig config)
Definition sparse_world.h:153
auto field(Coord3 coord) const noexcept -> const Schema::template value_type< Tag > &
Definition sparse_world.h:580
bool touch(ChunkKey key) noexcept
Definition sparse_world.h:336
auto resident_ref(ChunkKey key) const noexcept -> ResidentChunkRef
Definition sparse_world.h:233
std::uint64_t residency_fingerprint() const noexcept
Definition sparse_world.h:279
auto try_resolve(Coord3 coord) const noexcept -> std::optional< ResolvedTile< Shape > >
Definition sparse_world.h:558
std::span< const ChunkKey > resident_chunk_keys() const noexcept
Definition sparse_world.h:266
auto try_chunk(ChunkKey key) const noexcept -> const page_type *
Definition sparse_world.h:388
auto dirty_chunks(std::uint32_t flags) const -> std::vector< ChunkKey >
Definition sparse_world.h:525
auto try_chunk(ChunkKey key) noexcept -> page_type *
Definition sparse_world.h:379
std::size_t resident_slot(ChunkKey key) const noexcept
Definition sparse_world.h:215
auto resolve(Coord3 coord) const noexcept -> ResolvedTile< Shape >
Definition sparse_world.h:544
Definition world.h:22
Definition shape.h:75
Definition shape.h:39
Definition shape.h:67
Definition chunk_meta.h:26
Definition shape.h:30
Definition residency.h:20
Definition residency.h:38
Definition shape.h:88
Definition shape.h:225
Definition residency.h:17
Definition version.h:15