136 using shape_type =
Shape;
137 using schema_type = Schema;
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;
156 pages_.reserve(capacity_);
157 for (std::size_t slot = 0; slot < capacity_; ++slot) {
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);
168 slot_position_.assign(capacity_, 0);
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);
176 directory_.reset(capacity_);
180 [[nodiscard]] std::size_t
capacity() const noexcept {
return capacity_; }
188 return resident_keys_.size();
192 return resident_keys_.size() * page_byte_size;
196 static constexpr std::size_t
npos_slot = detail::ChunkDirectory::npos;
200 return key.value < chunk_count;
205 return directory_.find(key) != detail::ChunkDirectory::npos;
216 return directory_.find(key);
222 std::uint64_t generation = 0;
235 const auto slot = directory_.find(key);
236 if (slot == detail::ChunkDirectory::npos) {
247 const auto slot = directory_.find(key);
248 if (slot == detail::ChunkDirectory::npos) {
251 return slot_generation_[slot];
256 return handle.generation != 0 &&
267 return {resident_keys_.data(), resident_keys_.size()};
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);
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));
298 0x9e3779b97f4a7c15ull);
314 auto slot = directory_.find(key);
315 if (slot != detail::ChunkDirectory::npos) {
316 lru_move_to_mru(slot);
319 slot = acquire_slot();
320 pages_[slot].reset(key, chunk_coord<Shape>(key));
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_;
328 slot_position_[slot] = resident_keys_.size();
329 resident_keys_.push_back(key);
330 resident_slots_.push_back(slot);
331 directory_.insert(key, slot);
337 const auto slot = directory_.find(key);
338 if (slot == detail::ChunkDirectory::npos) {
341 lru_move_to_mru(slot);
352 const auto slot = directory_.find(key);
353 if (slot == detail::ChunkDirectory::npos) {
357 free_slots_.push_back(slot);
366 const auto slot = directory_.find(key);
367 TESS_ASSERT(slot != detail::ChunkDirectory::npos);
373 const auto slot = directory_.find(key);
374 TESS_ASSERT(slot != detail::ChunkDirectory::npos);
380 const auto slot = directory_.find(key);
381 if (slot == detail::ChunkDirectory::npos) {
384 return &pages_[slot];
389 ->
const page_type* {
390 const auto slot = directory_.find(key);
391 if (slot == detail::ChunkDirectory::npos) {
394 return &pages_[slot];
402 const auto slot = directory_.find(key);
403 TESS_ASSERT(slot != detail::ChunkDirectory::npos);
404 return metadata_[slot];
408 const auto slot = directory_.find(key);
409 TESS_ASSERT(slot != detail::ChunkDirectory::npos);
410 return metadata_[slot];
415 const auto slot = directory_.find(key);
416 if (slot == detail::ChunkDirectory::npos) {
419 return &metadata_[slot];
423 const auto slot = directory_.find(key);
424 if (slot == detail::ChunkDirectory::npos) {
427 return &metadata_[slot];
430 [[nodiscard]]
auto chunk_state(ChunkKey key)
const noexcept -> ChunkState {
431 return meta(key).state;
434 void set_chunk_state(ChunkKey key, ChunkState state)
noexcept {
435 meta(key).state = state;
441 [[nodiscard]]
auto dirty_flags(ChunkKey key)
const noexcept -> std::uint32_t {
442 return dirty_flags_[resident_slot_checked(key)];
445 [[nodiscard]]
auto active_flags(ChunkKey key)
const noexcept
447 return active_flags_[resident_slot_checked(key)];
450 [[nodiscard]]
auto dirty_bounds(ChunkKey key)
const noexcept -> Box3 {
451 return dirty_bounds_[resident_slot_checked(key)];
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);
460 void mark_topology_dirty(ChunkKey key, std::uint32_t flags,
461 Box3 bounds)
noexcept {
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;
471 void mark_topology_rebuilt(ChunkKey key)
noexcept {
472 ++meta(key).topology_version;
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);
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);
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);
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);
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);
510 std::vector<ChunkKey>& out)
const {
511 collect_matching_chunks(flags, dirty_flags_, out);
520 std::vector<ChunkKey>& out)
const {
521 collect_matching_chunks(flags, active_flags_, out);
526 -> std::vector<ChunkKey> {
527 std::vector<ChunkKey> chunks;
534 -> std::vector<ChunkKey> {
535 std::vector<ChunkKey> chunks;
546 TESS_ASSERT(tess::contains<Shape>(coord));
548 chunk_key<Shape>(chunk_coord<Shape>(coord)),
549 local_tile_id<Shape>(local_coord<Shape>(coord)),
559 -> std::optional<ResolvedTile<Shape>> {
560 if (!tess::contains<Shape>(coord)) {
570 template <
typename Tag>
572 -> Schema::template value_type<Tag>& {
573 const auto resolved =
resolve(coord);
574 return chunk(resolved.chunk_key)
579 template <
typename Tag>
581 ->
const Schema::template value_type<Tag>& {
582 const auto resolved =
resolve(coord);
583 return chunk(resolved.chunk_key)
591 template <
typename Tag>
593 -> Schema::template value_type<Tag>* {
595 if (!resolved.has_value()) {
598 auto* page =
try_chunk(resolved->chunk_key);
599 if (page ==
nullptr) {
602 return &page->template
field<Tag>(resolved->local_tile_id);
606 template <
typename Tag>
608 ->
const Schema::template value_type<Tag>* {
610 if (!resolved.has_value()) {
613 const auto* page =
try_chunk(resolved->chunk_key);
614 if (page ==
nullptr) {
617 return &page->template
field<Tag>(resolved->local_tile_id);
624 template <
typename Tag>
630 template <
typename Tag>
636 std::size_t acquire_slot() {
637 if (!free_slots_.empty()) {
638 const auto slot = free_slots_.back();
639 free_slots_.pop_back();
642 return evict_least_recently_used();
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);
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;
668 if (next != npos_slot) {
669 lru_prev_[next] = prev;
673 lru_prev_[slot] = npos_slot;
674 lru_next_[slot] = npos_slot;
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;
688 void lru_move_to_mru(std::size_t slot)
noexcept {
689 if (lru_tail_ == slot) {
699 void release_slot(std::size_t 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();
711 [[nodiscard]]
static constexpr std::size_t clamp_capacity(
712 std::size_t byte_budget)
noexcept {
713 if (page_byte_size == 0) {
716 const auto count = byte_budget / page_byte_size;
717 return count < 1 ? 1 : count;
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]);
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);
740 std::size_t byte_budget_;
741 std::size_t capacity_;
743 std::uint64_t generation_clock_ = 0;
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_;
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_;