diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index bf40e13fb98b1..aafaf45f276b0 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -7,7 +7,7 @@ use crate::{ entity::{Entities, Entity}, world::{FromWorld, World}, }; -use bevy_utils::tracing::{error, warn}; +use bevy_utils::tracing::{error, info, warn}; pub use command_queue::CommandQueue; pub use parallel_scope::*; use std::marker::PhantomData; @@ -588,6 +588,13 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> { }); } + /// Logs the components of the entity at the info level. + pub fn log_components(&mut self) { + self.commands.add(LogComponents { + entity: self.entity, + }); + } + /// Returns the underlying [`Commands`]. pub fn commands(&mut self) -> &mut Commands<'w, 's> { self.commands @@ -793,6 +800,22 @@ impl Command for RemoveResource { } } +/// [`Command`] to log the components of a given entity. See [`EntityCommands::log_components`]. +pub struct LogComponents { + entity: Entity, +} + +impl Command for LogComponents { + fn write(self, world: &mut World) { + let debug_infos: Vec<_> = world + .inspect_entity(self.entity) + .into_iter() + .map(|component_info| component_info.name()) + .collect(); + info!("Entity {:?}: {:?}", self.entity, debug_infos); + } +} + #[cfg(test)] #[allow(clippy::float_cmp, clippy::approx_constant)] mod tests { diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 5c75740f7bdfa..5f4a952e62c35 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -12,7 +12,8 @@ use crate::{ bundle::{Bundle, BundleInserter, BundleSpawner, Bundles}, change_detection::{MutUntyped, Ticks}, component::{ - Component, ComponentDescriptor, ComponentId, ComponentTicks, Components, StorageType, + Component, ComponentDescriptor, ComponentId, ComponentInfo, ComponentTicks, Components, + StorageType, }, entity::{AllocAtWithoutReplacement, Entities, Entity}, query::{QueryState, WorldQuery}, @@ -280,6 +281,30 @@ impl World { .unwrap_or_else(|| panic!("Entity {:?} does not exist", entity)) } + /// Returns the components of an [`Entity`](crate::entity::Entity) through [`ComponentInfo`](crate::component::ComponentInfo). + #[inline] + pub fn inspect_entity(&self, entity: Entity) -> Vec<&ComponentInfo> { + let entity_location = self + .entities() + .get(entity) + .unwrap_or_else(|| panic!("Entity {:?} does not exist", entity)); + + let archetype = self + .archetypes() + .get(entity_location.archetype_id) + .unwrap_or_else(|| { + panic!( + "Archetype {:?} does not exist", + entity_location.archetype_id + ) + }); + + archetype + .components() + .filter_map(|id| self.components().get_info(id)) + .collect() + } + /// Returns an [`EntityMut`] for the given `entity` (if it exists) or spawns one if it doesn't exist. /// This will return [`None`] if the `entity` exists with a different generation. /// @@ -1542,11 +1567,13 @@ mod tests { use super::World; use crate::{ change_detection::DetectChanges, - component::{ComponentDescriptor, ComponentId, StorageType}, + component::{ComponentDescriptor, ComponentId, ComponentInfo, StorageType}, ptr::OwningPtr, }; use bevy_ecs_macros::Component; + use bevy_utils::HashSet; use std::{ + any::TypeId, panic, sync::{ atomic::{AtomicBool, AtomicU32, Ordering}, @@ -1762,4 +1789,64 @@ mod tests { world.insert_resource_by_id(invalid_component_id, ptr); }); } + + #[derive(Component)] + struct Foo; + + #[derive(Component)] + struct Bar; + + #[derive(Component)] + struct Baz; + + #[test] + fn inspect_entity_components() { + let mut world = World::new(); + let ent0 = world.spawn().insert_bundle((Foo, Bar, Baz)).id(); + let ent1 = world.spawn().insert_bundle((Foo, Bar)).id(); + let ent2 = world.spawn().insert_bundle((Bar, Baz)).id(); + let ent3 = world.spawn().insert_bundle((Foo, Baz)).id(); + let ent4 = world.spawn().insert_bundle((Foo,)).id(); + let ent5 = world.spawn().insert_bundle((Bar,)).id(); + let ent6 = world.spawn().insert_bundle((Baz,)).id(); + + fn to_type_ids(component_infos: Vec<&ComponentInfo>) -> HashSet> { + component_infos + .into_iter() + .map(|component_info| component_info.type_id()) + .collect() + } + + let foo_id = TypeId::of::(); + let bar_id = TypeId::of::(); + let baz_id = TypeId::of::(); + assert_eq!( + to_type_ids(world.inspect_entity(ent0)), + [Some(foo_id), Some(bar_id), Some(baz_id)].into() + ); + assert_eq!( + to_type_ids(world.inspect_entity(ent1)), + [Some(foo_id), Some(bar_id)].into() + ); + assert_eq!( + to_type_ids(world.inspect_entity(ent2)), + [Some(bar_id), Some(baz_id)].into() + ); + assert_eq!( + to_type_ids(world.inspect_entity(ent3)), + [Some(foo_id), Some(baz_id)].into() + ); + assert_eq!( + to_type_ids(world.inspect_entity(ent4)), + [Some(foo_id)].into() + ); + assert_eq!( + to_type_ids(world.inspect_entity(ent5)), + [Some(bar_id)].into() + ); + assert_eq!( + to_type_ids(world.inspect_entity(ent6)), + [Some(baz_id)].into() + ); + } }