use bevy_ecs::component::Component;
use bevy_ecs::entity::Entity;
use bevy_ecs::world::World;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct EntityHandle(Entity);
impl EntityHandle {
#[inline]
pub const fn new(entity: Entity) -> Self {
Self(entity)
}
#[inline]
pub const fn entity(self) -> Entity {
self.0
}
#[inline]
#[must_use]
pub fn get<T: Component>(self, world: &World) -> Option<&T> {
world.get::<T>(self.0)
}
#[inline]
pub fn has<T: Component>(self, world: &World) -> bool {
world.get::<T>(self.0).is_some()
}
#[inline]
pub fn is_alive(self, world: &World) -> bool {
world.get_entity(self.0).is_ok()
}
#[inline]
pub fn bind(self, world: &World) -> BoundEntity<'_> {
BoundEntity::new(self.0, world)
}
}
impl std::fmt::Display for EntityHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "EntityHandle({})", self.0)
}
}
impl From<Entity> for EntityHandle {
#[inline]
fn from(entity: Entity) -> Self {
Self::new(entity)
}
}
impl From<EntityHandle> for Entity {
#[inline]
fn from(handle: EntityHandle) -> Self {
handle.0
}
}
#[derive(Clone, Copy)]
pub struct BoundEntity<'w> {
entity: Entity,
world: &'w World,
}
impl<'w> BoundEntity<'w> {
#[inline]
pub const fn new(entity: Entity, world: &'w World) -> Self {
Self { entity, world }
}
#[inline]
pub const fn entity(self) -> Entity {
self.entity
}
#[inline]
pub const fn handle(self) -> EntityHandle {
EntityHandle(self.entity)
}
#[inline]
#[must_use]
pub fn get<T: Component>(self) -> Option<&'w T> {
self.world.get::<T>(self.entity)
}
#[inline]
pub fn has<T: Component>(self) -> bool {
self.world.get::<T>(self.entity).is_some()
}
#[inline]
pub fn is_alive(self) -> bool {
self.world.get_entity(self.entity).is_ok()
}
#[inline]
#[must_use]
pub fn follow<T, F>(self, f: F) -> Option<BoundEntity<'w>>
where
T: Component,
F: FnOnce(&T) -> EntityHandle,
{
self.get::<T>().map(|c| f(c).bind(self.world))
}
#[inline]
#[must_use]
pub fn follow_opt<T, F>(self, f: F) -> Option<BoundEntity<'w>>
where
T: Component,
F: FnOnce(&T) -> Option<EntityHandle>,
{
self.get::<T>()
.and_then(|c| f(c).map(|h| h.bind(self.world)))
}
#[inline]
pub const fn nav(self) -> BoundEntityNav<'w> {
BoundEntityNav(self)
}
#[inline]
pub const fn world(self) -> &'w World {
self.world
}
}
impl PartialEq for BoundEntity<'_> {
fn eq(&self, other: &Self) -> bool {
self.entity == other.entity
}
}
impl Eq for BoundEntity<'_> {}
impl std::hash::Hash for BoundEntity<'_> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.entity.hash(state);
}
}
impl std::fmt::Debug for BoundEntity<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BoundEntity")
.field("entity", &self.entity)
.finish_non_exhaustive()
}
}
#[derive(Clone, Copy)]
pub struct BoundEntityNav<'w>(pub(crate) BoundEntity<'w>);
impl<'w> BoundEntityNav<'w> {
#[inline]
pub const fn inner(self) -> BoundEntity<'w> {
self.0
}
}
impl std::fmt::Debug for BoundEntityNav<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("BoundEntityNav").field(&self.0).finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Component)]
struct Name(&'static str);
#[derive(Component)]
struct Health(i32);
#[derive(Component)]
struct Target(EntityHandle);
#[derive(Component)]
struct OptionalTarget(Option<EntityHandle>);
#[test]
fn entity_handle_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<EntityHandle>();
}
#[test]
fn entity_handle_display() {
let entity = Entity::from_raw_u32(42).unwrap();
let handle = EntityHandle::new(entity);
let display = format!("{}", handle);
assert!(display.starts_with("EntityHandle("));
}
#[test]
fn handle_roundtrip() {
let entity = Entity::from_raw_u32(42).unwrap();
let handle = EntityHandle::new(entity);
assert_eq!(handle.entity(), entity);
let handle2: EntityHandle = entity.into();
let entity2: Entity = handle2.into();
assert_eq!(entity, entity2);
}
#[test]
fn handle_component_access() {
let mut world = World::new();
let entity = world.spawn((Name("test"), Health(100))).id();
let handle = EntityHandle::new(entity);
assert_eq!(handle.get::<Name>(&world).unwrap().0, "test");
assert_eq!(handle.get::<Health>(&world).unwrap().0, 100);
assert!(handle.has::<Name>(&world));
assert!(handle.has::<Health>(&world));
assert!(!handle.has::<Target>(&world));
}
#[test]
fn handle_stale_entity() {
let mut world = World::new();
let entity = world.spawn(Name("temporary")).id();
let handle = EntityHandle::new(entity);
assert!(handle.is_alive(&world));
assert!(handle.get::<Name>(&world).is_some());
world.despawn(entity);
assert!(!handle.is_alive(&world));
assert!(handle.get::<Name>(&world).is_none());
}
#[test]
fn bound_entity_access() {
let mut world = World::new();
let entity = world.spawn((Name("bound"), Health(50))).id();
let handle = EntityHandle::new(entity);
let bound = handle.bind(&world);
assert_eq!(bound.entity(), entity);
assert_eq!(bound.handle().entity(), entity);
assert_eq!(bound.get::<Name>().unwrap().0, "bound");
assert!(bound.has::<Health>());
assert!(bound.is_alive());
}
#[test]
fn bound_entity_follow() {
let mut world = World::new();
let target_entity = world.spawn(Name("target")).id();
let source_entity = world
.spawn((Name("source"), Target(EntityHandle::new(target_entity))))
.id();
let source = EntityHandle::new(source_entity).bind(&world);
let target = source.follow::<Target, _>(|t| t.0).unwrap();
assert_eq!(target.get::<Name>().unwrap().0, "target");
}
#[test]
fn bound_entity_follow_opt() {
let mut world = World::new();
let target_entity = world.spawn(Name("target")).id();
let with_target = world
.spawn(OptionalTarget(Some(EntityHandle::new(target_entity))))
.id();
let without_target = world.spawn(OptionalTarget(None)).id();
let with = EntityHandle::new(with_target).bind(&world);
let without = EntityHandle::new(without_target).bind(&world);
assert!(with.follow_opt::<OptionalTarget, _>(|t| t.0).is_some());
assert!(without.follow_opt::<OptionalTarget, _>(|t| t.0).is_none());
}
#[test]
fn bound_entity_stale() {
let mut world = World::new();
let entity = world.spawn(Name("temp")).id();
let handle = EntityHandle::new(entity);
let bound = handle.bind(&world);
assert!(bound.is_alive());
world.despawn(entity);
let bound2 = handle.bind(&world);
assert!(!bound2.is_alive());
assert!(bound2.get::<Name>().is_none());
}
#[test]
fn memory_layout() {
assert_eq!(std::mem::size_of::<EntityHandle>(), 8);
assert_eq!(std::mem::size_of::<BoundEntity<'_>>(), 16);
}
#[test]
fn bound_entity_eq_hash() {
let mut world = World::new();
let e1 = world.spawn(Name("a")).id();
let e2 = world.spawn(Name("b")).id();
let b1 = EntityHandle::new(e1).bind(&world);
let b1_copy = EntityHandle::new(e1).bind(&world);
let b2 = EntityHandle::new(e2).bind(&world);
assert_eq!(b1, b1_copy);
assert_ne!(b1, b2);
let mut set = std::collections::HashSet::new();
set.insert(b1);
assert!(set.contains(&b1_copy));
assert!(!set.contains(&b2));
}
#[test]
fn nav_wrapper_access() {
let mut world = World::new();
let entity = world.spawn(Name("nav")).id();
let bound = EntityHandle::new(entity).bind(&world);
let nav = bound.nav();
assert_eq!(nav.inner().entity(), entity);
}
}