Skip to content

Commit 89926f0

Browse files
committed
Fix ECS remove and reuse issue
1 parent 6011555 commit 89926f0

File tree

2 files changed

+61
-148
lines changed

2 files changed

+61
-148
lines changed

src/world.rs

Lines changed: 47 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -516,139 +516,64 @@ mod tests {
516516
assert_eq!(weight.0, 5000);
517517
}
518518
}
519-
}
520-
521-
/*
522-
#[cfg(test)]
523-
mod tests {
524-
use super::World;
525-
use crate::{World, Entity;
526-
527-
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
528-
struct Armor(u32);
529-
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
530-
struct HealthComponent(u32);
531-
struct SpeedComponent(u32);
532-
struct DamageComponent(u32);
533-
struct WeightComponent(u32);
534-
535-
fn spawn() -> World {
536-
let mut world = World::new();
537-
world.spawn(Some((Armor(100), HealthComponent(100), DamageComponent(300))));
538-
world.spawn(Some((HealthComponent(80), SpeedComponent(10))));
539-
world.spawn(Some((SpeedComponent(50), DamageComponent(45))));
540-
world.spawn(Some((DamageComponent(600), Armor(10))));
541-
542-
let bulk = (0..9).map(|_| (SpeedComponent(35), WeightComponent(5000)));
543-
world.spawn(bulk);
544-
545-
world
546-
}
547519

548520
#[test]
549-
fn spawn_and_query() {
550-
let world = spawn();
551-
552-
let mut iter = world.query::<(&Armor, &DamageComponent)>();
553-
554-
let item = iter.next();
555-
assert!(item.is_some());
556-
557-
if let Some((armor, damage)) = item {
558-
assert_eq!(armor.0, 100); // Armor(100)
559-
assert_eq!(damage.0, 300); // DamageComponent(300)
560-
}
561-
562-
let item = iter.next();
563-
assert!(item.is_some());
521+
fn exiled_entities_must_not_be_in_world() {
522+
let mut world = World::new();
564523

565-
let item = item.unwrap();
566-
assert_eq!(item.0 .0, 10); // Armor(10)
567-
assert_eq!(item.1 .0, 600); // DamageComponent(600)
524+
let cycles = 16;
525+
let entities_per_cycle = 8;
568526

569-
let item = iter.next();
570-
assert!(item.is_none());
571-
}
527+
let mut entities = std::collections::HashMap::new();
528+
let mut zombie_entities = vec![];
572529

573-
#[test]
574-
fn spawn_and_modify() {
575-
let world = spawn();
576-
{
577-
let iter = world.query::<(&mut SpeedComponent,)>()D;
578-
for (speed,) in iter {
579-
speed.0 = 123;
530+
for cycle in 0..cycles {
531+
for entity_value in 0..entities_per_cycle {
532+
let entity_id = world
533+
.spawn(Some((HealthComponent(cycle), SpeedComponent(entity_value))))
534+
.next()
535+
.expect("entity must be spawned");
536+
println!("Spawn {entity_id:?} | cycle={cycle}, entity_value={entity_value}");
537+
entities.insert(entity_id, true);
580538
}
581-
}
582-
{
583-
let iter = world.query::<(&SpeedComponent,)>();
584-
for (speed,) in iter {
585-
assert_eq!(speed.0, 123);
586-
}
587-
}
588-
}
589539

590-
#[test]
591-
fn spawn_and_exile() {
592-
let mut world = spawn();
593-
{
594-
let iter = world.query::<(&Entity, &mut Armor)>();
595-
let mut entity_to_delete = None;
596-
let mut entities_before = 0;
597-
for (entity, armor) in iter {
598-
if armor.0 == 100 {
599-
entity_to_delete = Some(*entity);
540+
for (entity_id, health, speed) in
541+
world.query::<(&Id<Entity>, &mut HealthComponent, &SpeedComponent)>()
542+
{
543+
println!(
544+
"Query {entity_id:?} | cycle={}, entity_value={}",
545+
health.0, speed.0
546+
);
547+
let entity_memo = entities.get(entity_id);
548+
assert!(entity_memo.is_some());
549+
let must_be_in_world = entity_memo.cloned().unwrap();
550+
println!("must_be_in_world (expect true): {must_be_in_world}");
551+
if !must_be_in_world {
552+
zombie_entities.push(*entity_id);
600553
}
601-
entities_before += 1;
602554
}
603-
assert!(entity_to_delete.is_some());
604-
605-
world.exile(entity_to_delete.unwrap());
606555

607-
let iter = world.query::<(&Entity, &mut Armor)>();
608-
let mut entities_after = 0;
609-
for (entity, _armor) in iter {
610-
assert_ne!(*entity, entity_to_delete.unwrap());
611-
entities_after += 1;
556+
let to_exile = entities
557+
.iter()
558+
.filter(|(_key, &value)| value)
559+
.map(|(key, _value)| *key)
560+
.enumerate()
561+
.filter(|(i, _)| i % 2 == 0)
562+
.map(|(_, id)| id)
563+
.collect::<Vec<_>>();
564+
565+
for entity_id in to_exile.into_iter() {
566+
println!("Exile {entity_id:?}");
567+
let must_be_in_world = entities.get(&entity_id).cloned().unwrap_or(false);
568+
println!("must_be_in_world (expect true): {must_be_in_world}");
569+
assert!(must_be_in_world);
570+
let entity = world.exile(&entity_id);
571+
assert!(entity.is_some());
572+
println!("entity exiled (expect true): {}", entity.is_some());
573+
entities.insert(entity_id, false);
612574
}
613-
614-
assert_eq!(entities_before - 1, entities_after);
615-
}
616-
}
617-
618-
#[test]
619-
fn spawn_and_get_by_entity() {
620-
let world = spawn();
621-
let entity = Entity::from(0);
622-
let query = world.get::<(&Armor, &HealthComponent)>(entity);
623-
assert!(query.is_some());
624-
if let Some((&armor, &health)) = query {
625-
assert_eq!(armor, Armor(100));
626-
assert_eq!(health, HealthComponent(100));
627-
}
628-
}
629-
630-
#[test]
631-
fn spawn_and_check_entities() {
632-
let mut world = World::default();
633-
let mut spawned = world.spawn(Some((Armor(1),)));
634-
635-
assert_eq!(spawned.next(), Some(Entity::from(0)));
636-
assert_eq!(spawned.first(), Entity::from(0));
637-
assert_eq!(spawned.last(), Some(Entity::from(0)));
638-
639-
let spawned: Vec<Entity> = world
640-
.spawn([
641-
(Armor(2), HealthComponent(100)),
642-
(Armor(4), HealthComponent(80)),
643-
(Armor(4), HealthComponent(90)),
644-
])
645-
.into();
646-
647-
assert_eq!(spawned.len(), 3);
648-
649-
for (i, ent) in spawned.iter().enumerate() {
650-
assert_eq!(ent, &Entity::from(i as u64 + 1));
651575
}
576+
println!("Zombie entities: {zombie_entities:?}");
577+
assert_eq!(zombie_entities.len(), 0);
652578
}
653579
}
654-
*/

