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}