Skip to main content

nexus_rt/
scheduler.rs

1//! Staged scheduler with boolean propagation.
2//!
3//! The scheduler is installed as a **driver** via [`SchedulerBuilder`].
4//! After event handlers process incoming data and write to resources,
5//! the scheduler runs reconciliation [`System`]s in stage order. This
6//! two-phase pattern (event → reconcile) separates reactive logic from
7//! derived-state computation.
8//!
9//! # Staged model
10//!
11//! Systems are grouped into **stages**. Each stage runs all its systems,
12//! then propagates a `bool` to the next stage: "did any system in this
13//! stage return `true`?" If no system fired, downstream stages are
14//! skipped.
15//!
16//! Topology is implicit in builder order — each `.then()` call creates
17//! the next stage. No explicit edge declarations, no topological sort.
18//!
19//! # Monomorphization
20//!
21//! The schedule chain is fully monomorphized. Each `.root()` / `.then()`
22//! wraps the accumulator in a `StageNode<Prev, S>` type layer. The
23//! compiler inlines the recursive `run_schedule` calls, eliminating all
24//! vtable dispatch. Same pattern as [`Pipeline`](crate::Pipeline) and
25//! [`Dag`](crate::Dag).
26//!
27//! # Propagation model
28//!
29//! Each system returns `bool`. `true` means "my outputs changed, run
30//! downstream." `false` means "nothing changed." Within a stage, all
31//! systems always run regardless of individual return values — the
32//! stage result is the OR of all system returns.
33//!
34//! # Sequence mechanics
35//!
36//! The global sequence counter is event-only — the scheduler never
37//! calls [`next_sequence`](crate::World::next_sequence).
38//!
39//! # Invariants
40//!
41//! - **Stage order**: Stages execute in builder order. All systems
42//!   within a stage run before any system in the next stage.
43//! - **All systems in a stage run**: Even if one returns `false`,
44//!   the rest still execute (side effects matter).
45//! - **Deterministic**: Same inputs produce same results. No
46//!   randomness, no thread-dependent ordering.
47//! - **No sequence bump**: The scheduler never advances the global
48//!   sequence. Event handlers own sequencing; the scheduler observes.
49//! - **No system limit**: The nested type encodes the full schedule
50//!   at compile time. No bitmask, no capacity constant.
51//!
52//! # Examples
53//!
54//! ```
55//! use nexus_rt::{WorldBuilder, Res, ResMut, Installer, Resource};
56//! use nexus_rt::scheduler::SchedulerBuilder;
57//!
58//! #[derive(Resource)]
59//! struct Val(u64);
60//!
61//! fn step_a(mut val: ResMut<Val>) -> bool {
62//!     val.0 += 1;
63//!     true
64//! }
65//!
66//! fn step_b(val: Res<Val>) -> bool {
67//!     val.0 > 0
68//! }
69//!
70//! let mut builder = WorldBuilder::new();
71//! builder.register(Val(0));
72//! let reg = builder.registry();
73//!
74//! let mut scheduler = builder.install_driver(
75//!     SchedulerBuilder::new()
76//!         .root(step_a, &reg)
77//!         .then(step_b, &reg)
78//! );
79//! let mut world = builder.build();
80//!
81//! assert_eq!(scheduler.run(&mut world), 2);
82//! ```
83
84use crate::driver::Installer;
85use crate::system::{IntoSystem, System};
86use crate::world::{Registry, World, WorldBuilder};
87
88// =============================================================================
89// StageEnd — terminal node
90// =============================================================================
91
92#[doc(hidden)]
93pub struct StageEnd;
94
95// =============================================================================
96// StageNode — one stage in the schedule chain
97// =============================================================================
98
99#[doc(hidden)]
100pub struct StageNode<Prev, S> {
101    prev: Prev,
102    stage: S,
103}
104
105// =============================================================================
106// RunSchedule — chain dispatch trait
107// =============================================================================
108
109#[doc(hidden)]
110pub trait RunSchedule: Send {
111    fn run_schedule(&mut self, world: &mut World) -> (usize, bool);
112    fn system_count(&self) -> usize;
113}
114
115impl RunSchedule for StageEnd {
116    #[inline(always)]
117    fn run_schedule(&mut self, _world: &mut World) -> (usize, bool) {
118        (0, true)
119    }
120
121    fn system_count(&self) -> usize {
122        0
123    }
124}
125
126impl<Prev: RunSchedule, S: StageRunner> RunSchedule for StageNode<Prev, S> {
127    #[inline(always)]
128    fn run_schedule(&mut self, world: &mut World) -> (usize, bool) {
129        let (prev_ran, prev_fired) = self.prev.run_schedule(world);
130        if !prev_fired {
131            return (prev_ran, false);
132        }
133        let (stage_ran, stage_fired) = self.stage.run_all(world);
134        (prev_ran + stage_ran, stage_fired)
135    }
136
137    fn system_count(&self) -> usize {
138        self.prev.system_count() + self.stage.system_count()
139    }
140}
141
142// =============================================================================
143// StageRunner — per-stage dispatch trait
144// =============================================================================
145
146#[doc(hidden)]
147pub trait StageRunner: Send {
148    fn run_all(&mut self, world: &mut World) -> (usize, bool);
149    fn system_count(&self) -> usize;
150}
151
152// =============================================================================
153// Stage1..Stage8 — macro-generated stage wrappers
154// =============================================================================
155
156macro_rules! impl_stage {
157    ($name:ident, $count:expr, $(($idx:tt, $S:ident)),+) => {
158        #[doc(hidden)]
159        pub struct $name<$($S),+>($(pub(crate) $S),+);
160
161        impl<$($S: System),+> StageRunner for $name<$($S),+> {
162            #[inline(always)]
163            fn run_all(&mut self, world: &mut World) -> (usize, bool) {
164                let mut fired = false;
165                $(fired |= self.$idx.run(world);)+
166                ($count, fired)
167            }
168
169            fn system_count(&self) -> usize {
170                $count
171            }
172        }
173    };
174}
175
176impl_stage!(Stage1, 1, (0, S0));
177impl_stage!(Stage2, 2, (0, S0), (1, S1));
178impl_stage!(Stage3, 3, (0, S0), (1, S1), (2, S2));
179impl_stage!(Stage4, 4, (0, S0), (1, S1), (2, S2), (3, S3));
180impl_stage!(Stage5, 5, (0, S0), (1, S1), (2, S2), (3, S3), (4, S4));
181impl_stage!(
182    Stage6,
183    6,
184    (0, S0),
185    (1, S1),
186    (2, S2),
187    (3, S3),
188    (4, S4),
189    (5, S5)
190);
191impl_stage!(
192    Stage7,
193    7,
194    (0, S0),
195    (1, S1),
196    (2, S2),
197    (3, S3),
198    (4, S4),
199    (5, S5),
200    (6, S6)
201);
202impl_stage!(
203    Stage8,
204    8,
205    (0, S0),
206    (1, S1),
207    (2, S2),
208    (3, S3),
209    (4, S4),
210    (5, S5),
211    (6, S6),
212    (7, S7)
213);
214
215// =============================================================================
216// IntoStage — converts functions/tuples into stages
217// =============================================================================
218
219/// Converts a single function or tuple of functions into a stage.
220///
221/// The `Params` type parameter is inference-only — same pattern as
222/// [`IntoHandler<E, Params>`](crate::IntoHandler). Never constructed,
223/// just guides the compiler to the right impl.
224pub trait IntoStage<Params> {
225    /// The concrete stage type produced.
226    type Stage: StageRunner + 'static;
227    /// Convert into a stage, resolving system parameters from the registry.
228    fn into_stage(self, registry: &Registry) -> Self::Stage;
229}
230
231impl<F, P, M> IntoStage<(P, M)> for F
232where
233    F: IntoSystem<P, M>,
234    F::System: 'static,
235{
236    type Stage = Stage1<F::System>;
237
238    fn into_stage(self, registry: &Registry) -> Self::Stage {
239        Stage1(self.into_system(registry))
240    }
241}
242
243macro_rules! impl_into_stage {
244    ($stage:ident, $(($F:ident, $P:ident, $M:ident, $idx:tt)),+) => {
245        impl<$($F, $P, $M),+> IntoStage<($(($P, $M),)+)> for ($($F,)+)
246        where
247            $($F: IntoSystem<$P, $M>, $F::System: 'static,)+
248        {
249            type Stage = $stage<$($F::System),+>;
250
251            fn into_stage(self, registry: &Registry) -> Self::Stage {
252                $stage($(self.$idx.into_system(registry)),+)
253            }
254        }
255    };
256}
257
258impl_into_stage!(Stage2, (F0, P0, M0, 0), (F1, P1, M1, 1));
259impl_into_stage!(Stage3, (F0, P0, M0, 0), (F1, P1, M1, 1), (F2, P2, M2, 2));
260impl_into_stage!(
261    Stage4,
262    (F0, P0, M0, 0),
263    (F1, P1, M1, 1),
264    (F2, P2, M2, 2),
265    (F3, P3, M3, 3)
266);
267impl_into_stage!(
268    Stage5,
269    (F0, P0, M0, 0),
270    (F1, P1, M1, 1),
271    (F2, P2, M2, 2),
272    (F3, P3, M3, 3),
273    (F4, P4, M4, 4)
274);
275impl_into_stage!(
276    Stage6,
277    (F0, P0, M0, 0),
278    (F1, P1, M1, 1),
279    (F2, P2, M2, 2),
280    (F3, P3, M3, 3),
281    (F4, P4, M4, 4),
282    (F5, P5, M5, 5)
283);
284impl_into_stage!(
285    Stage7,
286    (F0, P0, M0, 0),
287    (F1, P1, M1, 1),
288    (F2, P2, M2, 2),
289    (F3, P3, M3, 3),
290    (F4, P4, M4, 4),
291    (F5, P5, M5, 5),
292    (F6, P6, M6, 6)
293);
294impl_into_stage!(
295    Stage8,
296    (F0, P0, M0, 0),
297    (F1, P1, M1, 1),
298    (F2, P2, M2, 2),
299    (F3, P3, M3, 3),
300    (F4, P4, M4, 4),
301    (F5, P5, M5, 5),
302    (F6, P6, M6, 6),
303    (F7, P7, M7, 7)
304);
305
306// =============================================================================
307// SchedulerBuilder — entry point
308// =============================================================================
309
310/// Entry point for building a monomorphized schedule.
311///
312/// # Examples
313///
314/// ```
315/// use nexus_rt::{WorldBuilder, ResMut, Resource};
316/// use nexus_rt::scheduler::SchedulerBuilder;
317///
318/// #[derive(Resource)]
319/// struct Val(u64);
320///
321/// fn step_a(mut val: ResMut<Val>) -> bool {
322///     val.0 += 1;
323///     true
324/// }
325///
326/// fn step_b(val: nexus_rt::Res<Val>) -> bool {
327///     val.0 > 0
328/// }
329///
330/// let mut builder = WorldBuilder::new();
331/// builder.register(Val(0));
332/// let reg = builder.registry();
333///
334/// let mut scheduler = builder.install_driver(
335///     SchedulerBuilder::new()
336///         .root(step_a, &reg)
337///         .then(step_b, &reg)
338/// );
339/// let mut world = builder.build();
340/// assert_eq!(scheduler.run(&mut world), 2);
341/// ```
342pub struct SchedulerBuilder;
343
344impl SchedulerBuilder {
345    /// Create a new scheduler builder.
346    pub fn new() -> Self {
347        Self
348    }
349
350    /// Create the first stage from a system or tuple of systems.
351    pub fn root<S, Params>(self, stage: S, registry: &Registry) -> StageNode<StageEnd, S::Stage>
352    where
353        S: IntoStage<Params>,
354    {
355        StageNode {
356            prev: StageEnd,
357            stage: stage.into_stage(registry),
358        }
359    }
360}
361
362impl Default for SchedulerBuilder {
363    fn default() -> Self {
364        Self::new()
365    }
366}
367
368// =============================================================================
369// StageNode builder methods
370// =============================================================================
371
372impl<Prev, S> StageNode<Prev, S>
373where
374    Prev: RunSchedule + 'static,
375    S: StageRunner + 'static,
376{
377    /// Append a stage that runs after the current chain.
378    pub fn then<Next, Params>(
379        self,
380        stage: Next,
381        registry: &Registry,
382    ) -> StageNode<Self, Next::Stage>
383    where
384        Next: IntoStage<Params>,
385    {
386        StageNode {
387            prev: self,
388            stage: stage.into_stage(registry),
389        }
390    }
391}
392
393// =============================================================================
394// Installer impl on StageNode
395// =============================================================================
396
397impl<Prev, S> Installer for StageNode<Prev, S>
398where
399    Self: RunSchedule + 'static,
400{
401    type Poller = SystemScheduler<Self>;
402
403    fn install(self, _world: &mut WorldBuilder) -> Self::Poller {
404        SystemScheduler { chain: self }
405    }
406}
407
408// =============================================================================
409// SystemScheduler<Chain> — final type
410// =============================================================================
411
412/// Monomorphized staged scheduler. Created via [`SchedulerBuilder`].
413///
414/// All system calls are direct (no vtable dispatch). The `Chain` type
415/// parameter encodes the full schedule — the compiler inlines the
416/// recursive `run_schedule` chain.
417///
418/// # Propagation
419///
420/// The first stage always runs. Each subsequent stage runs only if
421/// the previous stage's systems returned at least one `true` (OR
422/// semantics within a stage).
423///
424/// Does NOT call [`next_sequence`](World::next_sequence) — the global
425/// sequence is event-only.
426pub struct SystemScheduler<Chain> {
427    chain: Chain,
428}
429
430impl<Chain: RunSchedule> SystemScheduler<Chain> {
431    /// Run all stages with boolean propagation.
432    ///
433    /// Returns the number of systems that actually ran.
434    pub fn run(&mut self, world: &mut World) -> usize {
435        let (ran, _) = self.chain.run_schedule(world);
436        ran
437    }
438
439    /// Returns the total number of systems across all stages.
440    pub fn len(&self) -> usize {
441        self.chain.system_count()
442    }
443
444    /// Returns `true` if the scheduler contains no systems.
445    pub fn is_empty(&self) -> bool {
446        self.chain.system_count() == 0
447    }
448}
449
450#[cfg(test)]
451mod tests {
452    use super::*;
453    use crate::ResMut;
454
455    // -- Helpers ----------------------------------------------------------
456
457    fn increment(mut val: ResMut<u64>) -> bool {
458        *val += 1;
459        true
460    }
461
462    fn set_flag(mut flag: ResMut<bool>) -> bool {
463        *flag = true;
464        true
465    }
466
467    fn false_source() -> bool {
468        false
469    }
470
471    fn should_not_run(mut val: ResMut<u64>) -> bool {
472        *val = 999;
473        true
474    }
475
476    fn source(mut val: ResMut<u64>) -> bool {
477        *val += 1;
478        *val <= 2
479    }
480
481    fn middle(mut val: ResMut<u64>) -> bool {
482        *val += 10;
483        true
484    }
485
486    fn leaf(mut val: ResMut<u64>) -> bool {
487        *val += 100;
488        true
489    }
490
491    fn double(mut val: ResMut<u64>) -> bool {
492        *val *= 2;
493        true
494    }
495
496    // -- Migrated tests ---------------------------------------------------
497
498    #[test]
499    fn single_root_always_runs() {
500        let mut builder = WorldBuilder::new();
501        builder.register::<u64>(0);
502        let reg = builder.registry();
503        let mut scheduler = builder.install_driver(SchedulerBuilder::new().root(increment, &reg));
504        let mut world = builder.build();
505
506        assert_eq!(scheduler.run(&mut world), 1);
507        assert_eq!(*world.resource::<u64>(), 1);
508    }
509
510    #[test]
511    fn linear_chain_propagation() {
512        let mut builder = WorldBuilder::new();
513        builder.register::<u64>(0);
514        let reg = builder.registry();
515        let mut scheduler = builder.install_driver(
516            SchedulerBuilder::new()
517                .root(source, &reg)
518                .then(middle, &reg)
519                .then(leaf, &reg),
520        );
521        let mut world = builder.build();
522
523        assert_eq!(scheduler.run(&mut world), 3);
524        assert_eq!(*world.resource::<u64>(), 111);
525    }
526
527    #[test]
528    fn propagation_stops_on_false() {
529        let mut builder = WorldBuilder::new();
530        builder.register::<u64>(0);
531        let reg = builder.registry();
532        let mut scheduler = builder.install_driver(
533            SchedulerBuilder::new()
534                .root(false_source, &reg)
535                .then(should_not_run, &reg),
536        );
537        let mut world = builder.build();
538
539        assert_eq!(scheduler.run(&mut world), 1);
540        assert_eq!(*world.resource::<u64>(), 0);
541    }
542
543    #[test]
544    fn staged_diamond() {
545        let mut builder = WorldBuilder::new();
546        builder.register::<u64>(0);
547        builder.register::<bool>(false);
548        let reg = builder.registry();
549        let mut scheduler = builder.install_driver(
550            SchedulerBuilder::new()
551                .root(increment, &reg)
552                .then((increment, set_flag), &reg)
553                .then(increment, &reg),
554        );
555        let mut world = builder.build();
556
557        assert_eq!(scheduler.run(&mut world), 4);
558        assert!(*world.resource::<bool>());
559        assert_eq!(*world.resource::<u64>(), 3);
560    }
561
562    #[test]
563    fn multiple_roots() {
564        let mut builder = WorldBuilder::new();
565        builder.register::<u64>(0);
566        let reg = builder.registry();
567        let mut scheduler = builder
568            .install_driver(SchedulerBuilder::new().root((increment, increment, increment), &reg));
569        let mut world = builder.build();
570
571        assert_eq!(scheduler.run(&mut world), 3);
572        assert_eq!(*world.resource::<u64>(), 3);
573    }
574
575    #[test]
576    fn scheduler_does_not_bump_sequence() {
577        let mut builder = WorldBuilder::new();
578        builder.register::<u64>(0);
579        let reg = builder.registry();
580        let mut scheduler = builder.install_driver(SchedulerBuilder::new().root(increment, &reg));
581        let mut world = builder.build();
582
583        let before = world.current_sequence();
584        scheduler.run(&mut world);
585        assert_eq!(world.current_sequence(), before);
586    }
587
588    #[test]
589    fn mutations_visible_downstream() {
590        let mut builder = WorldBuilder::new();
591        builder.register::<u64>(1);
592        let reg = builder.registry();
593        let mut scheduler = builder.install_driver(
594            SchedulerBuilder::new()
595                .root(double, &reg)
596                .then(double, &reg),
597        );
598        let mut world = builder.build();
599
600        scheduler.run(&mut world);
601        assert_eq!(*world.resource::<u64>(), 4);
602    }
603
604    // -- New tests --------------------------------------------------------
605
606    #[test]
607    fn multi_system_stage_all_run() {
608        let mut builder = WorldBuilder::new();
609        builder.register::<u64>(0);
610        builder.register::<bool>(false);
611        let reg = builder.registry();
612        let mut scheduler = builder
613            .install_driver(SchedulerBuilder::new().root((increment, increment, set_flag), &reg));
614        let mut world = builder.build();
615
616        assert_eq!(scheduler.run(&mut world), 3);
617        assert_eq!(*world.resource::<u64>(), 2);
618        assert!(*world.resource::<bool>());
619    }
620
621    #[test]
622    fn stage_propagation_any_semantics() {
623        fn false_increment(mut val: ResMut<u64>) -> bool {
624            *val += 1;
625            false
626        }
627
628        let mut builder = WorldBuilder::new();
629        builder.register::<u64>(0);
630        let reg = builder.registry();
631        let mut scheduler = builder.install_driver(
632            SchedulerBuilder::new()
633                .root((false_increment, increment), &reg)
634                .then(increment, &reg),
635        );
636        let mut world = builder.build();
637
638        assert_eq!(scheduler.run(&mut world), 3);
639        assert_eq!(*world.resource::<u64>(), 3);
640    }
641
642    #[test]
643    fn stage_propagation_all_false_stops() {
644        fn false_increment(mut val: ResMut<u64>) -> bool {
645            *val += 1;
646            false
647        }
648
649        let mut builder = WorldBuilder::new();
650        builder.register::<u64>(0);
651        let reg = builder.registry();
652        let mut scheduler = builder.install_driver(
653            SchedulerBuilder::new()
654                .root((false_increment, false_increment), &reg)
655                .then(should_not_run, &reg),
656        );
657        let mut world = builder.build();
658
659        assert_eq!(scheduler.run(&mut world), 2);
660        assert_eq!(*world.resource::<u64>(), 2);
661    }
662
663    #[test]
664    fn void_systems_in_stage() {
665        fn void_increment(mut val: ResMut<u64>) {
666            *val += 1;
667        }
668
669        let mut builder = WorldBuilder::new();
670        builder.register::<u64>(0);
671        let reg = builder.registry();
672        let mut scheduler = builder.install_driver(
673            SchedulerBuilder::new()
674                .root(void_increment, &reg)
675                .then(increment, &reg),
676        );
677        let mut world = builder.build();
678
679        assert_eq!(scheduler.run(&mut world), 2);
680        assert_eq!(*world.resource::<u64>(), 2);
681    }
682
683    #[test]
684    fn mixed_bool_void_stage() {
685        fn void_increment(mut val: ResMut<u64>) {
686            *val += 1;
687        }
688
689        fn false_increment(mut val: ResMut<u64>) -> bool {
690            *val += 1;
691            false
692        }
693
694        let mut builder = WorldBuilder::new();
695        builder.register::<u64>(0);
696        let reg = builder.registry();
697        let mut scheduler = builder.install_driver(
698            SchedulerBuilder::new()
699                .root((false_increment, void_increment), &reg)
700                .then(increment, &reg),
701        );
702        let mut world = builder.build();
703
704        assert_eq!(scheduler.run(&mut world), 3);
705        assert_eq!(*world.resource::<u64>(), 3);
706    }
707
708    #[test]
709    fn scheduler_chain_is_send() {
710        fn assert_send<T: Send>(_: &T) {}
711
712        let mut builder = WorldBuilder::new();
713        builder.register::<u64>(0);
714        builder.register::<bool>(false);
715        let reg = builder.registry();
716        let scheduler = builder.install_driver(
717            SchedulerBuilder::new()
718                .root(increment, &reg)
719                .then((increment, set_flag), &reg)
720                .then(double, &reg),
721        );
722        assert_send(&scheduler);
723    }
724
725    #[test]
726    fn three_stage_linear() {
727        let mut builder = WorldBuilder::new();
728        builder.register::<u64>(0);
729        builder.register::<bool>(false);
730        let reg = builder.registry();
731        let mut scheduler = builder.install_driver(
732            SchedulerBuilder::new()
733                .root(increment, &reg)
734                .then((increment, set_flag), &reg)
735                .then(double, &reg),
736        );
737        let mut world = builder.build();
738
739        assert_eq!(scheduler.run(&mut world), 4);
740        assert!(*world.resource::<bool>());
741        // u64: 0 +1 (root) +1 (stage1) = 2, then *2 (stage2) = 4
742        assert_eq!(*world.resource::<u64>(), 4);
743    }
744}