src/world/storage.rs

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -148,16 +148,27 @@ impl Container {
148148

149149
pub fn matches(&self, archetype: &mut Archetype) -> bool {
150150
!archetype.any(|component_type_id| !self.data.contains_key(component_type_id))
151+
&& archetype.len() == self.data.len()
151152
}
152153

153154
pub fn store(&mut self, entity: Entity) -> usize {
154155
let index = self.removed.pop().unwrap_or_else(|| self.next_index());
155156

156157
for (component_type_id, component) in entity.into_iter() {
157-
self.data
158+
let components_list = self
159+
.data
158160
.get_mut(&component_type_id)
159-
.expect("Entity should match container")
160-
.insert(index, Some(UnsafeCell::new(component)));
161+
.expect("Entity should match container");
162+
let components_list_len = components_list.len();
163+
let component_cell = Some(UnsafeCell::new(component));
164+
165+
if index < components_list_len {
166+
components_list[index] = component_cell;
167+
} else if index == components_list_len {
168+
components_list.push(component_cell);
169+
} else {
170+
panic!("Component index `{index}`is out of storage len `{components_list_len}`");
171+
}
161172
}
162173

163174
index
@@ -278,26 +289,3 @@ impl From<&Entity> for Container {
278289
}
279290
}
280291
}
281-
282-
/*
283-
#[cfg(test)]
284-
mod tests {
285-
struct Item1(u32);
286-
struct Item2(u32);
287-
use crate::world::container::Container;
288-
#[test]
289-
fn mutability() {
290-
let mut c = Container::new::<(Item1, Item2)>();
291-
c.push::<Item1>(Item1(123));
292-
c.push::<Item2>(Item2(666));
293-
294-
for i in c.get_mut::<Item1>().unwrap() {
295-
i.0 += 198;
296-
}
297-
298-
for i in c.get::<Item1>().unwrap() {
299-
assert_eq!(i.0, 321);
300-
}
301-
}
302-
}
303-
*/

0 commit comments

Comments
 (0)