Add ability to inspect entity's components (#5136)

# Objective

- Provide a way to see the components of an entity.
- Fixes #1467

## Solution

- Add `World::inspect_entity`. It accepts an `Entity` and returns a vector of `&ComponentInfo` that the entity has.
- Add `EntityCommands::log_components`. It logs the component names of the entity. (info level)

---

## Changelog

### Added
- Ability to inspect components of an entity through `World::inspect_entity` or `EntityCommands::log_components`
This commit is contained in:
harudagondi 2022-06-30 15:23:09 +00:00
parent 5f8e43833d
commit b3fa4790b7
2 changed files with 113 additions and 3 deletions

View file

@ -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<R: Resource> Command for RemoveResource<R> {
}
}
/// [`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 {

View file

@ -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<Option<TypeId>> {
component_infos
.into_iter()
.map(|component_info| component_info.type_id())
.collect()
}
let foo_id = TypeId::of::<Foo>();
let bar_id = TypeId::of::<Bar>();
let baz_id = TypeId::of::<Baz>();
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()
);
}
}