use specs::{
prelude::*,
storage::HashMapStorage,
world::{Builder, WorldExt},
};
#[derive(Clone, Debug, PartialEq)]
struct CompInt(i8);
impl Component for CompInt {
type Storage = VecStorage<Self>;
}
#[derive(Clone, Debug, PartialEq)]
struct CompBool(bool);
impl Component for CompBool {
type Storage = HashMapStorage<Self>;
}
fn create_world() -> World {
let mut w = World::new();
w.register::<CompInt>();
w.register::<CompBool>();
w
}
#[should_panic]
#[test]
fn task_panics() {
struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = ();
fn run(&mut self, _: ()) {
panic!()
}
}
let mut world = create_world();
world
.create_entity()
.with(CompInt(7))
.with(CompBool(false))
.build();
DispatcherBuilder::new()
.with(Sys, "s", &[])
.build()
.dispatch(&mut world);
}
#[test]
fn dynamic_create() {
struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = Entities<'a>;
fn run(&mut self, entities: Self::SystemData) {
entities.create();
}
}
let mut world = create_world();
let mut dispatcher = DispatcherBuilder::new().with(Sys, "s", &[]).build();
for _ in 0..1_000 {
dispatcher.dispatch(&mut world);
}
}
#[test]
fn dynamic_deletion() {
struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = Entities<'a>;
fn run(&mut self, entities: Self::SystemData) {
let e = entities.create();
entities.delete(e).unwrap();
}
}
let mut world = create_world();
let mut dispatcher = DispatcherBuilder::new().with(Sys, "s", &[]).build();
for _ in 0..1_000 {
dispatcher.dispatch(&mut world);
}
}
#[test]
fn dynamic_create_and_delete() {
let mut world = create_world();
{
let entities = &world.entities();
let five: Vec<_> = entities.create_iter().take(5).collect();
for e in five {
entities.delete(e).unwrap();
}
}
world.maintain();
}
#[test]
fn mixed_create_merge() {
use std::collections::HashSet;
let mut world = create_world();
let mut set = HashSet::new();
let add = |set: &mut HashSet<Entity>, e: Entity| {
assert!(!set.contains(&e));
set.insert(e);
};
let insert = |w: &mut World, set: &mut HashSet<Entity>, cnt: usize| {
for _ in 0..10 {
for _ in 0..cnt {
add(set, w.create_entity().build());
let e = w.create_entity().build();
w.delete_entity(e).unwrap();
add(set, w.entities().create());
add(set, w.entities().create());
add(set, w.create_entity().build());
}
w.maintain();
}
};
insert(&mut world, &mut set, 10);
for e in set.drain() {
world.entities().delete(e).unwrap();
}
insert(&mut world, &mut set, 20);
for e in set.drain() {
world.delete_entity(e).unwrap();
}
insert(&mut world, &mut set, 40);
}
#[test]
fn is_alive() {
let mut w = World::new();
let e = w.create_entity().build();
assert!(w.is_alive(e));
w.delete_entity(e).unwrap();
assert!(!w.is_alive(e));
let e2 = w.create_entity().build();
assert!(w.is_alive(e2));
w.entities().delete(e2).unwrap();
assert!(w.is_alive(e2));
w.maintain();
assert!(!w.is_alive(e2));
}
#[test]
fn stillborn_entities() {
struct LCG(u32);
const RANDMAX: u32 = 32_767;
impl LCG {
fn new() -> Self {
LCG(0xdead_beef)
}
fn geni(&mut self) -> i8 {
((self.gen() as i32) - 0x7f) as i8
}
fn gen(&mut self) -> u32 {
self.0 = self.0.wrapping_mul(214_013).wrapping_add(2_531_011);
self.0 % RANDMAX
}
}
#[derive(Debug, Default)]
struct Rand {
values: Vec<i8>,
}
struct SysRand(LCG);
impl<'a> System<'a> for SysRand {
type SystemData = Write<'a, Rand>;
fn run(&mut self, mut data: Self::SystemData) {
let rng = &mut self.0;
let count = (rng.gen() % 25) as usize;
let values: &mut Vec<i8> = &mut data.values;
values.clear();
for _ in 0..count {
values.push(rng.geni());
}
}
}
struct Delete;
impl<'a> System<'a> for Delete {
type SystemData = (Entities<'a>, ReadStorage<'a, CompInt>, Read<'a, Rand>);
fn run(&mut self, (entities, comp_int, rand): Self::SystemData) {
let mut lowest = Vec::new();
for (&CompInt(k), entity) in (&comp_int, &entities).join() {
if lowest.iter().all(|&(n, _)| n >= k) {
lowest.push((k, entity));
}
}
lowest.reverse();
lowest.truncate(rand.values.len());
for (_, eid) in lowest {
entities.delete(eid).unwrap();
}
}
}
struct Insert;
impl<'a> System<'a> for Insert {
type SystemData = (Entities<'a>, WriteStorage<'a, CompInt>, Read<'a, Rand>);
fn run(&mut self, (entities, mut comp_int, rand): Self::SystemData) {
for &i in &rand.values {
let result = comp_int.insert(entities.create(), CompInt(i));
if let Err(_) = result {
panic!("Couldn't insert {} into a stillborn entity", i);
}
}
}
}
let mut rng = LCG::new();
let mut world = create_world();
world.insert(Rand { values: Vec::new() });
for _ in 0..100 {
world.create_entity().with(CompInt(rng.geni())).build();
}
let mut dispatcher = DispatcherBuilder::new()
.with(SysRand(rng), "rand", &[])
.with(Delete, "del", &["rand"])
.with(Insert, "insert", &["del"])
.build();
for _ in 0..100 {
dispatcher.dispatch(&mut world);
}
}
#[test]
fn register_idempotency() {
let mut w = World::new();
w.register::<CompInt>();
let e = w.create_entity().with::<CompInt>(CompInt(10)).build();
w.register::<CompInt>();
let i = w.read_storage::<CompInt>().get(e).unwrap().0;
assert_eq!(i, 10);
}
#[test]
fn join_two_components() {
let mut world = create_world();
world
.create_entity()
.with(CompInt(1))
.with(CompBool(false))
.build();
world
.create_entity()
.with(CompInt(2))
.with(CompBool(true))
.build();
world.create_entity().with(CompInt(3)).build();
struct Iter;
impl<'a> System<'a> for Iter {
type SystemData = (ReadStorage<'a, CompInt>, ReadStorage<'a, CompBool>);
fn run(&mut self, (int, boolean): Self::SystemData) {
let (mut first, mut second) = (false, false);
for (int, boolean) in (&int, &boolean).join() {
if int.0 == 1 && !boolean.0 {
first = true;
} else if int.0 == 2 && boolean.0 {
second = true;
} else {
panic!(
"Entity with compent values that shouldn't be: {:?} {:?}",
int, boolean
);
}
}
assert!(
first,
"There should be entity with CompInt(1) and CompBool(false)"
);
assert!(
second,
"There should be entity with CompInt(2) and CompBool(true)"
);
}
}
let mut dispatcher = DispatcherBuilder::new().with(Iter, "iter", &[]).build();
dispatcher.dispatch(&mut world);
}
#[test]
#[cfg(feature = "parallel")]
fn par_join_two_components() {
use std::sync::{
atomic::{AtomicBool, Ordering},
Mutex,
};
let mut world = create_world();
world
.create_entity()
.with(CompInt(1))
.with(CompBool(false))
.build();
world
.create_entity()
.with(CompInt(2))
.with(CompBool(true))
.build();
world.create_entity().with(CompInt(3)).build();
let first = AtomicBool::new(false);
let second = AtomicBool::new(false);
let error = Mutex::new(None);
struct Iter<'a>(
&'a AtomicBool,
&'a AtomicBool,
&'a Mutex<Option<(i8, bool)>>,
);
impl<'a, 'b> System<'a> for Iter<'b> {
type SystemData = (ReadStorage<'a, CompInt>, ReadStorage<'a, CompBool>);
fn run(&mut self, (int, boolean): Self::SystemData) {
use rayon::iter::ParallelIterator;
let Iter(first, second, error) = *self;
(&int, &boolean).par_join().for_each(|(int, boolean)| {
if !first.load(Ordering::SeqCst) && int.0 == 1 && !boolean.0 {
first.store(true, Ordering::SeqCst);
} else if !second.load(Ordering::SeqCst) && int.0 == 2 && boolean.0 {
second.store(true, Ordering::SeqCst);
} else {
*error.lock().unwrap() = Some((int.0, boolean.0));
}
});
}
}
let mut dispatcher = DispatcherBuilder::new()
.with(Iter(&first, &second, &error), "iter", &[])
.build();
dispatcher.dispatch(&mut world);
assert_eq!(
*error.lock().unwrap(),
None,
"Entity shouldn't be in the join",
);
assert!(
first.load(Ordering::SeqCst),
"There should be entity with CompInt(1) and CompBool(false)"
);
assert!(
second.load(Ordering::SeqCst),
"There should be entity with CompInt(2) and CompBool(true)"
);
}
#[test]
#[cfg(feature = "parallel")]
fn par_join_with_maybe() {
use std::sync::{
atomic::{AtomicBool, Ordering},
Mutex,
};
let mut world = create_world();
world
.create_entity()
.with(CompInt(1))
.with(CompBool(false))
.build();
world
.create_entity()
.with(CompInt(2))
.with(CompBool(true))
.build();
world.create_entity().with(CompInt(3)).build();
let first = AtomicBool::new(false);
let second = AtomicBool::new(false);
let third = AtomicBool::new(false);
let error = Mutex::new(None);
struct Iter<'a>(
&'a AtomicBool,
&'a AtomicBool,
&'a AtomicBool,
&'a Mutex<Option<(i8, Option<bool>)>>,
);
impl<'a, 'b> System<'a> for Iter<'b> {
type SystemData = (ReadStorage<'a, CompInt>, ReadStorage<'a, CompBool>);
fn run(&mut self, (int, boolean): Self::SystemData) {
use rayon::iter::ParallelIterator;
let Iter(first, second, third, error) = *self;
(&int, boolean.maybe())
.par_join()
.for_each(|(int, boolean)| {
let boolean = boolean.map(|c| c.0);
if !first.load(Ordering::SeqCst) && int.0 == 1 && boolean == Some(false) {
first.store(true, Ordering::SeqCst);
} else if !second.load(Ordering::SeqCst) && int.0 == 2 && boolean == Some(true)
{
second.store(true, Ordering::SeqCst);
} else if !third.load(Ordering::SeqCst) && int.0 == 3 && boolean == None {
third.store(true, Ordering::SeqCst);
} else {
*error.lock().unwrap() = Some((int.0, boolean));
}
});
}
}
let mut dispatcher = DispatcherBuilder::new()
.with(Iter(&first, &second, &third, &error), "iter", &[])
.build();
dispatcher.dispatch(&mut world);
assert_eq!(
*error.lock().unwrap(),
None,
"Entity shouldn't be in the join",
);
assert!(
first.load(Ordering::SeqCst),
"There should be entity with CompInt(1) and CompBool(false)"
);
assert!(
second.load(Ordering::SeqCst),
"There should be entity with CompInt(2) and CompBool(true)"
);
assert!(
third.load(Ordering::SeqCst),
"There should be entity with CompInt(3) and no CompBool"
);
}
#[test]
#[cfg(feature = "parallel")]
fn par_join_many_entities_and_systems() {
use rayon::iter::ParallelIterator;
use std::sync::Mutex;
let failed = Mutex::new(vec![]);
let mut world = create_world();
for _ in 0..1000 {
world.create_entity().with(CompInt(-128)).build();
}
struct Incr;
impl<'a> System<'a> for Incr {
type SystemData = (Entities<'a>, WriteStorage<'a, CompInt>);
fn run(&mut self, (entities, mut ints): Self::SystemData) {
(&mut ints, &entities).par_join().for_each(|(int, _)| {
int.0 += 1;
});
}
}
let mut builder = DispatcherBuilder::new();
for _ in 0..255 {
builder.add(Incr, "", &[]);
}
struct FindFailed<'a>(&'a Mutex<Vec<(u32, i8)>>);
impl<'a, 'b> System<'a> for FindFailed<'b> {
type SystemData = (Entities<'a>, ReadStorage<'a, CompInt>);
fn run(&mut self, (entities, ints): Self::SystemData) {
(&ints, &entities).par_join().for_each(|(int, entity)| {
if int.0 != 127 {
self.0.lock().unwrap().push((entity.id(), int.0));
}
});
}
}
let mut dispatcher = builder
.with_barrier()
.with(FindFailed(&failed), "find_failed", &[])
.build();
dispatcher.dispatch(&mut world);
for &(id, n) in &*failed.lock().unwrap() {
panic!(
"Entity with id {} failed to count to 127. Count was {}",
id, n
);
}
}
#[test]
fn getting_specific_entity_with_join() {
let mut world = create_world();
world
.create_entity()
.with(CompInt(1))
.with(CompBool(true))
.build();
let entity = {
let ints = world.read_storage::<CompInt>();
let mut bools = world.write_storage::<CompBool>();
let entity = world.entities().join().next().unwrap();
assert_eq!(
Some((&CompInt(1), &mut CompBool(true))),
(&ints, &mut bools).join().get(entity, &world.entities())
);
bools.remove(entity);
assert_eq!(
None,
(&ints, &mut bools).join().get(entity, &world.entities())
);
entity
};
world.delete_entity(entity).unwrap();
world
.create_entity()
.with(CompInt(2))
.with(CompBool(false))
.build();
let ints = world.read_storage::<CompInt>();
let mut bools = world.write_storage::<CompBool>();
assert_eq!(
None,
(&ints, &mut bools).join().get(entity, &world.entities())
);
}
#[test]
fn maintain_entity_deletion() {
let mut world = World::new();
struct DeleteSys {
pub entity: Option<Entity>,
};
impl<'a> System<'a> for DeleteSys {
type SystemData = Entities<'a>;
fn run(&mut self, entities: Self::SystemData) {
if let Some(entity) = self.entity {
if let Err(err) = entities.delete(entity) {
println!("Failed deleting entity: {}", err);
}
}
self.entity = None;
}
}
let mut delete = DeleteSys { entity: None };
struct CheckSys;
impl<'a> System<'a> for CheckSys {
type SystemData = (
Entities<'a>,
ReadStorage<'a, CompInt>,
ReadStorage<'a, CompBool>,
);
fn run(&mut self, (entities, ints, bools): Self::SystemData) {
assert_eq!(
(&entities, &ints, &bools).join().count(),
(&ints, &bools).join().count()
);
}
}
let mut check = CheckSys;
System::setup(&mut check, &mut world);
let _e1 = world
.create_entity()
.with(CompInt(12))
.with(CompBool(true))
.build();
let e2 = world
.create_entity()
.with(CompInt(12))
.with(CompBool(true))
.build();
let _e3 = world
.create_entity()
.with(CompInt(12))
.with(CompBool(true))
.build();
world.maintain();
check.run_now(&world);
delete.entity = Some(e2);
delete.run_now(&world);
world.maintain();
check.run_now(&world);
}