226class DeltaCollector {
228 DeltaCollector() =
default;
236 void reserve(std::size_t chunk_capacity, std::size_t tile_capacity,
237 std::size_t entity_capacity, std::size_t overlay_capacity = 0,
238 std::size_t overlay_node_capacity = 0) {
239 pending_chunks_.reserve(chunk_capacity);
240 published_chunks_.reserve(chunk_capacity);
241 pending_tiles_.reserve(tile_capacity);
242 published_tiles_.reserve(tile_capacity);
243 pending_entities_.reserve(entity_capacity);
244 published_entities_.reserve(entity_capacity);
245 pending_overlays_.reserve(overlay_capacity);
246 published_overlays_.reserve(overlay_capacity);
247 pending_overlay_nodes_.reserve(overlay_node_capacity);
248 published_overlay_nodes_.reserve(overlay_node_capacity);
249 auto slots = std::size_t{8};
250 while (slots < entity_capacity * 2) {
253 if (slots > coalesce_slots_.size()) {
254 coalesce_slots_.assign(slots, CoalesceSlot{});
260 void begin_tick(std::uint64_t tick)
noexcept {
261 current_tick_ = tick;
262 if (pending_ticks_ == 0) {
263 pending_first_tick_ = tick;
265 pending_last_tick_ = tick;
272 if (options_.coalesce_moves) {
273 if (
auto* slot = find_coalesce_slot(entity);
274 slot !=
nullptr && slot->record_index != kBarrier) {
275 auto& record = pending_entities_[slot->record_index];
276 if (record.to == from) {
278 record.last_tick = current_tick_;
279 ++stats_.moves_coalesced;
284 const auto index = append_entity(
285 EntityDelta{entity, EntityDeltaKind::Moved, from, to, current_tick_});
286 if (options_.coalesce_moves && index != kDropped) {
287 upsert_coalesce_slot(entity, index);
292 record_barrier(
EntityDelta{entity, EntityDeltaKind::Teleported, from, to,
298 EntityDelta{entity, EntityDeltaKind::Spawned, at, at, current_tick_});
303 EntityDelta{entity, EntityDeltaKind::Despawned, at, at, current_tick_});
308 EntityDelta{entity, EntityDeltaKind::Parked, at, at, current_tick_});
313 EntityDelta{entity, EntityDeltaKind::Placed, at, at, current_tick_});
319 if (pending_chunks_.size() == pending_chunks_.capacity()) {
323 pending_chunks_.push_back(record);
324 ++stats_.chunk_records;
325 if (record.tile_count == 0) {
326 ++stats_.box_records;
328 return pending_chunks_.size() - 1;
334 auto append_tile_record(
TileDelta record) -> std::size_t {
335 if (pending_tiles_.size() == pending_tiles_.capacity()) {
338 pending_tiles_.push_back(record);
339 ++stats_.tile_records;
340 return pending_tiles_.size() - 1;
343 [[nodiscard]]
auto pending_tile_count()
const noexcept -> std::size_t {
344 return pending_tiles_.size();
347 void note_collected_mask(std::uint32_t dirty_mask)
noexcept {
348 pending_dirty_mask_ |= dirty_mask;
356 void drop_pending_tile_state()
noexcept {
357 pending_chunks_.clear();
358 pending_tiles_.clear();
359 pending_truncated_ =
false;
362 void mark_baseline_pending()
noexcept { baseline_pending_ =
true; }
370 if (remaining.
empty()) {
373 if (pending_overlays_.size() == pending_overlays_.capacity() ||
374 pending_overlay_nodes_.size() + remaining.
size() >
375 pending_overlay_nodes_.capacity()) {
376 ++stats_.overlay_truncations;
379 const auto first_node =
380 static_cast<std::uint32_t
>(pending_overlay_nodes_.size());
381 for (
const auto& node : remaining) {
382 pending_overlay_nodes_.push_back(node);
384 pending_overlays_.push_back(
386 static_cast<std::uint32_t
>(remaining.
size())});
387 ++stats_.overlay_records;
388 stats_.overlay_nodes_copied += remaining.
size();
399 if (baseline_pending_) {
400 drop_pending_entities();
405 const auto state_carrying =
406 !pending_chunks_.empty() || !pending_tiles_.empty() ||
407 !pending_entities_.empty() || baseline_pending_ || pending_truncated_ ||
410 header.from_version = version_;
411 if (state_carrying) {
414 header.to_version = version_;
415 header.first_tick = pending_first_tick_;
416 header.last_tick = pending_last_tick_;
417 header.ticks = pending_ticks_;
418 header.dirty_mask = pending_dirty_mask_;
419 header.baseline = baseline_pending_;
421 pending_truncated_ || (needs_baseline_ && !baseline_pending_);
422 if (baseline_pending_) {
423 needs_baseline_ =
false;
426 clear_coalesce_slots();
427 published_chunks_.swap(pending_chunks_);
428 published_tiles_.swap(pending_tiles_);
429 published_entities_.swap(pending_entities_);
430 published_overlays_.swap(pending_overlays_);
431 published_overlay_nodes_.swap(pending_overlay_nodes_);
432 pending_chunks_.clear();
433 pending_tiles_.clear();
434 pending_entities_.clear();
435 pending_overlays_.clear();
436 pending_overlay_nodes_.clear();
437 pending_dirty_mask_ = 0;
439 pending_first_tick_ = 0;
440 pending_last_tick_ = 0;
441 pending_truncated_ =
false;
442 baseline_pending_ =
false;
444 ++stats_.frames_published;
445 if (header.baseline) {
446 ++stats_.baselines_published;
453 published_overlay_nodes_};
460 void clear()
noexcept {
461 clear_coalesce_slots();
462 pending_chunks_.clear();
463 pending_tiles_.clear();
464 pending_entities_.clear();
465 pending_overlays_.clear();
466 pending_overlay_nodes_.clear();
467 pending_dirty_mask_ = 0;
469 pending_first_tick_ = 0;
470 pending_last_tick_ = 0;
471 pending_truncated_ =
false;
472 baseline_pending_ =
false;
473 needs_baseline_ =
true;
476 [[nodiscard]]
auto version()
const noexcept ->
RenderVersion {
488 static constexpr std::size_t kDropped =
static_cast<std::size_t
>(-1);
491 static constexpr std::size_t kBarrier =
static_cast<std::size_t
>(-2);
493 struct CoalesceSlot {
495 std::size_t record_index = kDropped;
499 const auto index = append_entity(record);
500 if (options_.coalesce_moves && index != kDropped) {
503 upsert_coalesce_slot(record.entity, kBarrier);
507 auto append_entity(
EntityDelta record) -> std::size_t {
508 if (pending_entities_.size() == pending_entities_.capacity()) {
512 pending_entities_.push_back(record);
513 ++stats_.entity_records;
514 return pending_entities_.size() - 1;
517 void note_truncation()
noexcept {
518 pending_truncated_ =
true;
519 ++stats_.truncations;
522 void drop_pending_entities()
noexcept {
523 clear_coalesce_slots();
524 pending_entities_.clear();
527 [[nodiscard]]
static auto mix(std::uint64_t value)
noexcept -> std::uint64_t {
528 value += 0x9E3779B97F4A7C15ULL;
529 value = (value ^ (value >> 30U)) * 0xBF58476D1CE4E5B9ULL;
530 value = (value ^ (value >> 27U)) * 0x94D049BB133111EBULL;
531 return value ^ (value >> 31U);
534 [[nodiscard]]
auto slot_mask()
const noexcept -> std::size_t {
535 return coalesce_slots_.size() - 1;
538 [[nodiscard]]
auto find_coalesce_slot(
EntityHandle entity)
noexcept
540 if (coalesce_slots_.empty()) {
543 auto index =
static_cast<std::size_t
>(mix(entity.value)) & slot_mask();
545 auto& slot = coalesce_slots_[index];
546 if (slot.entity.is_null()) {
549 if (slot.entity == entity) {
552 index = (index + 1) & slot_mask();
556 void upsert_coalesce_slot(
EntityHandle entity, std::size_t record_index) {
557 if (coalesce_slots_.empty()) {
560 auto index =
static_cast<std::size_t
>(mix(entity.value)) & slot_mask();
562 auto& slot = coalesce_slots_[index];
563 if (slot.entity.is_null() || slot.entity == entity) {
568 slot.entity = entity;
569 slot.record_index = record_index;
572 index = (index + 1) & slot_mask();
580 void clear_coalesce_slots()
noexcept {
581 if (coalesce_slots_.empty()) {
584 for (
const auto& record : pending_entities_) {
585 erase_coalesce_slot(record.entity);
589 void erase_coalesce_slot(
EntityHandle entity)
noexcept {
590 auto index =
static_cast<std::size_t
>(mix(entity.value)) & slot_mask();
592 auto& slot = coalesce_slots_[index];
593 if (slot.entity.is_null()) {
596 if (slot.entity == entity) {
599 index = (index + 1) & slot_mask();
604 next = (next + 1) & slot_mask();
605 const auto& candidate = coalesce_slots_[next];
606 if (candidate.entity.is_null()) {
610 static_cast<std::size_t
>(mix(candidate.entity.value)) & slot_mask();
611 const auto in_gap = (next > hole) ? (ideal > hole && ideal <= next)
612 : (ideal > hole || ideal <= next);
614 coalesce_slots_[hole] = candidate;
618 coalesce_slots_[hole] = CoalesceSlot{};
623 std::vector<TileChunkDelta> pending_chunks_;
624 std::vector<TileChunkDelta> published_chunks_;
625 std::vector<TileDelta> pending_tiles_;
626 std::vector<TileDelta> published_tiles_;
627 std::vector<EntityDelta> pending_entities_;
628 std::vector<EntityDelta> published_entities_;
629 std::vector<PathOverlayDelta> pending_overlays_;
630 std::vector<PathOverlayDelta> published_overlays_;
631 std::vector<Coord3> pending_overlay_nodes_;
632 std::vector<Coord3> published_overlay_nodes_;
633 std::vector<CoalesceSlot> coalesce_slots_;
634 std::uint32_t pending_dirty_mask_ = 0;
635 std::uint64_t current_tick_ = 0;
636 std::uint64_t pending_first_tick_ = 0;
637 std::uint64_t pending_last_tick_ = 0;
638 std::uint32_t pending_ticks_ = 0;
639 bool pending_truncated_ =
false;
640 bool baseline_pending_ =
false;
641 bool needs_baseline_ =
false;