Skip to main content

pebble/ecs/
mod.rs

1/// A handle to a specific entity in the ECS world. Pebble's own re-export
2/// of `hecs::Entity` — since `hecs` is the ECS world itself, not a
3/// swappable backend, referencing it doesn't require adding `hecs` as your
4/// own dependency. Include it in a query tuple (`Query<(Entity, &Health)>`)
5/// to get entity IDs back out of iteration, or pass one to
6/// [`Commands::despawn`](crate::ecs::commands::Commands).
7pub use hecs::Entity;
8
9pub mod commands;
10pub mod events;
11pub mod local;
12pub mod observers;
13pub mod plugin;
14pub mod promise;
15pub mod query;
16pub mod resources;
17pub mod schedule;
18pub mod system;
19pub mod system_param;
20
21#[cfg(test)]
22mod tests {
23    use std::{cell::RefCell, rc::Rc};
24
25    use crate::ecs::{
26        commands::{Commands, ResourceCommandQueue, TriggerQueue},
27        local::Local,
28        resources::{Read, Resources, Write},
29        schedule::Schedule,
30    };
31
32    #[test]
33    fn resources_insert_and_get() {
34        let mut resources = Resources::default();
35        resources.insert(42i32);
36
37        assert_eq!(*resources.get::<i32>(), 42);
38    }
39
40    #[test]
41    fn resources_get_mut_updates_value() {
42        let mut resources = Resources::default();
43        resources.insert(1i32);
44
45        *resources.get_mut::<i32>() += 1;
46
47        assert_eq!(*resources.get::<i32>(), 2);
48    }
49
50    #[test]
51    fn resources_contains() {
52        let mut resources = Resources::default();
53        assert!(!resources.contains::<i32>());
54
55        resources.insert(1i32);
56        assert!(resources.contains::<i32>());
57    }
58
59    #[test]
60    fn resources_remove() {
61        let mut resources = Resources::default();
62        resources.insert(7i32);
63
64        let removed = resources.remove::<i32>();
65
66        assert_eq!(removed, Some(7));
67        assert!(!resources.contains::<i32>());
68    }
69
70    #[test]
71    #[should_panic(expected = "Resource not found")]
72    fn resources_get_missing_panics() {
73        let resources = Resources::default();
74        resources.get::<i32>();
75    }
76
77    #[test]
78    fn schedule_runs_systems_in_order() {
79        let mut world = hecs::World::default();
80        let mut resources = Resources::default();
81        resources.insert(0i32);
82        resources.insert(hecs::CommandBuffer::default());
83
84        let mut schedule = Schedule::default();
85        schedule.add_system(|mut counter: Write<i32>| {
86            *counter += 1;
87        });
88
89        schedule.run(&mut world, &mut resources);
90        schedule.run(&mut world, &mut resources);
91
92        assert_eq!(*resources.get::<i32>(), 2);
93    }
94
95    #[test]
96    fn commands_spawn_is_applied_on_sync() {
97        let mut world = hecs::World::default();
98        let mut resources = Resources::default();
99        resources.insert(hecs::CommandBuffer::default());
100        resources.insert(ResourceCommandQueue::default());
101        resources.insert(TriggerQueue::default());
102
103        let mut schedule = Schedule::default();
104        schedule.add_system(|mut commands: Commands| {
105            commands.spawn((1i32,));
106        });
107
108        assert_eq!(world.len(), 0);
109
110        schedule.run(&mut world, &mut resources);
111
112        assert_eq!(world.len(), 1);
113    }
114
115    #[test]
116    fn commands_insert_resource_is_applied_on_sync() {
117        let mut world = hecs::World::default();
118        let mut resources = Resources::default();
119        resources.insert(hecs::CommandBuffer::default());
120        resources.insert(ResourceCommandQueue::default());
121        resources.insert(TriggerQueue::default());
122
123        let mut schedule = Schedule::default();
124        schedule.add_system(|mut commands: Commands| {
125            commands.insert_resource(42i32);
126        });
127
128        assert!(!resources.contains::<i32>());
129
130        schedule.run(&mut world, &mut resources);
131
132        assert_eq!(*resources.get::<i32>(), 42);
133    }
134
135    #[test]
136    fn commands_remove_resource_is_applied_on_sync() {
137        let mut world = hecs::World::default();
138        let mut resources = Resources::default();
139        resources.insert(hecs::CommandBuffer::default());
140        resources.insert(ResourceCommandQueue::default());
141        resources.insert(TriggerQueue::default());
142        resources.insert(42i32);
143
144        let mut schedule = Schedule::default();
145        schedule.add_system(|mut commands: Commands| {
146            commands.remove_resource::<i32>();
147        });
148
149        schedule.run(&mut world, &mut resources);
150
151        assert!(!resources.contains::<i32>());
152    }
153
154    #[test]
155    fn option_read_is_some_when_resource_present() {
156        let mut world = hecs::World::default();
157        let mut resources = Resources::default();
158        resources.insert(hecs::CommandBuffer::default());
159        resources.insert(42i32);
160
161        let mut schedule = Schedule::default();
162        schedule.add_system(|value: Option<Read<i32>>| {
163            assert_eq!(*value.expect("resource should be present"), 42);
164        });
165
166        schedule.run(&mut world, &mut resources);
167    }
168
169    #[test]
170    fn option_read_is_none_when_resource_missing() {
171        let mut world = hecs::World::default();
172        let mut resources = Resources::default();
173        resources.insert(hecs::CommandBuffer::default());
174
175        let mut schedule = Schedule::default();
176        schedule.add_system(|value: Option<Read<i32>>| {
177            assert!(value.is_none());
178        });
179
180        schedule.run(&mut world, &mut resources);
181    }
182
183    #[test]
184    fn option_write_mutates_resource_when_present() {
185        let mut world = hecs::World::default();
186        let mut resources = Resources::default();
187        resources.insert(hecs::CommandBuffer::default());
188        resources.insert(1i32);
189
190        let mut schedule = Schedule::default();
191        schedule.add_system(|value: Option<Write<i32>>| {
192            *value.expect("resource should be present") += 1;
193        });
194
195        schedule.run(&mut world, &mut resources);
196
197        assert_eq!(*resources.get::<i32>(), 2);
198    }
199
200    #[test]
201    fn option_write_is_none_when_resource_missing() {
202        let mut world = hecs::World::default();
203        let mut resources = Resources::default();
204        resources.insert(hecs::CommandBuffer::default());
205
206        let mut schedule = Schedule::default();
207        schedule.add_system(|value: Option<Write<i32>>| {
208            assert!(value.is_none());
209        });
210
211        schedule.run(&mut world, &mut resources);
212    }
213
214    #[test]
215    fn local_defaults_to_type_default() {
216        let mut world = hecs::World::default();
217        let mut resources = Resources::default();
218        resources.insert(hecs::CommandBuffer::default());
219
220        let mut schedule = Schedule::default();
221        schedule.add_system(|counter: Local<i32>| {
222            assert_eq!(*counter, 0);
223        });
224
225        schedule.run(&mut world, &mut resources);
226    }
227
228    #[test]
229    fn local_persists_across_runs() {
230        let mut world = hecs::World::default();
231        let mut resources = Resources::default();
232        resources.insert(hecs::CommandBuffer::default());
233
234        let log = Rc::new(RefCell::new(Vec::new()));
235        let log_clone = log.clone();
236
237        let mut schedule = Schedule::default();
238        schedule.add_system(move |mut counter: Local<i32>| {
239            *counter += 1;
240            log_clone.borrow_mut().push(*counter);
241        });
242
243        schedule.run(&mut world, &mut resources);
244        schedule.run(&mut world, &mut resources);
245        schedule.run(&mut world, &mut resources);
246
247        assert_eq!(*log.borrow(), vec![1, 2, 3]);
248    }
249
250    #[test]
251    fn local_state_is_isolated_per_system() {
252        let mut world = hecs::World::default();
253        let mut resources = Resources::default();
254        resources.insert(hecs::CommandBuffer::default());
255
256        let log_a = Rc::new(RefCell::new(Vec::new()));
257        let log_b = Rc::new(RefCell::new(Vec::new()));
258        let (la, lb) = (log_a.clone(), log_b.clone());
259
260        let mut schedule = Schedule::default();
261        schedule
262            .add_system(move |mut counter: Local<i32>| {
263                *counter += 1;
264                la.borrow_mut().push(*counter);
265            })
266            .add_system(move |mut counter: Local<i32>| {
267                *counter += 10;
268                lb.borrow_mut().push(*counter);
269            });
270
271        schedule.run(&mut world, &mut resources);
272        schedule.run(&mut world, &mut resources);
273
274        assert_eq!(*log_a.borrow(), vec![1, 2]);
275        assert_eq!(*log_b.borrow(), vec![10, 20]);
276    }
277
278    fn log_a(mut log: Write<Vec<&'static str>>) {
279        log.push("a");
280    }
281
282    fn log_b(mut log: Write<Vec<&'static str>>) {
283        log.push("b");
284    }
285
286    #[test]
287    fn before_reorders_execution_relative_to_registration_order() {
288        use crate::ecs::system_param::IntoSystemConfig;
289
290        let mut world = hecs::World::default();
291        let mut resources = Resources::default();
292        resources.insert(hecs::CommandBuffer::default());
293        resources.insert(Vec::<&'static str>::new());
294
295        // Registered b-then-a, but a is pinned to run before b.
296        let mut schedule = Schedule::default();
297        schedule.add_system(log_b).add_system(log_a.before(log_b));
298
299        schedule.run(&mut world, &mut resources);
300
301        assert_eq!(*resources.get::<Vec<&'static str>>(), vec!["a", "b"]);
302    }
303
304    #[test]
305    fn after_resolves_against_a_system_added_later() {
306        use crate::ecs::system_param::IntoSystemConfig;
307
308        let mut world = hecs::World::default();
309        let mut resources = Resources::default();
310        resources.insert(hecs::CommandBuffer::default());
311        resources.insert(Vec::<&'static str>::new());
312
313        // Registered a-then-b, but a is pinned to run after b — which
314        // isn't added until after the constraint is declared.
315        let mut schedule = Schedule::default();
316        schedule.add_system(log_a.after(log_b)).add_system(log_b);
317
318        schedule.run(&mut world, &mut resources);
319
320        assert_eq!(*resources.get::<Vec<&'static str>>(), vec!["b", "a"]);
321    }
322
323    #[test]
324    #[should_panic(expected = "cycle")]
325    fn cyclic_ordering_constraints_panic() {
326        use crate::ecs::system_param::IntoSystemConfig;
327
328        let mut world = hecs::World::default();
329        let mut resources = Resources::default();
330        resources.insert(hecs::CommandBuffer::default());
331        resources.insert(Vec::<&'static str>::new());
332
333        let mut schedule = Schedule::default();
334        schedule
335            .add_system(log_a.after(log_b))
336            .add_system(log_b.after(log_a));
337
338        schedule.run(&mut world, &mut resources);
339    }
340
341    fn log_c(mut log: Write<Vec<&'static str>>) {
342        log.push("c");
343    }
344
345    #[test]
346    fn higher_priority_runs_first_among_unconstrained_systems() {
347        use crate::ecs::system_param::IntoSystemConfig;
348
349        let mut world = hecs::World::default();
350        let mut resources = Resources::default();
351        resources.insert(hecs::CommandBuffer::default());
352        resources.insert(Vec::<&'static str>::new());
353
354        // Registered a, b, c with no after/before between them — priority
355        // alone should reorder them.
356        let mut schedule = Schedule::default();
357        schedule
358            .add_system(log_a)
359            .add_system(log_b.priority(10))
360            .add_system(log_c.priority(5));
361
362        schedule.run(&mut world, &mut resources);
363
364        assert_eq!(*resources.get::<Vec<&'static str>>(), vec!["b", "c", "a"]);
365    }
366
367    #[test]
368    fn explicit_constraint_wins_over_priority() {
369        use crate::ecs::system_param::IntoSystemConfig;
370
371        let mut world = hecs::World::default();
372        let mut resources = Resources::default();
373        resources.insert(hecs::CommandBuffer::default());
374        resources.insert(Vec::<&'static str>::new());
375
376        // b has higher priority than a, but a is explicitly pinned before
377        // b — the constraint must still win.
378        let mut schedule = Schedule::default();
379        schedule
380            .add_system(log_b.priority(10))
381            .add_system(log_a.priority(0).before(log_b));
382
383        schedule.run(&mut world, &mut resources);
384
385        assert_eq!(*resources.get::<Vec<&'static str>>(), vec!["a", "b"]);
386    }
387
388    #[test]
389    fn chain_forces_relative_order() {
390        use crate::ecs::system_param::Chain;
391
392        let mut world = hecs::World::default();
393        let mut resources = Resources::default();
394        resources.insert(hecs::CommandBuffer::default());
395        resources.insert(Vec::<&'static str>::new());
396
397        // Chained in c, a, b order — that's the order they must run in,
398        // regardless of being unrelated functions otherwise.
399        let mut schedule = Schedule::default();
400        schedule.add_systems((log_c, log_a, log_b).chain());
401
402        schedule.run(&mut world, &mut resources);
403
404        assert_eq!(*resources.get::<Vec<&'static str>>(), vec!["c", "a", "b"]);
405    }
406
407    #[test]
408    fn chain_after_only_constrains_its_first_system() {
409        use crate::ecs::system_param::Chain;
410
411        fn log_setup(mut log: Write<Vec<&'static str>>) {
412            log.push("setup");
413        }
414
415        let mut world = hecs::World::default();
416        let mut resources = Resources::default();
417        resources.insert(hecs::CommandBuffer::default());
418        resources.insert(Vec::<&'static str>::new());
419
420        let mut schedule = Schedule::default();
421        schedule
422            .add_systems((log_a, log_b).chain().after(log_setup))
423            .add_system(log_setup);
424
425        schedule.run(&mut world, &mut resources);
426
427        assert_eq!(*resources.get::<Vec<&'static str>>(), vec!["setup", "a", "b"]);
428    }
429}