1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 14:26:04 +00:00
azalea/azalea-entity/src/plugin/indexing.rs
2024-10-26 05:29:26 +00:00

229 lines
7.4 KiB
Rust

//! Stuff related to entity indexes and keeping track of entities in the world.
use std::{collections::HashMap, fmt::Debug};
use azalea_core::position::ChunkPos;
use azalea_world::{Instance, InstanceContainer, InstanceName, MinecraftEntityId};
use bevy_ecs::{
component::Component,
entity::Entity,
query::{Added, Changed},
system::{Commands, Query, Res, ResMut, Resource},
};
use derive_more::{Deref, DerefMut};
use nohash_hasher::IntMap;
use tracing::{debug, warn};
use uuid::Uuid;
use super::LoadedBy;
use crate::{EntityUuid, Position};
#[derive(Resource, Default)]
pub struct EntityUuidIndex {
/// An index of entities by their UUIDs
entity_by_uuid: HashMap<Uuid, Entity>,
}
/// An index of Minecraft entity IDs to Azalea ECS entities. This is a
/// `Component` so local players can keep track of entity IDs independently from
/// the instance.
///
/// If you need a per-instance instead of per-client version of this, you can
/// use [`Instance::entity_by_id`].
#[derive(Component, Default)]
pub struct EntityIdIndex {
/// An index of entities by their MinecraftEntityId
entity_by_id: IntMap<MinecraftEntityId, Entity>,
}
impl EntityUuidIndex {
pub fn new() -> Self {
Self {
entity_by_uuid: HashMap::default(),
}
}
pub fn get(&self, uuid: &Uuid) -> Option<Entity> {
self.entity_by_uuid.get(uuid).copied()
}
pub fn contains_key(&self, uuid: &Uuid) -> bool {
self.entity_by_uuid.contains_key(uuid)
}
pub fn insert(&mut self, uuid: Uuid, entity: Entity) {
self.entity_by_uuid.insert(uuid, entity);
}
pub fn remove(&mut self, uuid: &Uuid) -> Option<Entity> {
self.entity_by_uuid.remove(uuid)
}
}
impl EntityIdIndex {
pub fn get(&self, id: &MinecraftEntityId) -> Option<Entity> {
self.entity_by_id.get(id).copied()
}
pub fn contains_key(&self, id: &MinecraftEntityId) -> bool {
self.entity_by_id.contains_key(id)
}
pub fn insert(&mut self, id: MinecraftEntityId, entity: Entity) {
self.entity_by_id.insert(id, entity);
}
pub fn remove(&mut self, id: &MinecraftEntityId) -> Option<Entity> {
self.entity_by_id.remove(id)
}
}
impl Debug for EntityUuidIndex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EntityUuidIndex").finish()
}
}
/// The chunk position that an entity is currently in.
#[derive(Component, Debug, Deref, DerefMut)]
pub struct EntityChunkPos(pub ChunkPos);
/// Update the chunk position indexes in [`Instance::entities_by_chunk`].
///
/// [`Instance::entities_by_chunk`]: azalea_world::Instance::entities_by_chunk
pub fn update_entity_chunk_positions(
mut query: Query<(Entity, &Position, &InstanceName, &mut EntityChunkPos), Changed<Position>>,
instance_container: Res<InstanceContainer>,
) {
for (entity, pos, world_name, mut entity_chunk_pos) in query.iter_mut() {
let instance_lock = instance_container.get(world_name).unwrap();
let mut instance = instance_lock.write();
let old_chunk = **entity_chunk_pos;
let new_chunk = ChunkPos::from(*pos);
if old_chunk != new_chunk {
**entity_chunk_pos = new_chunk;
if old_chunk != new_chunk {
// move the entity from the old chunk to the new one
if let Some(entities) = instance.entities_by_chunk.get_mut(&old_chunk) {
entities.remove(&entity);
}
instance
.entities_by_chunk
.entry(new_chunk)
.or_default()
.insert(entity);
}
}
}
}
/// Insert new entities into [`Instance::entities_by_chunk`].
pub fn insert_entity_chunk_position(
query: Query<(Entity, &Position, &InstanceName), Added<EntityChunkPos>>,
instance_container: Res<InstanceContainer>,
) {
for (entity, pos, world_name) in query.iter() {
let instance_lock = instance_container.get(world_name).unwrap();
let mut instance = instance_lock.write();
let chunk = ChunkPos::from(*pos);
instance
.entities_by_chunk
.entry(chunk)
.or_default()
.insert(entity);
}
}
/// Despawn entities that aren't being loaded by anything.
#[allow(clippy::type_complexity)]
pub fn remove_despawned_entities_from_indexes(
mut commands: Commands,
mut entity_uuid_index: ResMut<EntityUuidIndex>,
instance_container: Res<InstanceContainer>,
query: Query<
(
Entity,
&EntityUuid,
&MinecraftEntityId,
&Position,
&InstanceName,
&LoadedBy,
),
Changed<LoadedBy>,
>,
) {
for (entity, uuid, minecraft_id, position, world_name, loaded_by) in &query {
let Some(instance_lock) = instance_container.get(world_name) else {
// the instance isn't even loaded by us, so we can safely delete the entity
debug!(
"Despawned entity {entity:?} because it's in an instance that isn't loaded anymore"
);
if entity_uuid_index.entity_by_uuid.remove(uuid).is_none() {
warn!(
"Tried to remove entity {entity:?} from the uuid index but it was not there."
);
}
// and now remove the entity from the ecs
commands.entity(entity).despawn();
continue;
};
let mut instance = instance_lock.write();
// if the entity has no references left, despawn it
if !loaded_by.is_empty() {
continue;
}
// remove the entity from the chunk index
let chunk = ChunkPos::from(*position);
if let Some(entities_in_chunk) = instance.entities_by_chunk.get_mut(&chunk) {
if entities_in_chunk.remove(&entity) {
// remove the chunk if there's no entities in it anymore
if entities_in_chunk.is_empty() {
instance.entities_by_chunk.remove(&chunk);
}
} else {
warn!(
"Tried to remove entity {entity:?} from chunk {chunk:?} but the entity was not there."
);
}
} else {
debug!("Tried to remove entity {entity:?} from chunk {chunk:?} but the chunk was not found.");
}
// remove it from the uuid index
if entity_uuid_index.entity_by_uuid.remove(uuid).is_none() {
warn!("Tried to remove entity {entity:?} from the uuid index but it was not there.");
}
if instance.entity_by_id.remove(minecraft_id).is_none() {
warn!("Tried to remove entity {entity:?} from the id index but it was not there.");
}
// and now remove the entity from the ecs
commands.entity(entity).despawn();
debug!("Despawned entity {entity:?} because it was not loaded by anything.");
}
}
pub fn add_entity_to_indexes(
entity_id: MinecraftEntityId,
ecs_entity: Entity,
entity_uuid: Option<Uuid>,
entity_id_index: &mut EntityIdIndex,
entity_uuid_index: &mut EntityUuidIndex,
instance: &mut Instance,
) {
// per-client id index
entity_id_index.insert(entity_id, ecs_entity);
// per-instance id index
instance.entity_by_id.insert(entity_id, ecs_entity);
if let Some(uuid) = entity_uuid {
// per-instance uuid index
entity_uuid_index.insert(uuid, ecs_entity);
}
}