mod handle;
mod ptr;
#[cfg(feature = "nav-traits")]
mod nav;
pub use handle::{BoundEntity, BoundEntityNav, EntityHandle};
pub use ptr::{EntityPtr, EntityPtrNav, EntityPtrNavMany, WorldRef};
#[cfg(feature = "nav-traits")]
pub use nav::{HasChildren, HasParent};
use bevy_ecs::entity::Entity;
use bevy_ecs::world::World;
pub trait WorldExt {
fn bind_entity(&self, entity: Entity) -> BoundEntity<'_>;
fn entity_ptr(&self, entity: Entity) -> EntityPtr;
}
impl WorldExt for World {
#[inline]
fn bind_entity(&self, entity: Entity) -> BoundEntity<'_> {
EntityHandle::new(entity).bind(self)
}
#[inline]
fn entity_ptr(&self, entity: Entity) -> EntityPtr {
unsafe { WorldRef::new(self) }.entity(entity)
}
}
#[cfg(test)]
mod integration_tests {
use super::*;
use bevy_ecs::component::Component;
use bevy_ecs::world::World;
#[derive(Component)]
struct Name(&'static str);
#[derive(Component)]
struct Health(i32);
#[derive(Component)]
struct Parent(EntityHandle);
#[derive(Component)]
struct Target(Option<EntityHandle>);
#[test]
fn mixed_handle_and_ptr() {
let mut world = World::new();
let grandchild = world.spawn(Name("grandchild")).id();
let child = world
.spawn((Name("child"), Parent(EntityHandle::new(grandchild))))
.id();
let root = world
.spawn((Name("root"), Parent(EntityHandle::new(child))))
.id();
let handle = EntityHandle::new(root);
let bound = handle.bind(&world);
assert_eq!(bound.get::<Name>().unwrap().0, "root");
let child_bound = bound.follow::<Parent, _>(|p| p.0).unwrap();
assert_eq!(child_bound.get::<Name>().unwrap().0, "child");
let w = unsafe { WorldRef::new(&world) };
let ptr = w.from_handle(child_bound.handle());
assert_eq!(ptr.get::<Name>().unwrap().0, "child");
let grandchild_ptr = ptr.follow::<Parent, _>(|p| p.0).unwrap();
assert_eq!(grandchild_ptr.get::<Name>().unwrap().0, "grandchild");
let grandchild_handle = grandchild_ptr.handle();
assert_eq!(
grandchild_handle.get::<Name>(&world).unwrap().0,
"grandchild"
);
}
#[test]
fn tree_traversal() {
let mut world = World::new();
let leaf = world.spawn(Name("leaf")).id();
let left = world.spawn(Name("left")).id();
let right = world
.spawn((Name("right"), Parent(EntityHandle::new(leaf))))
.id();
let root = world.spawn(Name("root")).id();
world
.entity_mut(root)
.insert(Target(Some(EntityHandle::new(left))));
world
.entity_mut(left)
.insert(Target(Some(EntityHandle::new(right))));
let w = unsafe { WorldRef::new(&world) };
let root_ptr = w.entity(root);
let left_ptr = root_ptr.follow_opt::<Target, _>(|t| t.0).unwrap();
let right_ptr = left_ptr.follow_opt::<Target, _>(|t| t.0).unwrap();
let leaf_ptr = right_ptr.follow::<Parent, _>(|p| p.0).unwrap();
assert_eq!(root_ptr.get::<Name>().unwrap().0, "root");
assert_eq!(left_ptr.get::<Name>().unwrap().0, "left");
assert_eq!(right_ptr.get::<Name>().unwrap().0, "right");
assert_eq!(leaf_ptr.get::<Name>().unwrap().0, "leaf");
}
#[test]
fn stale_reference_handling() {
let mut world = World::new();
let target = world.spawn(Name("target")).id();
let source = world
.spawn((Name("source"), Parent(EntityHandle::new(target))))
.id();
let source_handle = EntityHandle::new(source);
{
let bound = source_handle.bind(&world);
let target_bound = bound.follow::<Parent, _>(|p| p.0).unwrap();
assert!(target_bound.is_alive());
assert_eq!(target_bound.get::<Name>().unwrap().0, "target");
}
world.despawn(target);
let bound = source_handle.bind(&world);
let stale_bound = bound.follow::<Parent, _>(|p| p.0).unwrap();
assert!(!stale_bound.is_alive());
assert!(stale_bound.get::<Name>().is_none());
}
#[test]
fn handle_in_component() {
let mut world = World::new();
let target = world.spawn((Name("target"), Health(100))).id();
let source = world
.spawn((Name("source"), Parent(EntityHandle::new(target))))
.id();
let handle = EntityHandle::new(source);
let parent_handle = handle.bind(&world).get::<Parent>().unwrap().0;
assert_eq!(parent_handle.get::<Name>(&world).unwrap().0, "target");
assert_eq!(parent_handle.get::<Health>(&world).unwrap().0, 100);
}
#[derive(Component)]
struct Value(i32);
#[derive(Component)]
struct TreeChildren(Vec<EntityHandle>);
fn sum_tree(ptr: EntityPtr) -> i32 {
let mine = ptr.get::<Value>().map(|v| v.0).unwrap_or(0);
let children_sum: i32 = ptr
.get::<TreeChildren>()
.map(|c| c.0.iter().map(|h| sum_tree(ptr.follow_handle(*h))).sum())
.unwrap_or(0);
mine + children_sum
}
#[test]
fn sum_tree_values() {
let mut world = World::new();
let c = world.spawn(Value(2)).id();
let a = world
.spawn((Value(5), TreeChildren(vec![EntityHandle::new(c)])))
.id();
let b = world.spawn(Value(3)).id();
let root = world
.spawn((
Value(10),
TreeChildren(vec![EntityHandle::new(a), EntityHandle::new(b)]),
))
.id();
let w = unsafe { WorldRef::new(&world) };
let total = sum_tree(w.entity(root));
assert_eq!(total, 20); }
fn find_root(ptr: EntityPtr) -> EntityPtr {
match ptr.follow::<Parent, _>(|p| p.0) {
Some(parent_ptr) => find_root(parent_ptr),
None => ptr,
}
}
#[test]
fn find_root_test() {
let mut world = World::new();
let root = world.spawn(Name("root")).id();
let a = world
.spawn((Name("a"), Parent(EntityHandle::new(root))))
.id();
let b = world.spawn((Name("b"), Parent(EntityHandle::new(a)))).id();
let c = world.spawn((Name("c"), Parent(EntityHandle::new(b)))).id();
let w = unsafe { WorldRef::new(&world) };
assert_eq!(find_root(w.entity(c)).get::<Name>().unwrap().0, "root");
assert_eq!(find_root(w.entity(b)).get::<Name>().unwrap().0, "root");
assert_eq!(find_root(w.entity(a)).get::<Name>().unwrap().0, "root");
assert_eq!(find_root(w.entity(root)).get::<Name>().unwrap().0, "root");
}
fn tree_depth(ptr: EntityPtr) -> usize {
ptr.get::<TreeChildren>()
.map(|c| {
c.0.iter()
.map(|h| tree_depth(ptr.follow_handle(*h)))
.max()
.unwrap_or(0)
+ 1
})
.unwrap_or(0)
}
#[test]
fn tree_depth_test() {
let mut world = World::new();
let d = world.spawn(Name("d")).id();
let c = world
.spawn((Name("c"), TreeChildren(vec![EntityHandle::new(d)])))
.id();
let a = world
.spawn((Name("a"), TreeChildren(vec![EntityHandle::new(c)])))
.id();
let b = world.spawn(Name("b")).id();
let root = world
.spawn((
Name("root"),
TreeChildren(vec![EntityHandle::new(a), EntityHandle::new(b)]),
))
.id();
let w = unsafe { WorldRef::new(&world) };
assert_eq!(tree_depth(w.entity(root)), 3);
assert_eq!(tree_depth(w.entity(a)), 2);
assert_eq!(tree_depth(w.entity(c)), 1);
assert_eq!(tree_depth(w.entity(d)), 0);
}
#[test]
fn deep_chain_navigation() {
let mut world = World::new();
let e5 = world.spawn((Name("e5"), Health(5))).id();
let e4 = world
.spawn((Name("e4"), Health(4), Parent(EntityHandle::new(e5))))
.id();
let e3 = world
.spawn((Name("e3"), Health(3), Parent(EntityHandle::new(e4))))
.id();
let e2 = world
.spawn((Name("e2"), Health(2), Parent(EntityHandle::new(e3))))
.id();
let e1 = world
.spawn((Name("e1"), Health(1), Parent(EntityHandle::new(e2))))
.id();
let e0 = world
.spawn((Name("e0"), Health(0), Parent(EntityHandle::new(e1))))
.id();
let w = unsafe { WorldRef::new(&world) };
let ptr = w.entity(e0);
let deep = ptr
.follow::<Parent, _>(|p| p.0)
.and_then(|p| p.follow::<Parent, _>(|p| p.0))
.and_then(|p| p.follow::<Parent, _>(|p| p.0))
.and_then(|p| p.follow::<Parent, _>(|p| p.0))
.and_then(|p| p.follow::<Parent, _>(|p| p.0));
assert!(deep.is_some());
assert_eq!(deep.unwrap().get::<Name>().unwrap().0, "e5");
assert_eq!(deep.unwrap().get::<Health>().unwrap().0, 5);
}
#[test]
fn breadth_traversal() {
let mut world = World::new();
let leaves: Vec<_> = ["a0", "a1", "b0", "b1", "c0", "c1", "d0", "d1"]
.iter()
.map(|name| world.spawn(Name(name)).id())
.collect();
let a = world
.spawn((
Name("a"),
TreeChildren(vec![
EntityHandle::new(leaves[0]),
EntityHandle::new(leaves[1]),
]),
))
.id();
let b = world
.spawn((
Name("b"),
TreeChildren(vec![
EntityHandle::new(leaves[2]),
EntityHandle::new(leaves[3]),
]),
))
.id();
let c = world
.spawn((
Name("c"),
TreeChildren(vec![
EntityHandle::new(leaves[4]),
EntityHandle::new(leaves[5]),
]),
))
.id();
let d = world
.spawn((
Name("d"),
TreeChildren(vec![
EntityHandle::new(leaves[6]),
EntityHandle::new(leaves[7]),
]),
))
.id();
let root = world
.spawn((
Name("root"),
TreeChildren(vec![
EntityHandle::new(a),
EntityHandle::new(b),
EntityHandle::new(c),
EntityHandle::new(d),
]),
))
.id();
let w = unsafe { WorldRef::new(&world) };
let root_ptr = w.entity(root);
let children = root_ptr.get::<TreeChildren>().unwrap();
let grandchildren: Vec<_> = children
.0
.iter()
.flat_map(|h| {
let child_ptr = root_ptr.follow_handle(*h);
child_ptr
.get::<TreeChildren>()
.map(|tc| {
tc.0.iter()
.map(|gh| child_ptr.follow_handle(*gh))
.collect::<Vec<_>>()
})
.unwrap_or_default()
})
.collect();
assert_eq!(grandchildren.len(), 8);
let names: Vec<_> = grandchildren
.iter()
.filter_map(|p| p.get::<Name>())
.map(|n| n.0)
.collect();
assert!(names.contains(&"a0"));
assert!(names.contains(&"d1"));
}
#[test]
fn referential_transparency() {
let mut world = World::new();
let target = world.spawn((Name("target"), Health(42))).id();
let source = world
.spawn((Name("source"), Parent(EntityHandle::new(target))))
.id();
let w = unsafe { WorldRef::new(&world) };
let ptr = w.entity(source);
let result1 = ptr.follow::<Parent, _>(|p| p.0);
let result2 = ptr.follow::<Parent, _>(|p| p.0);
let result3 = ptr.follow::<Parent, _>(|p| p.0);
assert!(result1.is_some());
assert!(result2.is_some());
assert!(result3.is_some());
assert_eq!(result1.unwrap().entity(), result2.unwrap().entity());
assert_eq!(result2.unwrap().entity(), result3.unwrap().entity());
assert_eq!(
result1.unwrap().get::<Health>().unwrap().0,
result2.unwrap().get::<Health>().unwrap().0
);
}
#[test]
fn pure_function_composition() {
let mut world = World::new();
let d = world.spawn((Name("d"), Health(4))).id();
let c = world
.spawn((Name("c"), Health(3), Parent(EntityHandle::new(d))))
.id();
let b = world
.spawn((Name("b"), Health(2), Parent(EntityHandle::new(c))))
.id();
let a = world
.spawn((Name("a"), Health(1), Parent(EntityHandle::new(b))))
.id();
let w = unsafe { WorldRef::new(&world) };
let navigate_three = |ptr: EntityPtr| -> Option<i32> {
ptr.follow::<Parent, _>(|p| p.0)
.and_then(|p| p.follow::<Parent, _>(|p| p.0))
.and_then(|p| p.follow::<Parent, _>(|p| p.0))
.and_then(|p| p.get::<Health>().map(|h| h.0))
};
let result1 = navigate_three(w.entity(a));
let result2 = navigate_three(w.entity(a));
assert_eq!(result1, Some(4));
assert_eq!(result2, Some(4));
assert_eq!(result1, result2);
}
#[test]
fn option_chain_and_then() {
let mut world = World::new();
let end = world.spawn((Name("end"), Health(100))).id();
let mid = world
.spawn((Name("mid"), Parent(EntityHandle::new(end))))
.id();
let start = world
.spawn((Name("start"), Parent(EntityHandle::new(mid))))
.id();
let w = unsafe { WorldRef::new(&world) };
let ptr = w.entity(start);
let health = ptr
.follow::<Parent, _>(|p| p.0)
.and_then(|p| p.follow::<Parent, _>(|p| p.0))
.and_then(|p| p.get::<Health>())
.map(|h| h.0);
assert_eq!(health, Some(100));
let no_health = ptr
.follow::<Parent, _>(|p| p.0)
.and_then(|p| p.follow::<Parent, _>(|p| p.0))
.and_then(|p| p.follow::<Parent, _>(|p| p.0)) .and_then(|p| p.get::<Health>())
.map(|h| h.0);
assert_eq!(no_health, None);
}
#[test]
fn filter_map_over_children() {
let mut world = World::new();
let healthy1 = world.spawn((Name("healthy1"), Health(50))).id();
let healthy2 = world.spawn((Name("healthy2"), Health(75))).id();
let no_health1 = world.spawn(Name("no_health1")).id();
let no_health2 = world.spawn(Name("no_health2")).id();
let parent = world
.spawn((
Name("parent"),
TreeChildren(vec![
EntityHandle::new(healthy1),
EntityHandle::new(no_health1),
EntityHandle::new(healthy2),
EntityHandle::new(no_health2),
]),
))
.id();
let w = unsafe { WorldRef::new(&world) };
let ptr = w.entity(parent);
let healths: Vec<i32> = ptr
.get::<TreeChildren>()
.map(|tc| {
tc.0.iter()
.filter_map(|h| {
let child = ptr.follow_handle(*h);
child.get::<Health>().map(|h| h.0)
})
.collect()
})
.unwrap_or_default();
assert_eq!(healths.len(), 2);
assert!(healths.contains(&50));
assert!(healths.contains(&75));
let total: i32 = healths.iter().sum();
assert_eq!(total, 125);
}
#[derive(Component)]
struct TypeA(EntityHandle);
#[derive(Component)]
struct TypeB(EntityHandle);
#[test]
fn multi_component_chain() {
let mut world = World::new();
let end = world.spawn((Name("end"), Health(99))).id();
let mid = world
.spawn((Name("mid"), TypeB(EntityHandle::new(end))))
.id();
let start = world
.spawn((Name("start"), TypeA(EntityHandle::new(mid))))
.id();
let w = unsafe { WorldRef::new(&world) };
let ptr = w.entity(start);
let result = ptr
.follow::<TypeA, _>(|a| a.0)
.and_then(|p| p.follow::<TypeB, _>(|b| b.0))
.and_then(|p| p.get::<Health>())
.map(|h| h.0);
assert_eq!(result, Some(99));
}
#[test]
fn alternating_component_navigation() {
let mut world = World::new();
let e4 = world.spawn((Name("e4"), Health(44))).id();
let e3 = world.spawn((Name("e3"), TypeB(EntityHandle::new(e4)))).id();
let e2 = world.spawn((Name("e2"), TypeA(EntityHandle::new(e3)))).id();
let e1 = world.spawn((Name("e1"), TypeB(EntityHandle::new(e2)))).id();
let e0 = world.spawn((Name("e0"), TypeA(EntityHandle::new(e1)))).id();
let w = unsafe { WorldRef::new(&world) };
let ptr = w.entity(e0);
let result = ptr
.follow::<TypeA, _>(|a| a.0)
.and_then(|p| p.follow::<TypeB, _>(|b| b.0))
.and_then(|p| p.follow::<TypeA, _>(|a| a.0))
.and_then(|p| p.follow::<TypeB, _>(|b| b.0));
assert!(result.is_some());
assert_eq!(result.unwrap().get::<Name>().unwrap().0, "e4");
assert_eq!(result.unwrap().get::<Health>().unwrap().0, 44);
}
#[test]
fn world_ext_entity_ptr() {
let mut world = World::new();
let entity = world.spawn(Name("test")).id();
let ptr = world.entity_ptr(entity);
assert_eq!(ptr.get::<Name>().unwrap().0, "test");
}
#[test]
fn world_ext_bind_entity() {
let mut world = World::new();
let entity = world.spawn(Name("test")).id();
let bound = world.bind_entity(entity);
assert_eq!(bound.get::<Name>().unwrap().0, "test");
}
#[test]
fn entity_ptr_eq_hash() {
let mut world = World::new();
let e1 = world.spawn(()).id();
let e2 = world.spawn(()).id();
let ptr1 = world.entity_ptr(e1);
let ptr1_copy = world.entity_ptr(e1);
let ptr2 = world.entity_ptr(e2);
assert_eq!(ptr1, ptr1_copy);
assert_ne!(ptr1, ptr2);
let mut set = std::collections::HashSet::new();
set.insert(ptr1);
assert!(set.contains(&ptr1_copy));
assert!(!set.contains(&ptr2));
}
}
#[cfg(all(test, feature = "nav-traits"))]
mod nav_integration_tests {
use super::*;
use bevy_ecs::component::Component;
use bevy_ecs::world::World;
#[derive(Component)]
struct Name(&'static str);
#[derive(Component)]
struct ParentRef(Option<EntityHandle>);
impl HasParent for ParentRef {
fn parent_handle(&self) -> Option<EntityHandle> {
self.0
}
}
#[derive(Component)]
struct ChildRefs(Vec<EntityHandle>);
impl HasChildren for ChildRefs {
fn children_handles(&self) -> &[EntityHandle] {
&self.0
}
}
#[test]
fn bound_parent_chain() {
let mut world = World::new();
let grandparent = world.spawn(Name("grandparent")).id();
let parent = world
.spawn((
Name("parent"),
ParentRef(Some(EntityHandle::new(grandparent))),
))
.id();
let child = world
.spawn((Name("child"), ParentRef(Some(EntityHandle::new(parent)))))
.id();
let bound = EntityHandle::new(child).bind(&world);
let parent_bound = bound.nav().parent::<ParentRef>().unwrap();
assert_eq!(parent_bound.get::<Name>().unwrap().0, "parent");
let grandparent_bound = parent_bound.nav().parent::<ParentRef>().unwrap();
assert_eq!(grandparent_bound.get::<Name>().unwrap().0, "grandparent");
}
#[test]
fn ptr_children_iteration() {
let mut world = World::new();
let child1 = world.spawn(Name("child1")).id();
let child2 = world.spawn(Name("child2")).id();
let child3 = world.spawn(Name("child3")).id();
let parent = world
.spawn((
Name("parent"),
ChildRefs(vec![
EntityHandle::new(child1),
EntityHandle::new(child2),
EntityHandle::new(child3),
]),
))
.id();
let w = unsafe { WorldRef::new(&world) };
let parent_ptr = w.entity(parent);
let children: Vec<_> = parent_ptr.nav_many().children::<ChildRefs>().collect();
assert_eq!(children.len(), 3);
let names: Vec<_> = children
.iter()
.filter_map(|c| c.get::<Name>())
.map(|n| n.0)
.collect();
assert!(names.contains(&"child1"));
assert!(names.contains(&"child2"));
assert!(names.contains(&"child3"));
}
}