Skip to main content

nexus_rt/
callback.rs

1//! Context-owning handler dispatch.
2//!
3//! [`Callback`] is the unified dispatch type in nexus-rt. Context-free
4//! handlers use `Callback<(), CtxFree<F>, P>` (via [`IntoHandler`](crate::IntoHandler));
5//! context-owning handlers use `Callback<C, F, P>` (via [`IntoCallback`]).
6//!
7//! The function convention for context-owning callbacks is
8//! `fn handler(ctx: &mut C, params: Res<T>..., event: E)` — context first,
9//! [`Param`]-resolved resources in the middle, event last.
10//!
11//! Same HRTB double-bound pattern, same macro generation, same ~2-cycle
12//! dispatch. Named functions only (same closure limitation as
13//! [`IntoHandler`](crate::IntoHandler)).
14//!
15//! # When to use Callback
16//!
17//! Use [`Callback`] over [`IntoHandler`](crate::IntoHandler) when each
18//! handler instance needs its own private state that isn't shared via
19//! [`World`](crate::World):
20//!
21//! - **Per-timer context** — each timer carries its own order ID, retry
22//!   count, or deadline metadata.
23//! - **Per-connection state** — each socket handler carries its own codec
24//!   state, read buffer, or session context.
25//! - **Protocol state machines** — each handler instance tracks its own
26//!   position in a protocol handshake or reconnection sequence.
27//!
28//! The `ctx` field is `pub`, so drivers can read or mutate it between
29//! dispatches (e.g. to update a deadline or check a counter).
30//!
31//! # Returning callbacks from functions (Rust 2024)
32//!
33//! When a factory function takes `&Registry` and returns `impl Handler<E>`,
34//! Rust 2024 captures the registry borrow. Use `+ use<...>` to exclude it:
35//!
36//! ```ignore
37//! fn build_callback(
38//!     ctx: MyCtx,
39//!     reg: &Registry,
40//! ) -> impl Handler<DataEvent> + use<> {
41//!     on_data.into_callback(ctx, reg)
42//! }
43//! ```
44//!
45//! See the [crate-level docs](crate#returning-impl-handler-from-functions-rust-2024)
46//! for the full explanation.
47
48// Handler arity is architecturally required by the Param trait — handlers
49// take N typed parameters and the macro-generated dispatch impls expand
50// per-arity into call_inner functions with N + Input arguments. Module-level
51// allow rather than one inline attribute per arity expansion.
52#![allow(clippy::too_many_arguments)]
53
54use crate::Handler;
55use crate::handler::Param;
56use crate::world::{Registry, World};
57
58// =============================================================================
59// Callback<C, F, Params>
60// =============================================================================
61
62/// Unified dispatch type. Stores per-callback context alongside
63/// pre-resolved resource access.
64///
65/// - Context-free handlers: `Callback<(), CtxFree<F>, P>` — created via
66///   [`IntoHandler::into_handler`](crate::IntoHandler::into_handler).
67/// - Context-owning handlers: `Callback<C, F, P>` — created via
68///   [`IntoCallback::into_callback`].
69///
70/// Both implement [`Handler<E>`].
71///
72/// # Examples
73///
74/// ```
75/// use nexus_rt::{WorldBuilder, ResMut, IntoCallback, Handler, Resource};
76///
77/// #[derive(Resource)]
78/// struct Counter(u64);
79///
80/// struct Ctx { count: u64 }
81///
82/// fn handler(ctx: &mut Ctx, mut val: ResMut<Counter>, event: u32) {
83///     val.0 += event as u64;
84///     ctx.count += 1;
85/// }
86///
87/// let mut builder = WorldBuilder::new();
88/// builder.register(Counter(0));
89/// let mut world = builder.build();
90///
91/// let mut cb = handler.into_callback(Ctx { count: 0 }, world.registry());
92/// cb.run(&mut world, 10u32);
93///
94/// assert_eq!(cb.ctx.count, 1);
95/// assert_eq!(world.resource::<Counter>().0, 10);
96/// ```
97pub struct Callback<C, F, Params: Param> {
98    /// Per-callback owned state. Accessible outside dispatch.
99    pub ctx: C,
100    pub(crate) f: F,
101    pub(crate) state: Params::State,
102    pub(crate) name: &'static str,
103}
104
105// =============================================================================
106// IntoCallback
107// =============================================================================
108
109/// Converts a named function into a [`Callback`].
110///
111/// Identical to [`IntoHandler`](crate::IntoHandler) but injects `&mut C` as
112/// the first parameter. [`ResourceId`](crate::ResourceId)s resolved via
113/// `registry.id::<T>()` at call time — panics if any resource is not
114/// registered.
115///
116/// Use `IntoCallback` when each handler instance needs its own private
117/// state. For stateless handlers (or state shared via [`World`](crate::World)),
118/// prefer [`IntoHandler`](crate::IntoHandler).
119///
120/// # Named functions only
121///
122/// Closures do not work with `IntoCallback` due to Rust's HRTB inference
123/// limitations with GATs. Use named `fn` items instead.
124///
125/// # Examples
126///
127/// ```
128/// use nexus_rt::{WorldBuilder, ResMut, IntoCallback, Handler, Resource, no_event};
129///
130/// #[derive(Resource)]
131/// struct Counter(u64);
132///
133/// struct TimerCtx { order_id: u64, fires: u64 }
134///
135/// fn on_timeout(ctx: &mut TimerCtx, mut counter: ResMut<Counter>) {
136///     ctx.fires += 1;
137///     counter.0 += ctx.order_id;
138/// }
139///
140/// let mut builder = WorldBuilder::new();
141/// builder.register(Counter(0));
142/// let mut world = builder.build();
143///
144/// let mut cb = no_event(on_timeout).into_callback(
145///     TimerCtx { order_id: 42, fires: 0 },
146///     world.registry(),
147/// );
148/// cb.run(&mut world, ());
149///
150/// assert_eq!(cb.ctx.fires, 1);
151/// assert_eq!(world.resource::<Counter>().0, 42);
152/// ```
153///
154/// # Panics
155///
156/// Panics if any [`Param`] resource is not registered in the
157/// [`Registry`](crate::Registry).
158#[diagnostic::on_unimplemented(
159    message = "this function cannot be converted into a callback",
160    note = "callback signature: `fn(&mut Context, Res<A>, ..., Event)` — context first, then resources, event last",
161    note = "for Handler<()> with params, wrap with `no_event(fn_name)` to omit the event parameter",
162    note = "closures with resource parameters are not supported — use a named `fn` when using Param resources"
163)]
164pub trait IntoCallback<C, E, Params> {
165    /// The concrete Callback type produced.
166    type Callback: Handler<E>;
167
168    /// Convert this function + context into a Callback.
169    #[must_use = "the callback must be stored or dispatched — discarding it does nothing"]
170    fn into_callback(self, ctx: C, registry: &Registry) -> Self::Callback;
171}
172
173// =============================================================================
174// Arity 0: fn(ctx: &mut C, E) — context + event only, no Param
175// =============================================================================
176
177impl<C: Send + 'static, E, F: FnMut(&mut C, E) + Send + 'static> IntoCallback<C, E, ()> for F {
178    type Callback = Callback<C, F, ()>;
179
180    fn into_callback(self, ctx: C, registry: &Registry) -> Self::Callback {
181        Callback {
182            ctx,
183            f: self,
184            state: <() as Param>::init(registry),
185            name: std::any::type_name::<F>(),
186        }
187    }
188}
189
190impl<C: Send + 'static, E, F: FnMut(&mut C, E) + Send + 'static> Handler<E> for Callback<C, F, ()> {
191    fn run(&mut self, _world: &mut World, event: E) {
192        (self.f)(&mut self.ctx, event);
193    }
194
195    fn name(&self) -> &'static str {
196        self.name
197    }
198}
199
200// =============================================================================
201// Macro-generated impls (arities 1-8)
202// =============================================================================
203
204macro_rules! impl_into_callback {
205    ($($P:ident),+) => {
206        impl<C: Send + 'static, E, F: Send + 'static, $($P: Param + 'static),+>
207            IntoCallback<C, E, ($($P,)+)> for F
208        where
209            for<'a> &'a mut F:
210                FnMut(&mut C, $($P,)+ E) +
211                FnMut(&mut C, $($P::Item<'a>,)+ E),
212        {
213            type Callback = Callback<C, F, ($($P,)+)>;
214
215            fn into_callback(self, ctx: C, registry: &Registry) -> Self::Callback {
216                let state = <($($P,)+) as Param>::init(registry);
217                {
218                    #[allow(non_snake_case)]
219                    let ($($P,)+) = &state;
220                    registry.check_access(&[
221                        $(
222                            (<$P as Param>::resource_id($P),
223                             std::any::type_name::<$P>()),
224                        )+
225                    ]);
226                }
227                Callback { ctx, f: self, state, name: std::any::type_name::<F>() }
228            }
229        }
230
231        impl<C: Send + 'static, E, F: Send + 'static, $($P: Param + 'static),+>
232            Handler<E> for Callback<C, F, ($($P,)+)>
233        where
234            for<'a> &'a mut F:
235                FnMut(&mut C, $($P,)+ E) +
236                FnMut(&mut C, $($P::Item<'a>,)+ E),
237        {
238            #[allow(non_snake_case)]
239            fn run(&mut self, world: &mut World, event: E) {
240                fn call_inner<Ctx, $($P,)+ Ev>(
241                    mut f: impl FnMut(&mut Ctx, $($P,)+ Ev),
242                    ctx: &mut Ctx,
243                    $($P: $P,)+
244                    event: Ev,
245                ) {
246                    f(ctx, $($P,)+ event);
247                }
248
249                // SAFETY: state was produced by init() on the same registry
250                // that built this world. Single-threaded sequential dispatch
251                // ensures no mutable aliasing across params.
252                #[cfg(debug_assertions)]
253                world.clear_borrows();
254                let ($($P,)+) = unsafe {
255                    <($($P,)+) as Param>::fetch(world, &mut self.state)
256                };
257                call_inner(&mut self.f, &mut self.ctx, $($P,)+ event);
258            }
259
260            fn name(&self) -> &'static str {
261                self.name
262            }
263        }
264    };
265}
266
267all_tuples!(impl_into_callback);
268
269// =============================================================================
270// No-event callback impls — Handler<()> without trailing `_: ()` parameter
271// =============================================================================
272
273use crate::handler::NoEvent;
274
275// Arity 0: fn(&mut C) → Handler<()>
276impl<C: Send + 'static, F: FnMut(&mut C) + Send + 'static> IntoCallback<C, (), NoEvent<F>> for F {
277    type Callback = Callback<C, NoEvent<F>, ()>;
278
279    fn into_callback(self, ctx: C, registry: &Registry) -> Self::Callback {
280        Callback {
281            ctx,
282            f: NoEvent(self),
283            state: <() as Param>::init(registry),
284            name: std::any::type_name::<F>(),
285        }
286    }
287}
288
289impl<C: Send + 'static, F: FnMut(&mut C) + Send + 'static> Handler<()>
290    for Callback<C, NoEvent<F>, ()>
291{
292    fn run(&mut self, _world: &mut World, _event: ()) {
293        (self.f.0)(&mut self.ctx);
294    }
295
296    fn name(&self) -> &'static str {
297        self.name
298    }
299}
300
301macro_rules! impl_into_callback_no_event {
302    ($($P:ident),+) => {
303        impl<C: Send + 'static, F: Send + 'static, $($P: Param + 'static),+>
304            IntoCallback<C, (), ($($P,)+)> for NoEvent<F>
305        where
306            for<'a> &'a mut F:
307                FnMut(&mut C, $($P,)+) +
308                FnMut(&mut C, $($P::Item<'a>,)+),
309        {
310            type Callback = Callback<C, NoEvent<F>, ($($P,)+)>;
311
312            fn into_callback(self, ctx: C, registry: &Registry) -> Self::Callback {
313                let state = <($($P,)+) as Param>::init(registry);
314                {
315                    #[allow(non_snake_case)]
316                    let ($($P,)+) = &state;
317                    registry.check_access(&[
318                        $(
319                            (<$P as Param>::resource_id($P),
320                             std::any::type_name::<$P>()),
321                        )+
322                    ]);
323                }
324                Callback { ctx, f: self, state, name: std::any::type_name::<F>() }
325            }
326        }
327
328        impl<C: Send + 'static, F: Send + 'static, $($P: Param + 'static),+>
329            Handler<()> for Callback<C, NoEvent<F>, ($($P,)+)>
330        where
331            for<'a> &'a mut F:
332                FnMut(&mut C, $($P,)+) +
333                FnMut(&mut C, $($P::Item<'a>,)+),
334        {
335            #[allow(non_snake_case)]
336            fn run(&mut self, world: &mut World, _event: ()) {
337                fn call_inner<Ctx, $($P),+>(
338                    mut f: impl FnMut(&mut Ctx, $($P),+),
339                    ctx: &mut Ctx,
340                    $($P: $P,)+
341                ) {
342                    f(ctx, $($P),+);
343                }
344
345                #[cfg(debug_assertions)]
346                world.clear_borrows();
347                let ($($P,)+) = unsafe {
348                    <($($P,)+) as Param>::fetch(world, &mut self.state)
349                };
350                call_inner(&mut self.f.0, &mut self.ctx, $($P,)+);
351            }
352
353            fn name(&self) -> &'static str {
354                self.name
355            }
356        }
357    };
358}
359
360all_tuples!(impl_into_callback_no_event);
361
362// =============================================================================
363// Tests
364// =============================================================================
365
366#[cfg(test)]
367mod tests {
368    use super::*;
369    use crate::{Local, Res, ResMut, WorldBuilder, no_event};
370
371    // -- Helper types ---------------------------------------------------------
372
373    struct TimerCtx {
374        order_id: u64,
375        call_count: u64,
376    }
377
378    struct OrderCache {
379        expired: Vec<u64>,
380    }
381    impl crate::world::Resource for OrderCache {}
382
383    // -- Core dispatch --------------------------------------------------------
384
385    fn ctx_only_handler(ctx: &mut TimerCtx, _event: u32) {
386        ctx.call_count += 1;
387    }
388
389    #[test]
390    fn ctx_only_no_params() {
391        let mut world = WorldBuilder::new().build();
392        let mut cb = ctx_only_handler.into_callback(
393            TimerCtx {
394                order_id: 1,
395                call_count: 0,
396            },
397            world.registry_mut(),
398        );
399        cb.run(&mut world, 42u32);
400        assert_eq!(cb.ctx.call_count, 1);
401    }
402
403    fn ctx_one_res_handler(ctx: &mut TimerCtx, cache: Res<OrderCache>, _event: u32) {
404        ctx.call_count += cache.expired.len() as u64;
405    }
406
407    #[test]
408    fn ctx_one_res() {
409        let mut builder = WorldBuilder::new();
410        builder.register::<OrderCache>(OrderCache {
411            expired: vec![1, 2, 3],
412        });
413        let mut world = builder.build();
414
415        let mut cb = ctx_one_res_handler.into_callback(
416            TimerCtx {
417                order_id: 1,
418                call_count: 0,
419            },
420            world.registry_mut(),
421        );
422        cb.run(&mut world, 0u32);
423        assert_eq!(cb.ctx.call_count, 3);
424    }
425
426    fn ctx_one_res_mut_handler(ctx: &mut TimerCtx, mut cache: ResMut<OrderCache>, _event: u32) {
427        cache.expired.push(ctx.order_id);
428        ctx.call_count += 1;
429    }
430
431    #[test]
432    fn ctx_one_res_mut() {
433        let mut builder = WorldBuilder::new();
434        builder.register::<OrderCache>(OrderCache { expired: vec![] });
435        let mut world = builder.build();
436
437        let mut cb = ctx_one_res_mut_handler.into_callback(
438            TimerCtx {
439                order_id: 42,
440                call_count: 0,
441            },
442            world.registry_mut(),
443        );
444        cb.run(&mut world, 0u32);
445        assert_eq!(cb.ctx.call_count, 1);
446        assert_eq!(world.resource::<OrderCache>().expired, vec![42]);
447    }
448
449    fn ctx_two_params_handler(
450        ctx: &mut TimerCtx,
451        counter: Res<u64>,
452        mut cache: ResMut<OrderCache>,
453        _event: u32,
454    ) {
455        cache.expired.push(*counter);
456        ctx.call_count += 1;
457    }
458
459    #[test]
460    fn ctx_two_params() {
461        let mut builder = WorldBuilder::new();
462        builder.register::<u64>(99);
463        builder.register::<OrderCache>(OrderCache { expired: vec![] });
464        let mut world = builder.build();
465
466        let mut cb = ctx_two_params_handler.into_callback(
467            TimerCtx {
468                order_id: 0,
469                call_count: 0,
470            },
471            world.registry_mut(),
472        );
473        cb.run(&mut world, 0u32);
474        assert_eq!(cb.ctx.call_count, 1);
475        assert_eq!(world.resource::<OrderCache>().expired, vec![99]);
476    }
477
478    fn ctx_three_params_handler(
479        ctx: &mut TimerCtx,
480        a: Res<u64>,
481        b: Res<bool>,
482        mut c: ResMut<OrderCache>,
483        _event: u32,
484    ) {
485        if *b {
486            c.expired.push(*a);
487        }
488        ctx.call_count += 1;
489    }
490
491    #[test]
492    fn ctx_three_params() {
493        let mut builder = WorldBuilder::new();
494        builder.register::<u64>(7);
495        builder.register::<bool>(true);
496        builder.register::<OrderCache>(OrderCache { expired: vec![] });
497        let mut world = builder.build();
498
499        let mut cb = ctx_three_params_handler.into_callback(
500            TimerCtx {
501                order_id: 0,
502                call_count: 0,
503            },
504            world.registry_mut(),
505        );
506        cb.run(&mut world, 0u32);
507        assert_eq!(cb.ctx.call_count, 1);
508        assert_eq!(world.resource::<OrderCache>().expired, vec![7]);
509    }
510
511    // -- Context ownership ----------------------------------------------------
512
513    #[test]
514    fn ctx_mutated_persists() {
515        let mut world = WorldBuilder::new().build();
516        let mut cb = ctx_only_handler.into_callback(
517            TimerCtx {
518                order_id: 1,
519                call_count: 0,
520            },
521            world.registry_mut(),
522        );
523        cb.run(&mut world, 0u32);
524        cb.run(&mut world, 0u32);
525        cb.run(&mut world, 0u32);
526        assert_eq!(cb.ctx.call_count, 3);
527    }
528
529    #[test]
530    fn ctx_accessible_outside_dispatch() {
531        let mut world = WorldBuilder::new().build();
532        let mut cb = ctx_only_handler.into_callback(
533            TimerCtx {
534                order_id: 42,
535                call_count: 0,
536            },
537            world.registry_mut(),
538        );
539        assert_eq!(cb.ctx.order_id, 42);
540        assert_eq!(cb.ctx.call_count, 0);
541        cb.run(&mut world, 0u32);
542        assert_eq!(cb.ctx.call_count, 1);
543    }
544
545    #[test]
546    fn ctx_mutated_outside_dispatch() {
547        let mut world = WorldBuilder::new().build();
548        let mut cb = ctx_only_handler.into_callback(
549            TimerCtx {
550                order_id: 1,
551                call_count: 0,
552            },
553            world.registry_mut(),
554        );
555        cb.ctx.order_id = 99;
556        cb.run(&mut world, 0u32);
557        assert_eq!(cb.ctx.order_id, 99);
558        assert_eq!(cb.ctx.call_count, 1);
559    }
560
561    // -- Safety validation ----------------------------------------------------
562
563    #[test]
564    #[should_panic(expected = "not registered")]
565    fn panics_on_missing_resource() {
566        let mut world = WorldBuilder::new().build();
567
568        fn needs_cache(_ctx: &mut TimerCtx, _cache: Res<OrderCache>, _e: u32) {}
569
570        let _cb = needs_cache.into_callback(
571            TimerCtx {
572                order_id: 0,
573                call_count: 0,
574            },
575            world.registry_mut(),
576        );
577    }
578
579    #[test]
580    #[should_panic(expected = "not registered")]
581    fn panics_on_second_missing() {
582        let mut builder = WorldBuilder::new();
583        builder.register::<u64>(0);
584        let mut world = builder.build();
585
586        fn needs_two(_ctx: &mut TimerCtx, _a: Res<u64>, _b: Res<OrderCache>, _e: u32) {}
587
588        let _cb = needs_two.into_callback(
589            TimerCtx {
590                order_id: 0,
591                call_count: 0,
592            },
593            world.registry_mut(),
594        );
595    }
596
597    // -- Access conflict detection --------------------------------------------
598
599    #[test]
600    #[should_panic(expected = "conflicting access")]
601    fn callback_duplicate_access_panics() {
602        let mut builder = WorldBuilder::new();
603        builder.register::<u64>(0);
604        let mut world = builder.build();
605
606        fn bad(_ctx: &mut u64, a: Res<u64>, b: ResMut<u64>) {
607            let _ = (*a, &*b);
608        }
609
610        let _cb = no_event(bad).into_callback(0u64, world.registry_mut());
611    }
612
613    // -- Handler<E> interface -------------------------------------------------
614
615    #[test]
616    fn box_dyn_handler() {
617        let mut builder = WorldBuilder::new();
618        builder.register::<u64>(0);
619        let mut world = builder.build();
620
621        fn add_ctx(ctx: &mut u64, mut val: ResMut<u64>, event: u64) {
622            *val += event + *ctx;
623        }
624
625        let cb = add_ctx.into_callback(10u64, world.registry_mut());
626        let mut boxed: Box<dyn Handler<u64>> = Box::new(cb);
627        boxed.run(&mut world, 5u64);
628        // 0 + 5 + 10 = 15
629        assert_eq!(*world.resource::<u64>(), 15);
630    }
631
632    #[test]
633    fn callback_in_vec_dyn() {
634        let mut builder = WorldBuilder::new();
635        builder.register::<u64>(0);
636        let mut world = builder.build();
637
638        fn add(ctx: &mut u64, mut val: ResMut<u64>) {
639            *val += *ctx;
640        }
641        fn mul(ctx: &mut u64, mut val: ResMut<u64>) {
642            *val *= *ctx;
643        }
644
645        let cb_add = no_event(add).into_callback(3u64, world.registry_mut());
646        let cb_mul = no_event(mul).into_callback(2u64, world.registry_mut());
647
648        let mut handlers: Vec<Box<dyn Handler<()>>> = vec![Box::new(cb_add), Box::new(cb_mul)];
649
650        for h in &mut handlers {
651            h.run(&mut world, ());
652        }
653        // 0 + 3 = 3, then 3 * 2 = 6
654        assert_eq!(*world.resource::<u64>(), 6);
655    }
656
657    fn with_local(_ctx: &mut u64, mut local: Local<u64>, mut val: ResMut<u64>) {
658        *local += 1;
659        *val = *local;
660    }
661
662    #[test]
663    fn callback_with_local() {
664        let mut builder = WorldBuilder::new();
665        builder.register::<u64>(0);
666        let mut world = builder.build();
667
668        let mut cb = no_event(with_local).into_callback(0u64, world.registry_mut());
669        cb.run(&mut world, ());
670        cb.run(&mut world, ());
671        cb.run(&mut world, ());
672        assert_eq!(*world.resource::<u64>(), 3);
673    }
674
675    // -- Integration ----------------------------------------------------------
676
677    #[test]
678    fn callback_interop_with_handler() {
679        use crate::IntoHandler;
680
681        let mut builder = WorldBuilder::new();
682        builder.register::<u64>(0);
683        let mut world = builder.build();
684
685        fn sys_add(mut val: ResMut<u64>, event: u64) {
686            *val += event;
687        }
688        fn cb_mul(ctx: &mut u64, mut val: ResMut<u64>, _e: u64) {
689            *val *= *ctx;
690        }
691
692        let sys = sys_add.into_handler(world.registry_mut());
693        let cb = cb_mul.into_callback(3u64, world.registry_mut());
694
695        let mut handlers: Vec<Box<dyn Handler<u64>>> = vec![Box::new(sys), Box::new(cb)];
696
697        for h in &mut handlers {
698            h.run(&mut world, 5u64);
699        }
700        // 0 + 5 = 5, then 5 * 3 = 15
701        assert_eq!(*world.resource::<u64>(), 15);
702    }
703
704    // =========================================================================
705    // NoEvent — Callback<C, NoEvent<F>, P> as Handler<()>
706    // =========================================================================
707
708    fn no_event_ctx_only(ctx: &mut TimerCtx) {
709        ctx.call_count += 1;
710    }
711
712    #[test]
713    fn no_event_callback_arity_0() {
714        let mut world = WorldBuilder::new().build();
715        let mut cb = no_event_ctx_only.into_callback(
716            TimerCtx {
717                order_id: 1,
718                call_count: 0,
719            },
720            world.registry_mut(),
721        );
722        cb.run(&mut world, ());
723        cb.run(&mut world, ());
724        assert_eq!(cb.ctx.call_count, 2);
725    }
726
727    fn no_event_ctx_one_res(ctx: &mut TimerCtx, mut val: ResMut<u64>) {
728        *val += ctx.order_id;
729        ctx.call_count += 1;
730    }
731
732    #[test]
733    fn no_event_callback_arity_1() {
734        use crate::no_event;
735
736        let mut builder = WorldBuilder::new();
737        builder.register::<u64>(0);
738        let mut world = builder.build();
739
740        let mut cb = no_event(no_event_ctx_one_res).into_callback(
741            TimerCtx {
742                order_id: 42,
743                call_count: 0,
744            },
745            world.registry_mut(),
746        );
747        cb.run(&mut world, ());
748        assert_eq!(cb.ctx.call_count, 1);
749        assert_eq!(*world.resource::<u64>(), 42);
750    }
751
752    fn no_event_ctx_two_params(ctx: &mut TimerCtx, src: Res<u32>, mut dst: ResMut<u64>) {
753        *dst += *src as u64 + ctx.order_id;
754        ctx.call_count += 1;
755    }
756
757    #[test]
758    fn no_event_callback_arity_2() {
759        use crate::no_event;
760
761        let mut builder = WorldBuilder::new();
762        builder.register::<u32>(10);
763        builder.register::<u64>(0);
764        let mut world = builder.build();
765
766        let mut cb = no_event(no_event_ctx_two_params).into_callback(
767            TimerCtx {
768                order_id: 5,
769                call_count: 0,
770            },
771            world.registry_mut(),
772        );
773        cb.run(&mut world, ());
774        assert_eq!(cb.ctx.call_count, 1);
775        assert_eq!(*world.resource::<u64>(), 15); // 10 + 5
776    }
777
778    #[test]
779    fn no_event_callback_boxed() {
780        use crate::no_event;
781
782        let mut builder = WorldBuilder::new();
783        builder.register::<u64>(0);
784        let mut world = builder.build();
785
786        let cb = no_event(no_event_ctx_one_res).into_callback(
787            TimerCtx {
788                order_id: 7,
789                call_count: 0,
790            },
791            world.registry_mut(),
792        );
793        let mut boxed: Box<dyn Handler<()>> = Box::new(cb);
794        boxed.run(&mut world, ());
795        assert_eq!(*world.resource::<u64>(), 7);
796    }
797}