Skip to main content

ferroui_reactiveui/
rx_state.rs

1//! `RxState` (`ReactiveUI.Shared/RxState.cs`).
2
3use crate::{RxSchedulers, UnhandledErrorException};
4use ferroui_reactive::concurrency::SchedulerExt;
5use ferroui_reactive::Observer;
6use ferroui_system::{IObserver, ObservableError};
7use std::cell::RefCell;
8use std::rc::Rc;
9
10ferroui_system::ferro_thread_local! {
11    /// The handler of the errors nothing else handles (`_defaultExceptionHandler`).
12    static DEFAULT_EXCEPTION_HANDLER: RefCell<Option<Rc<dyn IObserver<ObservableError>>>> = const { RefCell::new(None) };
13}
14
15/// The message of the exception of the default handler.
16const UNHANDLED_ERROR_MESSAGE: &str = "An object implementing IHandleObservableErrors (often a ReactiveCommand or ObservableAsPropertyHelper) has errored, thereby breaking its observable pipeline. To prevent this, ensure the pipeline does not error, or Subscribe to the ThrownExceptions property of the object in question to handle the erroneous case.";
17
18/// The state of ReactiveUI that its objects share (`RxState`): the handler of the errors that no
19/// `thrown_exceptions` subscriber handles.
20///
21/// Deviation (DEVIATIONS.md, ReactiveUI): the state is per thread, as the objects that use it are
22/// (`docs/porting/reactive.md`, section 5.1).
23pub struct RxState;
24
25impl RxState {
26    /// The observer that receives the errors of the objects implementing
27    /// [`IHandleObservableErrors`](crate::IHandleObservableErrors) when nothing subscribes to
28    /// their `thrown_exceptions` (`DefaultExceptionHandler`).
29    ///
30    /// Unless [`initialize_exception_handler`](Self::initialize_exception_handler) set another
31    /// one first, it schedules on [`RxSchedulers::main_thread_scheduler`] a panic with an
32    /// [`UnhandledErrorException`] that names the error, where upstream throws that exception.
33    pub fn default_exception_handler() -> Rc<dyn IObserver<ObservableError>> {
34        if let Some(handler) = DEFAULT_EXCEPTION_HANDLER.with(|slot| slot.borrow().clone()) {
35            return handler;
36        }
37        Self::initialize_default_exception_handler()
38    }
39
40    /// Sets the handler of [`default_exception_handler`](Self::default_exception_handler) unless
41    /// one was set or read before (`InitializeExceptionHandler`); the builder of the application
42    /// calls it.
43    pub fn initialize_exception_handler(exception_handler: Rc<dyn IObserver<ObservableError>>) {
44        DEFAULT_EXCEPTION_HANDLER.with(|slot| {
45            let mut slot = slot.borrow_mut();
46            if slot.is_none() {
47                *slot = Some(exception_handler);
48            }
49        });
50    }
51
52    /// Forgets the handler, so that the next read or initialization sets it again
53    /// (`ResetForTesting`); for tests.
54    #[doc(hidden)]
55    pub fn reset_for_testing() {
56        DEFAULT_EXCEPTION_HANDLER.with(|slot| *slot.borrow_mut() = None);
57    }
58
59    fn initialize_default_exception_handler() -> Rc<dyn IObserver<ObservableError>> {
60        let handler = Observer::create(|exception: ObservableError| {
61            // Upstream breaks into an attached debugger first (`Debugger.Break`); there is no
62            // such call here.
63            let unhandled = UnhandledErrorException::with_inner(UNHANDLED_ERROR_MESSAGE, &*exception);
64            let _ = RxSchedulers::main_thread_scheduler().schedule_action(move || {
65                std::panic::panic_any(unhandled);
66            });
67        });
68        DEFAULT_EXCEPTION_HANDLER.with(|slot| slot.borrow_mut().get_or_insert(handler).clone())
69    }
70}