318class DeltaCollector {
320 DeltaCollector() =
default;
340 DeltaCollector(
const DeltaCollector&) =
delete;
341 auto operator=(
const DeltaCollector&) -> DeltaCollector& =
delete;
345 DeltaCollector(DeltaCollector&&) =
default;
347 auto operator=(DeltaCollector&&) -> DeltaCollector& =
default;
354 void reserve(std::size_t chunk_capacity, std::size_t tile_capacity,
355 std::size_t entity_capacity, std::size_t overlay_capacity = 0,
356 std::size_t overlay_node_capacity = 0) {
357 frame_generation_.invalidate();
358 pending_chunks_.reserve(chunk_capacity);
359 published_chunks_.reserve(chunk_capacity);
360 pending_tiles_.reserve(tile_capacity);
361 published_tiles_.reserve(tile_capacity);
362 pending_entities_.reserve(entity_capacity);
363 published_entities_.reserve(entity_capacity);
364 pending_overlays_.reserve(overlay_capacity);
365 published_overlays_.reserve(overlay_capacity);
366 pending_overlay_nodes_.reserve(overlay_node_capacity);
367 published_overlay_nodes_.reserve(overlay_node_capacity);
375 const auto entity_slots =
376 std::max(pending_entities_.capacity(), published_entities_.capacity());
377 auto slots = std::size_t{8};
378 while (slots < entity_slots * 2) {
381 if (slots > coalesce_slots_.size()) {
382 coalesce_slots_.assign(slots, CoalesceSlot{});
388 void begin_tick(std::uint64_t tick)
noexcept {
389 current_tick_ = tick;
390 if (pending_ticks_ == 0) {
391 pending_first_tick_ = tick;
393 pending_last_tick_ = tick;
400 if (options_.coalesce_moves) {
401 if (
auto* slot = find_coalesce_slot(entity);
402 slot !=
nullptr && slot->record_index != kBarrier) {
403 auto& record = pending_entities_[slot->record_index];
404 if (record.to == from) {
406 record.last_tick = current_tick_;
407 ++stats_.moves_coalesced;
412 const auto index = append_entity(
413 EntityDelta{entity, EntityDeltaKind::Moved, from, to, current_tick_});
414 if (options_.coalesce_moves && index != kDropped) {
415 upsert_coalesce_slot(entity, index);
420 record_barrier(
EntityDelta{entity, EntityDeltaKind::Teleported, from, to,
426 EntityDelta{entity, EntityDeltaKind::Spawned, at, at, current_tick_});
431 EntityDelta{entity, EntityDeltaKind::Despawned, at, at, current_tick_});
436 EntityDelta{entity, EntityDeltaKind::Parked, at, at, current_tick_});
441 EntityDelta{entity, EntityDeltaKind::Placed, at, at, current_tick_});
447 if (pending_chunks_.size() == pending_chunks_.capacity()) {
451 pending_chunks_.push_back(record);
452 ++stats_.chunk_records;
453 if (record.tile_count == 0) {
454 ++stats_.box_records;
456 return pending_chunks_.size() - 1;
462 auto append_tile_record(
TileDelta record) -> std::size_t {
463 if (pending_tiles_.size() == pending_tiles_.capacity()) {
466 pending_tiles_.push_back(record);
467 ++stats_.tile_records;
468 return pending_tiles_.size() - 1;
471 [[nodiscard]]
auto pending_tile_count()
const noexcept -> std::size_t {
472 return pending_tiles_.size();
475 void note_collected_mask(
DirtyMask dirty_mask)
noexcept {
476 pending_dirty_mask_ |= dirty_mask;
484 void drop_pending_tile_state()
noexcept {
485 pending_chunks_.clear();
486 pending_tiles_.clear();
487 pending_truncated_ =
false;
490 void mark_baseline_pending()
noexcept { baseline_pending_ =
true; }
498 if (remaining.
empty()) {
501 if (pending_overlays_.size() == pending_overlays_.capacity() ||
502 pending_overlay_nodes_.size() + remaining.
size() >
503 pending_overlay_nodes_.capacity()) {
504 ++stats_.overlay_truncations;
507 const auto first_node =
508 static_cast<std::uint32_t
>(pending_overlay_nodes_.size());
509 for (
const auto& node : remaining) {
510 pending_overlay_nodes_.push_back(node);
512 pending_overlays_.push_back(
514 static_cast<std::uint32_t
>(remaining.
size())});
515 ++stats_.overlay_records;
516 stats_.overlay_nodes_copied += remaining.
size();
527 frame_generation_.invalidate();
528 if (baseline_pending_) {
529 drop_pending_entities();
534 const auto state_carrying =
535 !pending_chunks_.empty() || !pending_tiles_.empty() ||
536 !pending_entities_.empty() || baseline_pending_ || pending_truncated_ ||
537 needs_baseline_ || moved_from_.value;
539 header.from_version = version_;
540 if (state_carrying) {
543 header.to_version = version_;
544 header.first_tick = pending_first_tick_;
545 header.last_tick = pending_last_tick_;
546 header.ticks = pending_ticks_;
547 header.dirty_mask = pending_dirty_mask_;
548 header.baseline = baseline_pending_;
550 pending_truncated_ ||
551 ((needs_baseline_ || moved_from_.value) && !baseline_pending_);
552 if (baseline_pending_) {
553 needs_baseline_ =
false;
554 moved_from_.value =
false;
557 clear_coalesce_slots();
558 published_chunks_.swap(pending_chunks_);
559 published_tiles_.swap(pending_tiles_);
560 published_entities_.swap(pending_entities_);
561 published_overlays_.swap(pending_overlays_);
562 published_overlay_nodes_.swap(pending_overlay_nodes_);
563 pending_chunks_.clear();
564 pending_tiles_.clear();
565 pending_entities_.clear();
566 pending_overlays_.clear();
567 pending_overlay_nodes_.clear();
568 pending_dirty_mask_ = {};
570 pending_first_tick_ = 0;
571 pending_last_tick_ = 0;
572 pending_truncated_ =
false;
573 baseline_pending_ =
false;
575 ++stats_.frames_published;
576 if (header.baseline) {
577 ++stats_.baselines_published;
584 published_overlay_nodes_,
585 frame_generation_.state,
586 frame_generation_.value()};
593 void clear()
noexcept {
594 clear_coalesce_slots();
595 pending_chunks_.clear();
596 pending_tiles_.clear();
597 pending_entities_.clear();
598 pending_overlays_.clear();
599 pending_overlay_nodes_.clear();
600 pending_dirty_mask_ = {};
602 pending_first_tick_ = 0;
603 pending_last_tick_ = 0;
604 pending_truncated_ =
false;
605 baseline_pending_ =
false;
606 needs_baseline_ =
true;
609 [[nodiscard]]
auto version()
const noexcept ->
RenderVersion {
621 static constexpr std::size_t kDropped =
static_cast<std::size_t
>(-1);
624 static constexpr std::size_t kBarrier =
static_cast<std::size_t
>(-2);
626 struct CoalesceSlot {
628 std::size_t record_index = kDropped;
632 const auto index = append_entity(record);
633 if (options_.coalesce_moves && index != kDropped) {
636 upsert_coalesce_slot(record.entity, kBarrier);
640 auto append_entity(
EntityDelta record) -> std::size_t {
641 if (pending_entities_.size() == pending_entities_.capacity()) {
645 pending_entities_.push_back(record);
646 ++stats_.entity_records;
647 return pending_entities_.size() - 1;
650 void note_truncation()
noexcept {
651 pending_truncated_ =
true;
652 ++stats_.truncations;
655 void drop_pending_entities()
noexcept {
656 clear_coalesce_slots();
657 pending_entities_.clear();
660 [[nodiscard]]
static auto mix(std::uint64_t value)
noexcept -> std::uint64_t {
661 value += 0x9E3779B97F4A7C15ULL;
662 value = (value ^ (value >> 30U)) * 0xBF58476D1CE4E5B9ULL;
663 value = (value ^ (value >> 27U)) * 0x94D049BB133111EBULL;
664 return value ^ (value >> 31U);
667 [[nodiscard]]
auto slot_mask()
const noexcept -> std::size_t {
668 return coalesce_slots_.size() - 1;
671 [[nodiscard]]
auto find_coalesce_slot(
EntityHandle entity)
noexcept
673 if (coalesce_slots_.empty()) {
676 auto index =
static_cast<std::size_t
>(mix(entity.value)) & slot_mask();
678 auto& slot = coalesce_slots_[index];
679 if (slot.entity.is_null()) {
682 if (slot.entity == entity) {
685 index = (index + 1) & slot_mask();
689 void upsert_coalesce_slot(
EntityHandle entity, std::size_t record_index) {
690 if (coalesce_slots_.empty()) {
693 auto index =
static_cast<std::size_t
>(mix(entity.value)) & slot_mask();
695 auto& slot = coalesce_slots_[index];
696 if (slot.entity.is_null() || slot.entity == entity) {
701 slot.entity = entity;
702 slot.record_index = record_index;
705 index = (index + 1) & slot_mask();
713 void clear_coalesce_slots()
noexcept {
714 if (coalesce_slots_.empty()) {
717 for (
const auto& record : pending_entities_) {
718 erase_coalesce_slot(record.entity);
722 void erase_coalesce_slot(
EntityHandle entity)
noexcept {
723 auto index =
static_cast<std::size_t
>(mix(entity.value)) & slot_mask();
725 auto& slot = coalesce_slots_[index];
726 if (slot.entity.is_null()) {
729 if (slot.entity == entity) {
732 index = (index + 1) & slot_mask();
737 next = (next + 1) & slot_mask();
738 const auto& candidate = coalesce_slots_[next];
739 if (candidate.entity.is_null()) {
743 static_cast<std::size_t
>(mix(candidate.entity.value)) & slot_mask();
744 const auto in_gap = (next > hole) ? (ideal > hole && ideal <= next)
745 : (ideal > hole || ideal <= next);
747 coalesce_slots_[hole] = candidate;
751 coalesce_slots_[hole] = CoalesceSlot{};
770 struct MovedFromFlag {
773 MovedFromFlag() =
default;
774 MovedFromFlag(
const MovedFromFlag&) =
default;
775 auto operator=(
const MovedFromFlag&) -> MovedFromFlag& =
default;
776 ~MovedFromFlag() =
default;
778 MovedFromFlag(MovedFromFlag&& other) noexcept : value{other.value} {
782 auto operator=(MovedFromFlag&& other)
noexcept -> MovedFromFlag& {
783 if (
this == &other) {
793 struct FrameGeneration {
794 std::shared_ptr<std::atomic<std::uint64_t>> state =
795 std::make_shared<std::atomic<std::uint64_t>>(1);
797 FrameGeneration() =
default;
798 FrameGeneration(
const FrameGeneration&) =
delete;
799 auto operator=(
const FrameGeneration&) -> FrameGeneration& =
delete;
800 ~FrameGeneration() =
default;
803 FrameGeneration(FrameGeneration&& other)
806 return std::make_shared<std::atomic<std::uint64_t>>(1);
810 auto operator=(FrameGeneration&& other) -> FrameGeneration& {
812 if (
this == &other) {
816 state = std::make_shared<std::atomic<std::uint64_t>>(1);
820 void invalidate()
noexcept {
821 state->fetch_add(1, std::memory_order_relaxed);
824 [[nodiscard]]
auto value()
const noexcept -> std::uint64_t {
825 return state->load(std::memory_order_relaxed);
833 MovedFromFlag moved_from_{};
834 FrameGeneration frame_generation_{};
837 std::vector<TileChunkDelta> pending_chunks_;
838 std::vector<TileChunkDelta> published_chunks_;
839 std::vector<TileDelta> pending_tiles_;
840 std::vector<TileDelta> published_tiles_;
841 std::vector<EntityDelta> pending_entities_;
842 std::vector<EntityDelta> published_entities_;
843 std::vector<PathOverlayDelta> pending_overlays_;
844 std::vector<PathOverlayDelta> published_overlays_;
845 std::vector<Coord3> pending_overlay_nodes_;
846 std::vector<Coord3> published_overlay_nodes_;
847 std::vector<CoalesceSlot> coalesce_slots_;
849 std::uint64_t current_tick_ = 0;
850 std::uint64_t pending_first_tick_ = 0;
851 std::uint64_t pending_last_tick_ = 0;
852 std::uint32_t pending_ticks_ = 0;
853 bool pending_truncated_ =
false;
854 bool baseline_pending_ =
false;
855 bool needs_baseline_ =
false;