1use std::{collections::BTreeMap, error::Error, fmt, iter::FusedIterator, mem, slice, sync::Arc};
9
10use glam::DVec3;
11use rustc_hash::{FxHashMap, FxHashSet};
12use smallvec::SmallVec;
13use steel_registry::vanilla_entities;
14use steel_utils::locks::SyncRwLock;
15use steel_utils::{BlockPos, ChunkPos, PackedSectionPos, SectionPos, WorldAabb};
16use uuid::Uuid;
17
18use self::entries::{EntityEntries, EntitySlot};
19use self::spatial_query::EntityQueryEntries;
20use super::ticking::VehicleChain;
21use super::{
22 Entity, NullEntityCallback, RemovalReason, SharedEntity, snapshot_old_pos_and_rot_for_tick,
23 tick_vehicle_passengers_if,
24};
25
26const ENTITY_SPATIAL_CELL_SIZE: f64 = 4.0;
28
29const ENTITY_SPATIAL_CELL_OFFSET: f64 = ENTITY_SPATIAL_CELL_SIZE / 2.0;
31
32const MAX_DIRECT_SPATIAL_CELL_PROBES: i128 = 4_096;
34const INLINE_QUERY_CAPACITY: usize = 32;
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37struct EntitySpatialCell {
38 x: i32,
39 y: i32,
40 z: i32,
41}
42
43impl EntitySpatialCell {
44 fn containing(x: f64, y: f64, z: f64) -> Self {
45 Self {
46 x: ((x + ENTITY_SPATIAL_CELL_OFFSET) / ENTITY_SPATIAL_CELL_SIZE).floor() as i32,
47 y: ((y + ENTITY_SPATIAL_CELL_OFFSET) / ENTITY_SPATIAL_CELL_SIZE).floor() as i32,
48 z: ((z + ENTITY_SPATIAL_CELL_OFFSET) / ENTITY_SPATIAL_CELL_SIZE).floor() as i32,
49 }
50 }
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
54struct EntityQueryOrder {
55 section: PackedSectionPos,
56 insertion: u64,
57}
58
59#[derive(Clone, Copy, Debug, PartialEq, Eq)]
61pub(crate) enum EntityCollisionCandidates {
62 All,
64 HardCollisionRelevant,
66}
67
68impl EntityCollisionCandidates {
69 #[must_use]
71 pub(crate) fn for_source(source: Option<&dyn Entity>) -> Self {
72 if source.is_some_and(Entity::is_hard_collision_relevant) {
73 Self::All
74 } else {
75 Self::HardCollisionRelevant
76 }
77 }
78}
79
80#[derive(Debug, Clone, Copy)]
82struct RetainedSpatialCells {
83 previous: Option<EntitySpatialCellBounds>,
85 current: Option<EntitySpatialCellBounds>,
87}
88
89impl RetainedSpatialCells {
90 const NONE: Self = Self {
91 previous: None,
92 current: None,
93 };
94
95 const fn between(previous: &[EntitySpatialCell], current: &[EntitySpatialCell]) -> Self {
96 if !EntitySpatialCellBounds::cells_overlap(previous, current) {
97 return Self::NONE;
98 }
99
100 Self {
101 previous: EntitySpatialCellBounds::from_cells(previous),
102 current: EntitySpatialCellBounds::from_cells(current),
103 }
104 }
105}
106
107#[derive(Debug, Clone, Copy)]
108struct EntitySpatialCellBounds {
109 minimum: EntitySpatialCell,
110 maximum: EntitySpatialCell,
111}
112
113impl EntitySpatialCellBounds {
114 fn from_aabb(aabb: &WorldAabb) -> Self {
115 Self {
116 minimum: EntitySpatialCell::containing(aabb.min_x(), aabb.min_y(), aabb.min_z()),
117 maximum: EntitySpatialCell::containing(aabb.max_x(), aabb.max_y(), aabb.max_z()),
118 }
119 }
120
121 const fn from_cells(cells: &[EntitySpatialCell]) -> Option<Self> {
122 let (Some(&minimum), Some(&maximum)) = (cells.first(), cells.last()) else {
124 return None;
125 };
126
127 Some(Self { minimum, maximum })
128 }
129
130 const fn cells_overlap(first: &[EntitySpatialCell], second: &[EntitySpatialCell]) -> bool {
131 let (Some(first), Some(second)) = (Self::from_cells(first), Self::from_cells(second))
132 else {
133 return false;
134 };
135
136 first.minimum.x <= second.maximum.x
137 && second.minimum.x <= first.maximum.x
138 && first.minimum.y <= second.maximum.y
139 && second.minimum.y <= first.maximum.y
140 && first.minimum.z <= second.maximum.z
141 && second.minimum.z <= first.maximum.z
142 }
143
144 const fn contains(self, cell: EntitySpatialCell) -> bool {
145 cell.x >= self.minimum.x
146 && cell.x <= self.maximum.x
147 && cell.y >= self.minimum.y
148 && cell.y <= self.maximum.y
149 && cell.z >= self.minimum.z
150 && cell.z <= self.maximum.z
151 }
152
153 fn direct_probe_count(self) -> i128 {
154 let width = i128::from(self.maximum.x) - i128::from(self.minimum.x) + 1;
155 let height = i128::from(self.maximum.y) - i128::from(self.minimum.y) + 1;
156 let depth = i128::from(self.maximum.z) - i128::from(self.minimum.z) + 1;
157 width * height * depth
158 }
159
160 fn cells(self) -> SmallVec<[EntitySpatialCell; 8]> {
161 let mut cells = SmallVec::new();
162 for x in self.minimum.x..=self.maximum.x {
163 for y in self.minimum.y..=self.maximum.y {
164 for z in self.minimum.z..=self.maximum.z {
165 cells.push(EntitySpatialCell { x, y, z });
166 }
167 }
168 }
169 cells
170 }
171}
172
173#[derive(Debug, Clone, PartialEq, Eq)]
175pub enum AddEntityError {
176 ChunkNotLoaded {
178 entity_id: i32,
180 chunk: ChunkPos,
182 },
183 DuplicateUuid {
185 entity_id: i32,
187 uuid: Uuid,
189 },
190 DuplicateId {
192 entity_id: i32,
194 },
195 RemovedEntity {
197 entity_id: i32,
199 },
200}
201
202impl fmt::Display for AddEntityError {
203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204 match self {
205 Self::ChunkNotLoaded { entity_id, chunk } => {
206 write!(f, "entity {entity_id} is in non-loaded chunk {chunk:?}")
207 }
208 Self::DuplicateUuid { entity_id, uuid } => {
209 write!(f, "entity {entity_id} has duplicate UUID {uuid}")
210 }
211 Self::DuplicateId { entity_id } => {
212 write!(f, "entity {entity_id} has a duplicate runtime ID")
213 }
214 Self::RemovedEntity { entity_id } => {
215 write!(f, "entity {entity_id} is already removed")
216 }
217 }
218 }
219}
220
221impl Error for AddEntityError {}
222
223#[derive(Debug, Clone, PartialEq, Eq)]
225pub enum EntityMoveError {
226 NotLive {
228 entity_id: i32,
230 },
231 Inactive {
233 entity_id: i32,
235 },
236 UnloadedDestination {
238 entity_id: i32,
240 chunk: ChunkPos,
242 },
243 InvalidSleepPosition {
245 entity_id: i32,
247 bed_position: BlockPos,
249 },
250}
251
252impl fmt::Display for EntityMoveError {
253 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254 match self {
255 Self::NotLive { entity_id } => {
256 write!(f, "entity {entity_id} is not live in the world")
257 }
258 Self::Inactive { entity_id } => {
259 write!(f, "entity {entity_id} is inactive outside live world state")
260 }
261 Self::UnloadedDestination { entity_id, chunk } => {
262 write!(
263 f,
264 "entity {entity_id} cannot move into non-loaded chunk {chunk:?}"
265 )
266 }
267 Self::InvalidSleepPosition {
268 entity_id,
269 bed_position,
270 } => {
271 write!(
272 f,
273 "entity {entity_id} cannot sleep at invalid bed position {bed_position:?}"
274 )
275 }
276 }
277 }
278}
279
280impl Error for EntityMoveError {}
281
282#[derive(Debug, Clone, Copy, PartialEq, Eq)]
284pub enum EntityOwnership {
285 ManagerOwned,
287 External,
289}
290
291#[derive(Debug, Clone, Copy, PartialEq, Eq)]
297pub enum EntityVisibility {
298 Hidden,
300 Tracked,
302 Ticking,
304}
305
306impl EntityVisibility {
307 #[must_use]
309 pub const fn is_accessible(self) -> bool {
310 matches!(self, Self::Tracked | Self::Ticking)
311 }
312
313 #[must_use]
315 pub const fn is_ticking(self) -> bool {
316 matches!(self, Self::Ticking)
317 }
318}
319
320#[derive(Default)]
322pub struct EntityLifecycleChanges {
323 pub tracking_started: Vec<SharedEntity>,
325 pub tracking_stopped: Vec<SharedEntity>,
327 pub ticking_started: Vec<SharedEntity>,
329 pub ticking_stopped: Vec<SharedEntity>,
331}
332
333impl fmt::Debug for EntityLifecycleChanges {
334 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335 f.debug_struct("EntityLifecycleChanges")
336 .field("tracking_started", &self.tracking_started.len())
337 .field("tracking_stopped", &self.tracking_stopped.len())
338 .field("ticking_started", &self.ticking_started.len())
339 .field("ticking_stopped", &self.ticking_stopped.len())
340 .finish()
341 }
342}
343
344impl EntityLifecycleChanges {
345 fn extend(&mut self, other: Self) {
346 self.tracking_started.extend(other.tracking_started);
347 self.tracking_stopped.extend(other.tracking_stopped);
348 self.ticking_started.extend(other.ticking_started);
349 self.ticking_stopped.extend(other.ticking_stopped);
350 }
351}
352
353#[derive(Debug, Clone)]
355pub struct EntityMoveUpdate {
356 pub entity_id: i32,
358 pub old_section: SectionPos,
360 pub new_section: SectionPos,
362 pub old_chunk: ChunkPos,
364 pub new_chunk: ChunkPos,
366 pub old_accessible: bool,
368 pub new_accessible: bool,
370 pub old_ticking: bool,
372 pub new_ticking: bool,
374}
375
376impl EntityMoveUpdate {
377 #[must_use]
379 pub fn section_changed(&self) -> bool {
380 self.old_section != self.new_section
381 }
382
383 #[must_use]
385 pub fn chunk_changed(&self) -> bool {
386 self.old_chunk != self.new_chunk
387 }
388
389 #[must_use]
391 pub const fn accessibility_changed(&self) -> bool {
392 self.old_accessible != self.new_accessible
393 }
394
395 #[must_use]
397 pub const fn became_accessible(&self) -> bool {
398 !self.old_accessible && self.new_accessible
399 }
400
401 #[must_use]
403 pub const fn became_inaccessible(&self) -> bool {
404 self.old_accessible && !self.new_accessible
405 }
406
407 #[must_use]
409 pub const fn became_ticking(&self) -> bool {
410 !self.old_ticking && self.new_ticking
411 }
412
413 #[must_use]
415 pub const fn became_non_ticking(&self) -> bool {
416 self.old_ticking && !self.new_ticking
417 }
418}
419
420#[derive(Debug, Clone, PartialEq, Eq)]
422pub struct UnsavedEntityReport {
423 pub entity_id: i32,
425 pub uuid: Uuid,
427 pub chunk: ChunkPos,
429}
430
431#[derive(Default)]
433pub struct ChunkEntityLoadResult {
434 pub restored: Vec<SharedEntity>,
436 pub tracking_started: Vec<SharedEntity>,
438 pub ticking_started: Vec<SharedEntity>,
440 pub needs_save: bool,
442}
443
444#[derive(Default)]
446pub struct ChunkEntityUnloadStart {
447 pub retained: Vec<SharedEntity>,
449 pub tracking_stopped: Vec<SharedEntity>,
451 pub ticking_stopped: Vec<SharedEntity>,
453}
454
455#[derive(Clone)]
456struct EntityEntry {
457 entity: SharedEntity,
458 _damage_history_owner: Arc<()>,
459 uuid: Uuid,
460 section: SectionPos,
461 chunk: ChunkPos,
462 bounding_box: WorldAabb,
463 spatial_cells: SmallVec<[EntitySpatialCell; 8]>,
464 hard_collision_relevant: bool,
465 query_order: EntityQueryOrder,
466 accessible: bool,
467 ownership: EntityOwnership,
468}
469
470struct TickContext {
472 manager_chunk: Option<ChunkPos>,
473}
474
475impl EntityEntry {
476 fn new(entity: SharedEntity, ownership: EntityOwnership) -> Self {
477 let section = SectionPos::from_entity_pos(entity.position());
478 let chunk = ChunkPos::new(section.x(), section.z());
479 let bounding_box = entity.bounding_box();
480 let hard_collision_relevant = entity.is_hard_collision_relevant();
481 Self {
482 _damage_history_owner: entity.base().damage_history().retain_owner(),
483 uuid: entity.uuid(),
484 entity,
485 section,
486 chunk,
487 bounding_box,
488 spatial_cells: EntitySpatialCellBounds::from_aabb(&bounding_box).cells(),
489 hard_collision_relevant,
490 query_order: EntityQueryOrder {
491 section: PackedSectionPos::from(section),
492 insertion: 0,
493 },
494 accessible: false,
495 ownership,
496 }
497 }
498
499 #[must_use]
500 fn should_save(&self) -> bool {
501 self.ownership == EntityOwnership::ManagerOwned
502 && (!self.entity.is_removed()
503 || self
504 .entity
505 .removal_reason()
506 .is_some_and(RemovalReason::should_save))
507 && !self.entity.is_passenger()
508 && !self.entity.has_exactly_one_player_passenger()
509 && self.entity.entity_type().can_serialize
510 }
511}
512
513#[derive(Default)]
514struct ManagerState {
515 chunk_visibility: FxHashMap<ChunkPos, EntityVisibility>,
516 live_by_id: EntityEntries,
517 live_by_uuid: FxHashMap<Uuid, i32>,
518 accessible_order: IndexedOrderedEntityIds,
519 by_section: BTreeMap<PackedSectionPos, OrderedEntityIds>,
520 by_spatial_cell: FxHashMap<EntitySpatialCell, SpatialCellEntities>,
521 by_hard_collision_cell: FxHashMap<EntitySpatialCell, SpatialCellEntities>,
522 by_chunk: FxHashMap<ChunkPos, FxHashSet<i32>>,
523 unloading_by_chunk: FxHashMap<ChunkPos, Vec<EntityEntry>>,
524 save_pending_by_chunk: FxHashMap<ChunkPos, Vec<EntityEntry>>,
525 tick_list: EntityTickList,
526 next_section_order: u64,
527}
528
529#[derive(Default)]
530struct OrderedEntityIds {
531 ids: Vec<i32>,
532}
533
534#[derive(Default)]
535struct SpatialCellEntities {
536 entities: Vec<EntitySlot>,
537}
538
539impl OrderedEntityIds {
540 fn insert(&mut self, entity_id: i32) -> bool {
541 if self.ids.contains(&entity_id) {
542 return false;
543 }
544 self.ids.push(entity_id);
545 true
546 }
547
548 fn remove(&mut self, entity_id: i32) -> bool {
549 let Some(index) = self.ids.iter().position(|id| *id == entity_id) else {
550 return false;
551 };
552 self.ids.remove(index);
553
554 true
555 }
556
557 const fn is_empty(&self) -> bool {
558 self.ids.is_empty()
559 }
560
561 fn iter(&self) -> slice::Iter<'_, i32> {
562 self.ids.iter()
563 }
564}
565
566#[derive(Default)]
570struct IndexedOrderedEntityIds {
571 nodes: Vec<OrderedEntityIdNode>,
572 node_by_id: FxHashMap<i32, usize>,
573 first: Option<usize>,
574 last: Option<usize>,
575 free: Option<usize>,
576}
577
578struct OrderedEntityIdNode {
579 entity_id: i32,
580 previous: Option<usize>,
581 next: Option<usize>,
582}
583
584impl IndexedOrderedEntityIds {
585 const MIN_COMPACTION_SIZE: usize = 64;
586
587 fn insert(&mut self, entity_id: i32) -> bool {
588 if self.node_by_id.contains_key(&entity_id) {
589 return false;
590 }
591 let previous = self.last;
592 let index = if let Some(free) = self.free {
593 self.free = self.nodes[free].next;
594 free
595 } else {
596 let index = self.nodes.len();
597 self.nodes.push(OrderedEntityIdNode {
598 entity_id,
599 previous: None,
600 next: None,
601 });
602 index
603 };
604
605 self.nodes[index] = OrderedEntityIdNode {
606 entity_id,
607 previous,
608 next: None,
609 };
610 let replaced = self.node_by_id.insert(entity_id, index);
611 debug_assert!(replaced.is_none());
612
613 if let Some(previous) = previous {
614 self.nodes[previous].next = Some(index);
615 } else {
616 self.first = Some(index);
617 }
618 self.last = Some(index);
619 true
620 }
621
622 fn remove(&mut self, entity_id: i32) -> bool {
623 let Some(index) = self.node_by_id.remove(&entity_id) else {
624 return false;
625 };
626
627 let previous = self.nodes[index].previous;
628 let next = self.nodes[index].next;
629 if let Some(previous) = previous {
630 self.nodes[previous].next = next;
631 } else {
632 self.first = next;
633 }
634 if let Some(next) = next {
635 self.nodes[next].previous = previous;
636 } else {
637 self.last = previous;
638 }
639
640 self.nodes[index] = OrderedEntityIdNode {
641 entity_id,
642 previous: None,
643 next: self.free,
644 };
645 self.free = Some(index);
646 self.compact_if_sparse();
647 true
648 }
649
650 fn len(&self) -> usize {
651 self.node_by_id.len()
652 }
653
654 fn iter(&self) -> IndexedOrderedEntityIdsIter<'_> {
655 IndexedOrderedEntityIdsIter {
656 nodes: &self.nodes,
657 next: self.first,
658 remaining: self.len(),
659 }
660 }
661
662 fn compact_if_sparse(&mut self) {
663 let live_len = self.len();
664 if self.nodes.len() < Self::MIN_COMPACTION_SIZE || live_len > self.nodes.len() / 2 {
665 return;
666 }
667
668 let mut compacted_nodes: Vec<OrderedEntityIdNode> = Vec::with_capacity(live_len);
669 let mut compacted_node_by_id = FxHashMap::default();
670 compacted_node_by_id.reserve(live_len);
671 let mut current = self.first;
672 while let Some(index) = current {
673 let node = &self.nodes[index];
674 let compacted_index = compacted_nodes.len();
675 let previous = compacted_index.checked_sub(1);
676 if let Some(previous) = previous {
677 compacted_nodes[previous].next = Some(compacted_index);
678 }
679 compacted_nodes.push(OrderedEntityIdNode {
680 entity_id: node.entity_id,
681 previous,
682 next: None,
683 });
684 compacted_node_by_id.insert(node.entity_id, compacted_index);
685 current = node.next;
686 }
687 debug_assert_eq!(compacted_nodes.len(), live_len);
688
689 self.nodes = compacted_nodes;
690 self.node_by_id = compacted_node_by_id;
691 self.first = (!self.nodes.is_empty()).then_some(0);
692 self.last = self.nodes.len().checked_sub(1);
693 self.free = None;
694 }
695}
696
697struct IndexedOrderedEntityIdsIter<'a> {
698 nodes: &'a [OrderedEntityIdNode],
699 next: Option<usize>,
700 remaining: usize,
701}
702
703impl<'a> Iterator for IndexedOrderedEntityIdsIter<'a> {
704 type Item = &'a i32;
705
706 fn next(&mut self) -> Option<Self::Item> {
707 let index = self.next?;
708 let node = &self.nodes[index];
709 self.next = node.next;
710 self.remaining -= 1;
711 Some(&node.entity_id)
712 }
713
714 fn size_hint(&self) -> (usize, Option<usize>) {
715 (self.remaining, Some(self.remaining))
716 }
717}
718
719impl ExactSizeIterator for IndexedOrderedEntityIdsIter<'_> {}
720impl FusedIterator for IndexedOrderedEntityIdsIter<'_> {}
721
722#[derive(Default)]
723struct EntityTickList {
724 active: FxHashMap<i32, SharedEntity>,
725 order: IndexedOrderedEntityIds,
726}
727
728impl EntityTickList {
729 fn add(&mut self, entity: &SharedEntity) -> bool {
730 let entity_id = entity.id();
731 if self.active.insert(entity_id, Arc::clone(entity)).is_some() {
732 return false;
733 }
734 self.order.insert(entity_id);
735 true
736 }
737
738 fn remove(&mut self, entity_id: i32) -> Option<SharedEntity> {
739 let removed = self.active.remove(&entity_id)?;
740 let removed_from_order = self.order.remove(entity_id);
741 debug_assert!(removed_from_order);
742 Some(removed)
743 }
744
745 fn contains(&self, entity_id: i32) -> bool {
746 self.active.contains_key(&entity_id)
747 }
748
749 fn snapshot(&self) -> Vec<SharedEntity> {
750 let mut snapshot = Vec::with_capacity(self.order.len());
751 for entity_id in self.order.iter() {
752 if let Some(entity) = self.active.get(entity_id) {
753 snapshot.push(Arc::clone(entity));
754 }
755 }
756 snapshot
757 }
758}
759
760pub struct WorldEntityManager {
762 state: SyncRwLock<ManagerState>,
763}
764
765impl fmt::Debug for WorldEntityManager {
766 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
767 let state = self.state.read();
768 f.debug_struct("WorldEntityManager")
769 .field("chunk_visibility", &state.chunk_visibility.len())
770 .field("live_entities", &state.live_by_id.len())
771 .field("unloading_chunks", &state.unloading_by_chunk.len())
772 .finish()
773 }
774}
775
776impl WorldEntityManager {
777 #[must_use]
779 pub fn new() -> Self {
780 Self {
781 state: SyncRwLock::new(ManagerState::default()),
782 }
783 }
784
785 #[must_use]
790 pub(crate) fn is_chunk_loaded(&self, pos: ChunkPos) -> bool {
791 self.state.read().chunk_visibility.contains_key(&pos)
792 }
793
794 pub fn on_chunk_loaded(&self, pos: ChunkPos) -> ChunkEntityLoadResult {
796 let mut state = self.state.write();
797 state
798 .chunk_visibility
799 .entry(pos)
800 .or_insert(EntityVisibility::Hidden);
801
802 let mut result = ChunkEntityLoadResult::default();
803 if let Some(entries) = state.unloading_by_chunk.remove(&pos) {
804 result.restored.reserve(entries.len());
805 for entry in entries {
806 if entry.entity.is_removed() {
807 if entry.should_save() {
808 result.needs_save = true;
809 state
810 .save_pending_by_chunk
811 .entry(pos)
812 .or_default()
813 .push(entry);
814 }
815 continue;
816 }
817
818 let entity = Arc::clone(&entry.entity);
819 Self::insert_live_entry(&mut state, entry);
820 let lifecycle = Self::apply_entity_lifecycle_after_insert(&mut state, entity.id());
821 result.tracking_started.extend(lifecycle.tracking_started);
822 result.ticking_started.extend(lifecycle.ticking_started);
823 result.restored.push(entity);
824 }
825 }
826
827 result
828 }
829
830 pub fn update_chunk_visibility(
832 &self,
833 pos: ChunkPos,
834 visibility: EntityVisibility,
835 ) -> EntityLifecycleChanges {
836 let mut state = self.state.write();
837 let previous = state
838 .chunk_visibility
839 .insert(pos, visibility)
840 .unwrap_or(EntityVisibility::Hidden);
841
842 if previous == visibility {
843 return EntityLifecycleChanges::default();
844 }
845
846 Self::apply_chunk_visibility_change(&mut state, pos, previous, visibility)
847 }
848
849 fn push_unique_entity(
850 entity: &SharedEntity,
851 seen: &mut FxHashSet<i32>,
852 entities: &mut Vec<SharedEntity>,
853 ) {
854 if seen.insert(entity.id()) {
855 entities.push(Arc::clone(entity));
856 }
857 }
858
859 pub fn begin_chunk_unload(&self, pos: ChunkPos) -> ChunkEntityUnloadStart {
862 let mut state = self.state.write();
863 let previous_visibility = state
864 .chunk_visibility
865 .remove(&pos)
866 .unwrap_or(EntityVisibility::Hidden);
867
868 let ids = Self::entity_ids_in_chunk_order(&state, pos);
869
870 let mut result = ChunkEntityUnloadStart::default();
871 let lifecycle = Self::apply_chunk_visibility_change(
872 &mut state,
873 pos,
874 previous_visibility,
875 EntityVisibility::Hidden,
876 );
877 let mut tracking_stopped_ids = lifecycle
878 .tracking_stopped
879 .iter()
880 .map(|entity| entity.id())
881 .collect::<FxHashSet<_>>();
882 result.tracking_stopped = lifecycle.tracking_stopped;
883 result.ticking_stopped = lifecycle.ticking_stopped;
884
885 let mut root_ids = Vec::new();
886
887 for entity_id in ids {
888 let Some(entry) = state.live_by_id.get(entity_id) else {
889 continue;
890 };
891 if entry.ownership != EntityOwnership::ManagerOwned {
892 continue;
893 }
894
895 Self::push_unique_entity(
896 &entry.entity,
897 &mut tracking_stopped_ids,
898 &mut result.tracking_stopped,
899 );
900 if !entry.entity.is_passenger() {
901 root_ids.push(entity_id);
902 }
903 }
904
905 let mut retained = Vec::new();
906 let mut visited = FxHashSet::default();
907 for entity_id in root_ids {
908 Self::retain_unloading_entity_tree(
909 &mut state,
910 entity_id,
911 &mut visited,
912 &mut retained,
913 &mut result.retained,
914 &mut tracking_stopped_ids,
915 &mut result.tracking_stopped,
916 );
917 }
918
919 if !retained.is_empty() {
920 state
921 .unloading_by_chunk
922 .entry(pos)
923 .or_default()
924 .extend(retained);
925 }
926
927 result
928 }
929
930 fn retain_unloading_entity_tree(
931 state: &mut ManagerState,
932 entity_id: i32,
933 visited: &mut FxHashSet<i32>,
934 retained: &mut Vec<EntityEntry>,
935 retained_entities: &mut Vec<SharedEntity>,
936 tracking_stopped_ids: &mut FxHashSet<i32>,
937 tracking_stopped: &mut Vec<SharedEntity>,
938 ) {
939 if !visited.insert(entity_id) {
940 return;
941 }
942
943 let Some(entry) = Self::remove_live_entry(state, entity_id) else {
944 return;
945 };
946
947 if entry.ownership != EntityOwnership::ManagerOwned {
948 let restored_id = entry.entity.id();
949 Self::insert_live_entry(state, entry);
950
951 let entity_to_tick = state.live_by_id.get(restored_id).and_then(|entry| {
952 let visibility = Self::lifecycle_visibility_for(
953 entry,
954 Self::chunk_visibility(state, entry.chunk),
955 );
956 visibility.is_ticking().then(|| Arc::clone(&entry.entity))
957 });
958
959 if let Some(entity) = entity_to_tick {
960 state.tick_list.add(&entity);
961 }
962
963 return;
964 }
965
966 let passengers = entry.entity.passengers();
967 Self::push_unique_entity(&entry.entity, tracking_stopped_ids, tracking_stopped);
968 retained_entities.push(Arc::clone(&entry.entity));
969 retained.push(entry);
970 for passenger in passengers {
971 Self::retain_unloading_entity_tree(
972 state,
973 passenger.id(),
974 visited,
975 retained,
976 retained_entities,
977 tracking_stopped_ids,
978 tracking_stopped,
979 );
980 }
981 }
982
983 pub fn finalize_chunk_unload(&self, pos: ChunkPos) {
985 let entries = self
986 .state
987 .write()
988 .unloading_by_chunk
989 .remove(&pos)
990 .unwrap_or_default();
991
992 for entry in entries {
993 entry
994 .entity
995 .set_level_callback(Arc::new(NullEntityCallback));
996 entry.entity.set_removed(RemovalReason::UnloadedToChunk);
997 }
998 }
999
1000 pub fn add_live_entity(
1007 &self,
1008 entity: SharedEntity,
1009 ownership: EntityOwnership,
1010 ) -> Result<EntityLifecycleChanges, AddEntityError> {
1011 let entry = Self::checked_live_entry(entity, ownership)?;
1012 let entity_id = entry.entity.id();
1013 let mut state = self.state.write();
1014 assert!(
1015 !Self::contains_id(&state, entity_id),
1016 "entity id {entity_id} is already registered in the world entity manager"
1017 );
1018 Self::validate_live_entries(&state, slice::from_ref(&entry), ownership, true)?;
1019 Self::insert_live_entry(&mut state, entry);
1020 Ok(Self::apply_entity_lifecycle_after_insert(
1021 &mut state, entity_id,
1022 ))
1023 }
1024
1025 pub fn add_live_entity_tree(
1031 &self,
1032 entities: &[SharedEntity],
1033 ownership: EntityOwnership,
1034 ) -> Result<EntityLifecycleChanges, AddEntityError> {
1035 let mut entries = Vec::with_capacity(entities.len());
1036 for entity in entities {
1037 entries.push(Self::checked_live_entry(Arc::clone(entity), ownership)?);
1038 }
1039
1040 let mut seen_ids = FxHashSet::default();
1041 let mut seen_uuids = FxHashSet::default();
1042 for entry in &entries {
1043 let entity_id = entry.entity.id();
1044 if !seen_ids.insert(entity_id) {
1045 return Err(AddEntityError::DuplicateId { entity_id });
1046 }
1047 if !seen_uuids.insert(entry.uuid) {
1048 return Err(AddEntityError::DuplicateUuid {
1049 entity_id,
1050 uuid: entry.uuid,
1051 });
1052 }
1053 }
1054
1055 let mut state = self.state.write();
1056 Self::validate_live_entries(&state, &entries, ownership, false)?;
1057 let entity_ids = entries
1058 .iter()
1059 .map(|entry| entry.entity.id())
1060 .collect::<Vec<_>>();
1061 for entry in entries {
1062 Self::insert_live_entry(&mut state, entry);
1063 }
1064 let mut lifecycle = EntityLifecycleChanges::default();
1065 for entity_id in entity_ids {
1066 lifecycle.extend(Self::apply_entity_lifecycle_after_insert(
1067 &mut state, entity_id,
1068 ));
1069 }
1070 Ok(lifecycle)
1071 }
1072
1073 fn checked_live_entry(
1074 entity: SharedEntity,
1075 ownership: EntityOwnership,
1076 ) -> Result<EntityEntry, AddEntityError> {
1077 if entity.is_removed() {
1078 return Err(AddEntityError::RemovedEntity {
1079 entity_id: entity.id(),
1080 });
1081 }
1082
1083 Ok(EntityEntry::new(entity, ownership))
1084 }
1085
1086 fn validate_live_entries(
1087 state: &ManagerState,
1088 entries: &[EntityEntry],
1089 ownership: EntityOwnership,
1090 require_loaded_chunks: bool,
1091 ) -> Result<(), AddEntityError> {
1092 for entry in entries {
1093 let entity_id = entry.entity.id();
1094 if Self::contains_id(state, entity_id) {
1095 return Err(AddEntityError::DuplicateId { entity_id });
1096 }
1097 if Self::contains_uuid(state, entry.uuid) {
1098 return Err(AddEntityError::DuplicateUuid {
1099 entity_id,
1100 uuid: entry.uuid,
1101 });
1102 }
1103 if require_loaded_chunks
1104 && ownership == EntityOwnership::ManagerOwned
1105 && !state.chunk_visibility.contains_key(&entry.chunk)
1106 {
1107 return Err(AddEntityError::ChunkNotLoaded {
1108 entity_id,
1109 chunk: entry.chunk,
1110 });
1111 }
1112 }
1113 Ok(())
1114 }
1115
1116 pub fn remove_live_entity(
1118 &self,
1119 entity_id: i32,
1120 reason: RemovalReason,
1121 ) -> Option<SharedEntity> {
1122 let mut state = self.state.write();
1123 let entry = Self::remove_live_entry(&mut state, entity_id)?;
1124 let entity = Arc::clone(&entry.entity);
1125
1126 if reason.should_save() && entry.should_save() {
1127 state
1128 .save_pending_by_chunk
1129 .entry(entry.chunk)
1130 .or_default()
1131 .push(entry);
1132 }
1133
1134 Some(entity)
1135 }
1136
1137 pub fn on_chunk_saved(&self, chunk: ChunkPos, saved_entity_ids: &[i32]) {
1139 if saved_entity_ids.is_empty() {
1140 return;
1141 }
1142
1143 let saved_entity_ids = saved_entity_ids.iter().copied().collect::<FxHashSet<_>>();
1144 let mut state = self.state.write();
1145 let Some(entries) = state.save_pending_by_chunk.get_mut(&chunk) else {
1146 return;
1147 };
1148
1149 entries.retain(|entry| !saved_entity_ids.contains(&entry.entity.id()));
1150 if entries.is_empty() {
1151 state.save_pending_by_chunk.remove(&chunk);
1152 }
1153 }
1154
1155 #[must_use]
1157 pub fn has_save_pending_for_chunk(&self, chunk: ChunkPos) -> bool {
1158 self.state
1159 .read()
1160 .save_pending_by_chunk
1161 .get(&chunk)
1162 .is_some_and(|entries| !entries.is_empty())
1163 }
1164
1165 pub fn validate_move(&self, entity_id: i32, new_pos: DVec3) -> Result<(), EntityMoveError> {
1167 let state = self.state.read();
1168
1169 let Some(entry) = state.live_by_id.get(entity_id) else {
1170 return Err(EntityMoveError::NotLive { entity_id });
1171 };
1172
1173 if entry.ownership == EntityOwnership::ManagerOwned {
1174 let new_section = SectionPos::from_entity_pos(new_pos);
1175 let new_chunk = ChunkPos::new(new_section.x(), new_section.z());
1176 if !Self::can_move_manager_owned_to_chunk(&state, entry, new_chunk) {
1177 return Err(EntityMoveError::UnloadedDestination {
1178 entity_id,
1179 chunk: new_chunk,
1180 });
1181 }
1182 }
1183
1184 Ok(())
1185 }
1186
1187 #[expect(
1189 clippy::too_many_lines,
1190 reason = "keeps movement index updates atomic under one manager write lock"
1191 )]
1192 pub fn commit_move(
1193 &self,
1194 entity_id: i32,
1195 new_pos: DVec3,
1196 ) -> Result<EntityMoveUpdate, EntityMoveError> {
1197 let mut state = self.state.write();
1198
1199 let Some(current) = state.live_by_id.get(entity_id) else {
1200 return Err(EntityMoveError::NotLive { entity_id });
1201 };
1202
1203 let new_section = SectionPos::from_entity_pos(new_pos);
1204 let new_chunk = ChunkPos::new(new_section.x(), new_section.z());
1205
1206 if current.ownership == EntityOwnership::ManagerOwned
1207 && !Self::can_move_manager_owned_to_chunk(&state, current, new_chunk)
1208 {
1209 return Err(EntityMoveError::UnloadedDestination {
1210 entity_id,
1211 chunk: new_chunk,
1212 });
1213 }
1214
1215 let old_section = current.section;
1216 let old_chunk = current.chunk;
1217 let old_accessible = Self::is_accessible(&state, current);
1218 let new_accessible = Self::is_accessible_at(&state, current.ownership, new_chunk);
1219 let old_visibility =
1220 Self::lifecycle_visibility_for(current, Self::chunk_visibility(&state, old_chunk));
1221 let new_visibility =
1222 Self::lifecycle_visibility_for(current, Self::chunk_visibility(&state, new_chunk));
1223 let old_ticking = old_visibility.is_ticking();
1224 let new_ticking = new_visibility.is_ticking();
1225
1226 let new_bounding_box = current.entity.bounding_box();
1227 let new_spatial_cells = EntitySpatialCellBounds::from_aabb(&new_bounding_box).cells();
1228 let spatial_cells_changed = current.spatial_cells != new_spatial_cells;
1229 let section_changed = old_section != new_section;
1230 let spatial_index_changed = spatial_cells_changed || section_changed;
1231 let current_section_order = current.query_order.insertion;
1232 let hard_collision_relevant = current.hard_collision_relevant;
1233
1234 let Some(entity_slot) = state.live_by_id.slot(entity_id) else {
1235 return Err(EntityMoveError::NotLive { entity_id });
1236 };
1237
1238 if !section_changed && !spatial_cells_changed {
1239 if let Some(entry) = state.live_by_id.get_mut(entity_id) {
1240 entry.bounding_box = new_bounding_box;
1241 }
1242
1243 return Ok(EntityMoveUpdate {
1244 entity_id,
1245 old_section,
1246 new_section,
1247 old_chunk,
1248 new_chunk,
1249 old_accessible,
1250 new_accessible,
1251 old_ticking,
1252 new_ticking,
1253 });
1254 }
1255
1256 let retained = if section_changed {
1258 RetainedSpatialCells::NONE
1259 } else {
1260 RetainedSpatialCells::between(¤t.spatial_cells, &new_spatial_cells)
1261 };
1262
1263 if section_changed {
1264 Self::remove_from_section(&mut state, old_section, entity_id);
1265 Self::remove_from_chunk(&mut state, old_chunk, entity_id);
1266 }
1267
1268 let new_section_order = if section_changed {
1269 Self::next_section_order(&mut state)
1270 } else {
1271 current_section_order
1272 };
1273
1274 if spatial_index_changed {
1275 let Some(entry) = state.live_by_id.get_mut(entity_id) else {
1276 return Err(EntityMoveError::NotLive { entity_id });
1277 };
1278
1279 let previous_cells = mem::take(&mut entry.spatial_cells);
1280 Self::remove_from_spatial_indexes(
1281 &mut state,
1282 &previous_cells,
1283 entity_slot,
1284 hard_collision_relevant,
1285 retained.current,
1286 );
1287 }
1288
1289 if let Some(entry) = state.live_by_id.get_mut(entity_id) {
1290 entry.section = new_section;
1291 entry.chunk = new_chunk;
1292 entry.bounding_box = new_bounding_box;
1293 entry.accessible = new_accessible;
1294
1295 if spatial_index_changed {
1296 entry.spatial_cells = new_spatial_cells;
1297 }
1298
1299 if section_changed {
1300 entry.query_order = EntityQueryOrder {
1301 section: PackedSectionPos::from(new_section),
1302 insertion: new_section_order,
1303 };
1304 }
1305 }
1306
1307 if spatial_index_changed {
1308 Self::insert_into_spatial_indexes(
1309 &mut state,
1310 entity_slot,
1311 hard_collision_relevant,
1312 retained.previous,
1313 );
1314 }
1315
1316 if section_changed {
1317 state
1318 .by_section
1319 .entry(PackedSectionPos::from(new_section))
1320 .or_default()
1321 .insert(entity_id);
1322 state
1323 .by_chunk
1324 .entry(new_chunk)
1325 .or_default()
1326 .insert(entity_id);
1327 }
1328
1329 if old_accessible && !new_accessible {
1330 state.accessible_order.remove(entity_id);
1331 } else if !old_accessible && new_accessible {
1332 state.accessible_order.insert(entity_id);
1333 }
1334
1335 if old_ticking && !new_ticking {
1336 state.tick_list.remove(entity_id);
1337 } else if !old_ticking && new_ticking {
1338 let ManagerState {
1339 live_by_id,
1340 tick_list,
1341 ..
1342 } = &mut *state;
1343
1344 if let Some(entry) = live_by_id.get(entity_id) {
1345 tick_list.add(&entry.entity);
1346 }
1347 }
1348
1349 Ok(EntityMoveUpdate {
1350 entity_id,
1351 old_section,
1352 new_section,
1353 old_chunk,
1354 new_chunk,
1355 old_accessible,
1356 new_accessible,
1357 old_ticking,
1358 new_ticking,
1359 })
1360 }
1361
1362 pub fn commit_bounding_box_change(&self, entity_id: i32) {
1367 let mut state = self.state.write();
1368
1369 let Some(current) = state.live_by_id.get(entity_id) else {
1370 return;
1371 };
1372
1373 let bounding_box = current.entity.bounding_box();
1374 let new_spatial_cells = EntitySpatialCellBounds::from_aabb(&bounding_box).cells();
1375 let hard_collision_relevant = current.hard_collision_relevant;
1376
1377 if current.spatial_cells == new_spatial_cells {
1378 if let Some(entry) = state.live_by_id.get_mut(entity_id) {
1379 entry.bounding_box = bounding_box;
1380 }
1381
1382 return;
1383 }
1384
1385 let retained = RetainedSpatialCells::between(¤t.spatial_cells, &new_spatial_cells);
1386
1387 let Some(entry) = state.live_by_id.get_mut(entity_id) else {
1388 return;
1389 };
1390
1391 let previous_cells = mem::take(&mut entry.spatial_cells);
1392
1393 let Some(entity_slot) = state.live_by_id.slot(entity_id) else {
1394 return;
1395 };
1396
1397 Self::remove_from_spatial_indexes(
1398 &mut state,
1399 &previous_cells,
1400 entity_slot,
1401 hard_collision_relevant,
1402 retained.current,
1403 );
1404
1405 if let Some(entry) = state.live_by_id.get_mut(entity_id) {
1406 entry.bounding_box = bounding_box;
1407 entry.spatial_cells = new_spatial_cells;
1408 }
1409
1410 Self::insert_into_spatial_indexes(
1411 &mut state,
1412 entity_slot,
1413 hard_collision_relevant,
1414 retained.previous,
1415 );
1416 }
1417
1418 fn can_move_manager_owned_to_chunk(
1419 state: &ManagerState,
1420 entry: &EntityEntry,
1421 new_chunk: ChunkPos,
1422 ) -> bool {
1423 state.chunk_visibility.contains_key(&new_chunk)
1424 || (entry.entity.is_passenger()
1425 && Self::has_live_loaded_root_vehicle(state, &entry.entity))
1426 }
1427
1428 fn has_live_loaded_root_vehicle(state: &ManagerState, entity: &SharedEntity) -> bool {
1429 let mut visited = FxHashSet::default();
1430 visited.insert(entity.id());
1431
1432 let mut passenger = Arc::clone(entity);
1433 let Some(mut vehicle) = passenger.vehicle() else {
1434 return false;
1435 };
1436
1437 loop {
1438 assert!(
1439 visited.insert(vehicle.id()),
1440 "cyclic passenger relationship involving entity {}",
1441 entity.id()
1442 );
1443 if vehicle.is_removed() || !vehicle.has_passenger(passenger.as_ref()) {
1444 return false;
1445 }
1446
1447 let Some(vehicle_entry) = state.live_by_id.get(vehicle.id()) else {
1448 return false;
1449 };
1450
1451 let Some(next_vehicle) = vehicle.vehicle() else {
1452 return match vehicle_entry.ownership {
1453 EntityOwnership::External => true,
1454 EntityOwnership::ManagerOwned => {
1455 state.chunk_visibility.contains_key(&vehicle_entry.chunk)
1456 }
1457 };
1458 };
1459
1460 passenger = vehicle;
1461 vehicle = next_vehicle;
1462 }
1463 }
1464
1465 #[must_use]
1466 pub fn get_by_id(&self, entity_id: i32) -> Option<SharedEntity> {
1468 self.state
1469 .read()
1470 .live_by_id
1471 .get(entity_id)
1472 .map(|entry| Arc::clone(&entry.entity))
1473 }
1474
1475 pub fn contains_live_or_unloading_entity(&self, entity: &SharedEntity) -> bool {
1477 let state = self.state.read();
1478 state
1479 .live_by_id
1480 .get(entity.id())
1481 .is_some_and(|entry| Arc::ptr_eq(&entry.entity, entity))
1482 || state
1483 .unloading_by_chunk
1484 .values()
1485 .flatten()
1486 .any(|entry| Arc::ptr_eq(&entry.entity, entity))
1487 }
1488
1489 #[must_use]
1490 pub fn get_accessible_by_id(&self, entity_id: i32) -> Option<SharedEntity> {
1492 let state = self.state.read();
1493 let entry = state.live_by_id.get(entity_id)?;
1494 entry.accessible.then(|| Arc::clone(&entry.entity))
1495 }
1496
1497 #[must_use]
1498 pub fn get_by_uuid(&self, uuid: &Uuid) -> Option<SharedEntity> {
1500 let state = self.state.read();
1501 let entity_id = state.live_by_uuid.get(uuid)?;
1502 state
1503 .live_by_id
1504 .get(*entity_id)
1505 .map(|entry| Arc::clone(&entry.entity))
1506 }
1507
1508 #[must_use]
1509 pub fn get_entities_in_aabb_matching(
1511 &self,
1512 aabb: &WorldAabb,
1513 mut predicate: impl FnMut(&dyn Entity) -> bool,
1514 ) -> Vec<SharedEntity> {
1515 self.get_entities_in_aabb(aabb)
1516 .into_iter()
1517 .filter(|entity| predicate(entity.as_ref()))
1518 .collect()
1519 }
1520
1521 #[must_use]
1523 pub fn has_entity_in_aabb_matching(
1524 &self,
1525 aabb: &WorldAabb,
1526 mut predicate: impl FnMut(&dyn Entity) -> bool,
1527 ) -> bool {
1528 let state = self.state.read();
1529
1530 for entry in Self::entity_query_entries(&state, aabb) {
1531 if entry.accessible && predicate(entry.entity.as_ref()) {
1532 return true;
1533 }
1534 }
1535
1536 false
1537 }
1538
1539 #[must_use]
1541 pub fn get_entity_bounding_boxes_in_aabb_matching(
1542 &self,
1543 aabb: &WorldAabb,
1544 mut predicate: impl FnMut(&dyn Entity) -> bool,
1545 ) -> Vec<WorldAabb> {
1546 let state = self.state.read();
1547 let mut result = Vec::new();
1548
1549 for entry in Self::entity_query_entries(&state, aabb) {
1550 if entry.accessible && predicate(entry.entity.as_ref()) {
1551 result.push(entry.bounding_box);
1552 }
1553 }
1554
1555 result
1556 }
1557
1558 #[must_use]
1563 pub(crate) fn get_movement_collision_boxes_in_aabb_matching(
1564 &self,
1565 aabb: &WorldAabb,
1566 candidates: EntityCollisionCandidates,
1567 mut predicate: impl FnMut(&dyn Entity) -> bool,
1568 ) -> Vec<WorldAabb> {
1569 let colliding_entities = self
1570 .movement_collision_candidate_snapshot(aabb, candidates)
1571 .into_iter()
1572 .filter(|entity| entity.bounding_box().intersects(*aabb) && predicate(entity.as_ref()))
1573 .collect::<SmallVec<[SharedEntity; 8]>>();
1574
1575 colliding_entities
1579 .into_iter()
1580 .map(|entity| entity.bounding_box())
1581 .collect()
1582 }
1583
1584 #[must_use]
1586 pub(crate) fn has_movement_collision_in_aabb_matching(
1587 &self,
1588 aabb: &WorldAabb,
1589 candidates: EntityCollisionCandidates,
1590 mut predicate: impl FnMut(&dyn Entity) -> bool,
1591 ) -> bool {
1592 self.movement_collision_candidate_snapshot(aabb, candidates)
1593 .into_iter()
1594 .any(|entity| entity.bounding_box().intersects(*aabb) && predicate(entity.as_ref()))
1595 }
1596
1597 fn movement_collision_candidate_snapshot(
1598 &self,
1599 aabb: &WorldAabb,
1600 candidates: EntityCollisionCandidates,
1601 ) -> SmallVec<[SharedEntity; 8]> {
1602 let state = self.state.read();
1603 let mut snapshot = SmallVec::new();
1604
1605 Self::query_entries(&state, aabb, candidates, None).for_each(|entry| {
1606 if entry.accessible {
1607 snapshot.push(Arc::clone(&entry.entity));
1608 }
1609 });
1610
1611 snapshot
1612 }
1613
1614 #[must_use]
1615 pub fn nearest_entity_in_aabb_matching(
1617 &self,
1618 aabb: &WorldAabb,
1619 origin: DVec3,
1620 mut predicate: impl FnMut(&dyn Entity) -> bool,
1621 ) -> Option<SharedEntity> {
1622 self.get_entities_in_aabb(aabb)
1623 .into_iter()
1624 .filter(|entity| predicate(entity.as_ref()))
1625 .min_by(|first, second| {
1626 first
1627 .position()
1628 .distance_squared(origin)
1629 .total_cmp(&second.position().distance_squared(origin))
1630 })
1631 }
1632
1633 #[must_use]
1634 pub fn get_entities_in_aabb(&self, aabb: &WorldAabb) -> Vec<SharedEntity> {
1636 let state = self.state.read();
1637 let mut result = Vec::new();
1638
1639 Self::entity_query_entries(&state, aabb).for_each(|entry| {
1640 if entry.accessible {
1641 result.push(Arc::clone(&entry.entity));
1642 }
1643 });
1644
1645 result
1646 }
1647
1648 pub(crate) fn try_with_entities_in_aabb(
1653 &self,
1654 aabb: &WorldAabb,
1655 borrow_limit: Option<usize>,
1656 action: impl FnOnce(&[&SharedEntity]) -> bool,
1657 ) -> Option<Vec<SharedEntity>> {
1658 let state = self.state.read();
1659 let mut entries = Self::entity_query_entries(&state, aabb).filter(|entry| entry.accessible);
1660 let mut entities = SmallVec::<[&SharedEntity; INLINE_QUERY_CAPACITY]>::new();
1661
1662 if let Some(limit) = borrow_limit {
1663 entries
1664 .by_ref()
1665 .take(limit)
1666 .for_each(|entry| entities.push(&entry.entity));
1667
1668 if let Some(next) = entries.next() {
1669 let mut snapshot = Vec::with_capacity(entities.capacity());
1670 snapshot.extend(entities.iter().map(|entity| Arc::clone(entity)));
1671 snapshot.push(Arc::clone(&next.entity));
1672 entries.for_each(|entry| snapshot.push(Arc::clone(&entry.entity)));
1673
1674 return Some(snapshot);
1675 }
1676 } else {
1677 entries.for_each(|entry| entities.push(&entry.entity));
1678 }
1679
1680 if action(&entities) {
1681 None
1682 } else {
1683 Some(entities.into_iter().map(Arc::clone).collect())
1684 }
1685 }
1686
1687 #[must_use]
1689 pub fn get_accessible_entities(&self) -> Vec<SharedEntity> {
1690 let state = self.state.read();
1691 state
1692 .accessible_order
1693 .iter()
1694 .filter_map(|entity_id| state.live_by_id.get(*entity_id))
1695 .filter(|entry| entry.accessible)
1696 .map(|entry| Arc::clone(&entry.entity))
1697 .collect()
1698 }
1699
1700 fn entity_query_entries<'a>(
1701 state: &'a ManagerState,
1702 aabb: &WorldAabb,
1703 ) -> EntityQueryEntries<'a> {
1704 Self::query_entries(state, aabb, EntityCollisionCandidates::All, Some(aabb))
1705 }
1706
1707 fn query_entries<'a>(
1708 state: &'a ManagerState,
1709 aabb: &WorldAabb,
1710 candidates: EntityCollisionCandidates,
1711 entry_bounds: Option<&WorldAabb>,
1712 ) -> EntityQueryEntries<'a> {
1713 let spatial_index = match candidates {
1714 EntityCollisionCandidates::All => &state.by_spatial_cell,
1715 EntityCollisionCandidates::HardCollisionRelevant => &state.by_hard_collision_cell,
1716 };
1717
1718 let bounds = EntitySpatialCellBounds::from_aabb(aabb);
1719 let mut populated_cells = SmallVec::new();
1720 let direct_probe_limit =
1721 MAX_DIRECT_SPATIAL_CELL_PROBES.min(spatial_index.capacity() as i128);
1722
1723 if bounds.direct_probe_count() <= direct_probe_limit {
1724 for x in bounds.minimum.x..=bounds.maximum.x {
1725 for y in bounds.minimum.y..=bounds.maximum.y {
1726 for z in bounds.minimum.z..=bounds.maximum.z {
1727 if let Some(cell) = spatial_index.get(&EntitySpatialCell { x, y, z }) {
1728 populated_cells.push(cell.entities.as_slice());
1729 }
1730 }
1731 }
1732 }
1733 } else {
1734 for (cell, entries) in spatial_index {
1735 if bounds.contains(*cell) {
1736 populated_cells.push(entries.entities.as_slice());
1737 }
1738 }
1739 }
1740
1741 EntityQueryEntries::new(state, entry_bounds, populated_cells)
1742 }
1743
1744 fn entity_ids_in_chunk_order(state: &ManagerState, chunk: ChunkPos) -> Vec<i32> {
1745 let first = PackedSectionPos::from(SectionPos::new(chunk.0.x, 0, chunk.0.y));
1746 let last = PackedSectionPos::from(SectionPos::new(chunk.0.x, -1, chunk.0.y));
1747 state
1748 .by_section
1749 .range(first..=last)
1750 .flat_map(|(_, entity_ids)| entity_ids.iter().copied())
1751 .collect()
1752 }
1753
1754 #[must_use]
1756 pub fn saveable_entities_outside_chunks(
1757 &self,
1758 saved_chunks: &[ChunkPos],
1759 ) -> Vec<UnsavedEntityReport> {
1760 let saved_chunks = saved_chunks.iter().copied().collect::<FxHashSet<_>>();
1761 let state = self.state.read();
1762 let mut seen = FxHashSet::default();
1763 let mut reports = Vec::new();
1764
1765 for entry in state.live_by_id.values() {
1766 Self::push_unsaved_entity_report(&saved_chunks, &mut seen, &mut reports, entry);
1767 }
1768
1769 for entries in state.unloading_by_chunk.values() {
1770 for entry in entries {
1771 Self::push_unsaved_entity_report(&saved_chunks, &mut seen, &mut reports, entry);
1772 }
1773 }
1774
1775 for entries in state.save_pending_by_chunk.values() {
1776 for entry in entries {
1777 Self::push_unsaved_entity_report(&saved_chunks, &mut seen, &mut reports, entry);
1778 }
1779 }
1780
1781 reports.sort_by_key(|report| (report.chunk.0.x, report.chunk.0.y, report.entity_id));
1782 reports
1783 }
1784
1785 #[must_use]
1786 pub fn get_saveable_entities_for_chunk(&self, chunk: ChunkPos) -> Vec<SharedEntity> {
1788 let state = self.state.read();
1789 let mut result = Vec::new();
1790 let mut seen_ids = FxHashSet::default();
1791 let mut seen_uuids = FxHashSet::default();
1792
1793 for entity_id in Self::entity_ids_in_chunk_order(&state, chunk) {
1794 let Some(entry) = state.live_by_id.get(entity_id) else {
1795 continue;
1796 };
1797 Self::push_saveable_entity(&mut result, &mut seen_ids, &mut seen_uuids, entry);
1798 }
1799
1800 if let Some(entries) = state.unloading_by_chunk.get(&chunk) {
1801 for entry in entries {
1802 Self::push_saveable_entity(&mut result, &mut seen_ids, &mut seen_uuids, entry);
1803 }
1804 }
1805
1806 if let Some(entries) = state.save_pending_by_chunk.get(&chunk) {
1807 for entry in entries {
1808 Self::push_saveable_entity(&mut result, &mut seen_ids, &mut seen_uuids, entry);
1809 }
1810 }
1811
1812 result
1813 }
1814
1815 #[must_use]
1816 pub fn live_entities_in_chunk(&self, chunk: ChunkPos) -> Vec<SharedEntity> {
1818 let state = self.state.read();
1819 Self::entity_ids_in_chunk_order(&state, chunk)
1820 .into_iter()
1821 .filter_map(|entity_id| state.live_by_id.get(entity_id))
1822 .map(|entry| Arc::clone(&entry.entity))
1823 .collect()
1824 }
1825
1826 #[must_use]
1827 pub fn count(&self) -> usize {
1829 self.state.read().live_by_id.len()
1830 }
1831
1832 pub fn tick_entities(&self, _tick_count: i32, runs_normally: bool) -> FxHashSet<ChunkPos> {
1834 let mut dirty_chunks = FxHashSet::default();
1835 let tick_candidates = self.ticking_entities_snapshot();
1836 for entity in tick_candidates {
1837 let Some(tick_context) = self.tick_context(entity.id()) else {
1838 continue;
1839 };
1840
1841 if entity.is_removed() {
1842 continue;
1843 }
1844
1845 if Self::is_entity_frozen_by_tick_rate(entity.as_ref(), runs_normally) {
1846 continue;
1847 }
1848
1849 let entity_chunk = tick_context.manager_chunk;
1850 entity.check_despawn();
1851 if entity.is_removed() {
1852 if let Some(chunk) = entity_chunk {
1853 dirty_chunks.insert(chunk);
1854 }
1855 continue;
1856 }
1857
1858 if Self::is_valid_passenger_or_stop_riding(&entity) {
1859 continue;
1860 }
1861
1862 self.tick_non_passenger(&entity, &mut dirty_chunks);
1865 }
1866 dirty_chunks
1867 }
1868
1869 fn ticking_entities_snapshot(&self) -> Vec<SharedEntity> {
1870 self.state.read().tick_list.snapshot()
1871 }
1872
1873 fn live_manager_owned_entity_chunk(&self, entity_id: i32) -> Option<ChunkPos> {
1874 self.state
1875 .read()
1876 .live_by_id
1877 .get(entity_id)
1878 .filter(|entry| entry.ownership == EntityOwnership::ManagerOwned)
1879 .map(|entry| entry.chunk)
1880 }
1881
1882 fn chunk_visibility(state: &ManagerState, chunk: ChunkPos) -> EntityVisibility {
1883 state
1884 .chunk_visibility
1885 .get(&chunk)
1886 .copied()
1887 .unwrap_or(EntityVisibility::Hidden)
1888 }
1889
1890 fn effective_visibility(
1891 entry: &EntityEntry,
1892 chunk_visibility: EntityVisibility,
1893 ) -> EntityVisibility {
1894 if entry.entity.is_always_ticking() {
1895 return EntityVisibility::Ticking;
1896 }
1897 if entry.ownership == EntityOwnership::External {
1898 return EntityVisibility::Tracked;
1899 }
1900 chunk_visibility
1901 }
1902
1903 fn lifecycle_visibility_for(
1904 entry: &EntityEntry,
1905 chunk_visibility: EntityVisibility,
1906 ) -> EntityVisibility {
1907 Self::effective_visibility(entry, chunk_visibility)
1908 }
1909
1910 fn apply_entity_lifecycle_after_insert(
1911 state: &mut ManagerState,
1912 entity_id: i32,
1913 ) -> EntityLifecycleChanges {
1914 let Some(entry) = state.live_by_id.get(entity_id) else {
1915 return EntityLifecycleChanges::default();
1916 };
1917 let visibility =
1918 Self::lifecycle_visibility_for(entry, Self::chunk_visibility(state, entry.chunk));
1919 let entity = Arc::clone(&entry.entity);
1920 let should_tick = visibility.is_ticking();
1921
1922 let mut lifecycle = EntityLifecycleChanges::default();
1923 if visibility.is_accessible() {
1924 lifecycle.tracking_started.push(Arc::clone(&entity));
1925 }
1926 if should_tick && state.tick_list.add(&entity) {
1927 lifecycle.ticking_started.push(entity);
1928 }
1929 lifecycle
1930 }
1931
1932 fn apply_chunk_visibility_change(
1933 state: &mut ManagerState,
1934 chunk: ChunkPos,
1935 previous: EntityVisibility,
1936 new: EntityVisibility,
1937 ) -> EntityLifecycleChanges {
1938 let entity_ids = Self::entity_ids_in_chunk_order(state, chunk);
1939 let mut lifecycle = EntityLifecycleChanges::default();
1940
1941 for entity_id in entity_ids {
1942 let Some(entry) = state.live_by_id.get_mut(entity_id) else {
1943 continue;
1944 };
1945
1946 entry.accessible = entry.ownership == EntityOwnership::External || new.is_accessible();
1947
1948 if entry.ownership != EntityOwnership::ManagerOwned {
1949 continue;
1950 }
1951
1952 let old_visibility = Self::lifecycle_visibility_for(entry, previous);
1953 let new_visibility = Self::lifecycle_visibility_for(entry, new);
1954 if old_visibility == new_visibility {
1955 continue;
1956 }
1957
1958 let entity = Arc::clone(&entry.entity);
1959 if old_visibility.is_ticking()
1960 && !new_visibility.is_ticking()
1961 && state.tick_list.remove(entity_id).is_some()
1962 {
1963 lifecycle.ticking_stopped.push(Arc::clone(&entity));
1964 }
1965
1966 if old_visibility.is_accessible() && !new_visibility.is_accessible() {
1967 state.accessible_order.remove(entity_id);
1968 lifecycle.tracking_stopped.push(Arc::clone(&entity));
1969 } else if !old_visibility.is_accessible() && new_visibility.is_accessible() {
1970 state.accessible_order.insert(entity_id);
1971 lifecycle.tracking_started.push(Arc::clone(&entity));
1972 }
1973
1974 if !old_visibility.is_ticking()
1975 && new_visibility.is_ticking()
1976 && state.tick_list.add(&entity)
1977 {
1978 lifecycle.ticking_started.push(entity);
1979 }
1980 }
1981
1982 lifecycle
1983 }
1984
1985 fn is_entity_frozen_by_tick_rate(entity: &dyn Entity, runs_normally: bool) -> bool {
1986 !runs_normally
1987 && entity.entity_type() != &vanilla_entities::PLAYER
1988 && entity.count_player_passengers() == 0
1989 }
1990
1991 fn has_pending_world_change_in_vehicle_chain(entity: &SharedEntity) -> bool {
1992 if entity.is_world_change_pending() {
1993 return true;
1994 }
1995
1996 let Some(mut vehicle) = entity.vehicle() else {
1997 return false;
1998 };
1999
2000 let mut visited = VehicleChain::new();
2001 visited.push(entity.id());
2002 loop {
2003 assert!(
2004 !visited.contains(&vehicle.id()),
2005 "cyclic passenger relationship involving entity {}",
2006 entity.id()
2007 );
2008 visited.push(vehicle.id());
2009 if vehicle.is_world_change_pending() {
2010 return true;
2011 }
2012 let Some(current) = vehicle.vehicle() else {
2013 return false;
2014 };
2015 vehicle = current;
2016 }
2017 }
2018
2019 fn is_accessible(state: &ManagerState, entry: &EntityEntry) -> bool {
2020 Self::is_accessible_at(state, entry.ownership, entry.chunk)
2021 }
2022
2023 fn is_accessible_at(state: &ManagerState, ownership: EntityOwnership, chunk: ChunkPos) -> bool {
2024 ownership == EntityOwnership::External
2025 || Self::chunk_visibility(state, chunk).is_accessible()
2026 }
2027
2028 fn is_valid_passenger_or_stop_riding(entity: &SharedEntity) -> bool {
2029 let Some(vehicle) = entity.vehicle() else {
2030 return false;
2031 };
2032
2033 if !vehicle.is_removed() && vehicle.has_passenger(entity.as_ref()) {
2034 Self::assert_acyclic_vehicle_chain(entity);
2035 return true;
2036 }
2037
2038 entity.stop_riding();
2039 false
2040 }
2041
2042 fn assert_acyclic_vehicle_chain(entity: &SharedEntity) {
2043 let mut visited = VehicleChain::new();
2044 visited.push(entity.id());
2045
2046 let mut vehicle = entity.vehicle();
2047 while let Some(current) = vehicle {
2048 assert!(
2049 !visited.contains(¤t.id()),
2050 "cyclic passenger relationship involving entity {}",
2051 entity.id()
2052 );
2053 visited.push(current.id());
2054 vehicle = current.vehicle();
2055 }
2056 }
2057
2058 fn tick_non_passenger(&self, entity: &SharedEntity, dirty_chunks: &mut FxHashSet<ChunkPos>) {
2059 snapshot_old_pos_and_rot_for_tick(entity.as_ref());
2060 entity.advance_tick_count();
2061 Arc::clone(entity).tick();
2062 self.mark_dirty_after_tick(entity, dirty_chunks);
2063 self.tick_vehicle_passengers(entity.as_ref(), dirty_chunks);
2064 }
2065
2066 fn tick_vehicle_passengers(
2067 &self,
2068 vehicle: &dyn Entity,
2069 dirty_chunks: &mut FxHashSet<ChunkPos>,
2070 ) {
2071 let mut post_tick = |entity: &SharedEntity| {
2072 self.mark_dirty_after_tick(entity, dirty_chunks);
2073 };
2074 tick_vehicle_passengers_if(vehicle, &mut post_tick, &mut |entity| {
2075 self.can_tick_entity_now(entity.id())
2076 });
2077 }
2078
2079 fn mark_dirty_after_tick(&self, entity: &SharedEntity, dirty_chunks: &mut FxHashSet<ChunkPos>) {
2080 if self.live_manager_owned_entity_chunk(entity.id()).is_some() {
2081 dirty_chunks.insert(ChunkPos::from_entity_pos(entity.position()));
2082 }
2083 }
2084
2085 fn can_tick_entity_now(&self, entity_id: i32) -> bool {
2086 self.tick_context(entity_id).is_some()
2087 }
2088
2089 fn tick_context(&self, entity_id: i32) -> Option<TickContext> {
2090 let state = self.state.read();
2091 let entry = state.live_by_id.get(entity_id)?;
2092
2093 if Self::has_pending_world_change_in_vehicle_chain(&entry.entity) {
2094 return None;
2095 }
2096
2097 let in_tick_list = state.tick_list.contains(entity_id);
2098 let can_tick = match entry.ownership {
2099 EntityOwnership::External => {
2100 entry.entity.entity_type() == &vanilla_entities::PLAYER || in_tick_list
2101 }
2102 EntityOwnership::ManagerOwned => in_tick_list,
2103 };
2104 can_tick.then_some(TickContext {
2105 manager_chunk: (entry.ownership == EntityOwnership::ManagerOwned)
2106 .then_some(entry.chunk),
2107 })
2108 }
2109
2110 fn insert_live_entry(state: &mut ManagerState, mut entry: EntityEntry) {
2111 let entity_id = entry.entity.id();
2112 entry.bounding_box = entry.entity.bounding_box();
2113 entry.spatial_cells = EntitySpatialCellBounds::from_aabb(&entry.bounding_box).cells();
2114 let is_accessible = Self::is_accessible_at(state, entry.ownership, entry.chunk);
2115 entry.accessible = is_accessible;
2116 assert!(
2117 !state.live_by_id.contains_key(entity_id),
2118 "entity id {entity_id} is already registered in the world entity manager"
2119 );
2120 assert!(
2121 state.live_by_uuid.insert(entry.uuid, entity_id).is_none(),
2122 "entity uuid {} is already registered in the world entity manager",
2123 entry.uuid
2124 );
2125 entry.query_order = EntityQueryOrder {
2126 section: PackedSectionPos::from(entry.section),
2127 insertion: Self::next_section_order(state),
2128 };
2129
2130 state
2131 .by_section
2132 .entry(PackedSectionPos::from(entry.section))
2133 .or_default()
2134 .insert(entity_id);
2135 state
2136 .by_chunk
2137 .entry(entry.chunk)
2138 .or_default()
2139 .insert(entity_id);
2140 let hard_collision_relevant = entry.hard_collision_relevant;
2141 let entity_slot = state.live_by_id.insert(entity_id, entry);
2142 Self::insert_into_spatial_indexes(state, entity_slot, hard_collision_relevant, None);
2143
2144 if is_accessible {
2145 state.accessible_order.insert(entity_id);
2146 }
2147 }
2148
2149 fn contains_uuid(state: &ManagerState, uuid: Uuid) -> bool {
2150 state.live_by_uuid.contains_key(&uuid)
2151 || state
2152 .unloading_by_chunk
2153 .values()
2154 .flatten()
2155 .any(|entry| entry.uuid == uuid)
2156 || state
2157 .save_pending_by_chunk
2158 .values()
2159 .flatten()
2160 .any(|entry| entry.uuid == uuid)
2161 }
2162
2163 fn contains_id(state: &ManagerState, entity_id: i32) -> bool {
2164 state.live_by_id.contains_key(entity_id)
2165 || state
2166 .unloading_by_chunk
2167 .values()
2168 .flatten()
2169 .any(|entry| entry.entity.id() == entity_id)
2170 || state
2171 .save_pending_by_chunk
2172 .values()
2173 .flatten()
2174 .any(|entry| entry.entity.id() == entity_id)
2175 }
2176
2177 fn push_saveable_entity(
2178 result: &mut Vec<SharedEntity>,
2179 seen_ids: &mut FxHashSet<i32>,
2180 seen_uuids: &mut FxHashSet<Uuid>,
2181 entry: &EntityEntry,
2182 ) {
2183 if !entry.should_save() || !seen_ids.insert(entry.entity.id()) {
2184 return;
2185 }
2186 assert!(
2187 seen_uuids.insert(entry.uuid),
2188 "duplicate saveable entity uuid {} in world entity manager",
2189 entry.uuid
2190 );
2191 result.push(Arc::clone(&entry.entity));
2192 }
2193
2194 fn push_unsaved_entity_report(
2195 saved_chunks: &FxHashSet<ChunkPos>,
2196 seen: &mut FxHashSet<i32>,
2197 reports: &mut Vec<UnsavedEntityReport>,
2198 entry: &EntityEntry,
2199 ) {
2200 if saved_chunks.contains(&entry.chunk)
2201 || !entry.should_save()
2202 || !seen.insert(entry.entity.id())
2203 {
2204 return;
2205 }
2206
2207 reports.push(UnsavedEntityReport {
2208 entity_id: entry.entity.id(),
2209 uuid: entry.uuid,
2210 chunk: entry.chunk,
2211 });
2212 }
2213
2214 fn remove_live_entry(state: &mut ManagerState, entity_id: i32) -> Option<EntityEntry> {
2215 let (entity_slot, entry) = state.live_by_id.remove(entity_id)?;
2216 state.tick_list.remove(entity_id);
2217 state.live_by_uuid.remove(&entry.uuid);
2218 state.accessible_order.remove(entity_id);
2219 Self::remove_from_section(state, entry.section, entity_id);
2220 Self::remove_from_spatial_indexes(
2221 state,
2222 &entry.spatial_cells,
2223 entity_slot,
2224 entry.hard_collision_relevant,
2225 None,
2226 );
2227
2228 Self::remove_from_chunk(state, entry.chunk, entity_id);
2229
2230 Some(entry)
2231 }
2232
2233 fn next_section_order(state: &mut ManagerState) -> u64 {
2234 assert!(
2235 state.next_section_order < u64::MAX,
2236 "entity section insertion order exhausted"
2237 );
2238
2239 let order = state.next_section_order;
2240 state.next_section_order += 1;
2241
2242 order
2243 }
2244
2245 fn insert_into_spatial_indexes(
2247 state: &mut ManagerState,
2248 entity_slot: EntitySlot,
2249 hard_collision_relevant: bool,
2250 retained: Option<EntitySpatialCellBounds>,
2251 ) {
2252 Self::insert_into_spatial_cells(
2253 state,
2254 entity_slot,
2255 EntityCollisionCandidates::All,
2256 retained,
2257 );
2258
2259 if hard_collision_relevant {
2260 Self::insert_into_spatial_cells(
2261 state,
2262 entity_slot,
2263 EntityCollisionCandidates::HardCollisionRelevant,
2264 retained,
2265 );
2266 }
2267 }
2268
2269 fn remove_from_spatial_indexes(
2271 state: &mut ManagerState,
2272 cells: &[EntitySpatialCell],
2273 entity_slot: EntitySlot,
2274 hard_collision_relevant: bool,
2275 retained: Option<EntitySpatialCellBounds>,
2276 ) {
2277 Self::remove_from_spatial_cells(
2278 state,
2279 cells,
2280 entity_slot,
2281 EntityCollisionCandidates::All,
2282 retained,
2283 );
2284
2285 if hard_collision_relevant {
2286 Self::remove_from_spatial_cells(
2287 state,
2288 cells,
2289 entity_slot,
2290 EntityCollisionCandidates::HardCollisionRelevant,
2291 retained,
2292 );
2293 }
2294 }
2295
2296 fn insert_into_spatial_cells(
2297 state: &mut ManagerState,
2298 entity_slot: EntitySlot,
2299 candidates: EntityCollisionCandidates,
2300 retained_bounds: Option<EntitySpatialCellBounds>,
2301 ) {
2302 match retained_bounds {
2303 Some(bounds) => {
2304 Self::insert_into_spatial_cells_matching(state, entity_slot, candidates, |cell| {
2305 !bounds.contains(cell)
2306 });
2307 }
2308 None => {
2309 Self::insert_into_spatial_cells_matching(state, entity_slot, candidates, |_| true);
2310 }
2311 }
2312 }
2313
2314 fn insert_into_spatial_cells_matching(
2315 state: &mut ManagerState,
2316 entity_slot: EntitySlot,
2317 candidates: EntityCollisionCandidates,
2318 accept_cell: impl Fn(EntitySpatialCell) -> bool,
2319 ) {
2320 let entry = state.live_by_id.entry(entity_slot);
2321 let query_order = entry.query_order;
2322 let spatial_index = match candidates {
2323 EntityCollisionCandidates::All => &mut state.by_spatial_cell,
2324 EntityCollisionCandidates::HardCollisionRelevant => &mut state.by_hard_collision_cell,
2325 };
2326
2327 for cell in &entry.spatial_cells {
2328 if !accept_cell(*cell) {
2329 continue;
2330 }
2331
2332 let entries = spatial_index.entry(*cell).or_default();
2333 let insertion_index = entries
2334 .entities
2335 .partition_point(|slot| state.live_by_id.entry(*slot).query_order < query_order);
2336 entries.entities.insert(insertion_index, entity_slot);
2337 }
2338 }
2339
2340 fn remove_from_spatial_cells(
2341 state: &mut ManagerState,
2342 cells: &[EntitySpatialCell],
2343 entity_slot: EntitySlot,
2344 candidates: EntityCollisionCandidates,
2345 retained_bounds: Option<EntitySpatialCellBounds>,
2346 ) {
2347 match retained_bounds {
2348 Some(bounds) => Self::remove_from_spatial_cells_matching(
2349 state,
2350 cells,
2351 entity_slot,
2352 candidates,
2353 |cell| !bounds.contains(cell),
2354 ),
2355 None => Self::remove_from_spatial_cells_matching(
2356 state,
2357 cells,
2358 entity_slot,
2359 candidates,
2360 |_| true,
2361 ),
2362 }
2363 }
2364
2365 fn remove_from_spatial_cells_matching(
2366 state: &mut ManagerState,
2367 cells: &[EntitySpatialCell],
2368 entity_slot: EntitySlot,
2369 candidates: EntityCollisionCandidates,
2370 accept_cell: impl Fn(EntitySpatialCell) -> bool,
2371 ) {
2372 let spatial_index = match candidates {
2373 EntityCollisionCandidates::All => &mut state.by_spatial_cell,
2374 EntityCollisionCandidates::HardCollisionRelevant => &mut state.by_hard_collision_cell,
2375 };
2376
2377 for cell in cells {
2378 if !accept_cell(*cell) {
2379 continue;
2380 }
2381
2382 let remove_cell = if let Some(entries) = spatial_index.get_mut(cell) {
2383 if let Some(index) = entries
2384 .entities
2385 .iter()
2386 .position(|slot| *slot == entity_slot)
2387 {
2388 entries.entities.remove(index);
2389 }
2390
2391 entries.entities.is_empty()
2392 } else {
2393 false
2394 };
2395
2396 if remove_cell {
2397 spatial_index.remove(cell);
2398 }
2399 }
2400 }
2401
2402 fn remove_from_section(state: &mut ManagerState, section: SectionPos, entity_id: i32) {
2403 let packed = PackedSectionPos::from(section);
2404 let remove_section = if let Some(entity_ids) = state.by_section.get_mut(&packed) {
2405 entity_ids.remove(entity_id);
2406 entity_ids.is_empty()
2407 } else {
2408 false
2409 };
2410 if remove_section {
2411 state.by_section.remove(&packed);
2412 }
2413 }
2414
2415 fn remove_from_chunk(state: &mut ManagerState, chunk: ChunkPos, entity_id: i32) {
2416 let remove_chunk = if let Some(entity_ids) = state.by_chunk.get_mut(&chunk) {
2417 entity_ids.remove(&entity_id);
2418 entity_ids.is_empty()
2419 } else {
2420 false
2421 };
2422 if remove_chunk {
2423 state.by_chunk.remove(&chunk);
2424 }
2425 }
2426}
2427
2428impl Default for WorldEntityManager {
2429 fn default() -> Self {
2430 Self::new()
2431 }
2432}
2433
2434mod entries;
2435mod spatial_query;
2436
2437#[cfg(feature = "benchmark-support")]
2438pub mod benchmark_support;
2439
2440#[cfg(test)]
2441mod tests;