moonshine_util/
defer.rs

1//! In Bevy it is possible to [register] and [run] systems manually using [`SystemId`].
2//!
3//! While this is useful enough, sometimes it may be necessary to defer a manual system
4//! execution until later during the update cycle.
5//!
6//! To solve this problem, you may use [`run_deferred_system`] to run a system manually in
7//! any [`Schedule`]. See [`RunDeferredSystem`] for more details and examples.
8//!
9//! Internally, this works by managing a queue of system IDs to be executed using
10//! [`run_deferred_systems`].
11//!
12//! [register]: bevy_ecs::world::World::register_system
13//! [run]: bevy_ecs::world::World::run_system
14//! [`run_deferred_system`]: RunDeferredSystem::run_deferred_system
15
16use std::marker::PhantomData;
17
18use bevy_app::prelude::*;
19use bevy_ecs::prelude::*;
20use bevy_ecs::schedule::ScheduleLabel;
21use bevy_ecs::system::SystemId;
22use bevy_ecs::world::DeferredWorld;
23use bevy_log::prelude::*;
24
25use crate::Static;
26
27/// A [`Plugin`] which adds the [`run_deferred_systems`]
28pub struct DefaultDeferredSystemsPlugin;
29
30impl Plugin for DefaultDeferredSystemsPlugin {
31    fn build(&self, app: &mut App) {
32        app.add_systems(First, run_deferred_systems::<First>)
33            .add_systems(PreUpdate, run_deferred_systems::<PreUpdate>)
34            .add_systems(Update, run_deferred_systems::<Update>)
35            .add_systems(PostUpdate, run_deferred_systems::<PostUpdate>)
36            .add_systems(Last, run_deferred_systems::<Last>);
37    }
38}
39
40/// Trait used to run deferred systems via [`World`].
41pub trait RunDeferredSystem {
42    /// Queues the given [`System`] for a single execution in the given [`Schedule`].
43    ///
44    /// # Usage
45    ///
46    /// You must add [`DefaultDeferredSystemsPlugin`] for deferred system execution to work
47    /// in standard Bevy schedules. You may also add [`run_deferred_systems`] manually to any
48    /// [`Schedule`] to provide deferred system execution support for it.
49    ///
50    /// # Example
51    /// ```
52    /// use bevy::prelude::*;
53    /// use bevy::ecs::world::DeferredWorld;
54    /// use bevy::ecs::lifecycle::HookContext;
55    /// use moonshine_util::prelude::*;
56    ///
57    /// #[derive(Component)]
58    /// #[component(on_insert = on_insert_foo)]
59    /// struct Foo;
60    ///
61    /// fn on_insert_foo(mut world: DeferredWorld, ctx: HookContext) {
62    ///     world.run_deferred_system(PostUpdate, |query: Query<&Foo>| {
63    ///         // ...
64    ///     });
65    /// }
66    /// ```
67    fn run_deferred_system<S: ScheduleLabel, M>(
68        &mut self,
69        schedule: S,
70        system: impl Static + IntoSystem<(), (), M>,
71    );
72
73    /// Same as [`run_deferred_system`](RunDeferredSystem::run_deferred_system), but for systems with
74    /// input parameters.
75    fn run_deferred_system_with<S: ScheduleLabel, I: Static, M>(
76        &mut self,
77        schedule: S,
78        system: impl Static + IntoSystem<In<I>, (), M>,
79        input: I,
80    );
81}
82
83impl RunDeferredSystem for World {
84    fn run_deferred_system<S: ScheduleLabel, M>(
85        &mut self,
86        _schedule: S,
87        system: impl Static + IntoSystem<(), (), M>,
88    ) {
89        let system = self.register_system_cached(system);
90        self.get_resource_or_init::<DeferredSystems<S>>()
91            .0
92            .push(Box::new(DeferredSystem(system)));
93    }
94
95    fn run_deferred_system_with<S: ScheduleLabel, I: Static, M>(
96        &mut self,
97        _schedule: S,
98        system: impl 'static + IntoSystem<In<I>, (), M>,
99        input: I,
100    ) {
101        let system = self.register_system_cached(system);
102        self.get_resource_or_init::<DeferredSystems<S>>()
103            .0
104            .push(Box::new(DeferredSystemWith(system, input)));
105    }
106}
107
108impl RunDeferredSystem for DeferredWorld<'_> {
109    fn run_deferred_system<S: ScheduleLabel, M>(
110        &mut self,
111        schedule: S,
112        system: impl Static + IntoSystem<(), (), M>,
113    ) {
114        self.commands()
115            .queue(move |world: &mut World| world.run_deferred_system(schedule, system));
116    }
117
118    fn run_deferred_system_with<S: ScheduleLabel, I: Static, M>(
119        &mut self,
120        schedule: S,
121        system: impl Static + IntoSystem<In<I>, (), M>,
122        input: I,
123    ) {
124        self.commands().queue(move |world: &mut World| {
125            world.run_deferred_system_with(schedule, system, input)
126        });
127    }
128}
129
130#[derive(Resource)]
131struct DeferredSystems<S: ScheduleLabel>(Vec<Box<dyn AnyDeferredSystem>>, PhantomData<S>);
132
133impl<S: ScheduleLabel> Default for DeferredSystems<S> {
134    fn default() -> Self {
135        Self(Vec::new(), PhantomData)
136    }
137}
138
139impl<S: ScheduleLabel> DeferredSystems<S> {
140    fn take(&mut self) -> Self {
141        Self(self.0.drain(..).collect(), PhantomData)
142    }
143
144    fn run(self, world: &mut World) {
145        for system in self.0 {
146            system.run(world);
147        }
148    }
149}
150
151trait AnyDeferredSystem: Static {
152    fn run(self: Box<Self>, world: &mut World);
153}
154
155struct DeferredSystem(SystemId);
156
157impl AnyDeferredSystem for DeferredSystem {
158    fn run(self: Box<Self>, world: &mut World) {
159        if let Err(why) = world.run_system(self.0) {
160            error!("deferred system error: {why}");
161        }
162    }
163}
164
165struct DeferredSystemWith<I: Static>(SystemId<In<I>>, I);
166
167impl<I: Static> AnyDeferredSystem for DeferredSystemWith<I> {
168    fn run(self: Box<Self>, world: &mut World) {
169        if let Err(why) = world.run_system_with(self.0, self.1) {
170            error!("deferred system error: {why}");
171        }
172    }
173}
174
175/// A [`System`] which executes all deferred systems in the given [`Schedule`].
176pub fn run_deferred_systems<S: ScheduleLabel>(world: &mut World) {
177    let Some(mut systems) = world.get_resource_mut::<DeferredSystems<S>>() else {
178        return;
179    };
180
181    systems.take().run(world);
182}
183
184#[test]
185fn test_deferred_system() {
186    use bevy::prelude::*;
187    use bevy::MinimalPlugins;
188
189    #[derive(Resource)]
190    struct Success;
191
192    let mut app = App::new();
193    app.add_plugins((MinimalPlugins, DefaultDeferredSystemsPlugin));
194
195    app.world_mut()
196        .run_deferred_system(Update, |mut commands: Commands| {
197            commands.insert_resource(Success)
198        });
199
200    app.world_mut().flush(); // Should be redundant, but just to be sure ...
201    assert!(!app.world().contains_resource::<Success>());
202
203    app.update();
204    assert!(app.world_mut().remove_resource::<Success>().is_some());
205
206    // Ensure the system does not run again
207    app.update();
208    assert!(!app.world().contains_resource::<Success>());
209}