1use alloc::boxed::Box;
4use alloc::string::String;
5use alloc::vec::Vec;
6use core::any::TypeId;
7
8use crate::event::{ComponentAdded, ComponentRemoved, EventReader, EventReaderStart};
9use crate::query::{PreparedQuery1, PreparedQuery2, QueryError, QueryPolicy, QuerySpec};
10use crate::schedule::condition::Condition;
11use crate::schedule::owner::ScheduleOwner;
12use crate::world::{World, WorldError};
13
14#[derive(Copy, Clone, Debug, Eq, PartialEq)]
16pub enum FlushMode {
17 Final,
19 Stage,
21 AfterSystem,
23}
24
25pub(crate) type SystemBody = Box<dyn FnMut(&mut crate::world::World, f32) -> Result<(), String>>;
26pub(crate) type SystemInitializer =
27 Box<dyn for<'world> FnOnce(&mut SystemInitContext<'world>) -> Result<SystemBody, String>>;
28
29pub(crate) enum SystemBodySource {
30 Ready(SystemBody),
31 Initialize(SystemInitializer),
32}
33
34pub struct SystemInitContext<'world> {
39 world: &'world mut World,
40}
41
42impl<'world> SystemInitContext<'world> {
43 pub(crate) fn new(world: &'world mut World) -> Self {
44 Self { world }
45 }
46
47 pub fn contains_resource<R: 'static>(&self) -> bool {
49 self.world.contains_resource::<R>()
50 }
51
52 pub fn resource<R: 'static>(&self) -> Result<Option<&R>, WorldError> {
54 self.world.resource::<R>()
55 }
56
57 pub fn event_reader<E: Clone + 'static>(
59 &mut self,
60 start: EventReaderStart,
61 ) -> Result<EventReader<E>, WorldError> {
62 self.world.event_reader::<E>(start)
63 }
64
65 pub fn on_add_reader<T: 'static>(
67 &mut self,
68 start: EventReaderStart,
69 ) -> Result<EventReader<ComponentAdded>, WorldError> {
70 self.world.on_add_reader::<T>(start)
71 }
72
73 pub fn on_remove_reader<T: 'static>(
75 &mut self,
76 start: EventReaderStart,
77 ) -> Result<EventReader<ComponentRemoved>, WorldError> {
78 self.world.on_remove_reader::<T>(start)
79 }
80
81 pub fn prepare_query1<T: 'static>(
83 &mut self,
84 spec: QuerySpec,
85 policy: QueryPolicy,
86 ) -> Result<PreparedQuery1<T>, QueryError> {
87 self.world.prepare_query1(spec, policy)
88 }
89
90 pub fn prepare_query2<A: 'static, B: 'static>(
92 &mut self,
93 spec: QuerySpec,
94 policy: QueryPolicy,
95 ) -> Result<PreparedQuery2<A, B>, QueryError> {
96 self.world.prepare_query2(spec, policy)
97 }
98}
99
100#[derive(Copy, Clone, Debug, Eq, PartialEq)]
101pub(crate) enum EventRoleKind {
102 Emits,
103 Consumes,
104 ConsumesOnAdd,
105 ConsumesOnRemove,
106}
107
108#[derive(Clone, Debug)]
109pub(crate) struct EventRole {
110 pub type_id: TypeId,
111 pub type_name: &'static str,
112 pub kind: EventRoleKind,
113}
114
115#[derive(Clone, Debug, Eq, PartialEq, Hash)]
117pub struct SystemId {
118 owner: ScheduleOwner,
119 index: u32,
120 generation: u32,
121}
122
123impl SystemId {
124 pub(crate) fn new(owner: ScheduleOwner, index: u32, generation: u32) -> Self {
125 Self {
126 owner,
127 index,
128 generation,
129 }
130 }
131
132 pub fn index(&self) -> usize {
134 self.index as usize
135 }
136
137 pub(crate) fn validate_owner(
138 &self,
139 owner: &ScheduleOwner,
140 generation: u32,
141 ) -> Result<(), crate::schedule::ScheduleError> {
142 if !self.owner.same(owner) {
143 return Err(crate::schedule::ScheduleError::OwnerMismatch);
144 }
145 if self.generation != generation {
146 return Err(crate::schedule::ScheduleError::StaleHandle);
147 }
148 Ok(())
149 }
150}
151
152#[derive(Clone, Debug, Eq, PartialEq)]
154pub struct SystemSet {
155 label: String,
156}
157
158impl SystemSet {
159 pub fn new(label: impl Into<String>) -> Self {
161 Self {
162 label: label.into(),
163 }
164 }
165
166 pub fn label(&self) -> &str {
168 &self.label
169 }
170}
171
172pub struct System {
174 pub(crate) name: String,
175 pub(crate) stage_label: String,
176 pub(crate) body: SystemBodySource,
177 pub(crate) enabled: bool,
178 pub(crate) flush_mode: FlushMode,
179 pub(crate) before: Vec<String>,
180 pub(crate) after: Vec<String>,
181 pub(crate) before_sets: Vec<String>,
182 pub(crate) after_sets: Vec<String>,
183 pub(crate) in_set: Option<String>,
184 pub(crate) conditions: Vec<Condition>,
185 pub(crate) required_resources: Vec<TypeId>,
186 pub(crate) event_roles: Vec<EventRole>,
187}
188
189impl System {
190 pub fn new(
192 name: impl Into<String>,
193 stage: impl Into<String>,
194 body: impl FnMut(&mut crate::world::World, f32) + 'static,
195 ) -> Self {
196 let mut handler = body;
197 Self {
198 name: name.into(),
199 stage_label: stage.into(),
200 body: SystemBodySource::Ready(Box::new(move |world, dt| {
201 handler(world, dt);
202 Ok(())
203 })),
204 enabled: true,
205 flush_mode: FlushMode::Final,
206 before: Vec::new(),
207 after: Vec::new(),
208 before_sets: Vec::new(),
209 after_sets: Vec::new(),
210 in_set: None,
211 conditions: Vec::new(),
212 required_resources: Vec::new(),
213 event_roles: Vec::new(),
214 }
215 }
216
217 pub fn try_new(
219 name: impl Into<String>,
220 stage: impl Into<String>,
221 body: impl FnMut(&mut crate::world::World, f32) -> Result<(), String> + 'static,
222 ) -> Self {
223 let mut handler = body;
224 Self {
225 name: name.into(),
226 stage_label: stage.into(),
227 body: SystemBodySource::Ready(Box::new(move |world, dt| handler(world, dt))),
228 enabled: true,
229 flush_mode: FlushMode::Final,
230 before: Vec::new(),
231 after: Vec::new(),
232 before_sets: Vec::new(),
233 after_sets: Vec::new(),
234 in_set: None,
235 conditions: Vec::new(),
236 required_resources: Vec::new(),
237 event_roles: Vec::new(),
238 }
239 }
240
241 pub fn with_local<L: 'static>(
243 name: impl Into<String>,
244 stage: impl Into<String>,
245 init: impl FnOnce(&mut SystemInitContext<'_>) -> Result<L, String> + 'static,
246 run: impl FnMut(&mut World, f32, &mut L) -> Result<(), String> + 'static,
247 ) -> Self {
248 let mut run = run;
249 let initializer = move |context: &mut SystemInitContext<'_>| {
250 let mut local = init(context)?;
251 let body: SystemBody = Box::new(move |world, dt| run(world, dt, &mut local));
252 Ok(body)
253 };
254 Self {
255 name: name.into(),
256 stage_label: stage.into(),
257 body: SystemBodySource::Initialize(Box::new(initializer)),
258 enabled: true,
259 flush_mode: FlushMode::Final,
260 before: Vec::new(),
261 after: Vec::new(),
262 before_sets: Vec::new(),
263 after_sets: Vec::new(),
264 in_set: None,
265 conditions: Vec::new(),
266 required_resources: Vec::new(),
267 event_roles: Vec::new(),
268 }
269 }
270
271 pub fn before(mut self, label: impl Into<String>) -> Self {
273 self.before.push(label.into());
274 self
275 }
276
277 pub fn after(mut self, label: impl Into<String>) -> Self {
279 self.after.push(label.into());
280 self
281 }
282
283 pub fn before_set(mut self, set: &SystemSet) -> Self {
285 self.before_sets.push(set.label.clone());
286 self
287 }
288
289 pub fn after_set(mut self, set: &SystemSet) -> Self {
291 self.after_sets.push(set.label.clone());
292 self
293 }
294
295 pub fn in_set(mut self, set: &SystemSet) -> Self {
297 self.in_set = Some(set.label.clone());
298 self
299 }
300
301 pub fn run_if(mut self, condition: Condition) -> Self {
303 self.conditions.push(condition);
304 self
305 }
306
307 pub fn requires_resource<R: 'static>(mut self) -> Self {
309 self.required_resources.push(TypeId::of::<R>());
310 self
311 }
312
313 pub fn emits<E: Clone + 'static>(mut self) -> Self {
315 self.push_event_role::<E>(EventRoleKind::Emits);
316 self
317 }
318
319 pub fn consumes<E: Clone + 'static>(mut self) -> Self {
321 self.push_event_role::<E>(EventRoleKind::Consumes);
322 self
323 }
324
325 pub fn consumes_on_add<T: 'static>(mut self) -> Self {
327 self.push_event_role::<T>(EventRoleKind::ConsumesOnAdd);
328 self
329 }
330
331 pub fn consumes_on_remove<T: 'static>(mut self) -> Self {
333 self.push_event_role::<T>(EventRoleKind::ConsumesOnRemove);
334 self
335 }
336
337 fn push_event_role<T: 'static>(&mut self, kind: EventRoleKind) {
338 let type_id = TypeId::of::<T>();
339 if self
340 .event_roles
341 .iter()
342 .any(|role| role.type_id == type_id && role.kind == kind)
343 {
344 return;
345 }
346 self.event_roles.push(EventRole {
347 type_id,
348 type_name: core::any::type_name::<T>(),
349 kind,
350 });
351 }
352
353 pub fn flush_mode(mut self, mode: FlushMode) -> Self {
355 self.flush_mode = mode;
356 self
357 }
358
359 pub fn flush_after(mut self) -> Self {
361 self.flush_mode = FlushMode::AfterSystem;
362 self
363 }
364
365 pub fn disabled(mut self) -> Self {
367 self.enabled = false;
368 self
369 }
370
371 pub fn name(&self) -> &str {
373 &self.name
374 }
375}
376
377#[cfg(test)]
378mod tests {
379 use super::*;
380 use crate::component::ComponentOptions;
381 use crate::event::EventOptions;
382 use crate::schedule::ScheduleError;
383 use crate::world::WorldBuilder;
384
385 #[test]
386 fn system_id_validate_owner_and_generation() {
387 let owner = ScheduleOwner::new();
388 let id = SystemId::new(owner.clone(), 0, 1);
389 assert!(id.validate_owner(&owner, 1).is_ok());
390 assert!(matches!(
391 id.validate_owner(&ScheduleOwner::new(), 1),
392 Err(ScheduleError::OwnerMismatch)
393 ));
394 assert!(matches!(
395 id.validate_owner(&owner, 0),
396 Err(ScheduleError::StaleHandle)
397 ));
398 }
399
400 #[test]
401 fn system_builder_fluent_api() {
402 let set = SystemSet::new("physics");
403 let _ = System::new("move", "Update", |_world, _dt| {})
404 .before("setup")
405 .after("cleanup")
406 .before_set(&set)
407 .after_set(&set)
408 .in_set(&set)
409 .run_if(Condition::always())
410 .requires_resource::<WorldBuilder>()
411 .emits::<u32>()
412 .consumes::<u32>()
413 .consumes_on_add::<u32>()
414 .consumes_on_remove::<u32>()
415 .flush_mode(FlushMode::Stage)
416 .flush_after()
417 .disabled()
418 .name();
419 }
420
421 #[test]
422 fn init_context_exposes_registered_runtime_state_and_prepared_queries() {
423 struct Position;
424 struct Velocity;
425 #[derive(Clone)]
426 struct Tick;
427
428 let mut builder = WorldBuilder::new();
429 builder
430 .register_component::<Position>(ComponentOptions::sparse())
431 .expect("position");
432 builder
433 .register_component::<Velocity>(ComponentOptions::sparse())
434 .expect("velocity");
435 builder.insert_resource(7_u32);
436 builder
437 .add_event::<Tick>(EventOptions::manual())
438 .expect("event");
439 let mut world = builder.build().expect("world");
440 let mut context = SystemInitContext::new(&mut world);
441
442 assert!(context.contains_resource::<u32>());
443 assert_eq!(context.resource::<u32>().expect("resource"), Some(&7));
444 context
445 .event_reader::<Tick>(EventReaderStart::FromNow)
446 .expect("event reader");
447 context
448 .on_add_reader::<Position>(EventReaderStart::FromNow)
449 .expect("add reader");
450 context
451 .on_remove_reader::<Position>(EventReaderStart::FromNow)
452 .expect("remove reader");
453 context
454 .prepare_query1::<Position>(QuerySpec::new(), QueryPolicy::Prepared)
455 .expect("query1");
456 context
457 .prepare_query2::<Position, Velocity>(QuerySpec::new(), QueryPolicy::Prepared)
458 .expect("query2");
459 }
460
461 #[test]
462 fn duplicate_event_role_is_suppressed() {
463 let system = System::new("writer", "Update", |_world, _dt| {})
464 .emits::<u32>()
465 .emits::<u32>();
466 assert_eq!(system.event_roles.len(), 1);
467 }
468}