Skip to content

Commit 4825051

Browse files
authored
Test entity labels, fixed corner cases, changed interface (#1152)
* Test entity labels, fixed corner cases, changed interface * add tests for entity_labels_system * fixed filling label_entities map * fixed corner cases when removing entities, Labels component * changed EntityLabels::get to return slice or empty slice instead of None or Some empty or non-empty slice Changing the interface of EntityLabels::get is beneficial, since else you would get different results in case there was an entity before that with this missing label or not. You would either get None or Some(&[]) and need to handle both, which is actually not necessary. * register type Labels in CorePlugin
1 parent 909b396 commit 4825051

File tree

2 files changed

+156
-6
lines changed

2 files changed

+156
-6
lines changed

crates/bevy_core/src/label.rs

Lines changed: 155 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ impl Labels {
4747
self.labels.insert(label.into());
4848
}
4949

50+
pub fn remove<T: Into<Cow<'static, str>>>(&mut self, label: T) {
51+
self.labels.remove(&label.into());
52+
}
53+
5054
pub fn iter(&self) -> impl Iterator<Item = &str> {
5155
self.labels.iter().map(|label| label.deref())
5256
}
@@ -60,37 +64,182 @@ pub struct EntityLabels {
6064
}
6165

6266
impl EntityLabels {
63-
pub fn get(&self, label: &str) -> Option<&[Entity]> {
67+
pub fn get(&self, label: &str) -> &[Entity] {
6468
self.label_entities
6569
.get(label)
6670
.map(|entities| entities.as_slice())
71+
.unwrap_or(&[])
6772
}
6873
}
6974

7075
pub(crate) fn entity_labels_system(
7176
mut entity_labels: ResMut<EntityLabels>,
72-
// TODO: use change tracking when add/remove events are added
73-
// mut query: Query<(Entity, Changed<Labels>)>,
77+
// the system runs in an early stage and so can't use a Changed<Labels> filter
7478
query: Query<(Entity, &Labels)>,
7579
) {
7680
let entity_labels = entity_labels.deref_mut();
81+
82+
for entity in query.removed::<Labels>() {
83+
if let Some(labels) = entity_labels.entity_labels.get(entity) {
84+
for label in labels.iter() {
85+
if let Some(entities) = entity_labels.label_entities.get_mut(label) {
86+
entities.retain(|e| e != entity);
87+
}
88+
}
89+
}
90+
}
91+
7792
for (entity, labels) in query.iter() {
7893
let current_labels = entity_labels
7994
.entity_labels
8095
.entry(entity)
8196
.or_insert_with(HashSet::default);
97+
8298
for removed_label in current_labels.difference(&labels.labels) {
8399
if let Some(entities) = entity_labels.label_entities.get_mut(removed_label) {
84100
entities.retain(|e| *e != entity);
85101
}
86102
}
87103

88104
for added_label in labels.labels.difference(&current_labels) {
89-
if let Some(entities) = entity_labels.label_entities.get_mut(added_label) {
90-
entities.push(entity);
91-
}
105+
entity_labels
106+
.label_entities
107+
.entry(added_label.clone())
108+
.or_insert_with(Vec::new)
109+
.push(entity);
92110
}
93111

94112
*current_labels = labels.labels.clone();
95113
}
96114
}
115+
116+
#[cfg(test)]
117+
mod tests {
118+
use super::*;
119+
120+
fn setup() -> (World, Resources, bevy_ecs::Schedule) {
121+
let world = World::new();
122+
let mut resources = Resources::default();
123+
resources.insert(EntityLabels::default());
124+
let mut schedule = bevy_ecs::Schedule::default();
125+
schedule.add_stage("test", SystemStage::serial());
126+
schedule.add_system_to_stage("test", entity_labels_system.system());
127+
(world, resources, schedule)
128+
}
129+
130+
fn holy_cow() -> Labels {
131+
Labels::from(["holy", "cow"].iter().cloned())
132+
}
133+
134+
fn holy_shamoni() -> Labels {
135+
Labels::from(["holy", "shamoni"].iter().cloned())
136+
}
137+
138+
#[test]
139+
fn adds_spawned_entity() {
140+
let (mut world, mut resources, mut schedule) = setup();
141+
142+
let e1 = world.spawn((holy_cow(),));
143+
schedule.initialize_and_run(&mut world, &mut resources);
144+
145+
let entity_labels = resources.get::<EntityLabels>().unwrap();
146+
assert_eq!(entity_labels.get("holy"), &[e1], "holy");
147+
assert_eq!(entity_labels.get("cow"), &[e1], "cow");
148+
assert_eq!(entity_labels.get("shalau"), &[], "shalau");
149+
}
150+
151+
#[test]
152+
fn add_labels() {
153+
let (mut world, mut resources, mut schedule) = setup();
154+
let e1 = world.spawn((holy_cow(),));
155+
schedule.initialize_and_run(&mut world, &mut resources);
156+
157+
world.get_mut::<Labels>(e1).unwrap().insert("shalau");
158+
schedule.initialize_and_run(&mut world, &mut resources);
159+
160+
let entity_labels = resources.get::<EntityLabels>().unwrap();
161+
assert_eq!(entity_labels.get("holy"), &[e1], "holy");
162+
assert_eq!(entity_labels.get("cow"), &[e1], "cow");
163+
assert_eq!(entity_labels.get("shalau"), &[e1], "shalau");
164+
}
165+
166+
#[test]
167+
fn remove_labels() {
168+
let (mut world, mut resources, mut schedule) = setup();
169+
let e1 = world.spawn((holy_cow(),));
170+
schedule.initialize_and_run(&mut world, &mut resources);
171+
172+
world.get_mut::<Labels>(e1).unwrap().remove("holy");
173+
schedule.initialize_and_run(&mut world, &mut resources);
174+
175+
let entity_labels = resources.get::<EntityLabels>().unwrap();
176+
assert_eq!(entity_labels.get("holy"), &[], "holy");
177+
assert_eq!(entity_labels.get("cow"), &[e1], "cow");
178+
assert_eq!(entity_labels.get("shalau"), &[], "shalau");
179+
}
180+
181+
#[test]
182+
fn removes_despawned_entity() {
183+
let (mut world, mut resources, mut schedule) = setup();
184+
let e1 = world.spawn((holy_cow(),));
185+
schedule.initialize_and_run(&mut world, &mut resources);
186+
187+
world.despawn(e1).unwrap();
188+
schedule.initialize_and_run(&mut world, &mut resources);
189+
190+
let entity_labels = resources.get::<EntityLabels>().unwrap();
191+
assert_eq!(entity_labels.get("holy"), &[], "holy");
192+
assert_eq!(entity_labels.get("cow"), &[], "cow");
193+
assert_eq!(entity_labels.get("shalau"), &[], "shalau");
194+
}
195+
196+
#[test]
197+
fn removes_labels_when_component_removed() {
198+
let (mut world, mut resources, mut schedule) = setup();
199+
let e1 = world.spawn((holy_cow(),));
200+
schedule.initialize_and_run(&mut world, &mut resources);
201+
202+
world.remove_one::<Labels>(e1).unwrap();
203+
schedule.initialize_and_run(&mut world, &mut resources);
204+
205+
let entity_labels = resources.get::<EntityLabels>().unwrap();
206+
assert_eq!(entity_labels.get("holy"), &[], "holy");
207+
assert_eq!(entity_labels.get("cow"), &[], "cow");
208+
assert_eq!(entity_labels.get("shalau"), &[], "shalau");
209+
}
210+
211+
#[test]
212+
fn adds_another_spawned_entity() {
213+
let (mut world, mut resources, mut schedule) = setup();
214+
let e1 = world.spawn((holy_cow(),));
215+
schedule.initialize_and_run(&mut world, &mut resources);
216+
217+
let e2 = world.spawn((holy_shamoni(),));
218+
schedule.initialize_and_run(&mut world, &mut resources);
219+
220+
let entity_labels = resources.get::<EntityLabels>().unwrap();
221+
assert_eq!(entity_labels.get("holy"), &[e1, e2], "holy");
222+
assert_eq!(entity_labels.get("cow"), &[e1], "cow");
223+
assert_eq!(entity_labels.get("shamoni"), &[e2], "shamoni");
224+
assert_eq!(entity_labels.get("shalau"), &[], "shalau");
225+
}
226+
227+
#[test]
228+
fn removes_despawned_entity_but_leaves_other() {
229+
let (mut world, mut resources, mut schedule) = setup();
230+
let e1 = world.spawn((holy_cow(),));
231+
schedule.initialize_and_run(&mut world, &mut resources);
232+
233+
let e2 = world.spawn((holy_shamoni(),));
234+
schedule.initialize_and_run(&mut world, &mut resources);
235+
236+
world.despawn(e1).unwrap();
237+
schedule.initialize_and_run(&mut world, &mut resources);
238+
239+
let entity_labels = resources.get::<EntityLabels>().unwrap();
240+
assert_eq!(entity_labels.get("holy"), &[e2], "holy");
241+
assert_eq!(entity_labels.get("cow"), &[], "cow");
242+
assert_eq!(entity_labels.get("shamoni"), &[e2], "shamoni");
243+
assert_eq!(entity_labels.get("shalau"), &[], "shalau");
244+
}
245+
}

crates/bevy_core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ impl Plugin for CorePlugin {
3636
.init_resource::<EntityLabels>()
3737
.init_resource::<FixedTimesteps>()
3838
.register_type::<Option<String>>()
39+
.register_type::<Labels>()
3940
.register_type::<Range<f32>>()
4041
.register_type::<Timer>()
4142
.add_system_to_stage(stage::FIRST, time_system.system())

0 commit comments

Comments
 (0)