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>
28 static constexpr std::size_t npos =
static_cast<std::size_t
>(-1);
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);
38 std::vector<std::size_t>{}.swap(direct_slots_);
39 std::size_t table = 2;
40 while (table < capacity * 2) {
43 buckets_.assign(table, Bucket{});
47 [[nodiscard]] std::size_t find(ChunkKey key)
const noexcept {
51 if (key.value >= direct_slots_.size()) {
54 return direct_slots_[
static_cast<std::size_t
>(key.value)];
57 std::size_t i = home(key);
58 while (buckets_[i].occupied) {
59 if (buckets_[i].key == key) {
60 return buckets_[i].slot;
69 void insert(ChunkKey key, std::size_t slot)
noexcept {
71 TESS_ASSERT(key.value < direct_slots_.size());
72 direct_slots_[
static_cast<std::size_t
>(key.value)] = slot;
76 std::size_t i = home(key);
77 while (buckets_[i].occupied) {
80 buckets_[i] = Bucket{key, slot,
true};
83 bool erase(ChunkKey key)
noexcept {
85 if (key.value >= direct_slots_.size()) {
88 auto& slot = direct_slots_[
static_cast<std::size_t
>(key.value)];
96 std::size_t i = home(key);
97 while (buckets_[i].occupied && buckets_[i].key != key) {
100 if (!buckets_[i].occupied) {
106 if (!buckets_[j].occupied) {
109 const std::size_t k = home(buckets_[j].key);
113 if (!in_cyclic_range(i, k, j)) {
114 buckets_[i] = buckets_[j];
118 buckets_[i] = Bucket{};
125 std::size_t slot = 0;
126 bool occupied =
false;
129 [[nodiscard]] std::size_t home(ChunkKey key)
const noexcept {
130 return static_cast<std::size_t
>(mix(key.value)) & mask_;
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);
140 static bool in_cyclic_range(std::size_t lo, std::size_t pos,
141 std::size_t hi)
noexcept {
143 return lo < pos && pos <= hi;
145 return lo < pos || pos <= hi;
148 std::vector<Bucket> buckets_;
149 std::vector<std::size_t> direct_slots_;
150 std::size_t mask_ = 0;
172template <
typename Shape,
typename Schema>
175 using shape_type =
Shape;
176 using schema_type = Schema;
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;
186 static constexpr std::size_t page_byte_size = page_type::byte_size;
198 pages_.reserve(capacity_);
199 for (std::size_t slot = 0; slot < capacity_; ++slot) {
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{});
210 slot_position_.assign(capacity_, 0);
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);
218 directory_.reset(capacity_, chunk_count);
222 [[nodiscard]] std::size_t
capacity() const noexcept {
return capacity_; }
230 return resident_keys_.size();
235 return resident_keys_.size() * page_byte_size;
239 static constexpr std::size_t
npos_slot = detail::ChunkDirectory::npos;
243 return key.value < chunk_count;
248 return directory_.find(key) != detail::ChunkDirectory::npos;
259 return directory_.find(key);
278 const auto slot = directory_.find(key);
279 if (slot == detail::ChunkDirectory::npos) {
290 const auto slot = directory_.find(key);
291 if (slot == detail::ChunkDirectory::npos) {
294 return slot_generation_[slot];
299 return handle.generation.valid() &&
310 return {resident_keys_.data(), resident_keys_.size()};
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);
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);
341 0x9e3779b97f4a7c15ull);
366 auto slot = directory_.find(key);
367 if (slot != detail::ChunkDirectory::npos) {
368 lru_move_to_mru(slot);
371 slot = acquire_slot();
372 pages_[slot].reset(key, chunk_coord<Shape>(key));
376 dirty_bounds_[slot] =
Box3{};
377 slot_key_[slot] = key;
378 slot_generation_[slot] = ++generation_clock_;
380 slot_position_[slot] = resident_keys_.size();
381 resident_keys_.push_back(key);
382 resident_slots_.push_back(slot);
383 directory_.insert(key, slot);
389 const auto slot = directory_.find(key);
390 if (slot == detail::ChunkDirectory::npos) {
393 lru_move_to_mru(slot);
404 const auto slot = directory_.find(key);
405 if (slot == detail::ChunkDirectory::npos) {
409 free_slots_.push_back(slot);
418 const auto slot = directory_.find(key);
419 TESS_ASSERT(slot != detail::ChunkDirectory::npos);
425 const auto slot = directory_.find(key);
426 TESS_ASSERT(slot != detail::ChunkDirectory::npos);
432 const auto slot = directory_.find(key);
433 if (slot == detail::ChunkDirectory::npos) {
436 return &pages_[slot];
441 ->
const page_type* {
442 const auto slot = directory_.find(key);
443 if (slot == detail::ChunkDirectory::npos) {
446 return &pages_[slot];
454 const auto slot = directory_.find(key);
455 TESS_ASSERT(slot != detail::ChunkDirectory::npos);
456 return metadata_[slot];
460 const auto slot = directory_.find(key);
461 TESS_ASSERT(slot != detail::ChunkDirectory::npos);
462 return metadata_[slot];
467 const auto slot = directory_.find(key);
468 if (slot == detail::ChunkDirectory::npos) {
471 return &metadata_[slot];
475 const auto slot = directory_.find(key);
476 if (slot == detail::ChunkDirectory::npos) {
479 return &metadata_[slot];
482 [[nodiscard]]
auto chunk_activity(ChunkKey key)
const noexcept
484 return active_mask(key).empty() ? ChunkActivity::Sleeping
485 : ChunkActivity::Active;
488 [[nodiscard]]
auto active_category_count(ChunkKey key)
const noexcept
490 return detail::popcount(active_mask(key));
496 [[nodiscard]]
auto dirty_mask(ChunkKey key)
const noexcept -> DirtyMask {
497 return dirty_masks_[resident_slot_checked(key)];
500 [[nodiscard]]
auto active_mask(ChunkKey key)
const noexcept -> ActiveMask {
501 return active_masks_[resident_slot_checked(key)];
504 [[nodiscard]]
auto dirty_bounds(ChunkKey key)
const noexcept -> Box3 {
505 return dirty_bounds_[resident_slot_checked(key)];
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);
528 const auto slot = resident_slot_checked(key);
529 detail::meta_mark_content_changed(metadata_[slot]);
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;
542 void mark_topology_rebuilt(ChunkKey key)
noexcept {
543 ++meta(key).topology_version;
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);
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]);
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]);
574 const auto slot = resident_slot_checked(key);
575 detail::meta_mark_active(active_masks_[slot], mask);
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);
589 collect_matching_chunks(mask, dirty_masks_, out);
598 std::vector<ChunkKey>& out)
const {
599 collect_matching_chunks(mask, active_masks_, out);
604 -> std::vector<ChunkKey> {
605 std::vector<ChunkKey> chunks;
612 -> std::vector<ChunkKey> {
613 std::vector<ChunkKey> chunks;
624 TESS_ASSERT(tess::contains<Shape>(coord));
626 chunk_key<Shape>(chunk_coord<Shape>(coord)),
627 local_tile_id<Shape>(local_coord<Shape>(coord)),
637 -> std::optional<ResolvedTile<Shape>> {
638 if (!tess::contains<Shape>(coord)) {
648 template <
typename Tag>
650 -> Schema::template value_type<Tag>& {
651 const auto resolved =
resolve(coord);
652 return chunk(resolved.chunk_key)
657 template <
typename Tag>
659 ->
const Schema::template value_type<Tag>& {
660 const auto resolved =
resolve(coord);
661 return chunk(resolved.chunk_key)
669 template <
typename Tag>
671 -> Schema::template value_type<Tag>* {
673 if (!resolved.has_value()) {
676 auto* page =
try_chunk(resolved->chunk_key);
677 if (page ==
nullptr) {
680 return &page->template
field<Tag>(resolved->local_tile_id);
684 template <
typename Tag>
686 ->
const Schema::template value_type<Tag>* {
688 if (!resolved.has_value()) {
691 const auto* page =
try_chunk(resolved->chunk_key);
692 if (page ==
nullptr) {
695 return &page->template
field<Tag>(resolved->local_tile_id);
702 template <
typename Tag>
708 template <
typename Tag>
714 std::size_t acquire_slot() {
715 if (!free_slots_.empty()) {
716 const auto slot = free_slots_.back();
717 free_slots_.pop_back();
720 return evict_least_recently_used();
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);
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;
745 if (next != npos_slot) {
746 lru_prev_[next] = prev;
750 lru_prev_[slot] = npos_slot;
751 lru_next_[slot] = npos_slot;
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;
765 void lru_move_to_mru(std::size_t slot)
noexcept {
766 if (lru_tail_ == slot) {
776 void release_slot(std::size_t 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();
788 [[nodiscard]]
static constexpr std::size_t clamp_capacity(
789 std::size_t byte_budget)
noexcept {
790 if constexpr (page_byte_size == 0) {
793 const auto count = byte_budget / page_byte_size;
794 return count < 1 ? 1 : count;
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]);
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);
817 std::size_t byte_budget_;
818 std::size_t capacity_;
820 ResidencyGeneration generation_clock_{};
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_;
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_;
842template <
typename Shape,
typename Schema>
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 metadata_types.h:49
Definition metadata_types.h:12
Definition chunk_meta.h:46
Definition residency.h:21
Definition metadata_types.h:118
Definition residency.h:40
Definition residency.h:18
Definition sparse_world.h:263