azalea_entity/plugin/
indexing.rs1use std::{collections::HashMap, fmt::Debug};
4
5use azalea_core::position::ChunkPos;
6use azalea_world::{Instance, InstanceContainer, InstanceName, MinecraftEntityId};
7use bevy_ecs::{
8 component::Component,
9 entity::Entity,
10 query::{Added, Changed, Without},
11 system::{Commands, Query, Res, ResMut, Resource},
12};
13use derive_more::{Deref, DerefMut};
14use nohash_hasher::IntMap;
15use tracing::{debug, warn};
16use uuid::Uuid;
17
18use super::LoadedBy;
19use crate::{EntityUuid, LocalEntity, Position};
20
21#[derive(Resource, Default)]
22pub struct EntityUuidIndex {
23 entity_by_uuid: HashMap<Uuid, Entity>,
25}
26
27#[derive(Component, Default)]
34pub struct EntityIdIndex {
35 entity_by_id: IntMap<MinecraftEntityId, Entity>,
37}
38
39impl EntityUuidIndex {
40 pub fn new() -> Self {
41 Self {
42 entity_by_uuid: HashMap::default(),
43 }
44 }
45
46 pub fn get(&self, uuid: &Uuid) -> Option<Entity> {
47 self.entity_by_uuid.get(uuid).copied()
48 }
49
50 pub fn contains_key(&self, uuid: &Uuid) -> bool {
51 self.entity_by_uuid.contains_key(uuid)
52 }
53
54 pub fn insert(&mut self, uuid: Uuid, entity: Entity) {
55 self.entity_by_uuid.insert(uuid, entity);
56 }
57
58 pub fn remove(&mut self, uuid: &Uuid) -> Option<Entity> {
59 self.entity_by_uuid.remove(uuid)
60 }
61}
62
63impl EntityIdIndex {
64 pub fn get(&self, id: MinecraftEntityId) -> Option<Entity> {
65 self.entity_by_id.get(&id).copied()
66 }
67
68 pub fn contains_key(&self, id: MinecraftEntityId) -> bool {
69 self.entity_by_id.contains_key(&id)
70 }
71
72 pub fn insert(&mut self, id: MinecraftEntityId, entity: Entity) {
73 self.entity_by_id.insert(id, entity);
74 }
75
76 pub fn remove(&mut self, id: MinecraftEntityId) -> Option<Entity> {
77 self.entity_by_id.remove(&id)
78 }
79}
80
81impl Debug for EntityUuidIndex {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 f.debug_struct("EntityUuidIndex").finish()
84 }
85}
86
87#[derive(Component, Debug, Deref, DerefMut)]
89pub struct EntityChunkPos(pub ChunkPos);
90
91pub fn update_entity_chunk_positions(
95 mut query: Query<(Entity, &Position, &InstanceName, &mut EntityChunkPos), Changed<Position>>,
96 instance_container: Res<InstanceContainer>,
97) {
98 for (entity, pos, world_name, mut entity_chunk_pos) in query.iter_mut() {
99 let instance_lock = instance_container.get(world_name).unwrap();
100 let mut instance = instance_lock.write();
101
102 let old_chunk = **entity_chunk_pos;
103 let new_chunk = ChunkPos::from(*pos);
104 if old_chunk != new_chunk {
105 **entity_chunk_pos = new_chunk;
106
107 if old_chunk != new_chunk {
108 if let Some(entities) = instance.entities_by_chunk.get_mut(&old_chunk) {
110 entities.remove(&entity);
111 }
112 instance
113 .entities_by_chunk
114 .entry(new_chunk)
115 .or_default()
116 .insert(entity);
117 }
118 }
119 }
120}
121
122pub fn insert_entity_chunk_position(
124 query: Query<(Entity, &Position, &InstanceName), Added<EntityChunkPos>>,
125 instance_container: Res<InstanceContainer>,
126) {
127 for (entity, pos, world_name) in query.iter() {
128 let instance_lock = instance_container.get(world_name).unwrap();
129 let mut instance = instance_lock.write();
130
131 let chunk = ChunkPos::from(*pos);
132 instance
133 .entities_by_chunk
134 .entry(chunk)
135 .or_default()
136 .insert(entity);
137 }
138}
139
140#[allow(clippy::type_complexity)]
142pub fn remove_despawned_entities_from_indexes(
143 mut commands: Commands,
144 mut entity_uuid_index: ResMut<EntityUuidIndex>,
145 instance_container: Res<InstanceContainer>,
146 query: Query<
147 (
148 Entity,
149 &EntityUuid,
150 &MinecraftEntityId,
151 &Position,
152 &InstanceName,
153 &LoadedBy,
154 ),
155 (Changed<LoadedBy>, Without<LocalEntity>),
156 >,
157) {
158 for (entity, uuid, minecraft_id, position, instance_name, loaded_by) in &query {
159 let Some(instance_lock) = instance_container.get(instance_name) else {
160 debug!(
162 "Despawned entity {entity:?} because it's in an instance that isn't loaded anymore"
163 );
164 if entity_uuid_index.entity_by_uuid.remove(uuid).is_none() {
165 warn!(
166 "Tried to remove entity {entity:?} from the uuid index but it was not there."
167 );
168 }
169 commands.entity(entity).despawn();
171
172 continue;
173 };
174
175 let mut instance = instance_lock.write();
176
177 if !loaded_by.is_empty() {
179 continue;
180 }
181
182 let chunk = ChunkPos::from(*position);
184 match instance.entities_by_chunk.get_mut(&chunk) {
185 Some(entities_in_chunk) => {
186 if entities_in_chunk.remove(&entity) {
187 if entities_in_chunk.is_empty() {
189 instance.entities_by_chunk.remove(&chunk);
190 }
191 } else {
192 warn!(
193 "Tried to remove entity {entity:?} from chunk {chunk:?} but the entity was not there."
194 );
195 }
196 }
197 _ => {
198 debug!(
199 "Tried to remove entity {entity:?} from chunk {chunk:?} but the chunk was not found."
200 );
201 }
202 }
203 if entity_uuid_index.entity_by_uuid.remove(uuid).is_none() {
205 warn!("Tried to remove entity {entity:?} from the uuid index but it was not there.");
206 }
207 if instance.entity_by_id.remove(minecraft_id).is_none() {
208 warn!("Tried to remove entity {entity:?} from the id index but it was not there.");
209 }
210 commands.entity(entity).despawn();
212 debug!("Despawned entity {entity:?} because it was not loaded by anything.");
213 }
214}
215
216pub fn add_entity_to_indexes(
217 entity_id: MinecraftEntityId,
218 ecs_entity: Entity,
219 entity_uuid: Option<Uuid>,
220 entity_id_index: &mut EntityIdIndex,
221 entity_uuid_index: &mut EntityUuidIndex,
222 instance: &mut Instance,
223) {
224 entity_id_index.insert(entity_id, ecs_entity);
226
227 instance.entity_by_id.insert(entity_id, ecs_entity);
229
230 if let Some(uuid) = entity_uuid {
231 entity_uuid_index.insert(uuid, ecs_entity);
233 }
234}