moonshine_check/
lib.rs

1#![deprecated(
2    since = "0.1.1",
3    note = "This crate is deprecated. Use component hooks instead."
4)]
5
6use bevy_app::prelude::*;
7use bevy_ecs::{
8    prelude::*,
9    query::{QueryEntityError, QueryFilter},
10    system::EntityCommands,
11};
12use bevy_log::prelude::*;
13use moonshine_kind::prelude::*;
14use moonshine_save::load::LoadSystem;
15
16pub mod prelude {
17    pub use super::{invalid, panic, purge};
18    pub use super::{repair, repair_remove};
19    pub use super::{repair_insert, repair_insert_default};
20    pub use super::{repair_replace, repair_replace_default, repair_replace_with};
21    pub use super::{Check, Valid};
22}
23
24/// An extension trait used to add checks to an [`App`].
25pub trait Check {
26    /// Adds a new checked requirement to this [`App`] with a given [`Policy`].
27    ///
28    /// # Usage
29    ///
30    /// All new instances of given [`Kind`] `T` will be checked against the given [`CheckFilter`] `F`.
31    ///
32    /// If the check succeeds, the given [`Policy`] will be invoked.
33    ///
34    /// # Example
35    /// ```
36    /// use bevy::prelude::*;
37    /// use moonshine_check::prelude::*;
38    ///
39    /// #[derive(Bundle)]
40    /// struct AppleBundle {
41    ///     apple: Apple,
42    ///     fresh: Fresh,
43    /// }
44    ///
45    /// #[derive(Component)]
46    /// struct Apple;
47    ///
48    /// #[derive(Component)]
49    /// struct Fresh;
50    ///
51    /// let mut app = App::new();
52    /// // ...
53    /// app.check::<Apple, Without<Fresh>>(purge());
54    /// ```
55    fn check<T: Kind, F: CheckFilter>(&mut self, _: Policy) -> &mut Self;
56}
57
58impl Check for App {
59    fn check<T: Kind, F: CheckFilter>(&mut self, policy: Policy) -> &mut Self {
60        let filter_name = || moonshine_util::get_short_name(std::any::type_name::<F>());
61        self.add_systems(
62            PreUpdate,
63            (move |query: Query<Instance<T>, Unchecked>,
64                   check: Query<(), F>,
65                   world: &World,
66                   mut commands: Commands| {
67                for instance in query.iter() {
68                    match check.get(instance.entity()) {
69                        // NOTE: Query Mismatch implies OK!
70                        Err(QueryEntityError::QueryDoesNotMatch(..)) => {
71                            if let Ok(mut entity) = commands.get_entity(instance.entity()) {
72                                entity.try_insert(Checked);
73                                debug!("{instance:?} is valid.");
74                            }
75                            continue;
76                        }
77                        Err(QueryEntityError::EntityDoesNotExist(_)) => {
78                            continue;
79                        }
80                        _ => {}
81                    };
82
83                    match &policy {
84                        Policy::Invalid => {
85                            if let Ok(mut entity) = commands.get_entity(instance.entity()) {
86                                entity.try_insert((Checked, Invalid));
87                                error!("{instance:?} is invalid: {}", filter_name());
88                            }
89                        }
90                        Policy::Purge => {
91                            if let Ok(mut entity) = commands.get_entity(instance.entity()) {
92                                entity.despawn();
93                                error!("{instance:?} is purged: {}", filter_name());
94                            }
95                        }
96                        Policy::Panic => {
97                            panic!("{instance:?} is strictly invalid: {}", filter_name());
98                        }
99                        Policy::Repair(fixer) => {
100                            if let Ok(mut entity) = commands.get_entity(instance.entity()) {
101                                // Inset `Checked` before fixing to let the fixer remove it if needed
102                                entity.try_insert(Checked);
103                                error!("{instance:?} is invalid: {}", filter_name());
104
105                                let entity = world.entity(instance.entity());
106                                fixer.fix(entity, &mut commands);
107                                warn!("{instance:?} was repaired.");
108                            }
109                        }
110                    }
111                }
112            })
113            .after(LoadSystem::Load)
114            .in_set(CheckSystems),
115        )
116    }
117}
118
119pub trait CheckFilter: 'static + QueryFilter + Send + Sync {}
120
121impl<F> CheckFilter for F where F: 'static + QueryFilter + Send + Sync {}
122
123#[derive(Clone, Debug, Hash, PartialEq, Eq, SystemSet)]
124pub struct CheckSystems;
125
126/// An action to be invoked if a [`Check`] *passes*.
127///
128/// See [`invalid`], [`purge`], [`panic`], and [`repair`] for details.
129pub enum Policy {
130    /// Mark the instance as invalid.
131    Invalid,
132    /// Despawn the instance and all of its children.
133    Purge,
134    /// Panic!
135    Panic,
136    /// Try to repair the instance with a given [`Fixer`].
137    Repair(Fixer),
138}
139
140/// A fixer to be used with a [`Policy::Repair`] to try and fix an invalid instance.
141pub struct Fixer(Box<dyn Fix>);
142
143impl Fixer {
144    pub fn new(f: impl Fix) -> Self {
145        Self(Box::new(f))
146    }
147
148    pub fn fix(&self, entity: EntityRef, commands: &mut Commands) {
149        self.0.fix(entity, commands)
150    }
151}
152
153pub trait Fix: 'static + Send + Sync {
154    fn fix(&self, entity: EntityRef, commands: &mut Commands);
155}
156
157impl<F: Fn(EntityRef, &mut Commands)> Fix for F
158where
159    F: 'static + Send + Sync,
160{
161    fn fix(&self, entity: EntityRef, commands: &mut Commands) {
162        self(entity, commands)
163    }
164}
165
166/// Returns a [`Policy`] which despawns matching instances and all of their children.
167///
168/// # Usage
169///
170/// Use this policy with [`Valid`] to allow systems to safely ignore invalid entities.
171///
172/// # Example
173/// ```
174/// use bevy::prelude::*;
175/// use moonshine_check::prelude::*;
176///
177/// #[derive(Bundle, Default)]
178/// struct AB {
179///     a: A,
180///     b: B,
181/// }
182///
183/// #[derive(Component, Default)]
184/// struct A;
185///
186/// #[derive(Component, Default)]
187/// struct B;
188///
189/// let mut app = App::new();
190/// app.add_plugins(MinimalPlugins)
191///     .check::<A, Without<B>>(invalid())
192///     .add_systems(Update, update_valid);
193///
194/// app.world_mut().spawn(AB::default()); // OK!
195/// app.world_mut().spawn(A); // Bug! `B` is missing!
196/// app.update();
197///
198/// fn update_valid(items: Query<Entity, (With<A>, Valid)>, query: Query<&B>) {
199///     for entity in items.iter() {
200///         // Guaranteed:
201///         assert!(query.contains(entity));
202///     }
203/// }
204/// ```
205pub fn invalid() -> Policy {
206    Policy::Invalid
207}
208
209/// Returns a [`Policy`] which despawns matching instances and all of their children.
210///
211/// # Usage
212///
213/// Use this policy to remove invalid entities from the world.
214///
215/// # Example
216/// ```
217/// use bevy::prelude::*;
218/// use moonshine_check::prelude::*;
219///
220/// #[derive(Bundle, Default)]
221/// struct AB {
222///     a: A,
223///     b: B,
224/// }
225///
226/// #[derive(Component, Default)]
227/// struct A;
228///
229/// #[derive(Component, Default)]
230/// struct B;
231///
232/// let mut app = App::new();
233/// app.add_plugins(MinimalPlugins)
234///     .check::<A, Without<B>>(purge())
235///     .add_systems(Update, update);
236///
237/// app.world_mut().spawn(AB::default()); // OK!
238/// app.world_mut().spawn(A); // Bug! `B` is missing!
239/// app.update();
240///
241/// fn update(items: Query<Entity, With<A>>, query: Query<&B>) {
242///     for entity in items.iter() {
243///         // Guaranteed:
244///         assert!(query.contains(entity));
245///     }
246/// }
247/// ```
248pub fn purge() -> Policy {
249    Policy::Purge
250}
251
252/// Returns a [`Policy`] which despawns matching instances and all of their children.
253///
254/// # Usage
255///
256/// Use this policy if you want to [`panic!`] on invalid entities.
257///
258/// In general, you should avoid using this policy as it can make your application unstable.
259/// It is recommended to use [`invalid`] or [`purge`] instead, especially in a production environment.
260///
261/// # Example
262/// ```should_panic
263/// use bevy::prelude::*;
264/// use moonshine_check::prelude::*;
265///
266/// #[derive(Bundle, Default)]
267/// struct AB {
268///     a: A,
269///     b: B,
270/// }
271///
272/// #[derive(Component, Default)]
273/// struct A;
274///
275/// #[derive(Component, Default)]
276/// struct B;
277///
278/// let mut app = App::new();
279/// app.add_plugins(MinimalPlugins)
280///     .check::<A, Without<B>>(panic())
281///     .add_systems(Update, update);
282///
283/// app.world_mut().spawn(AB::default()); // OK!
284/// app.world_mut().spawn(A); // Bug! `B` is missing!
285/// app.update();
286///
287/// fn update(items: Query<Entity, With<A>>, query: Query<&B>) {
288///     // Guaranteed:
289///     unreachable!();
290/// }
291/// ```
292pub fn panic() -> Policy {
293    Policy::Panic
294}
295
296/// Returns a [`Policy`] which tries to repair matching instances.
297///
298/// # Usage
299///
300/// Use this policy if the matching instances can be repaired by inserting or removing components.
301/// This is especially useful to handle backwards compatibility when loading from saved data.
302///
303/// # Example
304/// ```
305/// use bevy::prelude::*;
306/// use moonshine_check::prelude::*;
307///
308/// #[derive(Bundle, Default)]
309/// struct AB {
310///    a: A,
311///    b: B,
312/// }
313///
314/// #[derive(Component, Default)]
315/// struct A;
316///
317/// #[derive(Component, Default)]
318/// struct B;
319///
320/// let mut app = App::new();
321/// app.add_plugins(MinimalPlugins)
322///     .check::<A, Without<B>>(repair(|entity: EntityRef, commands: &mut Commands| {
323///         commands.entity(entity.id()).insert(B);
324///     }));
325///
326/// app.world_mut().spawn(A); // Bug! `B` is missing!
327/// app.update();
328///
329/// fn update(items: Query<Entity, With<A>>, query: Query<&B>) {
330///     for entity in items.iter() {
331///         // Guaranteed:
332///         assert!(query.contains(entity));
333///     }
334/// }
335pub fn repair(f: impl Fix) -> Policy {
336    Policy::Repair(Fixer::new(f))
337}
338
339pub fn repair_insert<T: Component + Clone>(component: T) -> Policy {
340    repair(move |entity: EntityRef, commands: &mut Commands| {
341        commands.entity(entity.id()).insert(component.clone());
342    })
343}
344
345pub fn repair_insert_default<T: Component + Default>() -> Policy {
346    repair(move |entity: EntityRef, commands: &mut Commands| {
347        commands.entity(entity.id()).insert(T::default());
348    })
349}
350
351pub fn repair_replace<T: Component, U: Component + Clone>(component: U) -> Policy {
352    repair(move |entity: EntityRef, commands: &mut Commands| {
353        commands
354            .entity(entity.id())
355            .remove::<T>()
356            .insert(component.clone());
357    })
358}
359
360pub fn repair_replace_default<T: Component, U: Component + Default>() -> Policy {
361    repair(move |entity: EntityRef, commands: &mut Commands| {
362        commands
363            .entity(entity.id())
364            .remove::<T>()
365            .insert(U::default());
366    })
367}
368
369pub fn repair_replace_with<T: Component, U: Component, F>(f: F) -> Policy
370where
371    F: 'static + Fn(&T) -> U + Send + Sync,
372{
373    repair(move |entity: EntityRef, commands: &mut Commands| {
374        let component = entity.get::<T>().unwrap();
375        commands
376            .entity(entity.id())
377            .remove::<T>()
378            .insert(f(component));
379    })
380}
381
382pub fn repair_remove<T: Component>() -> Policy {
383    repair(move |entity: EntityRef, commands: &mut Commands| {
384        commands.entity(entity.id()).remove::<T>();
385    })
386}
387
388/// A [`QueryFilter`] which indicates that an [`Entity`] has been checked and is valid.
389///
390/// See [`invalid`] for a usage example.
391#[derive(QueryFilter)]
392pub struct Valid(With<Checked>, Without<Invalid>);
393
394/// An extension trait used to force an [`Entity`] to be checked again.
395pub trait CheckAgain {
396    fn check_again(self) -> Self;
397}
398
399impl CheckAgain for &mut EntityCommands<'_> {
400    fn check_again(self) -> Self {
401        self.remove::<Checked>().remove::<Invalid>()
402    }
403}
404
405impl CheckAgain for &mut EntityWorldMut<'_> {
406    fn check_again(self) -> Self {
407        self.remove::<Checked>().remove::<Invalid>()
408    }
409}
410
411type Unchecked = Without<Checked>;
412
413#[derive(Component)]
414struct Checked;
415
416#[derive(Component)]
417struct Invalid;
418
419#[cfg(test)]
420mod tests {
421    use bevy::prelude::*;
422
423    use super::*;
424
425    #[derive(Component)]
426    struct Foo;
427
428    #[derive(Component)]
429    struct Bar;
430
431    #[test]
432    fn test_valid() {
433        let mut app = App::new();
434        app.add_plugins(MinimalPlugins)
435            .check::<Foo, Without<Bar>>(panic());
436
437        let entity = app.world_mut().spawn((Foo, Bar)).id();
438        app.update();
439
440        assert!(app.world().entity(entity).contains::<Checked>());
441        assert!(!app.world().entity(entity).contains::<Invalid>());
442    }
443
444    #[test]
445    fn test_invalid() {
446        let mut app = App::new();
447        app.add_plugins(MinimalPlugins)
448            .check::<Foo, Without<Bar>>(invalid());
449
450        let entity = app.world_mut().spawn(Foo).id();
451        app.update();
452
453        assert!(app.world().entity(entity).contains::<Checked>());
454        assert!(app.world().entity(entity).contains::<Invalid>());
455    }
456
457    #[test]
458    fn test_purge() {
459        let mut app = App::new();
460        app.add_plugins(MinimalPlugins)
461            .check::<Foo, Without<Bar>>(purge());
462
463        let entity = app.world_mut().spawn(Foo).id();
464        app.update();
465
466        assert!(app.world().get_entity(entity).is_err());
467    }
468
469    #[test]
470    #[should_panic]
471    fn test_panic() {
472        let mut app = App::new();
473        app.add_plugins(MinimalPlugins)
474            .check::<Foo, Without<Bar>>(panic());
475
476        app.world_mut().spawn(Foo);
477        app.update();
478    }
479
480    #[test]
481    fn test_repair() {
482        let mut app = App::new();
483        app.add_plugins(MinimalPlugins)
484            .check::<Foo, Without<Bar>>(repair(|entity: EntityRef, commands: &mut Commands| {
485                commands.entity(entity.id()).insert(Bar);
486            }));
487
488        let entity = app.world_mut().spawn(Foo).id();
489        app.update();
490
491        assert!(app.world().entity(entity).contains::<Bar>());
492        assert!(app.world().entity(entity).contains::<Checked>());
493    }
494
495    #[test]
496    #[should_panic]
497    fn test_check_again() {
498        #[derive(Component)]
499        struct Repaired;
500
501        let mut app = App::new();
502        app.add_plugins(MinimalPlugins)
503            .check::<Foo, Without<Bar>>(repair(|entity: EntityRef, commands: &mut Commands| {
504                // Avoid infinite repair loop
505                if entity.contains::<Repaired>() {
506                    panic!("Bar is still missing!");
507                }
508
509                // Oops! Maybe we forget to insert Bar ...
510                // Check again to be sure:
511                commands.entity(entity.id()).insert(Repaired).check_again();
512            }));
513
514        let entity = app.world_mut().spawn(Foo).id();
515        app.update();
516
517        assert!(!app.world().entity(entity).contains::<Bar>());
518        assert!(!app.world().entity(entity).contains::<Checked>());
519
520        app.update(); // Should panic!
521    }
522
523    #[test]
524    #[should_panic]
525    fn test_multiple() {
526        #[derive(Component)]
527        struct Baz;
528
529        let mut app = App::new();
530        app.add_plugins(MinimalPlugins)
531            .check::<Foo, Without<Bar>>(panic())
532            .check::<Foo, Without<Baz>>(panic());
533
534        app.world_mut().spawn((Foo, Bar));
535        app.update();
536    }
537}