bevy_mod_scripting_bindings/schedule.rs
1//! Dynamic scheduling from scripts
2
3use ::{
4 bevy_app::{
5 First, FixedFirst, FixedLast, FixedMain, FixedPostUpdate, FixedPreUpdate, FixedUpdate,
6 Last, PostStartup, PostUpdate, PreStartup, PreUpdate, RunFixedMainLoop, Startup, Update,
7 },
8 bevy_ecs::schedule::ScheduleLabel,
9};
10use bevy_ecs::resource::Resource;
11use bevy_platform::collections::HashMap;
12use bevy_system_reflection::ReflectSchedule;
13use parking_lot::RwLock;
14use std::{any::TypeId, sync::Arc};
15#[derive(Default, Clone, Resource)]
16/// A Send + Sync registry of bevy schedules.
17pub struct AppScheduleRegistry(Arc<RwLock<ScheduleRegistry>>);
18
19impl AppScheduleRegistry {
20 /// Reads the schedule registry.
21 pub fn read(&self) -> parking_lot::RwLockReadGuard<'_, ScheduleRegistry> {
22 self.0.read()
23 }
24
25 /// Writes to the schedule registry.
26 pub fn write(&self) -> parking_lot::RwLockWriteGuard<'_, ScheduleRegistry> {
27 self.0.write()
28 }
29
30 /// Creates a new schedule registry pre-populated with default bevy schedules.
31 pub fn new() -> Self {
32 Self(Arc::new(RwLock::new(ScheduleRegistry::new())))
33 }
34}
35
36#[derive(Default)]
37/// A registry of bevy schedules.
38pub struct ScheduleRegistry {
39 schedules: HashMap<TypeId, ReflectSchedule>,
40}
41
42#[profiling::all_functions]
43impl ScheduleRegistry {
44 /// Creates a new schedule registry containing all default bevy schedules.
45 pub fn new() -> Self {
46 let mut self_ = Self::default();
47 self_
48 .register(Update)
49 .register(First)
50 .register(PreUpdate)
51 .register(RunFixedMainLoop)
52 .register(PostUpdate)
53 .register(Last)
54 .register(PreStartup)
55 .register(Startup)
56 .register(PostStartup)
57 .register(FixedMain)
58 .register(FixedFirst)
59 .register(FixedPreUpdate)
60 .register(FixedUpdate)
61 .register(FixedPostUpdate)
62 .register(FixedLast);
63 self_
64 }
65
66 /// Retrieves a schedule by name
67 pub fn get_schedule_by_name(&self, name: &str) -> Option<&ReflectSchedule> {
68 self.schedules.iter().find_map(|(_, schedule)| {
69 (schedule.identifier() == name || schedule.type_path() == name).then_some(schedule)
70 })
71 }
72
73 /// Registers a schedule
74 pub fn register<T: ScheduleLabel + 'static>(&mut self, label: T) -> &mut Self {
75 let schedule = ReflectSchedule::from_label(label);
76 self.schedules.insert(TypeId::of::<T>(), schedule);
77 self
78 }
79
80 /// Retrieves the given schedule
81 pub fn get(&self, type_id: TypeId) -> Option<&ReflectSchedule> {
82 self.schedules.get(&type_id)
83 }
84
85 /// Retrieves the given schedule mutably
86 pub fn get_mut(&mut self, type_id: TypeId) -> Option<&mut ReflectSchedule> {
87 self.schedules.get_mut(&type_id)
88 }
89
90 /// Checks if the given schedule is contained
91 pub fn contains(&self, type_id: TypeId) -> bool {
92 self.schedules.contains_key(&type_id)
93 }
94
95 /// Creates an iterator over all schedules
96 pub fn iter(&self) -> impl Iterator<Item = (&TypeId, &ReflectSchedule)> {
97 self.schedules.iter()
98 }
99
100 /// Creates an iterator over all schedules mutably
101 pub fn iter_mut(&mut self) -> impl Iterator<Item = (&TypeId, &mut ReflectSchedule)> {
102 self.schedules.iter_mut()
103 }
104}
105
106#[cfg(test)]
107#[allow(
108 dead_code,
109 unused_imports,
110 reason = "tests are there but not working currently"
111)]
112mod tests {
113 // use crate::config::{GetPluginThreadConfig, ScriptingPluginConfiguration};
114 use ::{
115 bevy_app::{App, Plugin, Update},
116 bevy_ecs::{
117 entity::Entity,
118 schedule::{NodeId, Schedules, SystemKey},
119 system::IntoSystem,
120 },
121 bevy_system_reflection::ReflectSystem,
122 std::{cell::OnceCell, rc::Rc},
123 };
124
125 // use test_utils::make_test_plugin;
126
127 use super::*;
128
129 #[test]
130 fn test_schedule_registry() {
131 let mut registry = ScheduleRegistry::default();
132 registry.register(Update);
133
134 assert!(registry.contains(TypeId::of::<Update>()));
135
136 let schedule = registry.get(TypeId::of::<Update>()).unwrap();
137 assert_eq!(schedule.identifier(), "Update");
138 assert_eq!(schedule.type_path(), std::any::type_name::<Update>());
139 assert_eq!(
140 registry
141 .get_schedule_by_name("Update")
142 .unwrap()
143 .identifier(),
144 "Update"
145 );
146 }
147
148 fn test_system_generic<T>() {}
149 fn test_system() {}
150
151 #[test]
152 fn test_reflect_system_names() {
153 let system = IntoSystem::into_system(test_system_generic::<String>);
154 let system = ReflectSystem::from_system(&system, SystemKey::default());
155
156 assert_eq!(system.identifier(), "test_system_generic");
157 assert_eq!(
158 system.path(),
159 "bevy_mod_scripting_bindings::schedule::tests::test_system_generic<alloc::string::String>"
160 );
161
162 let system = IntoSystem::into_system(test_system);
163 let system = ReflectSystem::from_system(&system, SystemKey::default());
164
165 assert_eq!(system.identifier(), "test_system");
166 assert_eq!(
167 system.path(),
168 "bevy_mod_scripting_bindings::schedule::tests::test_system"
169 );
170 }
171
172 // make_test_plugin!(crate);
173
174 // #[test]
175 // fn test_into_system_set_identical_for_real_and_reflect_set() {
176 // let root_system = || {};
177 // let as_system = IntoSystem::into_system(root_system);
178 // let as_reflect_system = ReflectSystem::from_system(&as_system);
179
180 // let set1 = Box::new(IntoSystemSet::into_system_set(root_system)) as Box<dyn SystemSet>;
181 // let set2 =
182 // Box::new(IntoSystemSet::into_system_set(as_reflect_system)) as Box<dyn SystemSet>;
183
184 // let mut hasher1 = std::collections::hash_map::DefaultHasher::new();
185 // set1.dyn_hash(&mut hasher1);
186 // let mut hasher2 = std::collections::hash_map::DefaultHasher::new();
187 // set2.dyn_hash(&mut hasher2);
188 // pretty_assertions::assert_eq!(hasher1.finish(), hasher2.finish());
189
190 // pretty_assertions::assert_eq!(set1.system_type(), set2.system_type());
191 // assert!(set1.dyn_eq(&set2));
192 // }
193
194 #[derive(ScheduleLabel, Hash, PartialEq, Eq, Debug, Clone)]
195 struct TestSchedule;
196
197 fn test_system_a() {}
198 fn test_system_b() {}
199
200 /// Verifies that the given schedule graph contains the expected node names and edges.
201 ///
202 /// # Arguments
203 ///
204 /// * `app` - A mutable reference to the Bevy App.
205 /// * `schedule_label` - The schedule label to locate the schedule.
206 /// * `expected_nodes` - A slice of node names expected to be present.
207 /// * `expected_edges` - A slice of tuples representing expected edges (from, to).
208 pub fn verify_schedule_graph<T>(
209 app: &mut App,
210 schedule_label: T,
211 expected_nodes: &[&str],
212 expected_edges: &[(&str, &str)],
213 ) where
214 T: ScheduleLabel + std::hash::Hash + Eq + Clone + Send + Sync + 'static,
215 {
216 // Remove schedules, then remove the schedule to verify.
217 let mut schedules = app
218 .world_mut()
219 .remove_resource::<Schedules>()
220 .expect("Schedules resource not found");
221 let mut schedule = schedules
222 .remove(schedule_label.clone())
223 .expect("Schedule not found");
224
225 schedule.initialize(app.world_mut()).unwrap();
226 let graph = schedule.graph();
227
228 // Build a mapping from system name to its node id.
229
230 let resolve_name = |node_id: NodeId| {
231 let out = {
232 let name = match node_id {
233 NodeId::System(system_key) => graph
234 .systems
235 .get(system_key)
236 .map(|system| system.system().name().clone().to_string()),
237 NodeId::Set(system_set_key) => graph
238 .system_sets
239 .get(system_set_key)
240 .map(|set| format!("{set:?}").to_string()),
241 };
242
243 if let Some(name) = name {
244 name
245 } else {
246 // try schedule systems
247 let mut default = format!("{node_id:?}").to_string();
248 for (system_node, system) in schedule.systems().unwrap() {
249 if node_id == NodeId::System(system_node) {
250 default = system.name().clone().to_string();
251 }
252 }
253 default
254 }
255 };
256
257 // trim module path
258 let trim = "bevy_mod_scripting_bindings::schedule::tests::";
259 out.replace(trim, "")
260 };
261
262 let all_nodes = graph
263 .dependency()
264 .graph()
265 .nodes()
266 .map(&resolve_name)
267 .collect::<Vec<_>>();
268
269 // Assert expected nodes exist.
270 for &node in expected_nodes {
271 assert!(
272 all_nodes.contains(&node.to_owned()),
273 "Graph does not contain expected node '{node}' nodes: {all_nodes:?}"
274 );
275 }
276
277 // Collect all edges as (from, to) name pairs.
278 let mut found_edges = Vec::new();
279 for (from, to) in graph.dependency().graph().all_edges() {
280 let name_from = resolve_name(from);
281 let name_to = resolve_name(to);
282 found_edges.push((name_from, name_to));
283 }
284
285 // Assert each expected edge exists.
286 for &(exp_from, exp_to) in expected_edges {
287 assert!(
288 found_edges.contains(&(exp_from.to_owned(), exp_to.to_owned())),
289 "Expected edge ({exp_from} -> {exp_to}) not found. Found edges: {found_edges:?}"
290 );
291 }
292
293 // Optionally, reinsert the schedule back into the schedules resource.
294 schedules.insert(schedule);
295 app.world_mut().insert_resource(schedules);
296 }
297
298 // #[test]
299 // fn test_builder_creates_correct_system_graph_against_rust_systems() {
300 // let mut app = App::new();
301 // app.add_plugins((
302 // bevy::asset::AssetPlugin::default(),
303 // bevy::diagnostic::DiagnosticsPlugin,
304 // TestPlugin::default(), // assuming TestPlugin is defined appropriately
305 // ));
306
307 // let system_a = IntoSystem::into_system(test_system_a);
308
309 // let system_b = IntoSystem::into_system(test_system_b);
310
311 // let mut system_builder = ScriptSystemBuilder::new("test".into(), ScriptId::from("test"));
312 // // Set ordering: script system runs after "root1" and before "root2".
313 // system_builder
314 // .after(ReflectSystem::from_system(&system_a, NodeId::System(0)))
315 // .before(ReflectSystem::from_system(&system_b, NodeId::System(1)));
316
317 // app.init_schedule(TestSchedule);
318 // app.add_systems(TestSchedule, system_a);
319 // app.add_systems(TestSchedule, system_b);
320 // let _ = system_builder.build::<TestPlugin>(
321 // WorldGuard::new(app.world_mut()),
322 // &ReflectSchedule::from_label(TestSchedule),
323 // );
324
325 // verify_schedule_graph(
326 // &mut app,
327 // TestSchedule,
328 // // expected nodes
329 // &["test_system_a", "test_system_b", "script_system_test"],
330 // // expected edges (from, to), i.e. before, after, relationships
331 // &[
332 // ("SystemTypeSet(fn bevy_ecs::system::function_system::FunctionSystem<fn(), test_system_a>())", "script_system_test"),
333 // ("script_system_test", "SystemTypeSet(fn bevy_ecs::system::function_system::FunctionSystem<fn(), test_system_b>())"),
334 // ],
335 // );
336 // }
337
338 // #[test]
339 // fn test_builder_creates_correct_system_graph_against_script_systems() {
340 // let mut app = App::new();
341 // app.add_plugins((
342 // bevy::asset::AssetPlugin::default(),
343 // bevy::diagnostic::DiagnosticsPlugin,
344 // TestPlugin::default(), // assuming TestPlugin is defined appropriately
345 // ));
346 // app.init_schedule(TestSchedule);
347 // let reflect_schedule = ReflectSchedule::from_label(TestSchedule);
348
349 // let system_root = ScriptSystemBuilder::new("root".into(), "script_root.lua".into())
350 // .build::<TestPlugin>(WorldGuard::new(app.world_mut()), &reflect_schedule)
351 // .unwrap();
352
353 // let mut system_a = ScriptSystemBuilder::new("a".into(), "script_a.lua".into());
354 // system_a.before(system_root.clone());
355 // system_a
356 // .build::<TestPlugin>(WorldGuard::new(app.world_mut()), &reflect_schedule)
357 // .unwrap();
358
359 // let mut system_b = ScriptSystemBuilder::new("b".into(), "script_b.lua".into());
360 // system_b.after(system_root.clone());
361 // system_b
362 // .build::<TestPlugin>(WorldGuard::new(app.world_mut()), &reflect_schedule)
363 // .unwrap();
364
365 // verify_schedule_graph(
366 // &mut app,
367 // TestSchedule,
368 // // expected nodes
369 // &["script_system_root", "script_system_a", "script_system_b"],
370 // // expected edges (from, to), i.e. before, after, relationships
371 // &[
372 // // this doesn't work currently TODO: fix this, i.e. we inject the systems but not the ordering constraints
373 // // ("SystemTypeSet(fn bevy_ecs::system::function_system::FunctionSystem<fn(), test_system_a>())", "script_system_test"),
374 // // ("script_system_test", "SystemTypeSet(fn bevy_ecs::system::function_system::FunctionSystem<fn(), test_system_b>())"),
375 // ],
376 // );
377 // }
378}