141 static constexpr bool is_dense =
142 std::same_as<typename World::residency_type, AlwaysResident>;
143 static constexpr bool is_sparse =
144 std::same_as<typename World::residency_type, SparseResident>;
145 static_assert(is_dense || is_sparse,
146 "ChunkMaintenanceAdapter supports tess dense and sparse "
151 std::size_t slot = 0;
154 owner->run_slot(slot, budget);
166 std::atomic<bool> retry_debt =
false;
178 Rebuild rebuild, std::size_t backend_capacity = 0)
180 owned_dirty_mask_(owned_dirty_mask),
181 rebuild_(std::move(rebuild)),
182 slot_count_(world_slot_count(world)),
183 slots_(std::make_unique<Slot[]>(slot_count_)),
184 scheduler_(slot_count_,
185 backend_capacity == 0 ? slot_count_ : backend_capacity),
187 if (owned_dirty_mask_.empty()) {
188 ::tess::detail::fail_fast(
189 "ChunkMaintenanceAdapter requires a nonzero owned dirty mask");
191 for (std::size_t slot = 0; slot < slot_count_; ++slot) {
192 slots_[slot].task.owner =
this;
193 slots_[slot].task.slot = slot;
194 const auto handle = scheduler_.register_task(slots_[slot].task);
195 if (!handle.has_value()) {
196 ::tess::detail::fail_fast(
197 "ChunkMaintenanceAdapter fixed registry capacity mismatch");
199 handles_[slot] = handle.value();
200 if constexpr (is_dense) {
201 bind_slot(slot,
ChunkKey{slot}, {});
204 if constexpr (is_sparse) {
205 bind_current_sparse_residency();
223 return ChunkMarkResult::Released;
225 if (mask.empty() || !(mask & ~owned_dirty_mask_).empty()) {
226 return ChunkMarkResult::InvalidMask;
228 const auto slot = bound_slot(key);
229 if (!slot.has_value()) {
230 return ChunkMarkResult::Missing;
232 world_->mark_dirty(key, mask, bounds);
233 return map_mark_result(schedule_slot(slot.value()));
246 const auto slot = bound_slot(key);
247 if (!slot.has_value()) {
250 return schedule_slot(slot.value());
261 transition_ready_.store(
false, std::memory_order_release);
262#if TESS_HAS_EXCEPTIONS
264 const auto backend_result = scheduler_.run_some(budget);
265 const auto result = finalize_drain(backend_result, {});
266 transition_ready_.store(result == DrainResult::Idle,
267 std::memory_order_release);
270 transition_ready_.store(
false, std::memory_order_release);
274 const auto backend_result = scheduler_.run_some(budget);
275 const auto result = finalize_drain(backend_result, {});
276 transition_ready_.store(result == DrainResult::Idle,
277 std::memory_order_release);
286 [[nodiscard]]
auto flush() -> DrainResult {
287 transition_ready_.store(
false, std::memory_order_release);
288#if TESS_HAS_EXCEPTIONS
290 static_cast<void>(reoffer_retry_debt());
291 const auto backend_result = scheduler_.flush();
292 const auto follow_up = reoffer_retry_debt();
293 const auto result = finalize_drain(backend_result, follow_up);
294 transition_ready_.store(result == DrainResult::Idle,
295 std::memory_order_release);
298 transition_ready_.store(
false, std::memory_order_release);
302 static_cast<void>(reoffer_retry_debt());
303 const auto backend_result = scheduler_.flush();
304 const auto follow_up = reoffer_retry_debt();
305 const auto result = finalize_drain(backend_result, follow_up);
306 transition_ready_.store(result == DrainResult::Idle,
307 std::memory_order_release);
314 const auto slot_index = bound_slot(key);
315 if (!slot_index.has_value()) {
318 const auto& slot = slots_[slot_index.value()];
319 if (!slot.built || slot.token.key != key) {
322 const auto state = token_current(slot.token, slot_index.value())
323 ? ChunkProductState::Current
324 : ChunkProductState::Stale;
330 const auto slot = bound_slot(token.key);
331 return slot.has_value() && slots_[slot.value()].built &&
332 slots_[slot.value()].token == token &&
333 token_current(token, slot.value());
338 return scheduler_.metrics();
353 if (!transition_ready_.load(std::memory_order_acquire)) {
356 const auto handle = world_->ensure_resident(key);
357 if (!handle.generation.valid()) {
360 const auto ref = world_->resident_ref(key);
361 if (ref.meta ==
nullptr || ref.slot >= slot_count_ ||
362 ref.generation != handle.generation) {
363 ::tess::detail::fail_fast(
364 "ChunkMaintenanceAdapter sparse residency binding mismatch");
366 bind_slot(ref.slot, key, ref.generation);
375 return ChunkEvictionResult::Released;
377 if (!transition_ready_.load(std::memory_order_acquire)) {
378 return ChunkEvictionResult::NotIdle;
380 const auto ref = world_->resident_ref(key);
381 if (ref.meta ==
nullptr) {
382 return ChunkEvictionResult::Missing;
384 if (!world_->evict(key)) {
385 return ChunkEvictionResult::Missing;
387 unbind_slot(ref.slot);
388 return ChunkEvictionResult::Evicted;
403 return ChunkResidencyStatus::Released;
405 if (!transition_ready_.load(std::memory_order_acquire)) {
406 return ChunkResidencyStatus::NotIdle;
408 for (std::size_t slot = 0; slot < slot_count_; ++slot) {
411 bind_current_sparse_residency();
412 return ChunkResidencyStatus::Ready;
423 return ChunkAdapterReleaseResult::AlreadyReleased;
425 if (!transition_ready_.load(std::memory_order_acquire)) {
426 return ChunkAdapterReleaseResult::NotIdle;
428 for (std::size_t slot = 0; slot < slot_count_; ++slot) {
429 const auto result = scheduler_.try_release(handles_[slot]);
430 if (result != ReleaseResult::Released) {
431 return ChunkAdapterReleaseResult::NotIdle;
435 transition_ready_.store(
false, std::memory_order_release);
436 return ChunkAdapterReleaseResult::Released;
440 [[nodiscard]]
static auto world_slot_count(
const World& world)
442 if constexpr (is_dense) {
444 World::chunk_count <=
445 static_cast<std::uint64_t
>(std::numeric_limits<std::size_t>::max()));
446 return static_cast<std::size_t
>(World::chunk_count);
448 return world.capacity();
452 [[nodiscard]]
auto bound_slot(ChunkKey key)
const noexcept
453 -> std::optional<std::size_t> {
454 if constexpr (is_dense) {
455 if (key.value >= World::chunk_count) {
458 return static_cast<std::size_t
>(key.value);
460 const auto ref = world_->resident_ref(key);
461 if (ref.meta ==
nullptr || ref.slot >= slot_count_) {
464 const auto& slot = slots_[ref.slot];
465 if (!slot.bound || slot.key != key ||
466 slot.residency_generation != ref.generation) {
473 void bind_slot(std::size_t slot_index, ChunkKey key,
474 ResidencyGeneration generation)
noexcept {
475 auto& slot = slots_[slot_index];
476 if (!slot.bound || slot.key != key ||
477 slot.residency_generation != generation) {
479 slot.retry_debt.store(
false, std::memory_order_release);
483 slot.residency_generation = generation;
486 void unbind_slot(std::size_t slot_index)
noexcept {
487 auto& slot = slots_[slot_index];
490 slot.residency_generation = {};
491 slot.retry_debt.store(
false, std::memory_order_release);
494 void bind_current_sparse_residency()
497 for (
const auto key : world_->resident_chunk_keys()) {
498 const auto ref = world_->resident_ref(key);
499 if (ref.meta ==
nullptr || ref.slot >= slot_count_) {
500 ::tess::detail::fail_fast(
501 "ChunkMaintenanceAdapter sparse residency reconciliation "
504 bind_slot(ref.slot, key, ref.generation);
508 [[nodiscard]]
auto schedule_slot(std::size_t slot) -> ScheduleResult {
509 transition_ready_.store(
false, std::memory_order_release);
510 const auto claimed_debt =
511 slots_[slot].retry_debt.exchange(
false, std::memory_order_acq_rel);
512 const auto result = scheduler_.schedule(handles_[slot]);
513 if (claimed_debt && result != ScheduleResult::Accepted) {
514 slots_[slot].retry_debt.store(
true, std::memory_order_release);
519 struct RetryOfferSummary {
520 bool offered =
false;
521 bool stalled =
false;
524 [[nodiscard]]
auto reoffer_retry_debt() -> RetryOfferSummary {
525 auto summary = RetryOfferSummary{};
526 for (std::size_t slot = 0; slot < slot_count_; ++slot) {
527 if (!slots_[slot].retry_debt.load(std::memory_order_acquire)) {
530 summary.offered =
true;
531 const auto result = schedule_slot(slot);
532 summary.stalled = summary.stalled || result == ScheduleResult::Stalled;
537 [[nodiscard]]
auto has_retry_debt() const noexcept ->
bool {
538 for (std::size_t slot = 0; slot < slot_count_; ++slot) {
539 if (slots_[slot].retry_debt.load(std::memory_order_acquire)) {
546 [[nodiscard]]
auto finalize_drain(DrainResult result,
547 RetryOfferSummary follow_up)
const noexcept
549 if (result == DrainResult::Stalled || follow_up.stalled) {
550 return DrainResult::Stalled;
552 if (follow_up.offered || has_retry_debt()) {
553 return DrainResult::BudgetExhausted;
558 [[nodiscard]]
static auto map_mark_result(ScheduleResult result)
noexcept
561 case ScheduleResult::Accepted:
562 return ChunkMarkResult::Accepted;
563 case ScheduleResult::CapacityExhausted:
564 return ChunkMarkResult::CapacityExhausted;
565 case ScheduleResult::Stalled:
566 return ChunkMarkResult::Stalled;
568 ::tess::detail::fail_fast(
569 "ChunkMaintenanceAdapter received an invalid schedule result");
572 [[nodiscard]]
auto sparse_binding_current(
573 const Slot& slot, std::size_t slot_index)
const noexcept ->
bool {
574 if constexpr (is_dense) {
575 static_cast<void>(slot);
576 static_cast<void>(slot_index);
579 const auto ref = world_->resident_ref(slot.key);
580 return ref.meta !=
nullptr && ref.slot == slot_index &&
581 ref.generation == slot.residency_generation;
585 [[nodiscard]]
auto token_current(ChunkProductToken token,
586 std::size_t slot_index)
const noexcept
588 const auto& slot = slots_[slot_index];
589 if (!sparse_binding_current(slot, slot_index) || slot.key != token.key ||
590 slot.residency_generation != token.residency_generation) {
593 return world_->meta(token.key).content_version == token.content_version &&
594 (world_->dirty_mask(token.key) & owned_dirty_mask_).empty();
597 void run_slot(std::size_t slot_index, MaintenanceBudget& budget) {
598 auto& slot = slots_[slot_index];
599 if (!slot.bound || !budget.consume()) {
602 if (!sparse_binding_current(slot, slot_index)) {
605 const auto observed = world_->observe_dirty(slot.key, owned_dirty_mask_);
606 if (observed.mask.empty() &&
608 slot.token.content_version == observed.content_version)) {
609 slot.retry_debt.store(
false, std::memory_order_release);
612 if (!sparse_binding_current(slot, slot_index)) {
615 std::invoke(rebuild_, std::as_const(*world_), slot.key, observed,
617 if (!sparse_binding_current(slot, slot_index)) {
620 slot.token = ChunkProductToken{slot.key, observed.content_version,
621 slot.residency_generation};
623 if (world_->clear_dirty_observed(slot.key, observed)) {
624 slot.retry_debt.store(
false, std::memory_order_release);
627 if (schedule_slot(slot_index) != ScheduleResult::Accepted) {
628 slot.retry_debt.store(
true, std::memory_order_release);
633 DirtyMask owned_dirty_mask_;
635 std::size_t slot_count_;
636 std::unique_ptr<Slot[]> slots_;
637 RegisteredScheduler<Backend> scheduler_;
638 std::unique_ptr<MaintenanceHandle[]> handles_;
639 std::atomic<bool> transition_ready_ =
false;
640 bool released_ =
false;
ChunkMaintenanceAdapter(World &world, DirtyMask owned_dirty_mask, Rebuild rebuild, std::size_t backend_capacity=0)
Definition chunk_maintenance.h:177