intuicio_framework_ecs/
scheduler.rs

1use crate::{
2    bundle::{Bundle, BundleChain},
3    entity::Entity,
4    prelude::QuickPlugin,
5    query::Exclude,
6    systems::{System, SystemContext, SystemObject},
7    universe::Universe,
8    world::Relation,
9    Component,
10};
11use std::{
12    collections::{HashSet, VecDeque},
13    error::Error,
14};
15
16#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
17pub struct SystemPriority(pub usize);
18#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
19pub struct SystemOrder(pub usize);
20#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
21pub struct SystemGroupChild;
22#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
23pub struct SystemDependsOn;
24
25pub struct GraphScheduler<const LOCKING: bool> {
26    // TODO: named and unnamed thread pool for parallel systems execution.
27}
28
29impl<const LOCKING: bool> Default for GraphScheduler<LOCKING> {
30    fn default() -> Self {
31        Self {}
32    }
33}
34
35impl<const LOCKING: bool> GraphScheduler<LOCKING> {
36    pub fn run(&mut self, universe: &mut Universe) -> Result<(), Box<dyn Error>> {
37        let mut visited = HashSet::with_capacity(universe.systems.len());
38        Self::validate_no_cycles(
39            universe,
40            universe
41                .systems
42                .query::<LOCKING, (Entity, Exclude<Relation<SystemGroupChild>>)>()
43                .map(|(entity, _)| entity)
44                .collect(),
45            &mut visited,
46        )?;
47        visited.clear();
48        let mut queue = universe
49            .systems
50            .query::<LOCKING, (Entity, Exclude<Relation<SystemGroupChild>>)>()
51            .map(|(entity, _)| entity)
52            .collect::<VecDeque<_>>();
53        while let Some(entity) = queue.pop_front() {
54            Self::run_node(universe, entity, &mut visited, &mut queue)?;
55        }
56        universe.clear_changes();
57        universe.execute_commands::<LOCKING>();
58        universe.maintain_plugins();
59        Ok(())
60    }
61
62    fn validate_no_cycles(
63        universe: &Universe,
64        entities: Vec<Entity>,
65        visited: &mut HashSet<Entity>,
66    ) -> Result<(), Box<dyn Error>> {
67        for entity in entities {
68            if visited.contains(&entity) {
69                return Err(
70                    format!("Found systems graph cycle for system entity: {}", entity).into(),
71                );
72            }
73            visited.insert(entity);
74            Self::validate_no_cycles(
75                universe,
76                universe
77                    .systems
78                    .relations_outgoing::<LOCKING, SystemGroupChild>(entity)
79                    .map(|(_, _, entity)| entity)
80                    .collect(),
81                visited,
82            )?;
83        }
84        Ok(())
85    }
86
87    fn run_node(
88        universe: &Universe,
89        entity: Entity,
90        visited: &mut HashSet<Entity>,
91        queue: &mut VecDeque<Entity>,
92    ) -> Result<bool, Box<dyn Error>> {
93        if visited.contains(&entity) {
94            return Ok(true);
95        }
96        if universe
97            .systems
98            .relations_outgoing::<LOCKING, SystemDependsOn>(entity)
99            .any(|(_, _, other)| !visited.contains(&other))
100        {
101            queue.push_back(entity);
102            return Ok(false);
103        }
104        if let Ok(system) = universe.systems.component::<LOCKING, SystemObject>(entity) {
105            if system.should_run(SystemContext::new(universe, entity)) {
106                system.run(SystemContext::new(universe, entity))?;
107            }
108        }
109        visited.insert(entity);
110        Self::run_group(
111            universe,
112            universe
113                .systems
114                .relations_outgoing::<LOCKING, SystemGroupChild>(entity)
115                .map(|(_, _, entity)| entity),
116            visited,
117            queue,
118        )?;
119        Ok(true)
120    }
121
122    fn run_group(
123        universe: &Universe,
124        entities: impl Iterator<Item = Entity>,
125        visited: &mut HashSet<Entity>,
126        queue: &mut VecDeque<Entity>,
127    ) -> Result<(), Box<dyn Error>> {
128        let mut selected = entities
129            .map(|entity| {
130                let priority = universe
131                    .systems
132                    .component::<LOCKING, SystemPriority>(entity)
133                    .ok()
134                    .map(|priority| *priority)
135                    .unwrap_or_default();
136                let order = universe
137                    .systems
138                    .component::<LOCKING, SystemOrder>(entity)
139                    .ok()
140                    .map(|order| *order)
141                    .unwrap_or_default();
142                (entity, priority, order)
143            })
144            .collect::<Vec<_>>();
145        selected.sort_by(|(_, priority_a, order_a), (_, priority_b, order_b)| {
146            priority_a
147                .cmp(priority_b)
148                .reverse()
149                .then(order_a.cmp(order_b))
150        });
151        for (entity, _, _) in selected {
152            Self::run_node(universe, entity, visited, queue)?;
153        }
154        Ok(())
155    }
156}
157
158pub struct GraphSchedulerQuickPlugin<const LOCKING: bool, Tag: Send + Sync> {
159    plugin: QuickPlugin<Tag>,
160    order: usize,
161}
162
163impl<const LOCKING: bool, Tag: Send + Sync> Default for GraphSchedulerQuickPlugin<LOCKING, Tag> {
164    fn default() -> Self {
165        Self {
166            plugin: Default::default(),
167            order: 0,
168        }
169    }
170}
171
172impl<const LOCKING: bool, Tag: Send + Sync> GraphSchedulerQuickPlugin<LOCKING, Tag> {
173    pub fn new(plugin: QuickPlugin<Tag>) -> Self {
174        Self { plugin, order: 0 }
175    }
176
177    pub fn commit(self) -> QuickPlugin<Tag> {
178        self.plugin
179    }
180
181    pub fn quick(mut self, f: impl FnOnce(QuickPlugin<Tag>) -> QuickPlugin<Tag>) -> Self {
182        self.plugin = f(self.plugin);
183        self
184    }
185
186    pub fn group<ID: Component + Clone + PartialEq, L: Bundle + Send + Sync + 'static>(
187        mut self,
188        id: ID,
189        locals: L,
190        f: impl FnOnce(GraphSchedulerGroup<LOCKING, ID, Tag>) -> GraphSchedulerGroup<LOCKING, ID, Tag>,
191    ) -> Self {
192        self.plugin = self
193            .plugin
194            .system_meta(BundleChain((id.clone(), SystemOrder(self.order)), locals));
195        self.plugin = f(GraphSchedulerGroup {
196            id,
197            plugin: self.plugin,
198            order: 0,
199        })
200        .plugin;
201        self.order += 1;
202        self
203    }
204
205    pub fn system<ID: Component>(
206        mut self,
207        system: impl System,
208        id: ID,
209        locals: impl Bundle + Send + Sync + 'static,
210    ) -> Self {
211        self.plugin = self
212            .plugin
213            .system(system, BundleChain((id, SystemOrder(self.order)), locals));
214        self.order += 1;
215        self
216    }
217
218    pub fn resource<T: Component>(mut self, resource: T) -> Self {
219        self.plugin = self.plugin.resource(resource);
220        self
221    }
222}
223
224pub struct GraphSchedulerGroup<
225    const LOCKING: bool,
226    ID: Component + Clone + PartialEq,
227    Tag: Send + Sync,
228> {
229    id: ID,
230    plugin: QuickPlugin<Tag>,
231    order: usize,
232}
233
234impl<const LOCKING: bool, ID: Component + Clone + PartialEq, Tag: Send + Sync>
235    GraphSchedulerGroup<LOCKING, ID, Tag>
236{
237    pub fn quick(mut self, f: impl FnOnce(QuickPlugin<Tag>) -> QuickPlugin<Tag>) -> Self {
238        self.plugin = f(self.plugin);
239        self
240    }
241
242    pub fn group<L: Bundle + Send + Sync + 'static>(
243        mut self,
244        id: ID,
245        locals: L,
246        f: impl FnOnce(Self) -> Self,
247    ) -> Self {
248        self.plugin = self
249            .plugin
250            .system_meta(BundleChain((id.clone(), SystemOrder(self.order)), locals));
251        self.plugin = f(GraphSchedulerGroup {
252            id: id.clone(),
253            plugin: self.plugin,
254            order: 0,
255        })
256        .plugin;
257        self.plugin =
258            self.plugin
259                .system_relation::<LOCKING, _, _>(self.id.clone(), SystemGroupChild, id);
260        self.order += 1;
261        self
262    }
263
264    pub fn system(
265        mut self,
266        system: impl System,
267        id: ID,
268        locals: impl Bundle + Send + Sync + 'static,
269    ) -> Self {
270        self.plugin = self.plugin.system(
271            system,
272            BundleChain((id.clone(), SystemOrder(self.order)), locals),
273        );
274        self.plugin =
275            self.plugin
276                .system_relation::<LOCKING, _, _>(self.id.clone(), SystemGroupChild, id);
277        self.order += 1;
278        self
279    }
280}