Skip to main content

fret_runtime/
execution.rs

1use std::{sync::Arc, time::Duration};
2
3use fret_core::AppWindowId;
4
5use crate::ExecCapabilities;
6use crate::effect::Effect;
7use crate::model::ModelStore;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum DispatchPriority {
11    Low,
12    Normal,
13    High,
14}
15
16pub type Runnable = Box<dyn FnOnce() + Send + 'static>;
17
18/// Portable execution surface provided by the runner.
19///
20/// This is a semantic contract (ADR 0175). Implementations live in platform runners and must:
21///
22/// - Preserve the main-thread mutation invariant (UI/runtime state is main-thread only).
23/// - Keep the servicing thread/queue mapping stable for the lifetime of the process.
24/// - Provide a `wake()` that advances the runner to the next driver boundary.
25pub trait Dispatcher: Send + Sync + 'static {
26    /// Schedule work to run on the UI/main thread.
27    fn dispatch_on_main_thread(&self, task: Runnable);
28
29    /// Schedule work to run off the UI thread where available.
30    ///
31    /// On constrained platforms (e.g. wasm without threads) this may be implemented as
32    /// best-effort cooperative execution, but must still preserve the main-thread mutation
33    /// invariant.
34    fn dispatch_background(&self, task: Runnable, priority: DispatchPriority);
35
36    /// Schedule delayed work.
37    ///
38    /// Implementations must share the same timer substrate/time base as effect timers to avoid
39    /// split-brain scheduling (ADR 0175 / ADR 0034).
40    fn dispatch_after(&self, delay: Duration, task: Runnable);
41
42    /// Request that the runner reaches the next driver boundary promptly.
43    ///
44    /// Runners should coalesce repeated calls and may use `window` as a hint for multi-window
45    /// implementations.
46    fn wake(&self, window: Option<AppWindowId>);
47
48    /// Execution capabilities exposed by this dispatcher implementation.
49    fn exec_capabilities(&self) -> ExecCapabilities;
50}
51
52pub type DispatcherHandle = Arc<dyn Dispatcher>;
53
54pub trait InboxDrainHost {
55    fn request_redraw(&mut self, window: AppWindowId);
56    fn push_effect(&mut self, effect: Effect);
57    fn models_mut(&mut self) -> &mut ModelStore;
58}
59
60pub trait InboxDrain: Send + Sync + 'static {
61    fn drain(&self, host: &mut dyn InboxDrainHost, window: Option<AppWindowId>) -> bool;
62}
63
64#[derive(Default)]
65pub struct InboxDrainRegistry {
66    drainers: Vec<Arc<dyn InboxDrain>>,
67}
68
69impl InboxDrainRegistry {
70    pub fn register(&mut self, drainer: Arc<dyn InboxDrain>) {
71        self.drainers.push(drainer);
72    }
73
74    pub fn drain_all(&self, host: &mut dyn InboxDrainHost, window: Option<AppWindowId>) -> bool {
75        let mut did_work = false;
76        for drainer in &self.drainers {
77            did_work |= drainer.drain(host, window);
78        }
79        did_work
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[derive(Default)]
88    struct TestHost {
89        models: ModelStore,
90        redraws: Vec<AppWindowId>,
91        effects: Vec<Effect>,
92    }
93
94    impl InboxDrainHost for TestHost {
95        fn request_redraw(&mut self, window: AppWindowId) {
96            self.redraws.push(window);
97        }
98
99        fn push_effect(&mut self, effect: Effect) {
100            self.effects.push(effect);
101        }
102
103        fn models_mut(&mut self) -> &mut ModelStore {
104            &mut self.models
105        }
106    }
107
108    #[test]
109    fn inbox_drain_registry_invokes_drainers_and_reports_progress() {
110        struct Drainer;
111        impl InboxDrain for Drainer {
112            fn drain(&self, _host: &mut dyn InboxDrainHost, _window: Option<AppWindowId>) -> bool {
113                true
114            }
115        }
116
117        let mut host = TestHost::default();
118        let mut reg = InboxDrainRegistry::default();
119        reg.register(Arc::new(Drainer));
120
121        assert!(reg.drain_all(&mut host, None));
122    }
123}