1use std::marker::PhantomData;
52
53use crate::Handler;
54use crate::handler::Param;
55use crate::world::{Registry, World};
56
57pub trait Blueprint {
84 type Event;
86 type Params: Param + 'static;
88}
89
90pub trait CallbackBlueprint: Blueprint {
95 type Context: Send + 'static;
97}
98
99#[doc(hidden)]
109pub trait TemplateDispatch<P: Param, E> {
110 fn run_fn_ptr() -> unsafe fn(&mut P::State, &mut World, E);
112 fn validate(state: &P::State, registry: &Registry);
114}
115
116#[doc(hidden)]
118pub trait CallbackTemplateDispatch<C, P: Param, E> {
119 fn run_fn_ptr() -> unsafe fn(&mut C, &mut P::State, &mut World, E);
121 fn validate(state: &P::State, registry: &Registry);
123}
124
125impl<E, F: FnMut(E) + Send + 'static> TemplateDispatch<(), E> for F {
130 fn run_fn_ptr() -> unsafe fn(&mut (), &mut World, E) {
131 unsafe fn run<E, F: FnMut(E) + Send>(_state: &mut (), _world: &mut World, event: E) {
136 let mut f: F = unsafe { std::mem::zeroed() };
139 f(event);
140 }
141 run::<E, F>
142 }
143
144 fn validate(_state: &(), _registry: &Registry) {}
145}
146
147impl<C: Send + 'static, E, F: FnMut(&mut C, E) + Send + 'static> CallbackTemplateDispatch<C, (), E>
148 for F
149{
150 fn run_fn_ptr() -> unsafe fn(&mut C, &mut (), &mut World, E) {
151 unsafe fn run<C: Send, E, F: FnMut(&mut C, E) + Send>(
152 ctx: &mut C,
153 _state: &mut (),
154 _world: &mut World,
155 event: E,
156 ) {
157 let mut f: F = unsafe { std::mem::zeroed() };
159 f(ctx, event);
160 }
161 run::<C, E, F>
162 }
163
164 fn validate(_state: &(), _registry: &Registry) {}
165}
166
167macro_rules! impl_template_dispatch {
172 ($($P:ident),+) => {
173 impl<E, F: Send + 'static, $($P: Param + 'static),+> TemplateDispatch<($($P,)+), E> for F
174 where
175 for<'a> &'a mut F: FnMut($($P,)+ E) + FnMut($($P::Item<'a>,)+ E),
176 {
177 fn run_fn_ptr() -> unsafe fn(&mut ($($P::State,)+), &mut World, E) {
178 #[allow(non_snake_case)]
185 unsafe fn run<E, F: Send + 'static, $($P: Param + 'static),+>(
186 state: &mut ($($P::State,)+),
187 world: &mut World,
188 event: E,
189 ) where
190 for<'a> &'a mut F: FnMut($($P,)+ E) + FnMut($($P::Item<'a>,)+ E),
191 {
192 #[allow(clippy::too_many_arguments)]
193 fn call_inner<$($P,)+ Ev>(
194 mut f: impl FnMut($($P,)+ Ev),
195 $($P: $P,)+
196 event: Ev,
197 ) {
198 f($($P,)+ event);
199 }
200
201 #[cfg(debug_assertions)]
204 world.clear_borrows();
205 let ($($P,)+) = unsafe {
206 <($($P,)+) as Param>::fetch(world, state)
207 };
208 let mut f: F = unsafe { std::mem::zeroed() };
210 call_inner(&mut f, $($P,)+ event);
211 }
212 run::<E, F, $($P),+>
213 }
214
215 #[allow(non_snake_case)]
216 fn validate(state: &($($P::State,)+), registry: &Registry) {
217 let ($($P,)+) = state;
218 registry.check_access(&[
219 $((<$P as Param>::resource_id($P), std::any::type_name::<$P>()),)+
220 ]);
221 }
222 }
223
224 impl<C: Send + 'static, E, F: Send + 'static, $($P: Param + 'static),+>
225 CallbackTemplateDispatch<C, ($($P,)+), E> for F
226 where
227 for<'a> &'a mut F:
228 FnMut(&mut C, $($P,)+ E) +
229 FnMut(&mut C, $($P::Item<'a>,)+ E),
230 {
231 fn run_fn_ptr() -> unsafe fn(&mut C, &mut ($($P::State,)+), &mut World, E) {
232 #[allow(non_snake_case)]
233 unsafe fn run<C: Send, E, F: Send + 'static, $($P: Param + 'static),+>(
234 ctx: &mut C,
235 state: &mut ($($P::State,)+),
236 world: &mut World,
237 event: E,
238 ) where
239 for<'a> &'a mut F:
240 FnMut(&mut C, $($P,)+ E) +
241 FnMut(&mut C, $($P::Item<'a>,)+ E),
242 {
243 #[allow(clippy::too_many_arguments)]
244 fn call_inner<Ctx, $($P,)+ Ev>(
245 mut f: impl FnMut(&mut Ctx, $($P,)+ Ev),
246 ctx: &mut Ctx,
247 $($P: $P,)+
248 event: Ev,
249 ) {
250 f(ctx, $($P,)+ event);
251 }
252
253 #[cfg(debug_assertions)]
254 world.clear_borrows();
255 let ($($P,)+) = unsafe {
256 <($($P,)+) as Param>::fetch(world, state)
257 };
258 let mut f: F = unsafe { std::mem::zeroed() };
259 call_inner(&mut f, ctx, $($P,)+ event);
260 }
261 run::<C, E, F, $($P),+>
262 }
263
264 #[allow(non_snake_case)]
265 fn validate(state: &($($P::State,)+), registry: &Registry) {
266 let ($($P,)+) = state;
267 registry.check_access(&[
268 $((<$P as Param>::resource_id($P), std::any::type_name::<$P>()),)+
269 ]);
270 }
271 }
272 };
273}
274
275macro_rules! all_tuples {
276 ($m:ident) => {
277 $m!(P0);
278 $m!(P0, P1);
279 $m!(P0, P1, P2);
280 $m!(P0, P1, P2, P3);
281 $m!(P0, P1, P2, P3, P4);
282 $m!(P0, P1, P2, P3, P4, P5);
283 $m!(P0, P1, P2, P3, P4, P5, P6);
284 $m!(P0, P1, P2, P3, P4, P5, P6, P7);
285 };
286}
287
288all_tuples!(impl_template_dispatch);
289
290pub struct HandlerTemplate<K: Blueprint>
328where
329 <K::Params as Param>::State: Copy,
330{
331 prototype: <K::Params as Param>::State,
332 run_fn: unsafe fn(&mut <K::Params as Param>::State, &mut World, K::Event),
333 name: &'static str,
334 _key: PhantomData<fn() -> K>,
336}
337
338impl<K: Blueprint> HandlerTemplate<K>
339where
340 <K::Params as Param>::State: Copy,
341{
342 #[allow(clippy::needless_pass_by_value)]
353 pub fn new<F>(f: F, registry: &Registry) -> Self
354 where
355 F: TemplateDispatch<K::Params, K::Event>,
356 {
357 const {
358 assert!(
359 std::mem::size_of::<F>() == 0,
360 "F must be a ZST (named function item, not a closure or fn pointer)"
361 );
362 }
363 let _ = f;
364 let prototype = K::Params::init(registry);
365 F::validate(&prototype, registry);
366 Self {
367 prototype,
368 run_fn: F::run_fn_ptr(),
369 name: std::any::type_name::<F>(),
370 _key: PhantomData,
371 }
372 }
373
374 pub fn generate(&self) -> TemplatedHandler<K> {
376 TemplatedHandler {
377 state: self.prototype,
378 run_fn: self.run_fn,
379 name: self.name,
380 _key: PhantomData,
381 }
382 }
383}
384
385pub struct TemplatedHandler<K: Blueprint>
394where
395 <K::Params as Param>::State: Copy,
396{
397 state: <K::Params as Param>::State,
398 run_fn: unsafe fn(&mut <K::Params as Param>::State, &mut World, K::Event),
399 name: &'static str,
400 _key: PhantomData<fn() -> K>,
401}
402
403impl<K: Blueprint> Handler<K::Event> for TemplatedHandler<K>
404where
405 <K::Params as Param>::State: Copy,
406{
407 fn run(&mut self, world: &mut World, event: K::Event) {
408 unsafe { (self.run_fn)(&mut self.state, world, event) }
412 }
413
414 fn name(&self) -> &'static str {
415 self.name
416 }
417}
418
419type CallbackRunFn<K> = unsafe fn(
425 &mut <K as CallbackBlueprint>::Context,
426 &mut <<K as Blueprint>::Params as Param>::State,
427 &mut World,
428 <K as Blueprint>::Event,
429);
430
431pub struct CallbackTemplate<K: CallbackBlueprint>
470where
471 <K::Params as Param>::State: Copy,
472{
473 prototype: <K::Params as Param>::State,
474 run_fn: CallbackRunFn<K>,
475 name: &'static str,
476 _key: PhantomData<fn() -> K>,
477}
478
479impl<K: CallbackBlueprint> CallbackTemplate<K>
480where
481 <K::Params as Param>::State: Copy,
482{
483 #[allow(clippy::needless_pass_by_value)]
493 pub fn new<F>(f: F, registry: &Registry) -> Self
494 where
495 F: CallbackTemplateDispatch<K::Context, K::Params, K::Event>,
496 {
497 const {
498 assert!(
499 std::mem::size_of::<F>() == 0,
500 "F must be a ZST (named function item, not a closure or fn pointer)"
501 );
502 }
503 let _ = f;
504 let prototype = K::Params::init(registry);
505 F::validate(&prototype, registry);
506 Self {
507 prototype,
508 run_fn: F::run_fn_ptr(),
509 name: std::any::type_name::<F>(),
510 _key: PhantomData,
511 }
512 }
513
514 pub fn generate(&self, ctx: K::Context) -> TemplatedCallback<K> {
516 TemplatedCallback {
517 ctx,
518 state: self.prototype,
519 run_fn: self.run_fn,
520 name: self.name,
521 _key: PhantomData,
522 }
523 }
524}
525
526pub struct TemplatedCallback<K: CallbackBlueprint>
534where
535 <K::Params as Param>::State: Copy,
536{
537 ctx: K::Context,
538 state: <K::Params as Param>::State,
539 run_fn: CallbackRunFn<K>,
540 name: &'static str,
541 _key: PhantomData<fn() -> K>,
542}
543
544impl<K: CallbackBlueprint> TemplatedCallback<K>
545where
546 <K::Params as Param>::State: Copy,
547{
548 pub fn ctx(&self) -> &K::Context {
550 &self.ctx
551 }
552
553 pub fn ctx_mut(&mut self) -> &mut K::Context {
555 &mut self.ctx
556 }
557}
558
559impl<K: CallbackBlueprint> Handler<K::Event> for TemplatedCallback<K>
560where
561 <K::Params as Param>::State: Copy,
562{
563 fn run(&mut self, world: &mut World, event: K::Event) {
564 unsafe { (self.run_fn)(&mut self.ctx, &mut self.state, world, event) }
567 }
568
569 fn name(&self) -> &'static str {
570 self.name
571 }
572}
573
574#[macro_export]
590macro_rules! handler_blueprint {
591 ($name:ident, Event = $event:ty, Params = $params:ty) => {
592 struct $name;
593 impl $crate::template::Blueprint for $name {
594 type Event = $event;
595 type Params = $params;
596 }
597 };
598}
599
600#[macro_export]
608macro_rules! callback_blueprint {
609 ($name:ident, Context = $ctx:ty, Event = $event:ty, Params = $params:ty) => {
610 struct $name;
611 impl $crate::template::Blueprint for $name {
612 type Event = $event;
613 type Params = $params;
614 }
615 impl $crate::template::CallbackBlueprint for $name {
616 type Context = $ctx;
617 }
618 };
619}
620
621#[cfg(test)]
626mod tests {
627 use super::*;
628 use crate::{Res, ResMut, WorldBuilder};
629
630 struct OnTick;
633 impl Blueprint for OnTick {
634 type Event = u32;
635 type Params = (ResMut<'static, u64>,);
636 }
637
638 struct EventOnly;
639 impl Blueprint for EventOnly {
640 type Event = u32;
641 type Params = ();
642 }
643
644 struct TwoParams;
645 impl Blueprint for TwoParams {
646 type Event = ();
647 type Params = (Res<'static, u64>, ResMut<'static, bool>);
648 }
649
650 fn tick(mut counter: ResMut<u64>, event: u32) {
653 *counter += event as u64;
654 }
655
656 #[test]
657 fn handler_template_basic() {
658 let mut builder = WorldBuilder::new();
659 builder.register::<u64>(0);
660 let mut world = builder.build();
661
662 let template = HandlerTemplate::<OnTick>::new(tick, world.registry());
663 let mut h = template.generate();
664 h.run(&mut world, 10);
665 assert_eq!(*world.resource::<u64>(), 10);
666 }
667
668 #[test]
669 fn handler_template_stamps_independent() {
670 let mut builder = WorldBuilder::new();
671 builder.register::<u64>(0);
672 let mut world = builder.build();
673
674 let template = HandlerTemplate::<OnTick>::new(tick, world.registry());
675 let mut h1 = template.generate();
676 let mut h2 = template.generate();
677
678 h1.run(&mut world, 10);
679 h2.run(&mut world, 5);
680 assert_eq!(*world.resource::<u64>(), 15);
681 }
682
683 fn event_only_fn(event: u32) {
684 assert!(event > 0);
685 }
686
687 #[test]
688 fn handler_template_event_only() {
689 let mut world = WorldBuilder::new().build();
690 let template = HandlerTemplate::<EventOnly>::new(event_only_fn, world.registry());
691 let mut h = template.generate();
692 h.run(&mut world, 42);
693 }
694
695 fn two_params_fn(counter: Res<u64>, mut flag: ResMut<bool>, _event: ()) {
696 if *counter > 0 {
697 *flag = true;
698 }
699 }
700
701 #[test]
702 fn handler_template_two_params() {
703 let mut builder = WorldBuilder::new();
704 builder.register::<u64>(1);
705 builder.register::<bool>(false);
706 let mut world = builder.build();
707
708 let template = HandlerTemplate::<TwoParams>::new(two_params_fn, world.registry());
709 let mut h = template.generate();
710 h.run(&mut world, ());
711 assert!(*world.resource::<bool>());
712 }
713
714 #[test]
717 fn templated_handler_boxable() {
718 let mut builder = WorldBuilder::new();
719 builder.register::<u64>(0);
720 let mut world = builder.build();
721
722 let template = HandlerTemplate::<OnTick>::new(tick, world.registry());
723 let h = template.generate();
724 let mut boxed: Box<dyn Handler<u32>> = Box::new(h);
725 boxed.run(&mut world, 7);
726 assert_eq!(*world.resource::<u64>(), 7);
727 }
728
729 #[test]
732 fn templated_handler_name() {
733 let mut builder = WorldBuilder::new();
734 builder.register::<u64>(0);
735 let world = builder.build();
736
737 let template = HandlerTemplate::<OnTick>::new(tick, world.registry());
738 let h = template.generate();
739 assert!(h.name().contains("tick"));
740 }
741
742 #[test]
745 #[should_panic(expected = "not registered")]
746 fn handler_template_missing_resource() {
747 let world = WorldBuilder::new().build();
748 let _template = HandlerTemplate::<OnTick>::new(tick, world.registry());
749 }
750
751 #[test]
752 #[should_panic(expected = "conflicting access")]
753 fn handler_template_duplicate_access() {
754 struct BadBlueprint;
755 impl Blueprint for BadBlueprint {
756 type Event = ();
757 type Params = (Res<'static, u64>, ResMut<'static, u64>);
758 }
759
760 fn bad(a: Res<u64>, b: ResMut<u64>, _e: ()) {
761 let _ = (*a, &*b);
762 }
763
764 let mut builder = WorldBuilder::new();
765 builder.register::<u64>(0);
766 let world = builder.build();
767 let _template = HandlerTemplate::<BadBlueprint>::new(bad, world.registry());
768 }
769
770 struct TimerCtx {
773 order_id: u64,
774 fires: u64,
775 }
776
777 struct OnTimeout;
778 impl Blueprint for OnTimeout {
779 type Event = ();
780 type Params = (ResMut<'static, u64>,);
781 }
782 impl CallbackBlueprint for OnTimeout {
783 type Context = TimerCtx;
784 }
785
786 fn on_timeout(ctx: &mut TimerCtx, mut counter: ResMut<u64>, _event: ()) {
787 ctx.fires += 1;
788 *counter += ctx.order_id;
789 }
790
791 #[test]
792 fn callback_template_basic() {
793 let mut builder = WorldBuilder::new();
794 builder.register::<u64>(0);
795 let mut world = builder.build();
796
797 let template = CallbackTemplate::<OnTimeout>::new(on_timeout, world.registry());
798 let mut cb = template.generate(TimerCtx {
799 order_id: 42,
800 fires: 0,
801 });
802 cb.run(&mut world, ());
803 assert_eq!(cb.ctx().fires, 1);
804 assert_eq!(*world.resource::<u64>(), 42);
805 }
806
807 #[test]
808 fn callback_template_independent_contexts() {
809 let mut builder = WorldBuilder::new();
810 builder.register::<u64>(0);
811 let mut world = builder.build();
812
813 let template = CallbackTemplate::<OnTimeout>::new(on_timeout, world.registry());
814 let mut cb1 = template.generate(TimerCtx {
815 order_id: 10,
816 fires: 0,
817 });
818 let mut cb2 = template.generate(TimerCtx {
819 order_id: 20,
820 fires: 0,
821 });
822
823 cb1.run(&mut world, ());
824 cb2.run(&mut world, ());
825 assert_eq!(cb1.ctx().fires, 1);
826 assert_eq!(cb2.ctx().fires, 1);
827 assert_eq!(*world.resource::<u64>(), 30);
828 }
829
830 struct CtxOnlyKey;
831 impl Blueprint for CtxOnlyKey {
832 type Event = u32;
833 type Params = ();
834 }
835 impl CallbackBlueprint for CtxOnlyKey {
836 type Context = u64;
837 }
838
839 fn ctx_only(ctx: &mut u64, event: u32) {
840 *ctx += event as u64;
841 }
842
843 #[test]
844 fn callback_template_event_only() {
845 let mut world = WorldBuilder::new().build();
846 let template = CallbackTemplate::<CtxOnlyKey>::new(ctx_only, world.registry());
847 let mut cb = template.generate(0u64);
848 cb.run(&mut world, 5);
849 assert_eq!(*cb.ctx(), 5);
850 }
851
852 #[test]
853 fn callback_template_boxable() {
854 let mut builder = WorldBuilder::new();
855 builder.register::<u64>(0);
856 let mut world = builder.build();
857
858 let template = CallbackTemplate::<OnTimeout>::new(on_timeout, world.registry());
859 let cb = template.generate(TimerCtx {
860 order_id: 7,
861 fires: 0,
862 });
863 let mut boxed: Box<dyn Handler<()>> = Box::new(cb);
864 boxed.run(&mut world, ());
865 assert_eq!(*world.resource::<u64>(), 7);
866 }
867
868 #[test]
869 fn callback_template_ctx_accessible() {
870 let mut builder = WorldBuilder::new();
871 builder.register::<u64>(0);
872 let mut world = builder.build();
873
874 let template = CallbackTemplate::<OnTimeout>::new(on_timeout, world.registry());
875 let mut cb = template.generate(TimerCtx {
876 order_id: 42,
877 fires: 0,
878 });
879 assert_eq!(cb.ctx().order_id, 42);
880 cb.run(&mut world, ());
881 assert_eq!(cb.ctx().fires, 1);
882 cb.ctx_mut().order_id = 99;
883 cb.run(&mut world, ());
884 assert_eq!(*world.resource::<u64>(), 42 + 99);
885 }
886
887 handler_blueprint!(MacroOnTick, Event = u32, Params = (ResMut<'static, u64>,));
890
891 #[test]
892 fn macro_handler_blueprint() {
893 let mut builder = WorldBuilder::new();
894 builder.register::<u64>(0);
895 let mut world = builder.build();
896
897 let template = HandlerTemplate::<MacroOnTick>::new(tick, world.registry());
898 let mut h = template.generate();
899 h.run(&mut world, 3);
900 assert_eq!(*world.resource::<u64>(), 3);
901 }
902
903 callback_blueprint!(MacroOnTimeout, Context = TimerCtx, Event = (), Params = (ResMut<'static, u64>,));
904
905 #[test]
906 fn macro_callback_blueprint() {
907 let mut builder = WorldBuilder::new();
908 builder.register::<u64>(0);
909 let mut world = builder.build();
910
911 let template = CallbackTemplate::<MacroOnTimeout>::new(on_timeout, world.registry());
912 let mut cb = template.generate(TimerCtx {
913 order_id: 5,
914 fires: 0,
915 });
916 cb.run(&mut world, ());
917 assert_eq!(cb.ctx().fires, 1);
918 assert_eq!(*world.resource::<u64>(), 5);
919 }
920}