Skip to content

Commit

Permalink
Add ability to inspect entity's components (#5136)
Browse files Browse the repository at this point in the history
# 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`
  • Loading branch information
harudagondi committed Jun 30, 2022
1 parent 5f8e438 commit b3fa479
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 3 deletions.
25 changes: 24 additions & 1 deletion crates/bevy_ecs/src/system/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
91 changes: 89 additions & 2 deletions crates/bevy_ecs/src/world/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -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()
);
}
}

0 comments on commit b3fa479

Please sign in to comment.