Skip to main content

nexus_rt/
catch_unwind.rs

1//! Panic-catching annotation for handlers.
2
3use std::panic::AssertUnwindSafe;
4
5use crate::handler::Handler;
6use crate::world::World;
7
8/// Panic-catching wrapper for [`Handler`] implementations.
9///
10/// Catches panics during [`run()`](Handler::run) so the handler is never
11/// lost during move-out-fire dispatch. This is an annotation — wrap a
12/// concrete handler, then virtualize through your chosen storage (`Box`,
13/// `Flat`, `Flex`, typed slab, etc.).
14///
15/// By constructing this wrapper, the user asserts that the inner handler
16/// (and any [`World`] resources it mutates) can tolerate partial writes
17/// caused by an unwound `run()` call. This is the same assertion as
18/// [`std::panic::AssertUnwindSafe`], applied at the handler level.
19///
20/// # Examples
21///
22/// ```
23/// use nexus_rt::{CatchAssertUnwindSafe, WorldBuilder, ResMut, IntoHandler, Handler, Virtual};
24///
25/// fn tick(mut counter: ResMut<u64>, event: u32) {
26///     *counter += event as u64;
27/// }
28///
29/// let mut builder = WorldBuilder::new();
30/// builder.register::<u64>(0);
31/// let mut world = builder.build();
32///
33/// let handler = tick.into_handler(world.registry());
34/// let guarded = CatchAssertUnwindSafe::new(handler);
35/// let mut boxed: Virtual<u32> = Box::new(guarded);
36///
37/// boxed.run(&mut world, 10);
38/// assert_eq!(*world.resource::<u64>(), 10);
39/// ```
40pub struct CatchAssertUnwindSafe<H> {
41    handler: H,
42}
43
44impl<H> CatchAssertUnwindSafe<H> {
45    /// Wrap a handler with panic catching.
46    ///
47    /// The caller asserts that the handler and any resources it touches
48    /// are safe to continue using after a caught panic.
49    pub fn new(handler: H) -> Self {
50        Self { handler }
51    }
52}
53
54impl<E, H: Handler<E>> Handler<E> for CatchAssertUnwindSafe<H> {
55    fn run(&mut self, world: &mut World, event: E) {
56        let handler = &mut self.handler;
57        let _ = std::panic::catch_unwind(AssertUnwindSafe(|| {
58            handler.run(world, event);
59        }));
60    }
61
62    fn name(&self) -> &'static str {
63        self.handler.name()
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    use crate::{IntoHandler, ResMut, WorldBuilder};
71
72    fn normal_handler(mut val: ResMut<u64>, event: u64) {
73        *val += event;
74    }
75
76    #[test]
77    fn forwards_run() {
78        let mut builder = WorldBuilder::new();
79        builder.register::<u64>(0);
80        let mut world = builder.build();
81
82        let handler = normal_handler.into_handler(world.registry());
83        let mut guarded = CatchAssertUnwindSafe::new(handler);
84
85        guarded.run(&mut world, 10);
86        assert_eq!(*world.resource::<u64>(), 10);
87    }
88
89    fn panicking_handler(_val: ResMut<u64>, _event: u64) {
90        panic!("boom");
91    }
92
93    #[test]
94    fn survives_panic() {
95        let mut builder = WorldBuilder::new();
96        builder.register::<u64>(0);
97        let mut world = builder.build();
98
99        let handler = panicking_handler.into_handler(world.registry());
100        let mut guarded = CatchAssertUnwindSafe::new(handler);
101
102        // Should not panic — caught internally.
103        guarded.run(&mut world, 10);
104
105        // Handler survives — can be called again.
106        guarded.run(&mut world, 10);
107    }
108
109    #[test]
110    fn forwards_name() {
111        let mut builder = WorldBuilder::new();
112        builder.register::<u64>(0);
113        let world = builder.build();
114
115        let handler = normal_handler.into_handler(world.registry());
116        let guarded = CatchAssertUnwindSafe::new(handler);
117
118        assert!(guarded.name().contains("normal_handler"));
119    }
120}