Skip to main content

nexus_rt/
handler.rs

1//! Handler parameter resolution and dispatch primitives.
2
3// Handler arity is architecturally required by the Param trait — handlers
4// take N typed parameters and the macro-generated dispatch impls expand
5// per-arity into call_inner functions with N + Input arguments. Module-level
6// allow rather than one inline attribute per arity expansion.
7#![allow(clippy::too_many_arguments)]
8
9use std::ops::{Deref, DerefMut};
10
11use crate::Resource;
12use crate::callback::Callback;
13use crate::resource::{Res, ResMut, Seq, SeqMut};
14use crate::shutdown::Shutdown;
15use crate::world::{Registry, ResourceId, World};
16
17// =============================================================================
18// Param
19// =============================================================================
20
21/// Trait for types that can be resolved from a [`Registry`] at build time
22/// and fetched from [`World`] at dispatch time.
23///
24/// Analogous to Bevy's `SystemParam`.
25///
26/// Two-phase resolution:
27///
28/// 1. **Build time** — [`init`](Self::init) resolves opaque state (e.g. a
29///    [`ResourceId`]) from the registry. This panics if the required type
30///    isn't registered — giving an early build-time error.
31/// 2. **Dispatch time** — [`fetch`](Self::fetch) uses the cached state to
32///    produce a reference via a single pointer deref — zero framework overhead.
33///
34/// # Built-in impls
35///
36/// | Param | State | Description |
37/// |-------|-------|-------------|
38/// | [`Res<T>`] | `ResourceId` | Shared read |
39/// | [`ResMut<T>`] | `ResourceId` | Exclusive write |
40/// | [`Option<Res<T>>`] | `Option<ResourceId>` | Optional shared read |
41/// | [`Option<ResMut<T>>`] | `Option<ResourceId>` | Optional exclusive write |
42/// | [`Local<T>`] | `T` | Per-handler state (not in World) |
43/// | [`RegistryRef`] | `()` | Read-only registry access |
44/// | `()` | `()` | Event-only handlers |
45/// | Tuples of params | Tuple of states | Up to 8 params |
46pub trait Param {
47    /// Opaque state cached at build time (e.g. [`ResourceId`]).
48    ///
49    /// `Send` is required because state is stored in handler types
50    /// ([`Callback`]), and handlers must be `Send` (they live in
51    /// [`World`], which is `Send`).
52    type State: Send;
53
54    /// The item produced at dispatch time.
55    type Item<'w>;
56
57    /// Resolve state from the registry. Called once at build time.
58    ///
59    /// # Panics
60    ///
61    /// Panics if the required resource is not registered.
62    fn init(registry: &Registry) -> Self::State;
63
64    /// Fetch the item using cached state.
65    ///
66    /// # Safety
67    ///
68    /// - `state` must have been produced by [`init`](Self::init) on the
69    ///   same registry that built the `world`.
70    /// - Caller ensures no aliasing violations.
71    unsafe fn fetch<'w>(world: &'w World, state: &'w mut Self::State) -> Self::Item<'w>;
72
73    /// The ResourceId this param accesses, if any.
74    ///
75    /// Returns `None` for params that don't access World resources
76    /// (e.g. `Local<T>`). Used by [`Registry::check_access`] to enforce
77    /// one borrow per resource per handler.
78    fn resource_id(state: &Self::State) -> Option<ResourceId> {
79        let _ = state;
80        None
81    }
82}
83
84// -- Res<T> ------------------------------------------------------------------
85
86impl<T: Resource> Param for Res<'_, T> {
87    type State = ResourceId;
88    type Item<'w> = Res<'w, T>;
89
90    fn init(registry: &Registry) -> ResourceId {
91        registry.id::<T>()
92    }
93
94    #[inline(always)]
95    unsafe fn fetch<'w>(world: &'w World, state: &'w mut ResourceId) -> Res<'w, T> {
96        let id = *state;
97        #[cfg(debug_assertions)]
98        world.track_borrow(id);
99        // SAFETY: state was produced by init() on the same world.
100        // Caller ensures no mutable alias exists for T.
101        unsafe { Res::new(world.get::<T>(id)) }
102    }
103
104    fn resource_id(state: &ResourceId) -> Option<ResourceId> {
105        Some(*state)
106    }
107}
108
109// -- ResMut<T> ---------------------------------------------------------------
110
111impl<T: Resource> Param for ResMut<'_, T> {
112    type State = ResourceId;
113    type Item<'w> = ResMut<'w, T>;
114
115    fn init(registry: &Registry) -> ResourceId {
116        registry.id::<T>()
117    }
118
119    #[inline(always)]
120    unsafe fn fetch<'w>(world: &'w World, state: &'w mut ResourceId) -> ResMut<'w, T> {
121        let id = *state;
122        #[cfg(debug_assertions)]
123        world.track_borrow(id);
124        // SAFETY: state was produced by init() on the same world.
125        // Caller ensures no aliases exist for T.
126        unsafe { ResMut::new(world.get_mut::<T>(id)) }
127    }
128
129    fn resource_id(state: &ResourceId) -> Option<ResourceId> {
130        Some(*state)
131    }
132}
133
134// -- Option<Res<T>> ----------------------------------------------------------
135
136impl<T: Resource> Param for Option<Res<'_, T>> {
137    type State = Option<ResourceId>;
138    type Item<'w> = Option<Res<'w, T>>;
139
140    fn init(registry: &Registry) -> Option<ResourceId> {
141        registry.try_id::<T>()
142    }
143
144    #[inline(always)]
145    unsafe fn fetch<'w>(world: &'w World, state: &'w mut Option<ResourceId>) -> Option<Res<'w, T>> {
146        // SAFETY: state was produced by init() on the same world.
147        // Caller ensures no mutable alias exists for T.
148        state.map(|id| {
149            #[cfg(debug_assertions)]
150            world.track_borrow(id);
151            unsafe { Res::new(world.get::<T>(id)) }
152        })
153    }
154
155    fn resource_id(state: &Option<ResourceId>) -> Option<ResourceId> {
156        *state
157    }
158}
159
160// -- Option<ResMut<T>> -------------------------------------------------------
161
162impl<T: Resource> Param for Option<ResMut<'_, T>> {
163    type State = Option<ResourceId>;
164    type Item<'w> = Option<ResMut<'w, T>>;
165
166    fn init(registry: &Registry) -> Option<ResourceId> {
167        registry.try_id::<T>()
168    }
169
170    #[inline(always)]
171    unsafe fn fetch<'w>(
172        world: &'w World,
173        state: &'w mut Option<ResourceId>,
174    ) -> Option<ResMut<'w, T>> {
175        // SAFETY: state was produced by init() on the same world.
176        // Caller ensures no aliases exist for T.
177        state.map(|id| {
178            #[cfg(debug_assertions)]
179            world.track_borrow(id);
180            unsafe { ResMut::new(world.get_mut::<T>(id)) }
181        })
182    }
183
184    fn resource_id(state: &Option<ResourceId>) -> Option<ResourceId> {
185        *state
186    }
187}
188
189// -- Seq (read-only sequence) ------------------------------------------------
190
191impl Param for Seq {
192    type State = ();
193    type Item<'w> = Seq;
194
195    fn init(_registry: &Registry) {}
196
197    #[inline(always)]
198    unsafe fn fetch<'w>(world: &'w World, _state: &'w mut ()) -> Seq {
199        Seq(world.current_sequence())
200    }
201}
202
203// -- SeqMut (mutable sequence) -----------------------------------------------
204
205impl Param for SeqMut<'_> {
206    type State = ();
207    type Item<'w> = SeqMut<'w>;
208
209    fn init(_registry: &Registry) {}
210
211    #[inline(always)]
212    unsafe fn fetch<'w>(world: &'w World, _state: &'w mut ()) -> SeqMut<'w> {
213        SeqMut(world.sequence_cell())
214    }
215}
216
217// -- Shutdown ----------------------------------------------------------------
218
219impl Param for Shutdown<'_> {
220    type State = ();
221    type Item<'w> = Shutdown<'w>;
222
223    fn init(_registry: &Registry) {}
224
225    #[inline(always)]
226    unsafe fn fetch<'w>(world: &'w World, _state: &'w mut ()) -> Shutdown<'w> {
227        // Borrow the AtomicBool directly — lifetime-bound to World.
228        // No Arc::clone, no raw pointer. Safe by construction.
229        Shutdown(world.shutdown_flag())
230    }
231}
232
233// =============================================================================
234// Tuple impls
235// =============================================================================
236
237/// Unit impl — event-only handlers with no resource parameters.
238impl Param for () {
239    type State = ();
240    type Item<'w> = ();
241
242    fn init(_registry: &Registry) {}
243
244    #[inline(always)]
245    unsafe fn fetch<'w>(_world: &'w World, _state: &'w mut ()) {}
246}
247
248macro_rules! impl_param_tuple {
249    ($($P:ident),+) => {
250        impl<$($P: Param),+> Param for ($($P,)+) {
251            type State = ($($P::State,)+);
252            type Item<'w> = ($($P::Item<'w>,)+);
253
254            fn init(registry: &Registry) -> Self::State {
255                ($($P::init(registry),)+)
256            }
257
258            #[inline(always)]
259            #[allow(non_snake_case)]
260            unsafe fn fetch<'w>(world: &'w World, state: &'w mut Self::State) -> Self::Item<'w> {
261                let ($($P,)+) = state;
262                // SAFETY: caller upholds aliasing invariants for all params.
263                unsafe { ($($P::fetch(world, $P),)+) }
264            }
265        }
266    };
267}
268
269all_tuples!(impl_param_tuple);
270
271// =============================================================================
272// Local<T> — per-handler state
273// =============================================================================
274
275/// Per-handler local state. Stored inside the dispatch wrapper (e.g.
276/// [`Callback`] or pipeline step), not in [`World`].
277///
278/// Analogous to Bevy's `Local<T>`.
279///
280/// Initialized with [`Default::default()`] at handler creation time. Mutated
281/// freely at dispatch time — each handler/stage instance has its own
282/// independent copy.
283///
284/// # Examples
285///
286/// ```ignore
287/// fn count_events(mut count: Local<u64>, event: u32) {
288///     *count += 1;
289///     println!("event #{}: {}", *count, event);
290/// }
291/// ```
292pub struct Local<'s, T: Default + Send + 'static> {
293    value: &'s mut T,
294}
295
296impl<'s, T: Default + Send + 'static> Local<'s, T> {
297    pub(crate) fn new(value: &'s mut T) -> Self {
298        Self { value }
299    }
300}
301
302impl<T: Default + Send + std::fmt::Debug + 'static> std::fmt::Debug for Local<'_, T> {
303    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
304        self.value.fmt(f)
305    }
306}
307
308impl<T: Default + Send + 'static> Deref for Local<'_, T> {
309    type Target = T;
310
311    #[inline(always)]
312    fn deref(&self) -> &T {
313        self.value
314    }
315}
316
317impl<T: Default + Send + 'static> DerefMut for Local<'_, T> {
318    #[inline(always)]
319    fn deref_mut(&mut self) -> &mut T {
320        self.value
321    }
322}
323
324impl<T: Default + Send + 'static> Param for Local<'_, T> {
325    type State = T;
326    type Item<'s> = Local<'s, T>;
327
328    fn init(_registry: &Registry) -> T {
329        T::default()
330    }
331
332    #[inline(always)]
333    unsafe fn fetch<'s>(_world: &'s World, state: &'s mut T) -> Local<'s, T> {
334        // SAFETY: The dispatch wrapper (Callback or Stage) owns state
335        // exclusively. Single-threaded dispatch ensures no aliasing.
336        // Lifetime 's is bounded by the handler/stage's run() call.
337        Local::new(state)
338    }
339}
340
341// =============================================================================
342// RegistryRef — read-only access to the type registry during dispatch
343// =============================================================================
344
345/// Read-only access to the [`Registry`] during handler dispatch.
346///
347/// Allows handlers to create new handlers at runtime by calling
348/// [`into_handler`](crate::IntoHandler::into_handler) or
349/// [`into_callback`](crate::IntoCallback::into_callback) on the
350/// borrowed registry.
351///
352/// No [`ResourceId`] needed — the registry is part of [`World`]'s
353/// structure, not a registered resource.
354pub struct RegistryRef<'w> {
355    registry: &'w Registry,
356}
357
358impl Deref for RegistryRef<'_> {
359    type Target = Registry;
360
361    #[inline(always)]
362    fn deref(&self) -> &Registry {
363        self.registry
364    }
365}
366
367impl Param for RegistryRef<'_> {
368    type State = ();
369    type Item<'w> = RegistryRef<'w>;
370
371    fn init(_registry: &Registry) {}
372
373    #[inline(always)]
374    unsafe fn fetch<'w>(world: &'w World, _state: &'w mut ()) -> RegistryRef<'w> {
375        RegistryRef {
376            registry: world.registry(),
377        }
378    }
379}
380
381// =============================================================================
382// Handler<E> — object-safe dispatch trait
383// =============================================================================
384
385/// Object-safe dispatch trait for event handlers.
386///
387/// Analogous to Bevy's `System` trait.
388///
389/// Enables `Box<dyn Handler<E>>` for type-erased heterogeneous dispatch.
390/// Storage and scheduling are the driver's responsibility — this trait
391/// only defines the dispatch interface.
392///
393/// `Send` is required because handlers live in [`World`] (via driver
394/// storage like timer wheels), and `World` is `Send`. All concrete
395/// handler types ([`Callback`], [`HandlerFn`]) satisfy this automatically
396/// for typical usage (function pointers, `ResourceId` state, `Send` context).
397///
398/// Takes `&mut World` — drivers call this directly in their poll loop.
399pub trait Handler<E>: Send {
400    /// Run this handler with the given event.
401    fn run(&mut self, world: &mut World, event: E);
402
403    /// Returns the handler's name.
404    ///
405    /// Default returns `"<unnamed>"`. [`Callback`] captures the
406    /// function's [`type_name`](std::any::type_name) at construction time.
407    fn name(&self) -> &'static str {
408        "<unnamed>"
409    }
410}
411
412impl<E> Handler<E> for Box<dyn Handler<E>> {
413    fn run(&mut self, world: &mut World, event: E) {
414        (**self).run(world, event);
415    }
416
417    fn name(&self) -> &'static str {
418        (**self).name()
419    }
420}
421
422// =============================================================================
423// CtxFree<F> — coherence wrapper for context-free handlers
424// =============================================================================
425
426/// Wrapper that marks a function as context-free.
427///
428/// Prevents coherence overlap between the context-owning and context-free
429/// [`Handler`] impls on [`Callback`]. `CtxFree<F>` is a plain struct and
430/// can never satisfy `FnMut` bounds, so the compiler proves the two impls
431/// are disjoint.
432///
433/// Users don't construct this directly — [`IntoHandler`] wraps the
434/// function automatically.
435#[doc(hidden)]
436pub struct CtxFree<F>(pub(crate) F);
437
438/// Wrapper that marks a function as taking no event parameter.
439///
440/// Used in the `F` position of [`Callback`] and [`Step`](crate::pipeline::Step)
441/// to add `Handler<()>` / `StepCall<()>` impls that don't pass `()` to the
442/// user function. Same coherence trick as [`CtxFree`]: `NoEvent<F>` is a
443/// plain struct and never satisfies `FnMut`, so the new impls are provably
444/// disjoint from the existing ones.
445///
446/// For arity-0 functions (`fn()` with no parameters), the compiler can
447/// distinguish `FnMut()` from `FnMut(())` and picks the no-event impl
448/// automatically. For arities 1+, wrap the function with [`no_event()`]
449/// to disambiguate from the event-taking impls:
450///
451/// ```ignore
452/// // Arity 0 — works directly:
453/// fn standalone() { }
454/// let h = standalone.into_handler(reg);
455///
456/// // Arities 1+ — wrap with no_event():
457/// fn tick(mut counter: ResMut<Counter>) { counter.0 += 1; }
458/// let h = no_event(tick).into_handler(reg);
459/// ```
460pub struct NoEvent<F>(pub(crate) F);
461
462/// Wrap a function for use as a no-event handler, callback, or pipeline step.
463///
464/// When `E = ()`, functions with 1+ parameters are ambiguous: the compiler
465/// can't tell if the last parameter is the event or a [`Param`]. Wrapping
466/// with `no_event()` resolves this — the function receives only its
467/// [`Param`] parameters, no event.
468///
469/// Arity-0 functions (`fn()`) don't need this wrapper — the compiler can
470/// distinguish `FnMut()` from `FnMut(())` automatically.
471///
472/// # Examples
473///
474/// ```
475/// use nexus_rt::{no_event, ResMut, IntoHandler, Handler, WorldBuilder, Resource};
476///
477/// #[derive(Resource)]
478/// struct Counter(u64);
479///
480/// fn tick(mut counter: ResMut<Counter>) {
481///     counter.0 += 1;
482/// }
483///
484/// let mut builder = WorldBuilder::new();
485/// builder.register(Counter(0));
486/// let mut world = builder.build();
487///
488/// let mut h = no_event(tick).into_handler(world.registry());
489/// h.run(&mut world, ());
490///
491/// assert_eq!(world.resource::<Counter>().0, 1);
492/// ```
493pub fn no_event<F>(f: F) -> NoEvent<F> {
494    NoEvent(f)
495}
496
497/// Type alias for context-free handlers (no owned context).
498///
499/// This is `Callback<(), CtxFree<F>, Params>` — the `ctx: ()` field
500/// is a ZST (zero bytes), identical codegen.
501///
502/// Created by [`IntoHandler::into_handler`]. Use [`HandlerFn`] in type
503/// annotations when you need to name the concrete type rather than
504/// `Box<dyn Handler<E>>`.
505pub type HandlerFn<F, Params> = Callback<(), CtxFree<F>, Params>;
506
507// =============================================================================
508// IntoHandler — conversion trait
509// =============================================================================
510
511/// Converts a plain function into a [`Handler`].
512///
513/// Analogous to Bevy's `IntoSystem`.
514///
515/// Event `E` is always the last function parameter. Everything before
516/// it is resolved as [`Param`] from a [`Registry`].
517///
518/// # Named functions only
519///
520/// Closures do not work with `IntoHandler` due to Rust's HRTB inference
521/// limitations with GATs. Use named `fn` items instead. This is the same
522/// limitation as Bevy's system registration.
523///
524/// # Factory functions and `use<>` (Rust 2024)
525///
526/// If you write a function that takes `&Registry` and returns
527/// `impl Handler<E>`, Rust 2024 captures the registry borrow in the
528/// return type. Add `+ use<...>` listing only the type parameters the
529/// handler actually holds:
530///
531/// ```ignore
532/// fn build_handler<C: Config>(
533///     reg: &Registry,
534/// ) -> impl Handler<Order> + use<C> {
535///     process_order::<C>.into_handler(reg)
536/// }
537/// ```
538///
539/// See the [crate-level docs](crate#returning-impl-handler-from-functions-rust-2024)
540/// for details.
541///
542/// # Examples
543///
544/// ```
545/// use nexus_rt::{Res, ResMut, IntoHandler, WorldBuilder, Resource};
546///
547/// #[derive(Resource)]
548/// struct Counter(u64);
549/// #[derive(Resource)]
550/// struct Flag(bool);
551///
552/// fn tick(counter: Res<Counter>, mut flag: ResMut<Flag>, event: u32) {
553///     if event > 0 {
554///         flag.0 = true;
555///     }
556/// }
557///
558/// let mut builder = WorldBuilder::new();
559/// builder.register(Counter(0));
560/// builder.register(Flag(false));
561///
562/// let mut handler = tick.into_handler(builder.registry());
563/// ```
564#[diagnostic::on_unimplemented(
565    message = "this function cannot be converted into a handler",
566    note = "handler signature: `fn(Res<A>, ResMut<B>, ..., Event)` — resources first, event last",
567    note = "for Handler<()> with params, wrap with `no_event(fn_name)` to omit the event parameter",
568    note = "closures with resource parameters (Res<T>, ResMut<T>) are not supported — use a named `fn`",
569    note = "arity-0 closures (`fn(Event)` with no resources) ARE supported"
570)]
571pub trait IntoHandler<E, Params> {
572    /// The concrete handler type produced.
573    type Handler: Handler<E> + 'static;
574
575    /// Convert this function into a handler, resolving parameters from the registry.
576    #[must_use = "the handler must be stored or dispatched — discarding it does nothing"]
577    fn into_handler(self, registry: &Registry) -> Self::Handler;
578}
579
580// =============================================================================
581// Per-arity impls via macro — context-free path (Callback<(), CtxFree<F>, P>)
582// =============================================================================
583
584// Arity 0: fn(E) — event-only handler, no resource params.
585impl<E, F: FnMut(E) + Send + 'static> IntoHandler<E, ()> for F {
586    type Handler = Callback<(), CtxFree<F>, ()>;
587
588    fn into_handler(self, registry: &Registry) -> Self::Handler {
589        Callback {
590            ctx: (),
591            f: CtxFree(self),
592            state: <() as Param>::init(registry),
593            name: std::any::type_name::<F>(),
594        }
595    }
596}
597
598impl<E, F: FnMut(E) + Send + 'static> Handler<E> for Callback<(), CtxFree<F>, ()> {
599    fn run(&mut self, _world: &mut World, event: E) {
600        (self.f.0)(event);
601    }
602
603    fn name(&self) -> &'static str {
604        self.name
605    }
606}
607
608macro_rules! impl_into_handler {
609    ($($P:ident),+) => {
610        impl<E, F: Send + 'static, $($P: Param + 'static),+> IntoHandler<E, ($($P,)+)> for F
611        where
612            // Double-bound pattern (from Bevy):
613            // - First bound: compiler uses P directly to infer Param
614            //   types from the function signature (GATs aren't injective,
615            //   so P::Item<'w> alone can't determine P).
616            // - Second bound: verifies the function is callable with the
617            //   fetched items at any lifetime.
618            for<'a> &'a mut F: FnMut($($P,)+ E) + FnMut($($P::Item<'a>,)+ E),
619        {
620            type Handler = Callback<(), CtxFree<F>, ($($P,)+)>;
621
622            fn into_handler(self, registry: &Registry) -> Self::Handler {
623                let state = <($($P,)+) as Param>::init(registry);
624                {
625                    #[allow(non_snake_case)]
626                    let ($($P,)+) = &state;
627                    registry.check_access(&[
628                        $(
629                            (<$P as Param>::resource_id($P),
630                             std::any::type_name::<$P>()),
631                        )+
632                    ]);
633                }
634                Callback {
635                    ctx: (),
636                    f: CtxFree(self),
637                    state,
638                    name: std::any::type_name::<F>(),
639                }
640            }
641        }
642
643        impl<E, F: Send + 'static, $($P: Param + 'static),+> Handler<E>
644            for Callback<(), CtxFree<F>, ($($P,)+)>
645        where
646            for<'a> &'a mut F: FnMut($($P,)+ E) + FnMut($($P::Item<'a>,)+ E),
647        {
648            #[allow(non_snake_case)]
649            fn run(&mut self, world: &mut World, event: E) {
650                // Helper binds the HRTB lifetime at a concrete call site.
651                fn call_inner<$($P,)+ Ev>(
652                    mut f: impl FnMut($($P,)+ Ev),
653                    $($P: $P,)+
654                    event: Ev,
655                ) {
656                    f($($P,)+ event);
657                }
658
659                // SAFETY: state was produced by init() on the same registry
660                // that built this world. Single-threaded sequential dispatch
661                // ensures no mutable aliasing across params.
662                #[cfg(debug_assertions)]
663                world.clear_borrows();
664                let ($($P,)+) = unsafe {
665                    <($($P,)+) as Param>::fetch(world, &mut self.state)
666                };
667                call_inner(&mut self.f.0, $($P,)+ event);
668            }
669
670            fn name(&self) -> &'static str {
671                self.name
672            }
673        }
674    };
675}
676
677all_tuples!(impl_into_handler);
678
679// =============================================================================
680// No-event impls — Handler<()> without trailing `_: ()` parameter
681// =============================================================================
682
683// Arity 0: fn() → Handler<()>
684impl<F: FnMut() + Send + 'static> IntoHandler<(), NoEvent<F>> for F {
685    type Handler = Callback<(), CtxFree<NoEvent<F>>, ()>;
686
687    fn into_handler(self, registry: &Registry) -> Self::Handler {
688        Callback {
689            ctx: (),
690            f: CtxFree(NoEvent(self)),
691            state: <() as Param>::init(registry),
692            name: std::any::type_name::<F>(),
693        }
694    }
695}
696
697impl<F: FnMut() + Send + 'static> Handler<()> for Callback<(), CtxFree<NoEvent<F>>, ()> {
698    fn run(&mut self, _world: &mut World, _event: ()) {
699        (self.f.0.0)();
700    }
701
702    fn name(&self) -> &'static str {
703        self.name
704    }
705}
706
707macro_rules! impl_into_handler_no_event {
708    ($($P:ident),+) => {
709        impl<F: Send + 'static, $($P: Param + 'static),+>
710            IntoHandler<(), ($($P,)+)> for NoEvent<F>
711        where
712            for<'a> &'a mut F: FnMut($($P,)+) + FnMut($($P::Item<'a>,)+),
713        {
714            type Handler = Callback<(), CtxFree<NoEvent<F>>, ($($P,)+)>;
715
716            fn into_handler(self, registry: &Registry) -> Self::Handler {
717                let state = <($($P,)+) as Param>::init(registry);
718                {
719                    #[allow(non_snake_case)]
720                    let ($($P,)+) = &state;
721                    registry.check_access(&[
722                        $(
723                            (<$P as Param>::resource_id($P),
724                             std::any::type_name::<$P>()),
725                        )+
726                    ]);
727                }
728                Callback {
729                    ctx: (),
730                    f: CtxFree(self),
731                    state,
732                    name: std::any::type_name::<F>(),
733                }
734            }
735        }
736
737        impl<F: Send + 'static, $($P: Param + 'static),+> Handler<()>
738            for Callback<(), CtxFree<NoEvent<F>>, ($($P,)+)>
739        where
740            for<'a> &'a mut F: FnMut($($P,)+) + FnMut($($P::Item<'a>,)+),
741        {
742            #[allow(non_snake_case)]
743            fn run(&mut self, world: &mut World, _event: ()) {
744                fn call_inner<$($P),+>(
745                    mut f: impl FnMut($($P),+),
746                    $($P: $P,)+
747                ) {
748                    f($($P),+)
749                }
750
751                #[cfg(debug_assertions)]
752                world.clear_borrows();
753                let ($($P,)+) = unsafe {
754                    <($($P,)+) as Param>::fetch(world, &mut self.state)
755                };
756                call_inner(&mut self.f.0 .0, $($P,)+);
757            }
758
759            fn name(&self) -> &'static str {
760                self.name
761            }
762        }
763    };
764}
765
766all_tuples!(impl_into_handler_no_event);
767
768// =============================================================================
769// Opaque — marker for closures with unresolved dependencies
770// =============================================================================
771
772/// Marker occupying the `Params` position in step and handler traits to
773/// indicate that a closure manages its own resource access via
774/// `world.resource::<T>()` rather than through [`Param`] resolution.
775///
776/// `Opaque` is **not** a [`Param`]. It exists solely so the compiler can
777/// distinguish three disjoint impl tiers without coherence conflicts:
778///
779/// | `Params` type | Function shape | Resolution |
780/// |---|---|---|
781/// | `()` | `FnMut(E)` | No resources needed |
782/// | `(P0,)` … `(P0..P7,)` | `fn(Res<A>, ResMut<B>, E)` | Build-time [`Param::init`], dispatch-time [`Param::fetch`] |
783/// | `Opaque` | `FnMut(&mut World, E)` | None — caller owns all access |
784///
785/// Because `&mut World` does not implement `Param`, the `Opaque` impls
786/// are always disjoint from the arity-based impls — the compiler infers
787/// `Params` unambiguously from the closure/function signature.
788///
789/// # When to use
790///
791/// Prefer named functions with [`Param`] parameters — they resolve to a
792/// direct pointer dereference per resource (single deref, no HashMap
793/// lookup). Use `Opaque` closures as an escape hatch when:
794///
795/// - You need **conditional** resource access (different resources
796///   depending on runtime state).
797/// - You need access to a resource whose type isn't known at build time.
798/// - You're prototyping and want to defer the named-function refactor.
799///
800/// # Example
801///
802/// ```ignore
803/// // Named function — preferred, hot path:
804/// pipeline.guard(check_risk, &reg)    // fn(Res<Config>, &Order) -> bool
805///
806/// // Arity-0 closure — no World access:
807/// pipeline.guard(|o: &Order| o.price > 100.0, &reg)
808///
809/// // Opaque closure — escape hatch, HashMap lookups:
810/// pipeline.guard(|w: &mut World, o: &Order| {
811///     let cfg = w.resource::<Config>();
812///     o.price > cfg.threshold
813/// }, &reg)
814/// ```
815pub struct Opaque;
816
817/// Marker type for handlers whose parameters are already resolved.
818///
819/// Used by the blanket [`IntoHandler`] impl for any [`Handler<E>`].
820/// Enables passing already-built pipelines, templates, callbacks, and
821/// `Box<dyn Handler<E>>` where `IntoHandler` is expected.
822///
823/// The `registry` argument to [`IntoHandler::into_handler`] is ignored —
824/// the handler's parameters were resolved against the registry it was
825/// originally built with. Callers must ensure the handler is run against
826/// the same [`World`] it was resolved for.
827///
828/// When returning a resolved handler from a factory function, the Rust
829/// 2024 `+ use<...>` annotation applies — see [`IntoHandler`] docs.
830///
831/// Users never need to name this type — it's inferred automatically.
832pub struct Resolved;
833
834impl<E, H: Handler<E> + 'static> IntoHandler<E, Resolved> for H {
835    type Handler = H;
836
837    fn into_handler(self, _registry: &Registry) -> H {
838        self
839    }
840}
841
842// =============================================================================
843// OpaqueHandler — Handler<E> for FnMut(&mut World, E) closures
844// =============================================================================
845
846/// Wrapper for closures that receive `&mut World` directly as a [`Handler`].
847///
848/// Created by [`IntoHandler::into_handler`] when the function signature is
849/// `FnMut(&mut World, E)`. The closure handles its own resource access —
850/// no [`Param`] resolution occurs.
851///
852/// Prefer named functions with [`Param`] resolution for hot-path handlers.
853/// `OpaqueHandler` is an escape hatch for cases where dynamic or conditional
854/// resource access is needed.
855pub struct OpaqueHandler<F> {
856    f: F,
857    name: &'static str,
858}
859
860impl<E, F: FnMut(&mut World, E) + Send + 'static> Handler<E> for OpaqueHandler<F> {
861    fn run(&mut self, world: &mut World, event: E) {
862        (self.f)(world, event);
863    }
864
865    fn name(&self) -> &'static str {
866        self.name
867    }
868}
869
870impl<E, F: FnMut(&mut World, E) + Send + 'static> IntoHandler<E, Opaque> for F {
871    type Handler = OpaqueHandler<F>;
872
873    fn into_handler(self, _registry: &Registry) -> Self::Handler {
874        OpaqueHandler {
875            f: self,
876            name: std::any::type_name::<F>(),
877        }
878    }
879}
880
881// =============================================================================
882// Tests
883// =============================================================================
884
885#[cfg(test)]
886mod tests {
887    use super::*;
888    use crate::WorldBuilder;
889    use crate::world::Sequence;
890
891    // -- Param tests ----------------------------------------------------
892
893    #[test]
894    fn res_param() {
895        let mut builder = WorldBuilder::new();
896        builder.register::<u64>(42);
897        let mut world = builder.build();
898
899        let mut state = <Res<u64> as Param>::init(world.registry_mut());
900        // SAFETY: state from init on same registry, no aliasing.
901        let res = unsafe { <Res<u64> as Param>::fetch(&world, &mut state) };
902        assert_eq!(*res, 42);
903    }
904
905    #[test]
906    fn res_mut_param() {
907        let mut builder = WorldBuilder::new();
908        builder.register::<u64>(1);
909        let mut world = builder.build();
910
911        let mut state = <ResMut<u64> as Param>::init(world.registry_mut());
912        // SAFETY: state from init on same registry, no aliasing.
913        unsafe {
914            let mut res = <ResMut<u64> as Param>::fetch(&world, &mut state);
915            *res = 99;
916        }
917        // New dispatch phase — previous borrows dropped above.
918        #[cfg(debug_assertions)]
919        world.clear_borrows();
920        unsafe {
921            let mut read_state = <Res<u64> as Param>::init(world.registry_mut());
922            let res = <Res<u64> as Param>::fetch(&world, &mut read_state);
923            assert_eq!(*res, 99);
924        }
925    }
926
927    #[test]
928    fn tuple_param() {
929        let mut builder = WorldBuilder::new();
930        builder.register::<u64>(10);
931        builder.register::<bool>(true);
932        let mut world = builder.build();
933
934        let mut state = <(Res<u64>, ResMut<bool>) as Param>::init(world.registry_mut());
935        // SAFETY: different types, no aliasing.
936        unsafe {
937            let (counter, mut flag) =
938                <(Res<u64>, ResMut<bool>) as Param>::fetch(&world, &mut state);
939            assert_eq!(*counter, 10);
940            assert!(*flag);
941            *flag = false;
942        }
943        // New dispatch phase — previous borrows dropped above.
944        #[cfg(debug_assertions)]
945        world.clear_borrows();
946        unsafe {
947            let mut read_state = <Res<bool> as Param>::init(world.registry_mut());
948            let res = <Res<bool> as Param>::fetch(&world, &mut read_state);
949            assert!(!*res);
950        }
951    }
952
953    #[test]
954    fn empty_tuple_param() {
955        let mut world = WorldBuilder::new().build();
956        <() as Param>::init(world.registry_mut());
957        // SAFETY: no params to alias.
958        unsafe { <() as Param>::fetch(&world, &mut ()) };
959    }
960
961    // -- Handler dispatch tests -----------------------------------------------
962
963    fn event_only_handler(event: u32) {
964        assert_eq!(event, 42);
965    }
966
967    #[test]
968    fn event_only_dispatch() {
969        let mut world = WorldBuilder::new().build();
970        let mut sys = event_only_handler.into_handler(world.registry_mut());
971        sys.run(&mut world, 42u32);
972    }
973
974    fn one_res_handler(counter: Res<u64>, event: u32) {
975        assert_eq!(*counter, 10);
976        assert_eq!(event, 5);
977    }
978
979    #[test]
980    fn one_res_and_event() {
981        let mut builder = WorldBuilder::new();
982        builder.register::<u64>(10);
983        let mut world = builder.build();
984
985        let mut sys = one_res_handler.into_handler(world.registry_mut());
986        sys.run(&mut world, 5u32);
987    }
988
989    fn two_res_handler(counter: Res<u64>, flag: Res<bool>, event: u32) {
990        assert_eq!(*counter, 10);
991        assert!(*flag);
992        assert_eq!(event, 7);
993    }
994
995    #[test]
996    fn two_res_and_event() {
997        let mut builder = WorldBuilder::new();
998        builder.register::<u64>(10);
999        builder.register::<bool>(true);
1000        let mut world = builder.build();
1001
1002        let mut sys = two_res_handler.into_handler(world.registry_mut());
1003        sys.run(&mut world, 7u32);
1004    }
1005
1006    fn accumulate(mut counter: ResMut<u64>, event: u64) {
1007        *counter += event;
1008    }
1009
1010    #[test]
1011    fn mutation_through_res_mut() {
1012        let mut builder = WorldBuilder::new();
1013        builder.register::<u64>(0);
1014        let mut world = builder.build();
1015
1016        let mut sys = accumulate.into_handler(world.registry_mut());
1017
1018        sys.run(&mut world, 10u64);
1019        sys.run(&mut world, 5u64);
1020
1021        assert_eq!(*world.resource::<u64>(), 15);
1022    }
1023
1024    fn add_handler(mut counter: ResMut<u64>, event: u64) {
1025        *counter += event;
1026    }
1027
1028    fn mul_handler(mut counter: ResMut<u64>, event: u64) {
1029        *counter *= event;
1030    }
1031
1032    #[test]
1033    fn box_dyn_type_erasure() {
1034        let mut builder = WorldBuilder::new();
1035        builder.register::<u64>(0);
1036        let mut world = builder.build();
1037
1038        let sys_a = add_handler.into_handler(world.registry_mut());
1039        let sys_b = mul_handler.into_handler(world.registry_mut());
1040
1041        let mut handlers: Vec<Box<dyn Handler<u64>>> = vec![Box::new(sys_a), Box::new(sys_b)];
1042
1043        for h in &mut handlers {
1044            h.run(&mut world, 3u64);
1045        }
1046        // 0 + 3 = 3, then 3 * 3 = 9
1047        assert_eq!(*world.resource::<u64>(), 9);
1048    }
1049
1050    // -- Local<T> tests -------------------------------------------------------
1051
1052    fn local_counter(mut count: Local<u64>, _event: u32) {
1053        *count += 1;
1054    }
1055
1056    #[test]
1057    fn local_default_init() {
1058        let mut world = WorldBuilder::new().build();
1059        let mut sys = local_counter.into_handler(world.registry_mut());
1060        // Ran once — count should be 1 (started at 0). No panic means init worked.
1061        sys.run(&mut world, 1u32);
1062    }
1063
1064    #[test]
1065    fn local_persists_across_runs() {
1066        let mut builder = WorldBuilder::new();
1067        builder.register::<u64>(0);
1068        let mut world = builder.build();
1069
1070        fn accumulate_local(mut count: Local<u64>, mut total: ResMut<u64>, _event: u32) {
1071            *count += 1;
1072            *total = *count;
1073        }
1074
1075        let mut sys = accumulate_local.into_handler(world.registry_mut());
1076        sys.run(&mut world, 0u32);
1077        sys.run(&mut world, 0u32);
1078        sys.run(&mut world, 0u32);
1079
1080        assert_eq!(*world.resource::<u64>(), 3);
1081    }
1082
1083    #[test]
1084    fn local_independent_per_handler() {
1085        let mut builder = WorldBuilder::new();
1086        builder.register::<u64>(0);
1087        let mut world = builder.build();
1088
1089        fn inc_local(mut count: Local<u64>, mut total: ResMut<u64>, _event: u32) {
1090            *count += 1;
1091            *total += *count;
1092        }
1093
1094        let mut sys_a = inc_local.into_handler(world.registry_mut());
1095        let mut sys_b = inc_local.into_handler(world.registry_mut());
1096
1097        sys_a.run(&mut world, 0u32); // local=1, total=0+1=1
1098        sys_b.run(&mut world, 0u32); // local=1, total=1+1=2
1099        sys_a.run(&mut world, 0u32); // local=2, total=2+2=4
1100
1101        assert_eq!(*world.resource::<u64>(), 4);
1102    }
1103
1104    // -- Option<Res<T>> / Option<ResMut<T>> tests -----------------------------
1105
1106    #[test]
1107    fn option_res_none_when_missing() {
1108        let mut world = WorldBuilder::new().build();
1109        let mut state = <Option<Res<u64>> as Param>::init(world.registry_mut());
1110        let opt = unsafe { <Option<Res<u64>> as Param>::fetch(&world, &mut state) };
1111        assert!(opt.is_none());
1112    }
1113
1114    #[test]
1115    fn option_res_some_when_present() {
1116        let mut builder = WorldBuilder::new();
1117        builder.register::<u64>(42);
1118        let mut world = builder.build();
1119
1120        let mut state = <Option<Res<u64>> as Param>::init(world.registry_mut());
1121        let opt = unsafe { <Option<Res<u64>> as Param>::fetch(&world, &mut state) };
1122        assert_eq!(*opt.unwrap(), 42);
1123    }
1124
1125    #[test]
1126    fn option_res_mut_some_when_present() {
1127        let mut builder = WorldBuilder::new();
1128        builder.register::<u64>(1);
1129        let mut world = builder.build();
1130
1131        let mut state = <Option<ResMut<u64>> as Param>::init(world.registry_mut());
1132        unsafe {
1133            let opt = <Option<ResMut<u64>> as Param>::fetch(&world, &mut state);
1134            *opt.unwrap() = 99;
1135        }
1136        #[cfg(debug_assertions)]
1137        world.clear_borrows();
1138        unsafe {
1139            let mut read_state = <Res<u64> as Param>::init(world.registry_mut());
1140            let res = <Res<u64> as Param>::fetch(&world, &mut read_state);
1141            assert_eq!(*res, 99);
1142        }
1143    }
1144
1145    fn optional_handler(opt: Option<Res<String>>, _event: u32) {
1146        assert!(opt.is_none());
1147    }
1148
1149    #[test]
1150    fn option_in_handler() {
1151        let mut world = WorldBuilder::new().build();
1152        let mut sys = optional_handler.into_handler(world.registry_mut());
1153        sys.run(&mut world, 0u32);
1154    }
1155
1156    // -- Access conflict detection ----------------------------------------
1157
1158    #[test]
1159    #[should_panic(expected = "conflicting access")]
1160    fn duplicate_res_panics() {
1161        let mut builder = WorldBuilder::new();
1162        builder.register::<u64>(0);
1163        let mut world = builder.build();
1164
1165        fn bad(a: Res<u64>, b: Res<u64>) {
1166            let _ = (*a, *b);
1167        }
1168
1169        let _sys = no_event(bad).into_handler(world.registry_mut());
1170    }
1171
1172    #[test]
1173    #[should_panic(expected = "conflicting access")]
1174    fn duplicate_res_mut_panics() {
1175        let mut builder = WorldBuilder::new();
1176        builder.register::<u64>(0);
1177        let mut world = builder.build();
1178
1179        fn bad(a: ResMut<u64>, b: ResMut<u64>) {
1180            let _ = (&*a, &*b);
1181        }
1182
1183        let _sys = no_event(bad).into_handler(world.registry_mut());
1184    }
1185
1186    #[test]
1187    #[should_panic(expected = "conflicting access")]
1188    fn duplicate_mixed_panics() {
1189        let mut builder = WorldBuilder::new();
1190        builder.register::<u64>(0);
1191        let mut world = builder.build();
1192
1193        fn bad(a: Res<u64>, b: ResMut<u64>) {
1194            let _ = (*a, &*b);
1195        }
1196
1197        let _sys = no_event(bad).into_handler(world.registry_mut());
1198    }
1199
1200    #[test]
1201    fn different_types_no_conflict() {
1202        let mut builder = WorldBuilder::new();
1203        builder.register::<u64>(0);
1204        builder.register::<u32>(0);
1205        let mut world = builder.build();
1206
1207        fn ok(a: Res<u64>, b: ResMut<u32>) {
1208            let _ = (*a, &*b);
1209        }
1210
1211        let _sys = no_event(ok).into_handler(world.registry_mut());
1212    }
1213
1214    #[test]
1215    fn local_no_conflict() {
1216        let mut builder = WorldBuilder::new();
1217        builder.register::<u64>(0);
1218        let mut world = builder.build();
1219
1220        fn ok(local: Local<u64>, val: ResMut<u64>) {
1221            let _ = (&*local, &*val);
1222        }
1223
1224        let _sys = no_event(ok).into_handler(world.registry_mut());
1225    }
1226
1227    // -- OpaqueHandler tests --------------------------------------------------
1228
1229    #[test]
1230    fn opaque_handler_dispatch() {
1231        let mut builder = WorldBuilder::new();
1232        builder.register::<u64>(0);
1233        let mut world = builder.build();
1234
1235        let opaque_fn = |w: &mut World, event: u64| {
1236            let current = *w.resource::<u64>();
1237            *w.resource_mut::<u64>() = current + event;
1238        };
1239
1240        let mut h = opaque_fn.into_handler(world.registry_mut());
1241        h.run(&mut world, 10u64);
1242        h.run(&mut world, 5u64);
1243
1244        assert_eq!(*world.resource::<u64>(), 15);
1245    }
1246
1247    #[test]
1248    fn opaque_handler_boxed() {
1249        let mut builder = WorldBuilder::new();
1250        builder.register::<u64>(0);
1251        let mut world = builder.build();
1252
1253        let opaque_fn = |w: &mut World, event: u64| {
1254            *w.resource_mut::<u64>() += event;
1255        };
1256
1257        let mut h: Box<dyn Handler<u64>> = Box::new(opaque_fn.into_handler(world.registry_mut()));
1258        h.run(&mut world, 7u64);
1259
1260        assert_eq!(*world.resource::<u64>(), 7);
1261    }
1262
1263    // -- Seq / SeqMut tests ------------------------------------------------
1264
1265    #[test]
1266    fn seq_reads_current() {
1267        fn check(seq: Seq, mut out: ResMut<i64>) {
1268            *out = seq.get().as_i64();
1269        }
1270
1271        let mut builder = WorldBuilder::new();
1272        builder.register::<i64>(0);
1273        let mut world = builder.build();
1274        world.next_sequence(); // seq=1
1275
1276        let mut handler = no_event(check).into_handler(world.registry_mut());
1277        handler.run(&mut world, ());
1278        assert_eq!(*world.resource::<i64>(), 1);
1279    }
1280
1281    #[test]
1282    fn seq_mut_advances() {
1283        fn stamp(mut seq: SeqMut, mut counter: ResMut<u64>) {
1284            let a = seq.advance();
1285            let b = seq.advance();
1286            *counter = a.as_i64() as u64 * 100 + b.as_i64() as u64;
1287        }
1288
1289        let mut builder = WorldBuilder::new();
1290        builder.register::<u64>(0);
1291        let mut world = builder.build();
1292        // seq starts at 0, handler advances twice → 1, 2
1293        let mut handler = no_event(stamp).into_handler(world.registry_mut());
1294        handler.run(&mut world, ());
1295        assert_eq!(*world.resource::<u64>(), 100 + 2);
1296        // World sequence is now 2
1297        assert_eq!(world.current_sequence(), Sequence(2));
1298    }
1299
1300    #[test]
1301    fn seq_mut_persistent_across_dispatches() {
1302        fn advance(mut seq: SeqMut) {
1303            seq.advance();
1304        }
1305
1306        let builder = WorldBuilder::new();
1307        let mut world = builder.build();
1308        let mut handler = no_event(advance).into_handler(world.registry_mut());
1309        handler.run(&mut world, ());
1310        handler.run(&mut world, ());
1311        handler.run(&mut world, ());
1312        assert_eq!(world.current_sequence(), Sequence(3));
1313    }
1314
1315    // -- Seq position / arity coverage ------------------------------------
1316
1317    #[test]
1318    fn seq_only_param() {
1319        fn handle(seq: Seq) {
1320            assert!(seq.get().as_i64() >= 0);
1321        }
1322
1323        let builder = WorldBuilder::new();
1324        let mut world = builder.build();
1325        let mut h = no_event(handle).into_handler(world.registry_mut());
1326        h.run(&mut world, ());
1327    }
1328
1329    #[test]
1330    fn seq_first_with_res() {
1331        fn handle(seq: Seq, config: Res<u64>, mut out: ResMut<i64>) {
1332            *out = seq.get().as_i64() + *config as i64;
1333        }
1334
1335        let mut builder = WorldBuilder::new();
1336        builder.register::<u64>(100);
1337        builder.register::<i64>(0);
1338        let mut world = builder.build();
1339        world.next_sequence();
1340        let mut h = no_event(handle).into_handler(world.registry_mut());
1341        h.run(&mut world, ());
1342        assert_eq!(*world.resource::<i64>(), 101);
1343    }
1344
1345    #[test]
1346    fn seq_middle_position() {
1347        fn handle(config: Res<u64>, seq: Seq, mut out: ResMut<i64>) {
1348            *out = *config as i64 + seq.get().as_i64();
1349        }
1350
1351        let mut builder = WorldBuilder::new();
1352        builder.register::<u64>(50);
1353        builder.register::<i64>(0);
1354        let mut world = builder.build();
1355        world.next_sequence(); // seq=1
1356        let mut h = no_event(handle).into_handler(world.registry_mut());
1357        h.run(&mut world, ());
1358        assert_eq!(*world.resource::<i64>(), 51);
1359    }
1360
1361    #[test]
1362    fn seq_last_position() {
1363        fn handle(mut out: ResMut<i64>, seq: Seq) {
1364            *out = seq.get().as_i64();
1365        }
1366
1367        let mut builder = WorldBuilder::new();
1368        builder.register::<i64>(0);
1369        let mut world = builder.build();
1370        world.next_sequence();
1371        world.next_sequence(); // seq=2
1372        let mut h = no_event(handle).into_handler(world.registry_mut());
1373        h.run(&mut world, ());
1374        assert_eq!(*world.resource::<i64>(), 2);
1375    }
1376
1377    #[test]
1378    fn seq_mut_only_param() {
1379        fn handle(mut seq: SeqMut) {
1380            seq.advance();
1381        }
1382
1383        let builder = WorldBuilder::new();
1384        let mut world = builder.build();
1385        let mut h = no_event(handle).into_handler(world.registry_mut());
1386        h.run(&mut world, ());
1387        assert_eq!(world.current_sequence(), Sequence(1));
1388    }
1389
1390    #[test]
1391    fn seq_mut_first_with_res() {
1392        fn handle(mut seq: SeqMut, mut out: ResMut<i64>) {
1393            let s = seq.advance();
1394            *out = s.0;
1395        }
1396
1397        let mut builder = WorldBuilder::new();
1398        builder.register::<i64>(0);
1399        let mut world = builder.build();
1400        let mut h = no_event(handle).into_handler(world.registry_mut());
1401        h.run(&mut world, ());
1402        assert_eq!(*world.resource::<i64>(), 1);
1403    }
1404
1405    #[test]
1406    fn seq_mut_middle_position() {
1407        fn handle(config: Res<u64>, mut seq: SeqMut, mut out: ResMut<i64>) {
1408            let s = seq.advance();
1409            *out = s.0 + *config as i64;
1410        }
1411
1412        let mut builder = WorldBuilder::new();
1413        builder.register::<u64>(10);
1414        builder.register::<i64>(0);
1415        let mut world = builder.build();
1416        let mut h = no_event(handle).into_handler(world.registry_mut());
1417        h.run(&mut world, ());
1418        assert_eq!(*world.resource::<i64>(), 11);
1419    }
1420
1421    #[test]
1422    fn seq_mut_last_position() {
1423        fn handle(mut out: ResMut<i64>, mut seq: SeqMut) {
1424            let s = seq.advance();
1425            *out = s.0;
1426        }
1427
1428        let mut builder = WorldBuilder::new();
1429        builder.register::<i64>(0);
1430        let mut world = builder.build();
1431        let mut h = no_event(handle).into_handler(world.registry_mut());
1432        h.run(&mut world, ());
1433        assert_eq!(*world.resource::<i64>(), 1);
1434    }
1435
1436    #[test]
1437    fn seq_mut_multiple_advances_in_one_dispatch() {
1438        fn handle(mut seq: SeqMut, mut out: ResMut<Vec<i64>>) {
1439            out.push(seq.advance().0);
1440            out.push(seq.advance().0);
1441            out.push(seq.advance().0);
1442        }
1443
1444        let mut builder = WorldBuilder::new();
1445        builder.register::<Vec<i64>>(Vec::new());
1446        let mut world = builder.build();
1447        let mut h = no_event(handle).into_handler(world.registry_mut());
1448        h.run(&mut world, ());
1449        assert_eq!(*world.resource::<Vec<i64>>(), vec![1, 2, 3]);
1450        assert_eq!(world.current_sequence(), Sequence(3));
1451    }
1452
1453    // =========================================================================
1454    // Resolved — blanket IntoHandler for Handler<E>
1455    // =========================================================================
1456
1457    #[test]
1458    fn concrete_handler_satisfies_into_handler() {
1459        // A concrete Handler<E> type should satisfy IntoHandler<E, Resolved>
1460        fn accept_into_handler<E, P>(h: impl IntoHandler<E, P>, reg: &Registry) -> impl Handler<E> {
1461            h.into_handler(reg)
1462        }
1463
1464        let mut builder = WorldBuilder::new();
1465        builder.register::<u64>(0);
1466        let world = builder.build();
1467
1468        fn bump(mut val: ResMut<u64>, event: u64) {
1469            *val += event;
1470        }
1471
1472        // Build a concrete handler, pass it where IntoHandler is expected
1473        let handler = bump.into_handler(world.registry());
1474        let _resolved = accept_into_handler(handler, world.registry());
1475    }
1476
1477    #[test]
1478    fn handler_impl_into_handler_dispatches() {
1479        let mut builder = WorldBuilder::new();
1480        builder.register::<u64>(0);
1481
1482        fn add_event(mut val: ResMut<u64>, event: u64) {
1483            *val += event;
1484        }
1485
1486        // Build handler from function, then re-pass as IntoHandler via Resolved
1487        let handler = add_event.into_handler(builder.registry());
1488        let mut resolved = handler.into_handler(builder.registry());
1489        let mut world = builder.build();
1490
1491        resolved.run(&mut world, 42);
1492        assert_eq!(*world.resource::<u64>(), 42);
1493    }
1494
1495    // =========================================================================
1496    // NoEvent — Handler<()> without trailing `_: ()`
1497    // =========================================================================
1498
1499    fn no_event_standalone() {}
1500
1501    #[test]
1502    fn no_event_arity_0() {
1503        let mut world = WorldBuilder::new().build();
1504        let mut h = no_event_standalone.into_handler(world.registry_mut());
1505        h.run(&mut world, ());
1506    }
1507
1508    fn no_event_tick(mut counter: ResMut<u64>) {
1509        *counter += 1;
1510    }
1511
1512    #[test]
1513    fn no_event_arity_1() {
1514        use crate::no_event;
1515
1516        let mut builder = WorldBuilder::new();
1517        builder.register::<u64>(0);
1518        let mut world = builder.build();
1519
1520        let mut h = no_event(no_event_tick).into_handler(world.registry_mut());
1521        h.run(&mut world, ());
1522        h.run(&mut world, ());
1523        assert_eq!(*world.resource::<u64>(), 2);
1524    }
1525
1526    fn no_event_two_params(src: Res<u32>, mut dst: ResMut<u64>) {
1527        *dst += *src as u64;
1528    }
1529
1530    #[test]
1531    fn no_event_arity_2() {
1532        use crate::no_event;
1533
1534        let mut builder = WorldBuilder::new();
1535        builder.register::<u32>(7);
1536        builder.register::<u64>(0);
1537        let mut world = builder.build();
1538
1539        let mut h = no_event(no_event_two_params).into_handler(world.registry_mut());
1540        h.run(&mut world, ());
1541        assert_eq!(*world.resource::<u64>(), 7);
1542    }
1543
1544    #[test]
1545    fn no_event_boxed() {
1546        use crate::no_event;
1547
1548        let mut builder = WorldBuilder::new();
1549        builder.register::<u64>(0);
1550        let mut world = builder.build();
1551
1552        let h = no_event(no_event_tick).into_handler(world.registry_mut());
1553        let mut boxed: Box<dyn Handler<()>> = Box::new(h);
1554        boxed.run(&mut world, ());
1555        assert_eq!(*world.resource::<u64>(), 1);
1556    }
1557
1558    #[test]
1559    fn no_event_coexists_with_event_handler() {
1560        use crate::no_event;
1561
1562        let mut builder = WorldBuilder::new();
1563        builder.register::<u64>(0);
1564        let mut world = builder.build();
1565
1566        // No-event handler
1567        let mut h1 = no_event(no_event_tick).into_handler(world.registry_mut());
1568        h1.run(&mut world, ());
1569        assert_eq!(*world.resource::<u64>(), 1);
1570
1571        // Event handler on same resource — still works
1572        fn add_event(mut val: ResMut<u64>, event: u64) {
1573            *val += event;
1574        }
1575        let mut h2 = add_event.into_handler(world.registry_mut());
1576        h2.run(&mut world, 10);
1577        assert_eq!(*world.resource::<u64>(), 11);
1578    }
1579}