tess 1.0.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 map from ChunkKey to a resident slot index. When the residency
21// capacity covers the complete key space, a direct slot array avoids hashing
22// every tile access and uses less directory storage. Larger key spaces use an
23// open-addressing table sized to twice the residency capacity (rounded up to a
24// power of two). Neither representation reallocates after construction, and
25// hashed deletion uses backward-shift compaction so no tombstones accumulate.
26class ChunkDirectory {
27 public:
28 static constexpr std::size_t npos = static_cast<std::size_t>(-1);
29
30 void reset(std::size_t capacity, std::uint64_t key_count) {
31 if (key_count <= static_cast<std::uint64_t>(capacity)) {
32 std::vector<Bucket>{}.swap(buckets_);
33 direct_slots_.assign(static_cast<std::size_t>(key_count), npos);
34 mask_ = 0;
35 return;
36 }
37
38 std::vector<std::size_t>{}.swap(direct_slots_);
39 std::size_t table = 2;
40 while (table < capacity * 2) {
41 table <<= 1u;
42 }
43 buckets_.assign(table, Bucket{});
44 mask_ = table - 1;
45 }
46
47 [[nodiscard]] std::size_t find(ChunkKey key) const noexcept {
48 // Hashed tables always have a nonzero mask (their minimum size is two), so
49 // the mask is also the mode tag without adding another hot-path load.
50 if (mask_ == 0) {
51 if (key.value >= direct_slots_.size()) {
52 return npos;
53 }
54 return direct_slots_[static_cast<std::size_t>(key.value)];
55 }
56
57 std::size_t i = home(key);
58 while (buckets_[i].occupied) {
59 if (buckets_[i].key == key) {
60 return buckets_[i].slot;
61 }
62 i = (i + 1) & mask_;
63 }
64 return npos;
65 }
66
67 // Precondition: key is not already present and the table has a free bucket
68 // (guaranteed while the number of resident chunks stays <= capacity).
69 void insert(ChunkKey key, std::size_t slot) noexcept {
70 if (mask_ == 0) {
71 TESS_ASSERT(key.value < direct_slots_.size());
72 direct_slots_[static_cast<std::size_t>(key.value)] = slot;
73 return;
74 }
75
76 std::size_t i = home(key);
77 while (buckets_[i].occupied) {
78 i = (i + 1) & mask_;
79 }
80 buckets_[i] = Bucket{key, slot, true};
81 }
82
83 bool erase(ChunkKey key) noexcept {
84 if (mask_ == 0) {
85 if (key.value >= direct_slots_.size()) {
86 return false;
87 }
88 auto& slot = direct_slots_[static_cast<std::size_t>(key.value)];
89 if (slot == npos) {
90 return false;
91 }
92 slot = npos;
93 return true;
94 }
95
96 std::size_t i = home(key);
97 while (buckets_[i].occupied && buckets_[i].key != key) {
98 i = (i + 1) & mask_;
99 }
100 if (!buckets_[i].occupied) {
101 return false;
102 }
103 std::size_t j = i;
104 while (true) {
105 j = (j + 1) & mask_;
106 if (!buckets_[j].occupied) {
107 break;
108 }
109 const std::size_t k = home(buckets_[j].key);
110 // Move j back into the hole at i only when j's home slot is not
111 // cyclically inside (i, j] — otherwise moving it would break its
112 // probe chain.
113 if (!in_cyclic_range(i, k, j)) {
114 buckets_[i] = buckets_[j];
115 i = j;
116 }
117 }
118 buckets_[i] = Bucket{};
119 return true;
120 }
121
122 private:
123 struct Bucket {
124 ChunkKey key{};
125 std::size_t slot = 0;
126 bool occupied = false;
127 };
128
129 [[nodiscard]] std::size_t home(ChunkKey key) const noexcept {
130 return static_cast<std::size_t>(mix(key.value)) & mask_;
131 }
132
133 static std::uint64_t mix(std::uint64_t x) noexcept {
134 x += 0x9e3779b97f4a7c15ull;
135 x = (x ^ (x >> 30u)) * 0xbf58476d1ce4e5b9ull;
136 x = (x ^ (x >> 27u)) * 0x94d049bb133111ebull;
137 return x ^ (x >> 31u);
138 }
139
140 static bool in_cyclic_range(std::size_t lo, std::size_t pos,
141 std::size_t hi) noexcept {
142 if (lo <= hi) {
143 return lo < pos && pos <= hi;
144 }
145 return lo < pos || pos <= hi;
146 }
147
148 std::vector<Bucket> buckets_;
149 std::vector<std::size_t> direct_slots_;
150 std::size_t mask_ = 0;
151};
152
153} // namespace detail
154
172template <typename Shape, typename Schema>
173class World<Shape, Schema, SparseResident> {
174 public:
175 using shape_type = Shape;
176 using schema_type = Schema;
177 using residency_type = SparseResident;
178 using page_type = ChunkPage<Shape, Schema>;
179
180 static constexpr std::uint64_t chunk_count = ShapeTraits<Shape>::chunk_count;
181 static constexpr std::uint64_t local_tile_count =
182 ShapeTraits<Shape>::local_tile_count;
183 static constexpr std::size_t field_count = Schema::field_count;
184 // Tess-owned inline page storage; field-managed dynamic or referenced
185 // storage is outside this byte count.
186 static constexpr std::size_t page_byte_size = page_type::byte_size;
187
195 explicit World(ResidencyConfig config)
196 : byte_budget_(config.byte_budget),
197 capacity_(clamp_capacity(config.byte_budget)) {
198 pages_.reserve(capacity_);
199 for (std::size_t slot = 0; slot < capacity_; ++slot) {
200 pages_.emplace_back(ChunkKey{0}, ChunkCoord3{});
201 }
202 metadata_.assign(capacity_, ChunkMeta{});
203 dirty_masks_.assign(capacity_, DirtyMask{});
204 active_masks_.assign(capacity_, ActiveMask{});
205 dirty_bounds_.assign(capacity_, Box3{});
206 slot_key_.assign(capacity_, ChunkKey{});
207 slot_generation_.assign(capacity_, ResidencyGeneration{});
208 lru_prev_.assign(capacity_, npos_slot);
209 lru_next_.assign(capacity_, npos_slot);
210 slot_position_.assign(capacity_, 0);
211
212 resident_keys_.reserve(capacity_);
213 resident_slots_.reserve(capacity_);
214 free_slots_.reserve(capacity_);
215 for (std::size_t slot = capacity_; slot-- > 0;) {
216 free_slots_.push_back(slot);
217 }
218 directory_.reset(capacity_, chunk_count);
219 }
220
222 [[nodiscard]] std::size_t capacity() const noexcept { return capacity_; }
223
225 [[nodiscard]] std::size_t byte_budget() const noexcept {
226 return byte_budget_;
227 }
228
229 [[nodiscard]] std::size_t resident_count() const noexcept {
230 return resident_keys_.size();
231 }
232
234 [[nodiscard]] std::size_t resident_byte_size() const noexcept {
235 return resident_keys_.size() * page_byte_size;
236 }
237
239 static constexpr std::size_t npos_slot = detail::ChunkDirectory::npos;
240
242 [[nodiscard]] static constexpr bool contains(ChunkKey key) noexcept {
243 return key.value < chunk_count;
244 }
245
247 [[nodiscard]] bool is_resident(ChunkKey key) const noexcept {
248 return directory_.find(key) != detail::ChunkDirectory::npos;
249 }
250
258 [[nodiscard]] std::size_t resident_slot(ChunkKey key) const noexcept {
259 return directory_.find(key);
260 }
261
264 std::size_t slot = npos_slot;
265 ResidencyGeneration generation{};
266 const ChunkMeta* meta = nullptr;
267 };
268
276 [[nodiscard]] auto resident_ref(ChunkKey key) const noexcept
278 const auto slot = directory_.find(key);
279 if (slot == detail::ChunkDirectory::npos) {
280 return ResidentChunkRef{};
281 }
282 return ResidentChunkRef{slot, slot_generation_[slot], &metadata_[slot]};
283 }
284
289 ChunkKey key) const noexcept {
290 const auto slot = directory_.find(key);
291 if (slot == detail::ChunkDirectory::npos) {
292 return ResidencyGeneration{};
293 }
294 return slot_generation_[slot];
295 }
296
298 [[nodiscard]] bool valid(ResidencyHandle handle) const noexcept {
299 return handle.generation.valid() &&
300 residency_generation(handle.key) == handle.generation;
301 }
302
309 [[nodiscard]] std::span<const ChunkKey> resident_chunk_keys() const noexcept {
310 return {resident_keys_.data(), resident_keys_.size()};
311 }
312
323 [[nodiscard]] std::uint64_t residency_fingerprint() const noexcept {
324 const auto mix = [](std::uint64_t x) noexcept -> std::uint64_t {
325 x = (x ^ (x >> 30u)) * 0xbf58476d1ce4e5b9ull;
326 x = (x ^ (x >> 27u)) * 0x94d049bb133111ebull;
327 return x ^ (x >> 31u);
328 };
329 // Slot-direct iteration: resident_slots_ pairs with resident_keys_, so
330 // every term is a direct array read -- the by-key accessors would pay
331 // three directory probes per chunk for the same data.
332 auto acc = std::uint64_t{0};
333 for (const auto slot : resident_slots_) {
334 auto h = mix(slot_key_[slot].value);
335 h ^= mix(h + static_cast<std::uint64_t>(slot));
336 h ^= mix(h + slot_generation_[slot].value);
337 h ^= mix(h + metadata_[slot].content_version.value);
338 acc += h;
339 }
340 return mix(acc + static_cast<std::uint64_t>(resident_count()) +
341 0x9e3779b97f4a7c15ull);
342 }
343
363 if (!contains(key)) {
364 return ResidencyHandle{};
365 }
366 auto slot = directory_.find(key);
367 if (slot != detail::ChunkDirectory::npos) {
368 lru_move_to_mru(slot);
369 return ResidencyHandle{key, slot_generation_[slot]};
370 }
371 slot = acquire_slot();
372 pages_[slot].reset(key, chunk_coord<Shape>(key));
373 metadata_[slot] = ChunkMeta{};
374 dirty_masks_[slot] = DirtyMask{};
375 active_masks_[slot] = ActiveMask{};
376 dirty_bounds_[slot] = Box3{};
377 slot_key_[slot] = key;
378 slot_generation_[slot] = ++generation_clock_;
379 lru_push_mru(slot);
380 slot_position_[slot] = resident_keys_.size();
381 resident_keys_.push_back(key);
382 resident_slots_.push_back(slot);
383 directory_.insert(key, slot);
384 return ResidencyHandle{key, slot_generation_[slot]};
385 }
386
388 bool touch(ChunkKey key) noexcept {
389 const auto slot = directory_.find(key);
390 if (slot == detail::ChunkDirectory::npos) {
391 return false;
392 }
393 lru_move_to_mru(slot);
394 return true;
395 }
396
403 bool evict(ChunkKey key) {
404 const auto slot = directory_.find(key);
405 if (slot == detail::ChunkDirectory::npos) {
406 return false;
407 }
408 release_slot(slot);
409 free_slots_.push_back(slot);
410 return true;
411 }
412
417 [[nodiscard]] auto chunk(ChunkKey key) noexcept -> page_type& {
418 const auto slot = directory_.find(key);
419 TESS_ASSERT(slot != detail::ChunkDirectory::npos);
420 return pages_[slot];
421 }
422
424 [[nodiscard]] auto chunk(ChunkKey key) const noexcept -> const page_type& {
425 const auto slot = directory_.find(key);
426 TESS_ASSERT(slot != detail::ChunkDirectory::npos);
427 return pages_[slot];
428 }
429
431 [[nodiscard]] auto try_chunk(ChunkKey key) noexcept -> page_type* {
432 const auto slot = directory_.find(key);
433 if (slot == detail::ChunkDirectory::npos) {
434 return nullptr;
435 }
436 return &pages_[slot];
437 }
438
440 [[nodiscard]] auto try_chunk(ChunkKey key) const noexcept
441 -> const page_type* {
442 const auto slot = directory_.find(key);
443 if (slot == detail::ChunkDirectory::npos) {
444 return nullptr;
445 }
446 return &pages_[slot];
447 }
448
453 [[nodiscard]] auto meta(ChunkKey key) noexcept -> ChunkMeta& {
454 const auto slot = directory_.find(key);
455 TESS_ASSERT(slot != detail::ChunkDirectory::npos);
456 return metadata_[slot];
457 }
458
459 [[nodiscard]] auto meta(ChunkKey key) const noexcept -> const ChunkMeta& {
460 const auto slot = directory_.find(key);
461 TESS_ASSERT(slot != detail::ChunkDirectory::npos);
462 return metadata_[slot];
463 }
464
466 [[nodiscard]] auto try_meta(ChunkKey key) noexcept -> ChunkMeta* {
467 const auto slot = directory_.find(key);
468 if (slot == detail::ChunkDirectory::npos) {
469 return nullptr;
470 }
471 return &metadata_[slot];
472 }
473
474 [[nodiscard]] auto try_meta(ChunkKey key) const noexcept -> const ChunkMeta* {
475 const auto slot = directory_.find(key);
476 if (slot == detail::ChunkDirectory::npos) {
477 return nullptr;
478 }
479 return &metadata_[slot];
480 }
481
482 [[nodiscard]] auto chunk_activity(ChunkKey key) const noexcept
483 -> ChunkActivity {
484 return active_mask(key).empty() ? ChunkActivity::Sleeping
485 : ChunkActivity::Active;
486 }
487
488 [[nodiscard]] auto active_category_count(ChunkKey key) const noexcept
489 -> std::uint32_t {
490 return detail::popcount(active_mask(key));
491 }
492
493 // Hot-scan SoA columns split out of ChunkMeta; read-only -- mutate through
494 // mark_/clear_/observe_. The chunk
495 // must be resident (same contract as meta()).
496 [[nodiscard]] auto dirty_mask(ChunkKey key) const noexcept -> DirtyMask {
497 return dirty_masks_[resident_slot_checked(key)];
498 }
499
500 [[nodiscard]] auto active_mask(ChunkKey key) const noexcept -> ActiveMask {
501 return active_masks_[resident_slot_checked(key)];
502 }
503
504 [[nodiscard]] auto dirty_bounds(ChunkKey key) const noexcept -> Box3 {
505 return dirty_bounds_[resident_slot_checked(key)];
506 }
507
508 void mark_dirty(ChunkKey key, DirtyMask mask, Box3 bounds) noexcept {
509 const auto slot = resident_slot_checked(key);
510 detail::meta_mark_dirty(dirty_masks_[slot], dirty_bounds_[slot],
511 metadata_[slot], mask, bounds);
512 }
513
527 void mark_content_changed(ChunkKey key) noexcept {
528 const auto slot = resident_slot_checked(key);
529 detail::meta_mark_content_changed(metadata_[slot]);
530 }
531
532 void mark_topology_dirty(ChunkKey key, DirtyMask mask, Box3 bounds) noexcept {
533 if (mask.empty()) {
534 return;
535 }
536 const auto slot = resident_slot_checked(key);
537 detail::meta_mark_dirty(dirty_masks_[slot], dirty_bounds_[slot],
538 metadata_[slot], mask, bounds);
539 ++metadata_[slot].topology_version;
540 }
541
542 void mark_topology_rebuilt(ChunkKey key) noexcept {
543 ++meta(key).topology_version;
544 }
545
546 void clear_dirty(ChunkKey key, DirtyMask mask) noexcept {
547 const auto slot = resident_slot_checked(key);
548 detail::meta_clear_dirty(dirty_masks_[slot], dirty_bounds_[slot], mask);
549 }
550
551 [[nodiscard]] auto observe_dirty(ChunkKey key, DirtyMask mask) const noexcept
552 -> DirtyObservation {
553 const auto slot = resident_slot_checked(key);
554 return detail::meta_observe_dirty(dirty_masks_[slot], dirty_bounds_[slot],
555 metadata_[slot], mask,
556 slot_generation_[slot]);
557 }
558
566 bool clear_dirty_observed(ChunkKey key, DirtyObservation observed) noexcept {
567 const auto slot = resident_slot_checked(key);
568 return detail::meta_clear_dirty_observed(
569 dirty_masks_[slot], dirty_bounds_[slot], metadata_[slot], observed,
570 slot_generation_[slot]);
571 }
572
573 void mark_active(ChunkKey key, ActiveMask mask) noexcept {
574 const auto slot = resident_slot_checked(key);
575 detail::meta_mark_active(active_masks_[slot], mask);
576 }
577
578 void clear_active(ChunkKey key, ActiveMask mask) noexcept {
579 const auto slot = resident_slot_checked(key);
580 detail::meta_clear_active(active_masks_[slot], mask);
581 }
582
588 void collect_dirty_chunks(DirtyMask mask, std::vector<ChunkKey>& out) const {
589 collect_matching_chunks(mask, dirty_masks_, out);
590 }
591
598 std::vector<ChunkKey>& out) const {
599 collect_matching_chunks(mask, active_masks_, out);
600 }
601
603 [[nodiscard]] auto dirty_chunks(DirtyMask mask) const
604 -> std::vector<ChunkKey> {
605 std::vector<ChunkKey> chunks;
606 collect_dirty_chunks(mask, chunks);
607 return chunks;
608 }
609
611 [[nodiscard]] auto active_chunks(ActiveMask mask) const
612 -> std::vector<ChunkKey> {
613 std::vector<ChunkKey> chunks;
614 collect_active_chunks(mask, chunks);
615 return chunks;
616 }
617
622 [[nodiscard]] auto resolve(Coord3 coord) const noexcept
624 TESS_ASSERT(tess::contains<Shape>(coord));
625 return ResolvedTile<Shape>{
626 chunk_key<Shape>(chunk_coord<Shape>(coord)),
627 local_tile_id<Shape>(local_coord<Shape>(coord)),
628 };
629 }
630
636 [[nodiscard]] auto try_resolve(Coord3 coord) const noexcept
637 -> std::optional<ResolvedTile<Shape>> {
638 if (!tess::contains<Shape>(coord)) {
639 return std::nullopt;
640 }
641 return resolve(coord);
642 }
643
648 template <typename Tag>
649 [[nodiscard]] auto field(Coord3 coord) noexcept
650 -> Schema::template value_type<Tag>& {
651 const auto resolved = resolve(coord);
652 return chunk(resolved.chunk_key)
653 .template field<Tag>(resolved.local_tile_id);
654 }
655
657 template <typename Tag>
658 [[nodiscard]] auto field(Coord3 coord) const noexcept
659 -> const Schema::template value_type<Tag>& {
660 const auto resolved = resolve(coord);
661 return chunk(resolved.chunk_key)
662 .template field<Tag>(resolved.local_tile_id);
663 }
664
669 template <typename Tag>
670 [[nodiscard]] auto try_field(Coord3 coord) noexcept
671 -> Schema::template value_type<Tag>* {
672 const auto resolved = try_resolve(coord);
673 if (!resolved.has_value()) {
674 return nullptr;
675 }
676 auto* page = try_chunk(resolved->chunk_key);
677 if (page == nullptr) {
678 return nullptr;
679 }
680 return &page->template field<Tag>(resolved->local_tile_id);
681 }
682
684 template <typename Tag>
685 [[nodiscard]] auto try_field(Coord3 coord) const noexcept
686 -> const Schema::template value_type<Tag>* {
687 const auto resolved = try_resolve(coord);
688 if (!resolved.has_value()) {
689 return nullptr;
690 }
691 const auto* page = try_chunk(resolved->chunk_key);
692 if (page == nullptr) {
693 return nullptr;
694 }
695 return &page->template field<Tag>(resolved->local_tile_id);
696 }
697
702 template <typename Tag>
703 [[nodiscard]] auto field_span(ChunkKey key) noexcept {
704 return chunk(key).template field_span<Tag>();
705 }
706
708 template <typename Tag>
709 [[nodiscard]] auto field_span(ChunkKey key) const noexcept {
710 return chunk(key).template field_span<Tag>();
711 }
712
713 private:
714 std::size_t acquire_slot() {
715 if (!free_slots_.empty()) {
716 const auto slot = free_slots_.back();
717 free_slots_.pop_back();
718 return slot;
719 }
720 return evict_least_recently_used();
721 }
722
723 // Pops the head of the intrusive LRU list in O(1). The victim is handed
724 // straight back for reuse, so it is deliberately not returned to the free
725 // list.
726 std::size_t evict_least_recently_used() {
727 TESS_ASSERT(!resident_slots_.empty());
728 TESS_ASSERT(lru_head_ != npos_slot);
729 const auto victim = lru_head_;
730 release_slot(victim);
731 return victim;
732 }
733
734 // Intrusive doubly-linked LRU over slot indices: lru_head_ is the
735 // least-recently-used end (the eviction victim), lru_tail_ the
736 // most-recently-used. All operations are O(1).
737 void lru_unlink(std::size_t slot) noexcept {
738 const auto prev = lru_prev_[slot];
739 const auto next = lru_next_[slot];
740 if (prev != npos_slot) {
741 lru_next_[prev] = next;
742 } else {
743 lru_head_ = next;
744 }
745 if (next != npos_slot) {
746 lru_prev_[next] = prev;
747 } else {
748 lru_tail_ = prev;
749 }
750 lru_prev_[slot] = npos_slot;
751 lru_next_[slot] = npos_slot;
752 }
753
754 void lru_push_mru(std::size_t slot) noexcept {
755 lru_prev_[slot] = lru_tail_;
756 lru_next_[slot] = npos_slot;
757 if (lru_tail_ != npos_slot) {
758 lru_next_[lru_tail_] = slot;
759 } else {
760 lru_head_ = slot;
761 }
762 lru_tail_ = slot;
763 }
764
765 void lru_move_to_mru(std::size_t slot) noexcept {
766 if (lru_tail_ == slot) {
767 return;
768 }
769 lru_unlink(slot);
770 lru_push_mru(slot);
771 }
772
773 // Removes a resident chunk from the directory and resident set. The caller
774 // decides the slot's fate: explicit evict returns it to the free list;
775 // eviction under budget pressure reuses it immediately.
776 void release_slot(std::size_t slot) {
777 lru_unlink(slot);
778 directory_.erase(slot_key_[slot]);
779 const auto position = slot_position_[slot];
780 const auto last = resident_keys_.size() - 1;
781 resident_keys_[position] = resident_keys_[last];
782 resident_slots_[position] = resident_slots_[last];
783 slot_position_[resident_slots_[position]] = position;
784 resident_keys_.pop_back();
785 resident_slots_.pop_back();
786 }
787
788 [[nodiscard]] static constexpr std::size_t clamp_capacity(
789 std::size_t byte_budget) noexcept {
790 if constexpr (page_byte_size == 0) {
791 return 1;
792 }
793 const auto count = byte_budget / page_byte_size;
794 return count < 1 ? 1 : count;
795 }
796
797 // Reads a dense 4-byte mask column by resident slot instead of streaming
798 // ChunkMeta structs.
799 template <typename Mask>
800 void collect_matching_chunks(Mask mask, const std::vector<Mask>& column,
801 std::vector<ChunkKey>& out) const {
802 for (const auto slot : resident_slots_) {
803 if (static_cast<bool>(column[slot] & mask)) {
804 out.push_back(slot_key_[slot]);
805 }
806 }
807 }
808
809 // Directory probe + the meta() residency contract, shared by the
810 // mutation/accessor paths that index the SoA columns by slot.
811 [[nodiscard]] std::size_t resident_slot_checked(ChunkKey key) const noexcept {
812 const auto slot = directory_.find(key);
813 TESS_ASSERT(slot != detail::ChunkDirectory::npos);
814 return slot;
815 }
816
817 std::size_t byte_budget_;
818 std::size_t capacity_;
819
820 ResidencyGeneration generation_clock_{};
821
822 std::vector<page_type> pages_;
823 std::vector<ChunkMeta> metadata_;
824 std::vector<DirtyMask> dirty_masks_;
825 std::vector<ActiveMask> active_masks_;
826 std::vector<Box3> dirty_bounds_;
827 std::vector<ChunkKey> slot_key_;
828 std::vector<ResidencyGeneration> slot_generation_;
829 std::vector<std::size_t> lru_prev_;
830 std::vector<std::size_t> lru_next_;
831 std::size_t lru_head_ = npos_slot;
832 std::size_t lru_tail_ = npos_slot;
833 std::vector<std::size_t> slot_position_;
834
835 std::vector<ChunkKey> resident_keys_;
836 std::vector<std::size_t> resident_slots_;
837 std::vector<std::size_t> free_slots_;
838 detail::ChunkDirectory directory_;
839};
840
842template <typename Shape, typename Schema>
843using SparseResidentWorld = World<Shape, Schema, SparseResident>;
844
845} // namespace tess
Definition chunk_page.h:123
ResidencyGeneration residency_generation(ChunkKey key) const noexcept
Definition sparse_world.h:288
bool evict(ChunkKey key)
Definition sparse_world.h:403
static constexpr std::size_t npos_slot
Definition sparse_world.h:239
auto meta(ChunkKey key) noexcept -> ChunkMeta &
Definition sparse_world.h:453
std::size_t resident_count() const noexcept
Definition sparse_world.h:229
bool valid(ResidencyHandle handle) const noexcept
Definition sparse_world.h:298
auto field_span(ChunkKey key) noexcept
Definition sparse_world.h:703
std::size_t capacity() const noexcept
Definition sparse_world.h:222
bool is_resident(ChunkKey key) const noexcept
Definition sparse_world.h:247
auto try_field(Coord3 coord) noexcept -> Schema::template value_type< Tag > *
Definition sparse_world.h:670
auto try_field(Coord3 coord) const noexcept -> const Schema::template value_type< Tag > *
Definition sparse_world.h:685
std::size_t byte_budget() const noexcept
Definition sparse_world.h:225
auto field(Coord3 coord) noexcept -> Schema::template value_type< Tag > &
Definition sparse_world.h:649
ResidencyHandle ensure_resident(ChunkKey key)
Definition sparse_world.h:362
void collect_active_chunks(ActiveMask mask, std::vector< ChunkKey > &out) const
Definition sparse_world.h:597
std::size_t resident_byte_size() const noexcept
Definition sparse_world.h:234
void collect_dirty_chunks(DirtyMask mask, std::vector< ChunkKey > &out) const
Definition sparse_world.h:588
static constexpr bool contains(ChunkKey key) noexcept
Definition sparse_world.h:242
auto try_meta(ChunkKey key) noexcept -> ChunkMeta *
Definition sparse_world.h:466
auto active_chunks(ActiveMask mask) const -> std::vector< ChunkKey >
Definition sparse_world.h:611
auto field_span(ChunkKey key) const noexcept
Definition sparse_world.h:709
auto chunk(ChunkKey key) const noexcept -> const page_type &
Definition sparse_world.h:424
auto chunk(ChunkKey key) noexcept -> page_type &
Definition sparse_world.h:417
World(ResidencyConfig config)
Definition sparse_world.h:195
auto field(Coord3 coord) const noexcept -> const Schema::template value_type< Tag > &
Definition sparse_world.h:658
auto dirty_chunks(DirtyMask mask) const -> std::vector< ChunkKey >
Definition sparse_world.h:603
bool touch(ChunkKey key) noexcept
Definition sparse_world.h:388
void mark_content_changed(ChunkKey key) noexcept
Definition sparse_world.h:527
auto resident_ref(ChunkKey key) const noexcept -> ResidentChunkRef
Definition sparse_world.h:276
std::uint64_t residency_fingerprint() const noexcept
Definition sparse_world.h:323
auto try_resolve(Coord3 coord) const noexcept -> std::optional< ResolvedTile< Shape > >
Definition sparse_world.h:636
std::span< const ChunkKey > resident_chunk_keys() const noexcept
Definition sparse_world.h:309
auto try_chunk(ChunkKey key) const noexcept -> const page_type *
Definition sparse_world.h:440
bool clear_dirty_observed(ChunkKey key, DirtyObservation observed) noexcept
Definition sparse_world.h:566
auto try_chunk(ChunkKey key) noexcept -> page_type *
Definition sparse_world.h:431
std::size_t resident_slot(ChunkKey key) const noexcept
Definition sparse_world.h:258
auto resolve(Coord3 coord) const noexcept -> ResolvedTile< Shape >
Definition sparse_world.h:622
Definition world.h:22
Definition metadata_types.h:49
Definition shape.h:94
Definition shape.h:58
Definition shape.h:86
Definition chunk_meta.h:27
Definition shape.h:46
Definition metadata_types.h:12
Definition chunk_meta.h:46
Definition residency.h:21
Definition metadata_types.h:118
Definition residency.h:40
Definition shape.h:107
Definition shape.h:296
Definition residency.h:18