Skip to main content

arcs/systems/
name_table_bookkeeping.rs

1use crate::components::{Name, NameTable};
2use specs::prelude::*;
3
4/// A [`System`] which makes sure the global [`NameTable`] is kept up-to-date.
5#[derive(Debug)]
6pub struct NameTableBookkeeping {
7    changes: ReaderId<ComponentEvent>,
8    inserted: BitSet,
9}
10
11impl NameTableBookkeeping {
12    pub const NAME: &'static str = module_path!();
13
14    pub fn new(world: &World) -> Self {
15        NameTableBookkeeping {
16            changes: world.write_storage::<Name>().register_reader(),
17            inserted: BitSet::new(),
18        }
19    }
20}
21
22impl<'world> System<'world> for NameTableBookkeeping {
23    type SystemData = (
24        Entities<'world>,
25        ReadStorage<'world, Name>,
26        Write<'world, NameTable>,
27    );
28
29    fn run(&mut self, data: Self::SystemData) {
30        let (entities, names, mut name_table) = data;
31
32        // clear any left-over data
33        self.inserted.clear();
34
35        // record which changes have happened since we last ran
36        for event in names.channel().read(&mut self.changes) {
37            match dbg!(event) {
38                ComponentEvent::Inserted(id) => {
39                    self.inserted.add(*id);
40                },
41                ComponentEvent::Removed(id) => {
42                    name_table.remove_by_id(*id);
43                },
44                ComponentEvent::Modified(id) => {
45                    name_table.remove_by_id(*id);
46                    self.inserted.add(*id);
47                },
48            }
49        }
50
51        for (ent, name, _) in (&entities, &names, &self.inserted).join() {
52            use std::collections::hash_map::Entry;
53
54            match name_table.names.entry(name.clone()) {
55                Entry::Vacant(entry) => {
56                    entry.insert(ent);
57                },
58                Entry::Occupied(mut entry) => {
59                    log::warn!(
60                        "Duplicate name found when associating {:?} with \"{}\" (previous entity: {:?})",
61                        ent,
62                        name.as_ref(),
63                        entry.get()
64                    );
65                    entry.insert(ent);
66                },
67            }
68        }
69    }
70
71    fn setup(&mut self, world: &mut World) {
72        <Self::SystemData as shred::DynamicSystemData>::setup(
73            &self.accessor(),
74            world,
75        );
76
77        let entities = world.entities();
78        let names = world.read_storage::<Name>();
79        let mut name_table = world.write_resource::<NameTable>();
80
81        name_table.clear();
82
83        for (ent, name) in (&entities, &names).join() {
84            name_table.names.insert(name.clone(), ent);
85        }
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn setup_creates_all_outstanding_names() {
95        let mut world = World::new();
96        crate::components::register(&mut world);
97        let first = world.create_entity().with(Name::new("first")).build();
98        let second = world.create_entity().with(Name::new("second")).build();
99        let mut system = NameTableBookkeeping::new(&world);
100
101        System::setup(&mut system, &mut world);
102
103        let names = world.read_resource::<NameTable>();
104        assert_eq!(names.len(), 2);
105        assert_eq!(names.get("first").unwrap(), first);
106        assert_eq!(names.get("second").unwrap(), second);
107    }
108
109    #[test]
110    fn run_will_keep_the_nametable_updated() {
111        let mut world = World::new();
112        crate::components::register(&mut world);
113        let first = world.create_entity().with(Name::new("first")).build();
114        let second = world.create_entity().with(Name::new("second")).build();
115        let mut system = NameTableBookkeeping::new(&world);
116        System::setup(&mut system, &mut world);
117
118        // make some changes after the initial setup
119        let third = world.create_entity().with(Name::new("third")).build();
120        world.delete_entity(first).unwrap();
121        world.maintain();
122
123        // then run the system
124        system.run_now(&world);
125
126        let names = world.read_resource::<NameTable>();
127        println!("{:?}", *names);
128        assert_eq!(names.len(), 2);
129        assert!(names.get("first").is_none());
130        assert_eq!(names.get("second").unwrap(), second);
131        assert_eq!(names.get("third").unwrap(), third);
132    }
133}