1use std::marker::PhantomData;
63
64use crate::Handler;
65use crate::handler::Param;
66use crate::world::{Registry, World};
67
68pub trait Blueprint {
98 type Event;
100 type Params: Param + 'static;
102}
103
104pub trait CallbackBlueprint: Blueprint {
109 type Context: Send + 'static;
111}
112
113#[doc(hidden)]
123#[diagnostic::on_unimplemented(
124 message = "function signature doesn't match the blueprint's Event and Params types",
125 note = "the function must accept the blueprint's Params then Event, in that order"
126)]
127pub trait TemplateDispatch<P: Param, E> {
128 fn run_fn_ptr() -> unsafe fn(&mut P::State, &mut World, E);
130 fn validate(state: &P::State, registry: &Registry);
132}
133
134#[doc(hidden)]
136#[diagnostic::on_unimplemented(
137 message = "function signature doesn't match the callback blueprint's Context, Event, and Params types",
138 note = "the function must accept &mut Context first, then Params, then Event"
139)]
140pub trait CallbackTemplateDispatch<C, P: Param, E> {
141 fn run_fn_ptr() -> unsafe fn(&mut C, &mut P::State, &mut World, E);
143 fn validate(state: &P::State, registry: &Registry);
145}
146
147impl<E, F: FnMut(E) + Send + 'static> TemplateDispatch<(), E> for F {
152 fn run_fn_ptr() -> unsafe fn(&mut (), &mut World, E) {
153 unsafe fn run<E, F: FnMut(E) + Send>(_state: &mut (), _world: &mut World, event: E) {
158 let mut f: F = unsafe { std::mem::zeroed() };
161 f(event);
162 }
163 run::<E, F>
164 }
165
166 fn validate(_state: &(), _registry: &Registry) {}
167}
168
169impl<C: Send + 'static, E, F: FnMut(&mut C, E) + Send + 'static> CallbackTemplateDispatch<C, (), E>
170 for F
171{
172 fn run_fn_ptr() -> unsafe fn(&mut C, &mut (), &mut World, E) {
173 unsafe fn run<C: Send, E, F: FnMut(&mut C, E) + Send>(
176 ctx: &mut C,
177 _state: &mut (),
178 _world: &mut World,
179 event: E,
180 ) {
181 let mut f: F = unsafe { std::mem::zeroed() };
183 f(ctx, event);
184 }
185 run::<C, E, F>
186 }
187
188 fn validate(_state: &(), _registry: &Registry) {}
189}
190
191macro_rules! impl_template_dispatch {
196 ($($P:ident),+) => {
197 impl<E, F: Send + 'static, $($P: Param + 'static),+> TemplateDispatch<($($P,)+), E> for F
198 where
199 for<'a> &'a mut F: FnMut($($P,)+ E) + FnMut($($P::Item<'a>,)+ E),
200 {
201 fn run_fn_ptr() -> unsafe fn(&mut ($($P::State,)+), &mut World, E) {
202 #[allow(non_snake_case)]
209 unsafe fn run<E, F: Send + 'static, $($P: Param + 'static),+>(
210 state: &mut ($($P::State,)+),
211 world: &mut World,
212 event: E,
213 ) where
214 for<'a> &'a mut F: FnMut($($P,)+ E) + FnMut($($P::Item<'a>,)+ E),
215 {
216 #[allow(clippy::too_many_arguments)]
217 fn call_inner<$($P,)+ Ev>(
218 mut f: impl FnMut($($P,)+ Ev),
219 $($P: $P,)+
220 event: Ev,
221 ) {
222 f($($P,)+ event);
223 }
224
225 #[cfg(debug_assertions)]
228 world.clear_borrows();
229 let ($($P,)+) = unsafe {
230 <($($P,)+) as Param>::fetch(world, state)
231 };
232 let mut f: F = unsafe { std::mem::zeroed() };
234 call_inner(&mut f, $($P,)+ event);
235 }
236 run::<E, F, $($P),+>
237 }
238
239 #[allow(non_snake_case)]
240 fn validate(state: &($($P::State,)+), registry: &Registry) {
241 let ($($P,)+) = state;
242 registry.check_access(&[
243 $((<$P as Param>::resource_id($P), std::any::type_name::<$P>()),)+
244 ]);
245 }
246 }
247
248 impl<C: Send + 'static, E, F: Send + 'static, $($P: Param + 'static),+>
249 CallbackTemplateDispatch<C, ($($P,)+), E> for F
250 where
251 for<'a> &'a mut F:
252 FnMut(&mut C, $($P,)+ E) +
253 FnMut(&mut C, $($P::Item<'a>,)+ E),
254 {
255 fn run_fn_ptr() -> unsafe fn(&mut C, &mut ($($P::State,)+), &mut World, E) {
256 #[allow(non_snake_case)]
259 unsafe fn run<C: Send, E, F: Send + 'static, $($P: Param + 'static),+>(
260 ctx: &mut C,
261 state: &mut ($($P::State,)+),
262 world: &mut World,
263 event: E,
264 ) where
265 for<'a> &'a mut F:
266 FnMut(&mut C, $($P,)+ E) +
267 FnMut(&mut C, $($P::Item<'a>,)+ E),
268 {
269 #[allow(clippy::too_many_arguments)]
270 fn call_inner<Ctx, $($P,)+ Ev>(
271 mut f: impl FnMut(&mut Ctx, $($P,)+ Ev),
272 ctx: &mut Ctx,
273 $($P: $P,)+
274 event: Ev,
275 ) {
276 f(ctx, $($P,)+ event);
277 }
278
279 #[cfg(debug_assertions)]
283 world.clear_borrows();
284 let ($($P,)+) = unsafe {
285 <($($P,)+) as Param>::fetch(world, state)
286 };
287 let mut f: F = unsafe { std::mem::zeroed() };
289 call_inner(&mut f, ctx, $($P,)+ event);
290 }
291 run::<C, E, F, $($P),+>
292 }
293
294 #[allow(non_snake_case)]
295 fn validate(state: &($($P::State,)+), registry: &Registry) {
296 let ($($P,)+) = state;
297 registry.check_access(&[
298 $((<$P as Param>::resource_id($P), std::any::type_name::<$P>()),)+
299 ]);
300 }
301 }
302 };
303}
304
305all_tuples!(impl_template_dispatch);
306
307pub struct HandlerTemplate<K: Blueprint>
348where
349 <K::Params as Param>::State: Copy,
350{
351 prototype: <K::Params as Param>::State,
352 run_fn: unsafe fn(&mut <K::Params as Param>::State, &mut World, K::Event),
353 name: &'static str,
354 _key: PhantomData<fn() -> K>,
356}
357
358impl<K: Blueprint> HandlerTemplate<K>
359where
360 <K::Params as Param>::State: Copy,
361{
362 #[allow(clippy::needless_pass_by_value)]
373 pub fn new<F>(f: F, registry: &Registry) -> Self
374 where
375 F: TemplateDispatch<K::Params, K::Event>,
376 {
377 const {
378 assert!(
379 std::mem::size_of::<F>() == 0,
380 "F must be a ZST (named function item, not a closure or fn pointer)"
381 );
382 }
383 let _ = f;
384 let prototype = K::Params::init(registry);
385 F::validate(&prototype, registry);
386 Self {
387 prototype,
388 run_fn: F::run_fn_ptr(),
389 name: std::any::type_name::<F>(),
390 _key: PhantomData,
391 }
392 }
393
394 #[must_use = "the generated handler must be stored or dispatched"]
396 pub fn generate(&self) -> TemplatedHandler<K> {
397 TemplatedHandler {
398 state: self.prototype,
399 run_fn: self.run_fn,
400 name: self.name,
401 _key: PhantomData,
402 }
403 }
404}
405
406pub struct TemplatedHandler<K: Blueprint>
415where
416 <K::Params as Param>::State: Copy,
417{
418 state: <K::Params as Param>::State,
419 run_fn: unsafe fn(&mut <K::Params as Param>::State, &mut World, K::Event),
420 name: &'static str,
421 _key: PhantomData<fn() -> K>,
422}
423
424impl<K: Blueprint> Handler<K::Event> for TemplatedHandler<K>
425where
426 <K::Params as Param>::State: Copy,
427{
428 fn run(&mut self, world: &mut World, event: K::Event) {
429 unsafe { (self.run_fn)(&mut self.state, world, event) }
433 }
434
435 fn name(&self) -> &'static str {
436 self.name
437 }
438}
439
440type CallbackRunFn<K> = unsafe fn(
446 &mut <K as CallbackBlueprint>::Context,
447 &mut <<K as Blueprint>::Params as Param>::State,
448 &mut World,
449 <K as Blueprint>::Event,
450);
451
452pub struct CallbackTemplate<K: CallbackBlueprint>
494where
495 <K::Params as Param>::State: Copy,
496{
497 prototype: <K::Params as Param>::State,
498 run_fn: CallbackRunFn<K>,
499 name: &'static str,
500 _key: PhantomData<fn() -> K>,
501}
502
503impl<K: CallbackBlueprint> CallbackTemplate<K>
504where
505 <K::Params as Param>::State: Copy,
506{
507 #[allow(clippy::needless_pass_by_value)]
517 pub fn new<F>(f: F, registry: &Registry) -> Self
518 where
519 F: CallbackTemplateDispatch<K::Context, K::Params, K::Event>,
520 {
521 const {
522 assert!(
523 std::mem::size_of::<F>() == 0,
524 "F must be a ZST (named function item, not a closure or fn pointer)"
525 );
526 }
527 let _ = f;
528 let prototype = K::Params::init(registry);
529 F::validate(&prototype, registry);
530 Self {
531 prototype,
532 run_fn: F::run_fn_ptr(),
533 name: std::any::type_name::<F>(),
534 _key: PhantomData,
535 }
536 }
537
538 #[must_use = "the generated callback must be stored or dispatched"]
540 pub fn generate(&self, ctx: K::Context) -> TemplatedCallback<K> {
541 TemplatedCallback {
542 ctx,
543 state: self.prototype,
544 run_fn: self.run_fn,
545 name: self.name,
546 _key: PhantomData,
547 }
548 }
549}
550
551pub struct TemplatedCallback<K: CallbackBlueprint>
559where
560 <K::Params as Param>::State: Copy,
561{
562 ctx: K::Context,
563 state: <K::Params as Param>::State,
564 run_fn: CallbackRunFn<K>,
565 name: &'static str,
566 _key: PhantomData<fn() -> K>,
567}
568
569impl<K: CallbackBlueprint> TemplatedCallback<K>
570where
571 <K::Params as Param>::State: Copy,
572{
573 pub fn ctx(&self) -> &K::Context {
575 &self.ctx
576 }
577
578 pub fn ctx_mut(&mut self) -> &mut K::Context {
580 &mut self.ctx
581 }
582}
583
584impl<K: CallbackBlueprint> Handler<K::Event> for TemplatedCallback<K>
585where
586 <K::Params as Param>::State: Copy,
587{
588 fn run(&mut self, world: &mut World, event: K::Event) {
589 unsafe { (self.run_fn)(&mut self.ctx, &mut self.state, world, event) }
592 }
593
594 fn name(&self) -> &'static str {
595 self.name
596 }
597}
598
599#[macro_export]
615macro_rules! handler_blueprint {
616 ($name:ident, Event = $event:ty, Params = $params:ty) => {
617 struct $name;
618 impl $crate::template::Blueprint for $name {
619 type Event = $event;
620 type Params = $params;
621 }
622 };
623}
624
625#[macro_export]
633macro_rules! callback_blueprint {
634 ($name:ident, Context = $ctx:ty, Event = $event:ty, Params = $params:ty) => {
635 struct $name;
636 impl $crate::template::Blueprint for $name {
637 type Event = $event;
638 type Params = $params;
639 }
640 impl $crate::template::CallbackBlueprint for $name {
641 type Context = $ctx;
642 }
643 };
644}
645
646#[cfg(test)]
651mod tests {
652 use super::*;
653 use crate::{Res, ResMut, WorldBuilder};
654
655 struct OnTick;
658 impl Blueprint for OnTick {
659 type Event = u32;
660 type Params = (ResMut<'static, u64>,);
661 }
662
663 struct EventOnly;
664 impl Blueprint for EventOnly {
665 type Event = u32;
666 type Params = ();
667 }
668
669 struct TwoParams;
670 impl Blueprint for TwoParams {
671 type Event = ();
672 type Params = (Res<'static, u64>, ResMut<'static, bool>);
673 }
674
675 fn tick(mut counter: ResMut<u64>, event: u32) {
678 *counter += event as u64;
679 }
680
681 #[test]
682 fn handler_template_basic() {
683 let mut builder = WorldBuilder::new();
684 builder.register::<u64>(0);
685 let mut world = builder.build();
686
687 let template = HandlerTemplate::<OnTick>::new(tick, world.registry());
688 let mut h = template.generate();
689 h.run(&mut world, 10);
690 assert_eq!(*world.resource::<u64>(), 10);
691 }
692
693 #[test]
694 fn handler_template_stamps_independent() {
695 let mut builder = WorldBuilder::new();
696 builder.register::<u64>(0);
697 let mut world = builder.build();
698
699 let template = HandlerTemplate::<OnTick>::new(tick, world.registry());
700 let mut h1 = template.generate();
701 let mut h2 = template.generate();
702
703 h1.run(&mut world, 10);
704 h2.run(&mut world, 5);
705 assert_eq!(*world.resource::<u64>(), 15);
706 }
707
708 fn event_only_fn(event: u32) {
709 assert!(event > 0);
710 }
711
712 #[test]
713 fn handler_template_event_only() {
714 let mut world = WorldBuilder::new().build();
715 let template = HandlerTemplate::<EventOnly>::new(event_only_fn, world.registry());
716 let mut h = template.generate();
717 h.run(&mut world, 42);
718 }
719
720 fn two_params_fn(counter: Res<u64>, mut flag: ResMut<bool>, _event: ()) {
721 if *counter > 0 {
722 *flag = true;
723 }
724 }
725
726 #[test]
727 fn handler_template_two_params() {
728 let mut builder = WorldBuilder::new();
729 builder.register::<u64>(1);
730 builder.register::<bool>(false);
731 let mut world = builder.build();
732
733 let template = HandlerTemplate::<TwoParams>::new(two_params_fn, world.registry());
734 let mut h = template.generate();
735 h.run(&mut world, ());
736 assert!(*world.resource::<bool>());
737 }
738
739 #[test]
742 fn templated_handler_boxable() {
743 let mut builder = WorldBuilder::new();
744 builder.register::<u64>(0);
745 let mut world = builder.build();
746
747 let template = HandlerTemplate::<OnTick>::new(tick, world.registry());
748 let h = template.generate();
749 let mut boxed: Box<dyn Handler<u32>> = Box::new(h);
750 boxed.run(&mut world, 7);
751 assert_eq!(*world.resource::<u64>(), 7);
752 }
753
754 #[test]
757 fn templated_handler_name() {
758 let mut builder = WorldBuilder::new();
759 builder.register::<u64>(0);
760 let world = builder.build();
761
762 let template = HandlerTemplate::<OnTick>::new(tick, world.registry());
763 let h = template.generate();
764 assert!(h.name().contains("tick"));
765 }
766
767 #[test]
770 #[should_panic(expected = "not registered")]
771 fn handler_template_missing_resource() {
772 let world = WorldBuilder::new().build();
773 let _template = HandlerTemplate::<OnTick>::new(tick, world.registry());
774 }
775
776 #[test]
777 #[should_panic(expected = "conflicting access")]
778 fn handler_template_duplicate_access() {
779 struct BadBlueprint;
780 impl Blueprint for BadBlueprint {
781 type Event = ();
782 type Params = (Res<'static, u64>, ResMut<'static, u64>);
783 }
784
785 fn bad(a: Res<u64>, b: ResMut<u64>, _e: ()) {
786 let _ = (*a, &*b);
787 }
788
789 let mut builder = WorldBuilder::new();
790 builder.register::<u64>(0);
791 let world = builder.build();
792 let _template = HandlerTemplate::<BadBlueprint>::new(bad, world.registry());
793 }
794
795 struct TimerCtx {
798 order_id: u64,
799 fires: u64,
800 }
801
802 struct OnTimeout;
803 impl Blueprint for OnTimeout {
804 type Event = ();
805 type Params = (ResMut<'static, u64>,);
806 }
807 impl CallbackBlueprint for OnTimeout {
808 type Context = TimerCtx;
809 }
810
811 fn on_timeout(ctx: &mut TimerCtx, mut counter: ResMut<u64>, _event: ()) {
812 ctx.fires += 1;
813 *counter += ctx.order_id;
814 }
815
816 #[test]
817 fn callback_template_basic() {
818 let mut builder = WorldBuilder::new();
819 builder.register::<u64>(0);
820 let mut world = builder.build();
821
822 let template = CallbackTemplate::<OnTimeout>::new(on_timeout, world.registry());
823 let mut cb = template.generate(TimerCtx {
824 order_id: 42,
825 fires: 0,
826 });
827 cb.run(&mut world, ());
828 assert_eq!(cb.ctx().fires, 1);
829 assert_eq!(*world.resource::<u64>(), 42);
830 }
831
832 #[test]
833 fn callback_template_independent_contexts() {
834 let mut builder = WorldBuilder::new();
835 builder.register::<u64>(0);
836 let mut world = builder.build();
837
838 let template = CallbackTemplate::<OnTimeout>::new(on_timeout, world.registry());
839 let mut cb1 = template.generate(TimerCtx {
840 order_id: 10,
841 fires: 0,
842 });
843 let mut cb2 = template.generate(TimerCtx {
844 order_id: 20,
845 fires: 0,
846 });
847
848 cb1.run(&mut world, ());
849 cb2.run(&mut world, ());
850 assert_eq!(cb1.ctx().fires, 1);
851 assert_eq!(cb2.ctx().fires, 1);
852 assert_eq!(*world.resource::<u64>(), 30);
853 }
854
855 struct CtxOnlyKey;
856 impl Blueprint for CtxOnlyKey {
857 type Event = u32;
858 type Params = ();
859 }
860 impl CallbackBlueprint for CtxOnlyKey {
861 type Context = u64;
862 }
863
864 fn ctx_only(ctx: &mut u64, event: u32) {
865 *ctx += event as u64;
866 }
867
868 #[test]
869 fn callback_template_event_only() {
870 let mut world = WorldBuilder::new().build();
871 let template = CallbackTemplate::<CtxOnlyKey>::new(ctx_only, world.registry());
872 let mut cb = template.generate(0u64);
873 cb.run(&mut world, 5);
874 assert_eq!(*cb.ctx(), 5);
875 }
876
877 #[test]
878 fn callback_template_boxable() {
879 let mut builder = WorldBuilder::new();
880 builder.register::<u64>(0);
881 let mut world = builder.build();
882
883 let template = CallbackTemplate::<OnTimeout>::new(on_timeout, world.registry());
884 let cb = template.generate(TimerCtx {
885 order_id: 7,
886 fires: 0,
887 });
888 let mut boxed: Box<dyn Handler<()>> = Box::new(cb);
889 boxed.run(&mut world, ());
890 assert_eq!(*world.resource::<u64>(), 7);
891 }
892
893 #[test]
894 fn callback_template_ctx_accessible() {
895 let mut builder = WorldBuilder::new();
896 builder.register::<u64>(0);
897 let mut world = builder.build();
898
899 let template = CallbackTemplate::<OnTimeout>::new(on_timeout, world.registry());
900 let mut cb = template.generate(TimerCtx {
901 order_id: 42,
902 fires: 0,
903 });
904 assert_eq!(cb.ctx().order_id, 42);
905 cb.run(&mut world, ());
906 assert_eq!(cb.ctx().fires, 1);
907 cb.ctx_mut().order_id = 99;
908 cb.run(&mut world, ());
909 assert_eq!(*world.resource::<u64>(), 42 + 99);
910 }
911
912 handler_blueprint!(MacroOnTick, Event = u32, Params = (ResMut<'static, u64>,));
915
916 #[test]
917 fn macro_handler_blueprint() {
918 let mut builder = WorldBuilder::new();
919 builder.register::<u64>(0);
920 let mut world = builder.build();
921
922 let template = HandlerTemplate::<MacroOnTick>::new(tick, world.registry());
923 let mut h = template.generate();
924 h.run(&mut world, 3);
925 assert_eq!(*world.resource::<u64>(), 3);
926 }
927
928 struct Offset(i64);
931 impl crate::world::Resource for Offset {}
932 struct Scale(u32);
933 impl crate::world::Resource for Scale {}
934 struct Tag(u32);
935 impl crate::world::Resource for Tag {}
936
937 struct FiveParamBlueprint;
938 impl Blueprint for FiveParamBlueprint {
939 type Event = u32;
940 type Params = (
941 ResMut<'static, u64>,
942 Res<'static, bool>,
943 ResMut<'static, Offset>,
944 Res<'static, Scale>,
945 ResMut<'static, Tag>,
946 );
947 }
948
949 fn five_param_fn(
950 mut counter: ResMut<u64>,
951 flag: Res<bool>,
952 mut offset: ResMut<Offset>,
953 scale: Res<Scale>,
954 mut tag: ResMut<Tag>,
955 event: u32,
956 ) {
957 if *flag {
958 *counter += event as u64;
959 }
960 offset.0 += (scale.0 as i64) * (event as i64);
961 tag.0 = event;
962 }
963
964 #[test]
965 fn handler_template_five_params() {
966 let mut builder = WorldBuilder::new();
967 builder.register::<u64>(0);
968 builder.register::<bool>(true);
969 builder.register(Offset(0));
970 builder.register(Scale(2));
971 builder.register(Tag(0));
972 let mut world = builder.build();
973
974 let template = HandlerTemplate::<FiveParamBlueprint>::new(five_param_fn, world.registry());
975
976 let mut h1 = template.generate();
977 let mut h2 = template.generate();
978
979 h1.run(&mut world, 10);
980 assert_eq!(*world.resource::<u64>(), 10);
981 assert_eq!(world.resource::<Offset>().0, 20);
982 assert_eq!(world.resource::<Tag>().0, 10);
983
984 h2.run(&mut world, 5);
985 assert_eq!(*world.resource::<u64>(), 15);
986 assert_eq!(world.resource::<Offset>().0, 30);
987 assert_eq!(world.resource::<Tag>().0, 5);
988 }
989
990 callback_blueprint!(MacroOnTimeout, Context = TimerCtx, Event = (), Params = (ResMut<'static, u64>,));
991
992 #[test]
993 fn macro_callback_blueprint() {
994 let mut builder = WorldBuilder::new();
995 builder.register::<u64>(0);
996 let mut world = builder.build();
997
998 let template = CallbackTemplate::<MacroOnTimeout>::new(on_timeout, world.registry());
999 let mut cb = template.generate(TimerCtx {
1000 order_id: 5,
1001 fires: 0,
1002 });
1003 cb.run(&mut world, ());
1004 assert_eq!(cb.ctx().fires, 1);
1005 assert_eq!(*world.resource::<u64>(), 5);
1006 }
1007}