1use crate::Entity;
142use crate::Schedule;
143use crate::dynamic::{DynEcs, DynJoin, DynQuery, DynWorld, EventBus, QueryTuple, ResourceHost};
144use std::cell::RefCell;
145use std::marker::PhantomData;
146
147pub struct Res<'world, T> {
150 value: &'world T,
151}
152
153impl<T> std::ops::Deref for Res<'_, T> {
154 type Target = T;
155 fn deref(&self) -> &T {
156 self.value
157 }
158}
159
160pub struct ResMut<'world, T> {
163 value: &'world mut T,
164}
165
166impl<T> std::ops::Deref for ResMut<'_, T> {
167 type Target = T;
168 fn deref(&self) -> &T {
169 self.value
170 }
171}
172
173impl<T> std::ops::DerefMut for ResMut<'_, T> {
174 fn deref_mut(&mut self) -> &mut T {
175 self.value
176 }
177}
178
179pub trait EventHost {
184 fn event_bus_mut(&mut self) -> &mut EventBus;
186
187 fn event_bus(&self) -> &EventBus;
191}
192
193impl EventHost for DynWorld {
194 fn event_bus_mut(&mut self) -> &mut EventBus {
195 &mut self.events
196 }
197
198 fn event_bus(&self) -> &EventBus {
199 &self.events
200 }
201}
202
203impl EventHost for DynEcs {
204 fn event_bus_mut(&mut self) -> &mut EventBus {
205 &mut self.events
206 }
207
208 fn event_bus(&self) -> &EventBus {
209 &self.events
210 }
211}
212
213pub trait ExtractParam<W> {
224 type State: Send + 'static;
226 type Owned;
228 type Item<'item>;
230 fn init() -> Self::State;
232 fn extract(state: &mut Self::State, host: &mut W) -> Self::Owned;
234 fn build(owned: &mut Self::Owned) -> Self::Item<'_>;
236 fn apply(state: &mut Self::State, owned: Self::Owned, host: &mut W);
239}
240
241impl<W: ResourceHost, T: Send + Sync + 'static> ExtractParam<W> for Res<'_, T> {
242 type State = ();
243 type Owned = T;
244 type Item<'item> = Res<'item, T>;
245 fn init() -> Self::State {}
246 fn extract(_state: &mut (), host: &mut W) -> T {
247 host.resource_map_mut().remove::<T>().unwrap_or_else(|| {
248 panic!(
249 "system requires resource {} to be present",
250 std::any::type_name::<T>()
251 )
252 })
253 }
254 fn build(owned: &mut T) -> Res<'_, T> {
255 Res { value: owned }
256 }
257 fn apply(_state: &mut (), owned: T, host: &mut W) {
258 host.resource_map_mut().insert(owned);
259 }
260}
261
262impl<W: ResourceHost, T: Send + Sync + 'static> ExtractParam<W> for ResMut<'_, T> {
263 type State = ();
264 type Owned = T;
265 type Item<'item> = ResMut<'item, T>;
266 fn init() -> Self::State {}
267 fn extract(_state: &mut (), host: &mut W) -> T {
268 host.resource_map_mut().remove::<T>().unwrap_or_else(|| {
269 panic!(
270 "system requires resource {} to be present",
271 std::any::type_name::<T>()
272 )
273 })
274 }
275 fn build(owned: &mut T) -> ResMut<'_, T> {
276 ResMut { value: owned }
277 }
278 fn apply(_state: &mut (), owned: T, host: &mut W) {
279 host.resource_map_mut().insert(owned);
280 }
281}
282
283pub struct EventReader<'a, T: Clone + Send + Sync + 'static> {
290 events: &'a [T],
291}
292
293impl<T: Clone + Send + Sync + 'static> EventReader<'_, T> {
294 pub fn iter(&self) -> std::slice::Iter<'_, T> {
296 self.events.iter()
297 }
298
299 pub fn len(&self) -> usize {
301 self.events.len()
302 }
303
304 pub fn is_empty(&self) -> bool {
306 self.events.is_empty()
307 }
308}
309
310impl<'a, T: Clone + Send + Sync + 'static> IntoIterator for &'a EventReader<'_, T> {
311 type Item = &'a T;
312 type IntoIter = std::slice::Iter<'a, T>;
313 fn into_iter(self) -> Self::IntoIter {
314 self.events.iter()
315 }
316}
317
318impl<W: EventHost, T: Clone + Send + Sync + 'static> ExtractParam<W> for EventReader<'_, T> {
319 type State = u64;
320 type Owned = Vec<T>;
321 type Item<'item> = EventReader<'item, T>;
322 fn init() -> u64 {
323 0
324 }
325 fn extract(state: &mut u64, host: &mut W) -> Vec<T> {
326 host.event_bus_mut().consume::<T>(state).to_vec()
327 }
328 fn build(owned: &mut Vec<T>) -> EventReader<'_, T> {
329 EventReader { events: owned }
330 }
331 fn apply(_state: &mut u64, _owned: Vec<T>, _host: &mut W) {}
332}
333
334pub struct EventWriter<'a, T: Send + Sync + 'static> {
339 pending: &'a mut Vec<T>,
340}
341
342impl<T: Send + Sync + 'static> EventWriter<'_, T> {
343 pub fn send(&mut self, event: T) {
345 self.pending.push(event);
346 }
347
348 pub fn send_batch(&mut self, events: impl IntoIterator<Item = T>) {
350 self.pending.extend(events);
351 }
352}
353
354impl<W: EventHost, T: Send + Sync + 'static> ExtractParam<W> for EventWriter<'_, T> {
355 type State = ();
356 type Owned = Vec<T>;
357 type Item<'item> = EventWriter<'item, T>;
358 fn init() -> Self::State {}
359 fn extract(_state: &mut (), _host: &mut W) -> Vec<T> {
360 Vec::new()
361 }
362 fn build(owned: &mut Vec<T>) -> EventWriter<'_, T> {
363 EventWriter { pending: owned }
364 }
365 fn apply(_state: &mut (), owned: Vec<T>, host: &mut W) {
366 for event in owned {
367 host.event_bus_mut().send::<T>(event);
368 }
369 }
370}
371
372pub trait QueryFilter {
375 fn apply<Q: QueryTuple>(query: DynQuery<'_, Q>) -> DynQuery<'_, Q>;
377}
378
379impl QueryFilter for () {
380 fn apply<Q: QueryTuple>(query: DynQuery<'_, Q>) -> DynQuery<'_, Q> {
381 query
382 }
383}
384
385pub struct With<T>(PhantomData<fn() -> T>);
388
389impl<T: Send + Sync + Default + 'static> QueryFilter for With<T> {
390 fn apply<Q: QueryTuple>(query: DynQuery<'_, Q>) -> DynQuery<'_, Q> {
391 query.with::<T>()
392 }
393}
394
395pub struct Without<T>(PhantomData<fn() -> T>);
397
398impl<T: Send + Sync + Default + 'static> QueryFilter for Without<T> {
399 fn apply<Q: QueryTuple>(query: DynQuery<'_, Q>) -> DynQuery<'_, Q> {
400 query.without::<T>()
401 }
402}
403
404pub struct Changed<T>(PhantomData<fn() -> T>);
407
408impl<T: Send + Sync + Default + 'static> QueryFilter for Changed<T> {
409 fn apply<Q: QueryTuple>(query: DynQuery<'_, Q>) -> DynQuery<'_, Q> {
410 query.changed::<T>()
411 }
412}
413
414pub struct Added<T>(PhantomData<fn() -> T>);
417
418impl<T: Send + Sync + Default + 'static> QueryFilter for Added<T> {
419 fn apply<Q: QueryTuple>(query: DynQuery<'_, Q>) -> DynQuery<'_, Q> {
420 query.added::<T>()
421 }
422}
423
424pub struct WithTag<T>(PhantomData<fn() -> T>);
426
427impl<T: 'static> QueryFilter for WithTag<T> {
428 fn apply<Q: QueryTuple>(query: DynQuery<'_, Q>) -> DynQuery<'_, Q> {
429 query.with_tag_type::<T>()
430 }
431}
432
433pub struct WithoutTag<T>(PhantomData<fn() -> T>);
435
436impl<T: 'static> QueryFilter for WithoutTag<T> {
437 fn apply<Q: QueryTuple>(query: DynQuery<'_, Q>) -> DynQuery<'_, Q> {
438 query.without_tag_type::<T>()
439 }
440}
441
442macro_rules! impl_query_filter_tuple {
443 ($($filter:ident),+) => {
444 impl<$($filter: QueryFilter),+> QueryFilter for ($($filter,)+) {
445 fn apply<Q: QueryTuple>(query: DynQuery<'_, Q>) -> DynQuery<'_, Q> {
446 $(let query = $filter::apply(query);)+
447 query
448 }
449 }
450 };
451}
452
453impl_query_filter_tuple!(A);
454impl_query_filter_tuple!(A, B);
455impl_query_filter_tuple!(A, B, C);
456impl_query_filter_tuple!(A, B, C, D);
457
458enum QueryState<'world, Q: QueryTuple> {
459 Eager(DynQuery<'world, Q>),
460 Lazy(&'world RefCell<&'world mut DynWorld>),
461 Join(DynJoin<'world, Q>),
462}
463
464pub struct Query<'world, Q: QueryTuple, F: QueryFilter = ()> {
469 state: QueryState<'world, Q>,
470 filter: PhantomData<fn() -> F>,
471}
472
473impl<'world, Q: QueryTuple, F: QueryFilter> Query<'world, Q, F> {
474 pub fn for_each(self, f: impl for<'item> FnMut(Entity, Q::Item<'item>)) {
476 match self.state {
477 QueryState::Eager(query) => F::apply(query).for_each(f),
478 QueryState::Lazy(cell) => {
479 let mut guard = cell.borrow_mut();
480 let world: &mut DynWorld = &mut guard;
481 F::apply(world.query::<Q>()).for_each(f);
482 }
483 QueryState::Join(join) => join.for_each(f),
484 }
485 }
486
487 #[cfg(not(target_family = "wasm"))]
489 pub fn par_for_each<Fun>(self, f: Fun)
490 where
491 Fun: for<'item> Fn(Entity, Q::Item<'item>) + Send + Sync,
492 {
493 match self.state {
494 QueryState::Eager(query) => F::apply(query).par_for_each(f),
495 QueryState::Lazy(cell) => {
496 let mut guard = cell.borrow_mut();
497 let world: &mut DynWorld = &mut guard;
498 F::apply(world.query::<Q>()).par_for_each(f);
499 }
500 QueryState::Join(join) => join.par_for_each(f),
501 }
502 }
503}
504
505pub trait WorldParam {
509 type Item<'world>;
511 fn build(world: &mut DynWorld) -> Self::Item<'_>;
513}
514
515impl<'a, Q: QueryTuple, F: QueryFilter> WorldParam for Query<'a, Q, F> {
516 type Item<'world> = Query<'world, Q, F>;
517 fn build(world: &mut DynWorld) -> Query<'_, Q, F> {
518 Query {
519 state: QueryState::Eager(world.query::<Q>()),
520 filter: PhantomData,
521 }
522 }
523}
524
525pub trait EcsParam {
533 type Item<'ecs>;
535 fn build_ecs(ecs: &mut DynEcs) -> Self::Item<'_>;
537}
538
539impl<'a, Q: QueryTuple> EcsParam for Query<'a, Q, ()> {
540 type Item<'ecs> = Query<'ecs, Q, ()>;
541 fn build_ecs(ecs: &mut DynEcs) -> Query<'_, Q, ()> {
542 Query {
543 state: QueryState::Join(ecs.query_join::<Q>()),
544 filter: PhantomData,
545 }
546 }
547}
548
549pub trait MultiQueryParam {
554 type Item<'world>;
556 fn build_lazy<'world>(cell: &'world RefCell<&'world mut DynWorld>) -> Self::Item<'world>;
558}
559
560impl<'a, Q: QueryTuple, F: QueryFilter> MultiQueryParam for Query<'a, Q, F> {
561 type Item<'world> = Query<'world, Q, F>;
562 fn build_lazy<'world>(cell: &'world RefCell<&'world mut DynWorld>) -> Query<'world, Q, F> {
563 Query {
564 state: QueryState::Lazy(cell),
565 filter: PhantomData,
566 }
567 }
568}
569
570pub struct ParamSet<'world, T> {
574 world: &'world mut DynWorld,
575 marker: PhantomData<fn() -> T>,
576}
577
578impl<'a, T: 'static> WorldParam for ParamSet<'a, T>
579where
580 ParamSet<'a, T>: ParamSetAccess,
581{
582 type Item<'world> = ParamSet<'world, T>;
583 fn build(world: &mut DynWorld) -> ParamSet<'_, T> {
584 ParamSet {
585 world,
586 marker: PhantomData,
587 }
588 }
589}
590
591pub trait ParamSetAccess {}
593
594impl<P0: WorldParam, P1: WorldParam> ParamSetAccess for ParamSet<'_, (P0, P1)> {}
595
596impl<'world, P0: WorldParam, P1: WorldParam> ParamSet<'world, (P0, P1)> {
597 pub fn p0(&mut self) -> P0::Item<'_> {
599 P0::build(self.world)
600 }
601
602 pub fn p1(&mut self) -> P1::Item<'_> {
604 P1::build(self.world)
605 }
606}
607
608impl<P0: WorldParam, P1: WorldParam, P2: WorldParam> ParamSetAccess for ParamSet<'_, (P0, P1, P2)> {}
609
610impl<'world, P0: WorldParam, P1: WorldParam, P2: WorldParam> ParamSet<'world, (P0, P1, P2)> {
611 pub fn p0(&mut self) -> P0::Item<'_> {
613 P0::build(self.world)
614 }
615
616 pub fn p1(&mut self) -> P1::Item<'_> {
618 P1::build(self.world)
619 }
620
621 pub fn p2(&mut self) -> P2::Item<'_> {
623 P2::build(self.world)
624 }
625}
626
627pub trait IntoSystem<W, Marker>: Sized {
635 fn into_runner(self) -> impl FnMut(&mut W) + Send + 'static;
638}
639
640pub trait ScheduleExt<W> {
643 fn add_system<Marker>(
647 &mut self,
648 name: &'static str,
649 system: impl IntoSystem<W, Marker>,
650 ) -> &mut Self;
651
652 fn add_system_if<Marker>(
661 &mut self,
662 name: &'static str,
663 condition: impl Fn(&W) -> bool + Send + 'static,
664 system: impl IntoSystem<W, Marker>,
665 ) -> &mut Self;
666
667 fn add_systems<Marker>(&mut self, systems: impl IntoSystems<W, Marker>) -> &mut Self;
673}
674
675impl<W> ScheduleExt<W> for Schedule<W> {
676 fn add_system<Marker>(
677 &mut self,
678 name: &'static str,
679 system: impl IntoSystem<W, Marker>,
680 ) -> &mut Self {
681 self.push(name, system.into_runner())
682 }
683
684 fn add_system_if<Marker>(
685 &mut self,
686 name: &'static str,
687 condition: impl Fn(&W) -> bool + Send + 'static,
688 system: impl IntoSystem<W, Marker>,
689 ) -> &mut Self {
690 self.push_if(name, condition, system.into_runner())
691 }
692
693 fn add_systems<Marker>(&mut self, systems: impl IntoSystems<W, Marker>) -> &mut Self {
694 systems.register(self);
695 self
696 }
697}
698
699fn add_named_system<W, Marker>(schedule: &mut Schedule<W>, system: impl IntoSystem<W, Marker>) {
700 let base = std::any::type_name_of_val(&system);
701 let name = if schedule.contains(base) {
702 let mut suffix = 2;
703 loop {
704 let candidate = format!("{base}#{suffix}");
705 if !schedule.contains(&candidate) {
706 break Box::leak(candidate.into_boxed_str()) as &'static str;
707 }
708 suffix += 1;
709 }
710 } else {
711 base
712 };
713 schedule.push(name, system.into_runner());
714}
715
716pub trait IntoSystems<W, Marker> {
720 fn register(self, schedule: &mut Schedule<W>);
722}
723
724macro_rules! impl_into_systems {
725 ($(($system:ident, $marker:ident)),+) => {
726 impl<W, $($system, $marker,)+> IntoSystems<W, ($($marker,)+)> for ($($system,)+)
727 where
728 $($system: IntoSystem<W, $marker>,)+
729 {
730 #[allow(non_snake_case)]
731 fn register(self, schedule: &mut Schedule<W>) {
732 let ($($system,)+) = self;
733 $(add_named_system(schedule, $system);)+
734 }
735 }
736 };
737}
738
739impl_into_systems!((S0, M0));
740impl_into_systems!((S0, M0), (S1, M1));
741impl_into_systems!((S0, M0), (S1, M1), (S2, M2));
742impl_into_systems!((S0, M0), (S1, M1), (S2, M2), (S3, M3));
743impl_into_systems!((S0, M0), (S1, M1), (S2, M2), (S3, M3), (S4, M4));
744impl_into_systems!((S0, M0), (S1, M1), (S2, M2), (S3, M3), (S4, M4), (S5, M5));
745impl_into_systems!(
746 (S0, M0),
747 (S1, M1),
748 (S2, M2),
749 (S3, M3),
750 (S4, M4),
751 (S5, M5),
752 (S6, M6)
753);
754impl_into_systems!(
755 (S0, M0),
756 (S1, M1),
757 (S2, M2),
758 (S3, M3),
759 (S4, M4),
760 (S5, M5),
761 (S6, M6),
762 (S7, M7)
763);
764
765pub struct ExtractSystemMarker<E>(PhantomData<fn() -> E>);
767
768pub struct QuerySystemMarker<E, Q>(PhantomData<fn() -> (E, Q)>);
771
772impl<Func, Q> IntoSystem<DynWorld, QuerySystemMarker<(), Q>> for Func
773where
774 Q: WorldParam,
775 Func: FnMut(Q) + for<'world> FnMut(Q::Item<'world>) + Send + 'static,
776{
777 fn into_runner(mut self) -> impl FnMut(&mut DynWorld) + Send + 'static {
778 move |world: &mut DynWorld| {
779 let query = Q::build(world);
780 self(query);
781 }
782 }
783}
784
785pub struct HostSystemMarker<E>(PhantomData<fn() -> E>);
788
789macro_rules! impl_extract_system {
790 ($($param:ident $state:ident $owned:ident),+) => {
791 impl<W, Func, $($param,)+> IntoSystem<W, ExtractSystemMarker<($($param,)+)>>
792 for Func
793 where
794 W: 'static,
795 $($param: ExtractParam<W>,)+
796 Func: FnMut($($param,)+)
797 + for<'item> FnMut($($param::Item<'item>,)+)
798 + Send
799 + 'static,
800 {
801 fn into_runner(mut self) -> impl FnMut(&mut W) + Send + 'static {
802 $(let mut $state = <$param as ExtractParam<W>>::init();)+
803 move |host: &mut W| {
804 $(let mut $owned = <$param as ExtractParam<W>>::extract(&mut $state, host);)+
805 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
806 self($(<$param as ExtractParam<W>>::build(&mut $owned),)+);
807 }));
808 $(<$param as ExtractParam<W>>::apply(&mut $state, $owned, host);)+
809 if let Err(panic) = result {
810 std::panic::resume_unwind(panic);
811 }
812 }
813 }
814 }
815
816 impl<W, Func, $($param,)+> IntoSystem<W, HostSystemMarker<($($param,)+)>>
817 for Func
818 where
819 W: 'static,
820 $($param: ExtractParam<W>,)+
821 Func: FnMut($($param,)+ &mut W)
822 + for<'item> FnMut($($param::Item<'item>,)+ &'item mut W)
823 + Send
824 + 'static,
825 {
826 fn into_runner(mut self) -> impl FnMut(&mut W) + Send + 'static {
827 $(let mut $state = <$param as ExtractParam<W>>::init();)+
828 move |host: &mut W| {
829 $(let mut $owned = <$param as ExtractParam<W>>::extract(&mut $state, host);)+
830 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
831 self($(<$param as ExtractParam<W>>::build(&mut $owned),)+ host);
832 }));
833 $(<$param as ExtractParam<W>>::apply(&mut $state, $owned, host);)+
834 if let Err(panic) = result {
835 std::panic::resume_unwind(panic);
836 }
837 }
838 }
839 }
840 };
841}
842
843macro_rules! impl_extract_query_system {
844 ($($param:ident $state:ident $owned:ident),+) => {
845 impl<Func, $($param,)+ Q>
846 IntoSystem<DynWorld, QuerySystemMarker<($($param,)+), Q>> for Func
847 where
848 $($param: ExtractParam<DynWorld>,)+
849 Q: WorldParam,
850 Func: FnMut($($param,)+ Q)
851 + for<'item> FnMut($($param::Item<'item>,)+ Q::Item<'item>)
852 + Send
853 + 'static,
854 {
855 fn into_runner(mut self) -> impl FnMut(&mut DynWorld) + Send + 'static {
856 $(let mut $state = <$param as ExtractParam<DynWorld>>::init();)+
857 move |world: &mut DynWorld| {
858 $(let mut $owned =
859 <$param as ExtractParam<DynWorld>>::extract(&mut $state, world);)+
860 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
861 let query = Q::build(world);
862 self($(<$param as ExtractParam<DynWorld>>::build(&mut $owned),)+ query);
863 }));
864 $(<$param as ExtractParam<DynWorld>>::apply(&mut $state, $owned, world);)+
865 if let Err(panic) = result {
866 std::panic::resume_unwind(panic);
867 }
868 }
869 }
870 }
871 };
872}
873
874impl_extract_system!(P0 state0 owned0);
875impl_extract_system!(P0 state0 owned0, P1 state1 owned1);
876impl_extract_system!(P0 state0 owned0, P1 state1 owned1, P2 state2 owned2);
877impl_extract_system!(P0 state0 owned0, P1 state1 owned1, P2 state2 owned2, P3 state3 owned3);
878impl_extract_system!(
879 P0 state0 owned0, P1 state1 owned1, P2 state2 owned2, P3 state3 owned3, P4 state4 owned4
880);
881impl_extract_system!(
882 P0 state0 owned0, P1 state1 owned1, P2 state2 owned2, P3 state3 owned3, P4 state4 owned4,
883 P5 state5 owned5
884);
885impl_extract_system!(
886 P0 state0 owned0, P1 state1 owned1, P2 state2 owned2, P3 state3 owned3, P4 state4 owned4,
887 P5 state5 owned5, P6 state6 owned6
888);
889impl_extract_system!(
890 P0 state0 owned0, P1 state1 owned1, P2 state2 owned2, P3 state3 owned3, P4 state4 owned4,
891 P5 state5 owned5, P6 state6 owned6, P7 state7 owned7
892);
893
894impl_extract_query_system!(P0 state0 owned0);
895impl_extract_query_system!(P0 state0 owned0, P1 state1 owned1);
896impl_extract_query_system!(P0 state0 owned0, P1 state1 owned1, P2 state2 owned2);
897impl_extract_query_system!(P0 state0 owned0, P1 state1 owned1, P2 state2 owned2, P3 state3 owned3);
898impl_extract_query_system!(
899 P0 state0 owned0, P1 state1 owned1, P2 state2 owned2, P3 state3 owned3, P4 state4 owned4
900);
901impl_extract_query_system!(
902 P0 state0 owned0, P1 state1 owned1, P2 state2 owned2, P3 state3 owned3, P4 state4 owned4,
903 P5 state5 owned5
904);
905impl_extract_query_system!(
906 P0 state0 owned0, P1 state1 owned1, P2 state2 owned2, P3 state3 owned3, P4 state4 owned4,
907 P5 state5 owned5, P6 state6 owned6
908);
909
910impl<Func, Q> IntoSystem<DynEcs, QuerySystemMarker<(), Q>> for Func
911where
912 Q: EcsParam,
913 Func: FnMut(Q) + for<'ecs> FnMut(Q::Item<'ecs>) + Send + 'static,
914{
915 fn into_runner(mut self) -> impl FnMut(&mut DynEcs) + Send + 'static {
916 move |ecs: &mut DynEcs| {
917 let query = Q::build_ecs(ecs);
918 self(query);
919 }
920 }
921}
922
923macro_rules! impl_extract_ecs_query_system {
924 ($($param:ident $state:ident $owned:ident),+) => {
925 impl<Func, $($param,)+ Q>
926 IntoSystem<DynEcs, QuerySystemMarker<($($param,)+), Q>> for Func
927 where
928 $($param: ExtractParam<DynEcs>,)+
929 Q: EcsParam,
930 Func: FnMut($($param,)+ Q)
931 + for<'item> FnMut($($param::Item<'item>,)+ Q::Item<'item>)
932 + Send
933 + 'static,
934 {
935 fn into_runner(mut self) -> impl FnMut(&mut DynEcs) + Send + 'static {
936 $(let mut $state = <$param as ExtractParam<DynEcs>>::init();)+
937 move |ecs: &mut DynEcs| {
938 $(let mut $owned = <$param as ExtractParam<DynEcs>>::extract(&mut $state, ecs);)+
939 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
940 let query = Q::build_ecs(ecs);
941 self($(<$param as ExtractParam<DynEcs>>::build(&mut $owned),)+ query);
942 }));
943 $(<$param as ExtractParam<DynEcs>>::apply(&mut $state, $owned, ecs);)+
944 if let Err(panic) = result {
945 std::panic::resume_unwind(panic);
946 }
947 }
948 }
949 }
950 };
951}
952
953impl_extract_ecs_query_system!(P0 state0 owned0);
954impl_extract_ecs_query_system!(P0 state0 owned0, P1 state1 owned1);
955impl_extract_ecs_query_system!(P0 state0 owned0, P1 state1 owned1, P2 state2 owned2);
956impl_extract_ecs_query_system!(
957 P0 state0 owned0, P1 state1 owned1, P2 state2 owned2, P3 state3 owned3
958);
959impl_extract_ecs_query_system!(
960 P0 state0 owned0, P1 state1 owned1, P2 state2 owned2, P3 state3 owned3, P4 state4 owned4
961);
962impl_extract_ecs_query_system!(
963 P0 state0 owned0, P1 state1 owned1, P2 state2 owned2, P3 state3 owned3, P4 state4 owned4,
964 P5 state5 owned5
965);
966impl_extract_ecs_query_system!(
967 P0 state0 owned0, P1 state1 owned1, P2 state2 owned2, P3 state3 owned3, P4 state4 owned4,
968 P5 state5 owned5, P6 state6 owned6
969);
970
971pub struct MultiQuerySystemMarker<R, Q>(PhantomData<fn() -> (R, Q)>);
974
975macro_rules! impl_multi_query_bare {
976 ($($query:ident $qbind:ident),+) => {
977 impl<Func, $($query,)+>
978 IntoSystem<DynWorld, MultiQuerySystemMarker<(), ($($query,)+)>> for Func
979 where
980 $($query: MultiQueryParam,)+
981 Func: FnMut($($query,)+)
982 + for<'world> FnMut($($query::Item<'world>,)+)
983 + Send
984 + 'static,
985 {
986 #[allow(non_snake_case)]
987 fn into_runner(mut self) -> impl FnMut(&mut DynWorld) + Send + 'static {
988 move |world: &mut DynWorld| {
989 let cell = RefCell::new(world);
990 $(let $qbind = $query::build_lazy(&cell);)+
991 self($($qbind,)+);
992 }
993 }
994 }
995 };
996}
997
998macro_rules! impl_extract_multi_query {
999 (($($param:ident $state:ident $owned:ident),+), ($($query:ident $qbind:ident),+)) => {
1000 impl<Func, $($param,)+ $($query,)+>
1001 IntoSystem<DynWorld, MultiQuerySystemMarker<($($param,)+), ($($query,)+)>> for Func
1002 where
1003 $($param: ExtractParam<DynWorld>,)+
1004 $($query: MultiQueryParam,)+
1005 Func: FnMut($($param,)+ $($query,)+)
1006 + for<'item> FnMut($($param::Item<'item>,)+ $($query::Item<'item>,)+)
1007 + Send
1008 + 'static,
1009 {
1010 fn into_runner(mut self) -> impl FnMut(&mut DynWorld) + Send + 'static {
1011 $(let mut $state = <$param as ExtractParam<DynWorld>>::init();)+
1012 move |world: &mut DynWorld| {
1013 $(let mut $owned =
1014 <$param as ExtractParam<DynWorld>>::extract(&mut $state, world);)+
1015 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1016 let cell = RefCell::new(&mut *world);
1017 $(let $qbind = $query::build_lazy(&cell);)+
1018 self(
1019 $(<$param as ExtractParam<DynWorld>>::build(&mut $owned),)+
1020 $($qbind,)+
1021 );
1022 }));
1023 $(<$param as ExtractParam<DynWorld>>::apply(&mut $state, $owned, world);)+
1024 if let Err(panic) = result {
1025 std::panic::resume_unwind(panic);
1026 }
1027 }
1028 }
1029 }
1030 };
1031}
1032
1033impl_multi_query_bare!(Q0 q0, Q1 q1);
1034impl_multi_query_bare!(Q0 q0, Q1 q1, Q2 q2);
1035impl_multi_query_bare!(Q0 q0, Q1 q1, Q2 q2, Q3 q3);
1036
1037impl_extract_multi_query!((P0 state0 owned0), (Q0 q0, Q1 q1));
1038impl_extract_multi_query!((P0 state0 owned0), (Q0 q0, Q1 q1, Q2 q2));
1039impl_extract_multi_query!((P0 state0 owned0, P1 state1 owned1), (Q0 q0, Q1 q1));
1040impl_extract_multi_query!((P0 state0 owned0, P1 state1 owned1), (Q0 q0, Q1 q1, Q2 q2));
1041impl_extract_multi_query!((P0 state0 owned0, P1 state1 owned1, P2 state2 owned2), (Q0 q0, Q1 q1));
1042impl_extract_multi_query!(
1043 (P0 state0 owned0, P1 state1 owned1, P2 state2 owned2),
1044 (Q0 q0, Q1 q1, Q2 q2)
1045);
1046
1047#[cfg(test)]
1048mod tests {
1049 use super::*;
1050 use crate::Schedule;
1051 use crate::dynamic::{ComponentRegistry, DynEcs, DynWorld, ResourceMap};
1052
1053 struct Engine {
1054 resources: ResourceMap,
1055 frames: u32,
1056 }
1057
1058 impl ResourceHost for Engine {
1059 fn resource_map_mut(&mut self) -> &mut ResourceMap {
1060 &mut self.resources
1061 }
1062 fn resource_map(&self) -> &ResourceMap {
1063 &self.resources
1064 }
1065 }
1066
1067 #[derive(Default, Clone, Debug, PartialEq)]
1068 struct Position {
1069 x: f32,
1070 y: f32,
1071 }
1072
1073 #[derive(Default, Clone, Debug, PartialEq)]
1074 struct Velocity {
1075 x: f32,
1076 y: f32,
1077 }
1078
1079 #[derive(Default, Clone, Debug, PartialEq)]
1080 struct Health {
1081 value: f32,
1082 }
1083
1084 crate::impl_component!(Position, Velocity, Health);
1085
1086 struct DeltaTime(f32);
1087 struct Score(u32);
1088 struct Tally(u32);
1089 struct Seen(Vec<u32>);
1090 struct Enabled(bool);
1091 struct Frozen;
1092
1093 #[derive(Clone, Debug, PartialEq)]
1094 struct Collision {
1095 entity: u32,
1096 }
1097
1098 fn run<Marker>(world: &mut DynWorld, system: impl IntoSystem<DynWorld, Marker>) {
1099 let mut runner = system.into_runner();
1100 runner(world);
1101 }
1102
1103 #[test]
1104 fn single_resource_param() {
1105 let mut world = DynWorld::new();
1106 world.insert_resource(Score(0));
1107 run(&mut world, |mut score: ResMut<Score>| score.0 += 5);
1108 assert_eq!(world.resource::<Score>().unwrap().0, 5);
1109 }
1110
1111 #[test]
1112 fn multiple_resource_params() {
1113 let mut world = DynWorld::new();
1114 world.insert_resource(DeltaTime(2.0));
1115 world.insert_resource(Score(1));
1116 run(
1117 &mut world,
1118 |delta: Res<DeltaTime>, mut score: ResMut<Score>| {
1119 score.0 += delta.0 as u32;
1120 },
1121 );
1122 assert_eq!(world.resource::<Score>().unwrap().0, 3);
1123 }
1124
1125 #[test]
1126 fn query_only_param() {
1127 let mut world = DynWorld::new();
1128 world.spawn((Position { x: 1.0, y: 2.0 },));
1129 run(&mut world, |query: Query<&mut Position>| {
1130 query.for_each(|_entity, position| position.x += 10.0);
1131 });
1132 let position = world.query_ref::<&Position>().single().unwrap().1.clone();
1133 assert_eq!(position, Position { x: 11.0, y: 2.0 });
1134 }
1135
1136 #[test]
1137 fn resources_and_query_together() {
1138 let mut world = DynWorld::new();
1139 world.insert_resource(DeltaTime(0.5));
1140 world.insert_resource(Score(0));
1141 world.spawn((
1142 Position { x: 0.0, y: 0.0 },
1143 Velocity { x: 2.0, y: 4.0 },
1144 Health { value: 100.0 },
1145 ));
1146 world.spawn((Position { x: 5.0, y: 5.0 }, Velocity { x: 1.0, y: 1.0 }));
1147
1148 run(
1149 &mut world,
1150 |delta: Res<DeltaTime>,
1151 mut score: ResMut<Score>,
1152 query: Query<(&mut Position, &Velocity, Option<&mut Health>)>| {
1153 query.for_each(|_entity, (position, velocity, health)| {
1154 position.x += velocity.x * delta.0;
1155 position.y += velocity.y * delta.0;
1156 if let Some(health) = health {
1157 health.value *= 0.9;
1158 }
1159 });
1160 score.0 += 1;
1161 },
1162 );
1163
1164 assert_eq!(world.resource::<Score>().unwrap().0, 1);
1165 let mut positions: Vec<(f32, f32)> = world
1166 .query_ref::<&Position>()
1167 .iter()
1168 .map(|(_entity, position)| (position.x, position.y))
1169 .collect();
1170 positions.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
1171 assert_eq!(positions, vec![(1.0, 2.0), (5.5, 5.5)]);
1172 }
1173
1174 #[test]
1175 fn with_filter_narrows_match() {
1176 let mut world = DynWorld::new();
1177 let frozen = world.spawn((Position { x: 0.0, y: 0.0 },));
1178 world.add_tag_type::<Frozen>(frozen);
1179 world.spawn((Position { x: 0.0, y: 0.0 },));
1180
1181 run(&mut world, |query: Query<&mut Position, With<Health>>| {
1182 query.for_each(|_entity, position| position.x = 1.0);
1183 });
1184 assert!(
1185 world
1186 .query_ref::<&Position>()
1187 .iter()
1188 .all(|(_entity, position)| position.x == 0.0)
1189 );
1190
1191 run(
1192 &mut world,
1193 |query: Query<&mut Position, WithoutTag<Frozen>>| {
1194 query.for_each(|_entity, position| position.x = 2.0);
1195 },
1196 );
1197 let moved = world
1198 .query_ref::<&Position>()
1199 .iter()
1200 .filter(|(_entity, position)| position.x == 2.0)
1201 .count();
1202 assert_eq!(moved, 1);
1203 }
1204
1205 #[test]
1206 fn changed_filter_visits_only_mutated() {
1207 let mut world = DynWorld::new();
1208 world.set_change_detection(true);
1209 let entity = world.spawn((Position { x: 0.0, y: 0.0 },));
1210 world.spawn((Position { x: 0.0, y: 0.0 },));
1211 world.step();
1212 world.get_mut::<Position>(entity).unwrap().x = 1.0;
1213
1214 let mut visited = 0;
1215 run(&mut world, |query: Query<&Position, Changed<Position>>| {
1216 query.for_each(|_entity, _position| {});
1217 });
1218 world
1219 .query_ref::<&Position>()
1220 .changed::<Position>()
1221 .iter()
1222 .for_each(|_| visited += 1);
1223 assert_eq!(visited, 1);
1224 }
1225
1226 #[test]
1227 fn param_set_lends_conflicting_queries() {
1228 let mut world = DynWorld::new();
1229 world.spawn((Position { x: 1.0, y: 1.0 }, Velocity { x: 3.0, y: 3.0 }));
1230
1231 run(
1232 &mut world,
1233 |mut set: ParamSet<(Query<&mut Position>, Query<&Velocity>)>| {
1234 let mut velocity = (0.0, 0.0);
1235 set.p1()
1236 .for_each(|_entity, value| velocity = (value.x, value.y));
1237 set.p0().for_each(|_entity, position| {
1238 position.x += velocity.0;
1239 position.y += velocity.1;
1240 });
1241 },
1242 );
1243
1244 let position = world.query_ref::<&Position>().single().unwrap().1.clone();
1245 assert_eq!(position, Position { x: 4.0, y: 4.0 });
1246 }
1247
1248 #[test]
1249 fn two_direct_queries_disjoint_components() {
1250 let mut world = DynWorld::new();
1251 world.spawn((Position { x: 1.0, y: 1.0 }, Velocity { x: 3.0, y: 4.0 }));
1252
1253 run(
1254 &mut world,
1255 |positions: Query<&mut Position>, velocities: Query<&Velocity>| {
1256 let mut sample = (0.0, 0.0);
1257 velocities.for_each(|_entity, velocity| sample = (velocity.x, velocity.y));
1258 positions.for_each(|_entity, position| {
1259 position.x += sample.0;
1260 position.y += sample.1;
1261 });
1262 },
1263 );
1264
1265 let position = world.query_ref::<&Position>().single().unwrap().1.clone();
1266 assert_eq!(position, Position { x: 4.0, y: 5.0 });
1267 }
1268
1269 #[test]
1270 fn two_direct_queries_same_component_run_sequentially() {
1271 let mut world = DynWorld::new();
1272 world.spawn((Position { x: 0.0, y: 0.0 },));
1273
1274 run(
1275 &mut world,
1276 |first: Query<&mut Position>, second: Query<&mut Position>| {
1277 first.for_each(|_entity, position| position.x += 1.0);
1278 second.for_each(|_entity, position| position.x += 10.0);
1279 },
1280 );
1281
1282 assert_eq!(world.query_ref::<&Position>().single().unwrap().1.x, 11.0);
1283 }
1284
1285 #[test]
1286 fn resource_with_two_direct_queries() {
1287 let mut world = DynWorld::new();
1288 world.insert_resource(Score(0));
1289 world.spawn((Position { x: 0.0, y: 0.0 }, Velocity { x: 2.0, y: 0.0 }));
1290
1291 run(
1292 &mut world,
1293 |mut score: ResMut<Score>,
1294 positions: Query<&mut Position>,
1295 velocities: Query<&Velocity>| {
1296 let mut velocity_x = 0.0;
1297 velocities.for_each(|_entity, velocity| velocity_x = velocity.x);
1298 positions.for_each(|_entity, position| position.x += velocity_x);
1299 score.0 += 1;
1300 },
1301 );
1302
1303 assert_eq!(world.resource::<Score>().unwrap().0, 1);
1304 assert_eq!(world.query_ref::<&Position>().single().unwrap().1.x, 2.0);
1305 }
1306
1307 #[test]
1308 fn schedule_add_system_runs_param_systems() {
1309 let mut world = DynWorld::new();
1310 world.insert_resource(Score(0));
1311 world.spawn((Position { x: 0.0, y: 0.0 }, Velocity { x: 1.0, y: 0.0 }));
1312
1313 fn movement(query: Query<(&mut Position, &Velocity)>) {
1314 query.for_each(|_entity, (position, velocity)| position.x += velocity.x);
1315 }
1316 fn scoring(mut score: ResMut<Score>) {
1317 score.0 += 1;
1318 }
1319
1320 let mut schedule = Schedule::new();
1321 schedule.add_system("movement", movement);
1322 schedule.add_system("scoring", scoring);
1323 schedule.run(&mut world);
1324 schedule.run(&mut world);
1325
1326 assert_eq!(world.resource::<Score>().unwrap().0, 2);
1327 let position = world.query_ref::<&Position>().single().unwrap().1.clone();
1328 assert_eq!(position, Position { x: 2.0, y: 0.0 });
1329 }
1330
1331 #[test]
1332 fn add_systems_registers_a_tuple() {
1333 let mut world = DynWorld::new();
1334 world.insert_resource(Score(0));
1335 world.spawn((Position { x: 0.0, y: 0.0 }, Velocity { x: 1.0, y: 0.0 }));
1336
1337 fn movement(query: Query<(&mut Position, &Velocity)>) {
1338 query.for_each(|_entity, (position, velocity)| position.x += velocity.x);
1339 }
1340 fn scoring(mut score: ResMut<Score>) {
1341 score.0 += 1;
1342 }
1343
1344 let mut schedule = Schedule::new();
1345 schedule.add_systems((movement, scoring));
1346 assert_eq!(schedule.len(), 2);
1347 schedule.run(&mut world);
1348
1349 assert_eq!(world.resource::<Score>().unwrap().0, 1);
1350 assert_eq!(world.query_ref::<&Position>().single().unwrap().1.x, 1.0);
1351 }
1352
1353 #[test]
1354 fn add_systems_dedups_repeated_registration() {
1355 fn noop(_query: Query<&Position>) {}
1356
1357 let mut schedule = Schedule::<DynWorld>::new();
1358 schedule.add_systems((noop,));
1359 schedule.add_systems((noop,));
1360 assert_eq!(schedule.len(), 2);
1361 }
1362
1363 #[test]
1364 fn insert_resources_batches_a_tuple() {
1365 let mut world = DynWorld::new();
1366 world.insert_resources((DeltaTime(0.5), Score(7)));
1367 assert_eq!(world.resource::<DeltaTime>().unwrap().0, 0.5);
1368 assert_eq!(world.resource::<Score>().unwrap().0, 7);
1369 }
1370
1371 #[test]
1372 fn resource_system_runs_on_custom_host() {
1373 let mut engine = Engine {
1374 resources: ResourceMap::default(),
1375 frames: 0,
1376 };
1377 engine.resources.insert(Score(0));
1378
1379 let mut schedule = Schedule::<Engine>::new();
1380 schedule.add_system("bump", |mut score: ResMut<Score>| score.0 += 3);
1381 schedule.run(&mut engine);
1382
1383 assert_eq!(engine.resources.get::<Score>().unwrap().0, 3);
1384 }
1385
1386 #[test]
1387 fn host_system_gets_resources_and_the_host() {
1388 fn tick(mut score: ResMut<Score>, engine: &mut Engine) {
1389 score.0 += 1;
1390 engine.frames += 1;
1391 }
1392
1393 let mut engine = Engine {
1394 resources: ResourceMap::default(),
1395 frames: 0,
1396 };
1397 engine.resources.insert(Score(10));
1398
1399 let mut schedule = Schedule::<Engine>::new();
1400 schedule.add_system("tick", tick);
1401 schedule.run(&mut engine);
1402 schedule.run(&mut engine);
1403
1404 assert_eq!(engine.resources.get::<Score>().unwrap().0, 12);
1405 assert_eq!(engine.frames, 2);
1406 }
1407
1408 #[test]
1409 fn host_system_on_dynworld_can_query() {
1410 fn count_positions(mut score: ResMut<Score>, world: &mut DynWorld) {
1411 score.0 += world.query_ref::<&Position>().iter().count() as u32;
1412 }
1413
1414 let mut world = DynWorld::new();
1415 world.insert_resource(Score(0));
1416 world.spawn((Position { x: 0.0, y: 0.0 },));
1417 world.spawn((Position { x: 1.0, y: 1.0 },));
1418
1419 run(&mut world, count_positions);
1420
1421 assert_eq!(world.resource::<Score>().unwrap().0, 2);
1422 }
1423
1424 #[test]
1425 fn query_param_runs_over_a_group() {
1426 fn movement(query: Query<(&mut Position, &Velocity)>) {
1427 query.for_each(|_entity, (position, velocity)| position.x += velocity.x);
1428 }
1429
1430 let mut registry = ComponentRegistry::new();
1431 registry.register::<Position>();
1432 registry.register::<Velocity>();
1433 let mut ecs = DynEcs::new();
1434 ecs.add_world(registry);
1435 let entity = ecs.spawn_with((Position { x: 1.0, y: 0.0 }, Velocity { x: 3.0, y: 0.0 }));
1436
1437 let mut schedule = Schedule::<DynEcs>::new();
1438 schedule.add_system("movement", movement);
1439 schedule.run(&mut ecs);
1440
1441 assert_eq!(ecs.get::<Position>(entity).unwrap().x, 4.0);
1442 }
1443
1444 #[test]
1445 fn resource_and_query_over_a_group() {
1446 fn tick(mut score: ResMut<Score>, query: Query<(&mut Position, &Velocity)>) {
1447 query.for_each(|_entity, (position, velocity)| position.x += velocity.x);
1448 score.0 += 1;
1449 }
1450
1451 let mut registry = ComponentRegistry::new();
1452 registry.register::<Position>();
1453 registry.register::<Velocity>();
1454 let mut ecs = DynEcs::new();
1455 ecs.add_world(registry);
1456 ecs.insert_resource(Score(0));
1457 let entity = ecs.spawn_with((Position { x: 0.0, y: 0.0 }, Velocity { x: 5.0, y: 0.0 }));
1458
1459 let mut schedule = Schedule::<DynEcs>::new();
1460 schedule.add_system("tick", tick);
1461 schedule.run(&mut ecs);
1462
1463 assert_eq!(ecs.resource::<Score>().unwrap().0, 1);
1464 assert_eq!(ecs.get::<Position>(entity).unwrap().x, 5.0);
1465 }
1466
1467 #[test]
1468 fn added_filter_visits_only_new() {
1469 let mut world = DynWorld::new();
1470 world.set_change_detection(true);
1471 world.spawn((Position { x: 0.0, y: 0.0 },));
1472 world.step();
1473 world.spawn((Position { x: 9.0, y: 9.0 },));
1474
1475 run(
1476 &mut world,
1477 |query: Query<&mut Position, Added<Position>>| {
1478 query.for_each(|_entity, position| position.y = 100.0);
1479 },
1480 );
1481 let marked = world
1482 .query_ref::<&Position>()
1483 .iter()
1484 .filter(|(_entity, position)| position.y == 100.0)
1485 .map(|(_entity, position)| position.x)
1486 .collect::<Vec<_>>();
1487 assert_eq!(marked, vec![9.0]);
1488 }
1489
1490 #[test]
1491 fn tuple_filter_combines_constraints() {
1492 let mut world = DynWorld::new();
1493 world.spawn((Position { x: 0.0, y: 0.0 }, Health { value: 1.0 }));
1494 world.spawn((
1495 Position { x: 0.0, y: 0.0 },
1496 Health { value: 1.0 },
1497 Velocity { x: 0.0, y: 0.0 },
1498 ));
1499
1500 run(
1501 &mut world,
1502 |query: Query<&mut Position, (With<Health>, Without<Velocity>)>| {
1503 query.for_each(|_entity, position| position.x = 1.0);
1504 },
1505 );
1506 let moved = world
1507 .query_ref::<&Position>()
1508 .iter()
1509 .filter(|(_entity, position)| position.x == 1.0)
1510 .count();
1511 assert_eq!(moved, 1);
1512 }
1513
1514 #[test]
1515 fn par_for_each_runs_over_query() {
1516 let mut world = DynWorld::new();
1517 for index in 0..64 {
1518 world.spawn((
1519 Position {
1520 x: index as f32,
1521 y: 0.0,
1522 },
1523 Velocity { x: 1.0, y: 0.0 },
1524 ));
1525 }
1526 run(&mut world, |query: Query<(&mut Position, &Velocity)>| {
1527 query.par_for_each(|_entity, (position, velocity)| position.x += velocity.x);
1528 });
1529 let total: f32 = world
1530 .query_ref::<&Position>()
1531 .iter()
1532 .map(|(_entity, position)| position.x)
1533 .sum();
1534 assert_eq!(total, (0..64).map(|index| index as f32 + 1.0).sum());
1535 }
1536
1537 #[test]
1538 #[should_panic(expected = "DeltaTime")]
1539 fn missing_resource_panics() {
1540 let mut world = DynWorld::new();
1541 run(&mut world, |_delta: Res<DeltaTime>| {});
1542 }
1543
1544 fn emit_pair(mut writer: EventWriter<Collision>) {
1545 writer.send(Collision { entity: 1 });
1546 writer.send(Collision { entity: 2 });
1547 }
1548
1549 #[test]
1550 fn writer_flushes_after_run_and_reader_consumes_once() {
1551 let mut world = DynWorld::new();
1552 world.insert_resource(Score(0));
1553
1554 fn tally(reader: EventReader<Collision>, mut score: ResMut<Score>) {
1555 score.0 += reader.len() as u32;
1556 }
1557
1558 let mut schedule = Schedule::new();
1559 schedule.add_system("emit", emit_pair);
1560 schedule.add_system("tally", tally);
1561
1562 schedule.run(&mut world);
1563 assert_eq!(world.resource::<Score>().unwrap().0, 2);
1564
1565 world.step();
1566 schedule.run(&mut world);
1567 assert_eq!(
1568 world.resource::<Score>().unwrap().0,
1569 4,
1570 "the reader consumes only the events sent this frame"
1571 );
1572 }
1573
1574 #[test]
1575 fn reader_coexists_with_a_query() {
1576 let mut world = DynWorld::new();
1577 world.events.send::<Collision>(Collision { entity: 0 });
1578 world.spawn((Position { x: 0.0, y: 0.0 },));
1579
1580 fn shift(reader: EventReader<Collision>, query: Query<&mut Position>) {
1581 let delta = reader.len() as f32;
1582 query.for_each(|_entity, position| position.x += delta);
1583 }
1584
1585 let mut schedule = Schedule::new();
1586 schedule.add_system("shift", shift);
1587 schedule.run(&mut world);
1588
1589 assert_eq!(world.query_ref::<&Position>().single().unwrap().1.x, 1.0);
1590 }
1591
1592 #[test]
1593 fn resource_reader_and_query_in_one_system() {
1594 let mut world = DynWorld::new();
1595 world.insert_resource(DeltaTime(2.0));
1596 world.events.send::<Collision>(Collision { entity: 7 });
1597 world.spawn((Position { x: 0.0, y: 0.0 }, Velocity { x: 1.0, y: 0.0 }));
1598
1599 fn integrate(
1600 delta: Res<DeltaTime>,
1601 reader: EventReader<Collision>,
1602 query: Query<(&mut Position, &Velocity)>,
1603 ) {
1604 let bump = reader.len() as f32;
1605 query.for_each(|_entity, (position, velocity)| {
1606 position.x += velocity.x * delta.0 + bump;
1607 });
1608 }
1609
1610 let mut schedule = Schedule::new();
1611 schedule.add_system("integrate", integrate);
1612 schedule.run(&mut world);
1613
1614 assert_eq!(world.query_ref::<&Position>().single().unwrap().1.x, 3.0);
1615 }
1616
1617 #[test]
1618 fn writer_coexists_with_a_query() {
1619 let mut world = DynWorld::new();
1620 world.spawn((Position { x: 3.0, y: 0.0 },));
1621 world.spawn((Position { x: 5.0, y: 0.0 },));
1622
1623 fn report(mut writer: EventWriter<Collision>, query: Query<&Position>) {
1624 query.for_each(|_entity, position| {
1625 writer.send(Collision {
1626 entity: position.x as u32,
1627 });
1628 });
1629 }
1630
1631 let mut schedule = Schedule::new();
1632 schedule.add_system("report", report);
1633 schedule.run(&mut world);
1634
1635 let mut ids: Vec<u32> = world
1636 .events
1637 .read::<Collision>()
1638 .iter()
1639 .map(|collision| collision.entity)
1640 .collect();
1641 ids.sort();
1642 assert_eq!(ids, vec![3, 5]);
1643 }
1644
1645 #[test]
1646 fn two_readers_keep_independent_cursors() {
1647 let mut world = DynWorld::new();
1648 world.insert_resource(Score(0));
1649 world.insert_resource(Tally(0));
1650 world.events.send::<Collision>(Collision { entity: 1 });
1651 world.events.send::<Collision>(Collision { entity: 2 });
1652 world.events.send::<Collision>(Collision { entity: 3 });
1653
1654 fn reader_a(reader: EventReader<Collision>, mut score: ResMut<Score>) {
1655 score.0 += reader.len() as u32;
1656 }
1657 fn reader_b(reader: EventReader<Collision>, mut tally: ResMut<Tally>) {
1658 tally.0 += reader.len() as u32;
1659 }
1660
1661 let mut schedule = Schedule::new();
1662 schedule.add_system("reader_a", reader_a);
1663 schedule.add_system("reader_b", reader_b);
1664 schedule.run(&mut world);
1665
1666 assert_eq!(world.resource::<Score>().unwrap().0, 3);
1667 assert_eq!(world.resource::<Tally>().unwrap().0, 3);
1668 }
1669
1670 #[test]
1671 fn reader_iterates_each_event_once() {
1672 let mut world = DynWorld::new();
1673 world.insert_resource(Seen(Vec::new()));
1674 world.events.send::<Collision>(Collision { entity: 10 });
1675 world.events.send::<Collision>(Collision { entity: 20 });
1676
1677 fn collect(reader: EventReader<Collision>, mut seen: ResMut<Seen>) {
1678 for collision in &reader {
1679 seen.0.push(collision.entity);
1680 }
1681 }
1682
1683 let mut schedule = Schedule::new();
1684 schedule.add_system("collect", collect);
1685 schedule.run(&mut world);
1686
1687 assert_eq!(world.resource::<Seen>().unwrap().0, vec![10, 20]);
1688 }
1689
1690 #[test]
1691 fn reader_is_empty_before_any_send() {
1692 let mut world = DynWorld::new();
1693 world.insert_resource(Score(0));
1694
1695 fn tally(reader: EventReader<Collision>, mut score: ResMut<Score>) {
1696 assert!(reader.is_empty());
1697 score.0 += reader.len() as u32;
1698 }
1699
1700 let mut schedule = Schedule::new();
1701 schedule.add_system("tally", tally);
1702 schedule.run(&mut world);
1703
1704 assert_eq!(world.resource::<Score>().unwrap().0, 0);
1705 }
1706
1707 #[test]
1708 fn events_flow_over_a_group() {
1709 let mut ecs = DynEcs::new();
1710 ecs.insert_resource(Score(0));
1711
1712 fn tally(reader: EventReader<Collision>, mut score: ResMut<Score>) {
1713 score.0 += reader.len() as u32;
1714 }
1715
1716 let mut schedule = Schedule::<DynEcs>::new();
1717 schedule.add_system("emit", emit_pair);
1718 schedule.add_system("tally", tally);
1719 schedule.run(&mut ecs);
1720
1721 assert_eq!(ecs.resource::<Score>().unwrap().0, 2);
1722 }
1723
1724 #[test]
1725 fn add_system_if_gates_a_param_system() {
1726 let mut world = DynWorld::new();
1727 world.insert_resource(Score(0));
1728 world.insert_resource(Enabled(false));
1729
1730 fn bump(mut score: ResMut<Score>) {
1731 score.0 += 1;
1732 }
1733
1734 let mut schedule = Schedule::new();
1735 schedule.add_system_if(
1736 "bump",
1737 |world: &DynWorld| world.resource::<Enabled>().unwrap().0,
1738 bump,
1739 );
1740
1741 schedule.run(&mut world);
1742 assert_eq!(
1743 world.resource::<Score>().unwrap().0,
1744 0,
1745 "the system is skipped while the condition is false"
1746 );
1747
1748 world.resource_mut::<Enabled>().unwrap().0 = true;
1749 schedule.run(&mut world);
1750 assert_eq!(
1751 world.resource::<Score>().unwrap().0,
1752 1,
1753 "the system runs once the condition holds"
1754 );
1755 }
1756}