xuko_ecs/schedule.rs
1use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
2
3use super::World;
4use std::sync::{Arc, RwLock};
5
6/// A system schedule for the [`Scheduler`]
7#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub struct Schedule(usize);
10
11impl From<usize> for Schedule {
12 fn from(value: usize) -> Self {
13 Self(value)
14 }
15}
16
17/// Pre-defined schedule keys
18pub mod schedules {
19 use super::Schedule;
20
21 /// Schedule that can be used for the initialization of a game
22 pub const INIT: Schedule = Schedule(0);
23
24 /// Schedule that can be used for the main loop of a game
25 pub const UPDATE: Schedule = Schedule(1);
26
27 /// Schedule that can be used for the deinitialization of a game
28 pub const EXIT: Schedule = Schedule(2);
29
30 /// Schedule that can be used for the render step of a game
31 pub const RENDER: Schedule = Schedule(3);
32
33 /// Schedule that can be used after the render step of a game
34 pub const POST_RENDER: Schedule = Schedule(4);
35}
36
37/// System trait
38pub trait System: Send + Sync + 'static {
39 /// Whether this [`System`] takes a mutable reference to the [`World`] or not
40 fn is_mut(&self) -> bool;
41
42 /// Apply the system on a reference to [`World`]
43 fn apply(&self, world: &World) {
44 let _ = world;
45 }
46
47 /// Apply the system on a mutable reference to [`World`]
48 fn apply_mut(&self, world: &mut World) {
49 let _ = world;
50 }
51}
52
53/// System that takes an immutable reference to the [`World`]
54pub type FnSystem = fn(&World);
55
56/// System that takes an mutable reference to the [`World`]
57pub type FnMutSystem = fn(&mut World);
58
59impl System for FnSystem {
60 fn is_mut(&self) -> bool {
61 false
62 }
63
64 fn apply(&self, world: &World) {
65 self(world);
66 }
67}
68
69impl System for FnMutSystem {
70 fn is_mut(&self) -> bool {
71 true
72 }
73
74 fn apply_mut(&self, world: &mut World) {
75 self(world);
76 }
77}
78
79type SchedulerWorld = Arc<RwLock<World>>;
80
81/// The [`Scheduler`]
82///
83/// Defines and runs systems and applies them to a [`World`]
84///
85/// # Examples
86///
87/// ```
88/// use xuko_ecs::{World, schedule::{Scheduler, FnMutSystem, schedules}};
89///
90/// let mut scheduler = Scheduler::new(World::new());
91///
92/// struct NumberName(String);
93///
94/// fn add_numbers(world: &mut World) {
95/// let numbers = [
96/// (NumberName("PI".to_string()), 3.14159f32),
97/// (NumberName("Nine".to_string()), 9f32),
98/// ];
99///
100/// for (name, value) in numbers {
101/// let entity = world.spawn();
102/// let entity = world.get_entity_mut(entity).unwrap();
103///
104/// entity.add(name);
105/// entity.add(value);
106/// }
107/// }
108///
109/// scheduler.add_system(schedules::INIT, add_numbers as FnMutSystem);
110/// scheduler.run_schedule(schedules::INIT);
111/// ```
112pub struct Scheduler {
113 world: SchedulerWorld,
114 schedules: indexmap::IndexMap<Schedule, Vec<Box<dyn System>>>,
115}
116
117impl Scheduler {
118 /// Create a new [Scheduler]
119 pub fn new(world: World) -> Self {
120 Self {
121 world: Arc::new(RwLock::new(world)),
122 schedules: Default::default(),
123 }
124 }
125
126 /// Add a system to schedule `schedule`
127 pub fn add_system(&mut self, schedule: Schedule, system: impl System) {
128 // TODO: add IDs to systems
129 self.schedules
130 .entry(schedule)
131 .or_default()
132 .push(Box::new(system));
133 }
134
135 /// Run schedule `schedule`
136 pub fn run_schedule(&self, schedule: Schedule) {
137 let world = Arc::clone(&self.world);
138
139 if let Some(systems) = self.schedules.get(&schedule) {
140 systems.par_iter().for_each(|s| {
141 let rc = world.clone();
142 if s.is_mut() {
143 let mut lock = rc.write().unwrap();
144 s.apply_mut(&mut lock);
145 } else {
146 let lock = rc.read().unwrap();
147 s.apply(&lock);
148 }
149 });
150 }
151 }
152}