Skip to main content

nexus_rt/
handler.rs

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