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 }
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}