Skip to main content

incular_runtime/
application_activations.rs

1//! Application-level activation delivery and router bridging.
2
3use incular_platform::ApplicationActivation;
4use incular_widgets::{RouteInformation, RouteInformationProvider};
5use std::{
6    cell::{Cell, RefCell},
7    collections::VecDeque,
8    fmt,
9    rc::{Rc, Weak},
10};
11
12/// Retained callback for application activations.
13pub type ApplicationActivationListener = Rc<dyn Fn(ApplicationActivation)>;
14
15#[derive(Default)]
16struct ActivationState {
17    pending: VecDeque<ApplicationActivation>,
18    listeners: Vec<Weak<dyn Fn(ApplicationActivation)>>,
19}
20
21/// Delivery flag beside the queue. The flag lives in a `Cell`, not in
22/// the `RefCell`, so releasing a drain never needs a borrow and cannot
23/// fail. Queue borrows stay scoped to enqueue/dequeue/snapshot
24/// operations and never span a callback.
25#[derive(Default)]
26struct ActivationDrain {
27    queue: RefCell<ActivationState>,
28    active: Cell<bool>,
29}
30
31/// Cloneable application-scoped activation stream.
32///
33/// Ownership and delivery contract:
34///
35/// - Identity: one activation is one [`ApplicationActivation`] value. There
36///   are no IDs and no deduplication: two equal values are two activations
37///   and both are delivered.
38/// - Queued: [`publish`](Self::publish) appends to a service-owned FIFO.
39///   Activations published before any listener exists (for example before
40///   the route bridge is installed) wait there; there is no second queue.
41/// - Delivered: exactly once per listener live for that event's snapshot,
42///   in arrival order, while delivery completes without listener failure.
43///   A listener publishing reentrantly enqueues behind the in-flight
44///   event. Delivery is synchronous fan-out, not the runtime request
45///   channel: native code pushes through
46///   `Application::handle_application_activation`, and the bridge forwards
47///   mapped URLs into the route provider, whose own listeners (such as the
48///   widgets router) apply them.
49/// - Listener panic: delivery is not transactional. A panicking listener
50///   aborts its event's remaining deliveries — that event is not replayed
51///   to listeners that missed it, so exactly-once holds only for
52///   failure-free delivery. The queue itself is preserved and the drain
53///   releases, so a later publish resumes with the queued events first.
54/// - Acknowledged/retried/discarded: none. Listeners return nothing to
55///   acknowledge, nothing retries, and pending activations are never
56///   dropped by the service — they wait until a listener subscribes, even
57///   across bridge replacement or window closure, which this
58///   application-scoped service never observes.
59/// - Router installation and replacement: installing the route bridge
60///   subscribes and immediately drains whatever queued earlier.
61///   Replacing it (drop, then bridge again) leaves pending activations
62///   buffered for the new bridge; the old bridge delivers nothing
63///   further once its subscription dies.
64#[derive(Clone, Default)]
65pub struct ApplicationActivationService {
66    state: Rc<ActivationDrain>,
67}
68
69impl fmt::Debug for ApplicationActivationService {
70    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
71        let state = self.state.queue.borrow();
72        formatter
73            .debug_struct("ApplicationActivationService")
74            .field("pending", &state.pending.len())
75            .field("listeners", &state.listeners.len())
76            .finish()
77    }
78}
79
80impl ApplicationActivationService {
81    /// Observes future activations and drains any activations buffered before
82    /// the first listener was installed.
83    pub fn subscribe(
84        &self,
85        listener: impl Fn(ApplicationActivation) + 'static,
86    ) -> ApplicationActivationSubscription {
87        let listener: ApplicationActivationListener = Rc::new(listener);
88        self.state
89            .queue
90            .borrow_mut()
91            .listeners
92            .push(Rc::downgrade(&listener));
93        drain(&self.state);
94        ApplicationActivationSubscription {
95            _listener: listener,
96        }
97    }
98
99    #[doc(hidden)]
100    pub fn publish(&self, activation: ApplicationActivation) {
101        self.state.queue.borrow_mut().pending.push_back(activation);
102        drain(&self.state);
103    }
104
105    #[must_use]
106    pub fn pending_count(&self) -> usize {
107        self.state.queue.borrow().pending.len()
108    }
109}
110
111/// Owns an in-progress activation drain and is the sole authority
112/// releasing it. The flag release is infallible (`Cell::set`); the
113/// queue itself is left untouched, so undelivered activations stay
114/// buffered and resume on the next drain.
115struct DispatchGuard {
116    shared: Rc<ActivationDrain>,
117}
118impl DispatchGuard {
119    fn acquire(shared: &Rc<ActivationDrain>) -> Option<Self> {
120        if shared.active.get() {
121            return None;
122        }
123        {
124            let mut queue = shared.queue.borrow_mut();
125            queue
126                .listeners
127                .retain(|listener| listener.strong_count() != 0);
128            if queue.listeners.is_empty() || queue.pending.is_empty() {
129                return None;
130            }
131        }
132        // No user code runs between the checks above and arming the flag,
133        // so no reentrant dispatch can interleave here.
134        shared.active.set(true);
135        Some(Self {
136            shared: shared.clone(),
137        })
138    }
139}
140impl Drop for DispatchGuard {
141    fn drop(&mut self) {
142        self.shared.active.set(false);
143    }
144}
145
146fn drain(state: &Rc<ActivationDrain>) {
147    let Some(_drain) = DispatchGuard::acquire(state) else {
148        return;
149    };
150
151    loop {
152        let next = {
153            let mut queue = state.queue.borrow_mut();
154            queue
155                .listeners
156                .retain(|listener| listener.strong_count() != 0);
157            if queue.listeners.is_empty() {
158                return;
159            }
160            queue.pending.pop_front().map(|activation| {
161                (
162                    activation,
163                    queue
164                        .listeners
165                        .iter()
166                        .filter_map(Weak::upgrade)
167                        .collect::<Vec<_>>(),
168                )
169            })
170        };
171        let Some((activation, listeners)) = next else {
172            return;
173        };
174        for listener in listeners {
175            listener(activation.clone());
176        }
177    }
178}
179
180/// Owns one activation listener for as long as it should remain subscribed.
181pub struct ApplicationActivationSubscription {
182    _listener: ApplicationActivationListener,
183}
184
185impl fmt::Debug for ApplicationActivationSubscription {
186    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
187        formatter.write_str("ApplicationActivationSubscription(..)")
188    }
189}
190
191/// Keeps the deliberate application-activation to Router bridge alive.
192///
193/// URL interpretation is supplied by the application route layer as `map`;
194/// desktop/native code never parses an application route.
195pub struct ActivationRouteBridge {
196    _subscription: ApplicationActivationSubscription,
197    delivered_routes: Rc<Cell<u64>>,
198}
199
200impl fmt::Debug for ActivationRouteBridge {
201    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
202        formatter
203            .debug_struct("ActivationRouteBridge")
204            .field("delivered_routes", &self.delivered_routes.get())
205            .finish()
206    }
207}
208
209impl ActivationRouteBridge {
210    pub(crate) fn new(
211        activations: &ApplicationActivationService,
212        provider: Rc<dyn RouteInformationProvider>,
213        map: impl Fn(&str) -> Option<RouteInformation> + 'static,
214    ) -> Self {
215        let delivered_routes = Rc::new(Cell::new(0_u64));
216        let delivered = delivered_routes.clone();
217        let subscription = activations.subscribe(move |activation| {
218            for url in activation.urls() {
219                if let Some(information) = map(url.as_str()) {
220                    provider.set_platform_route_information(information);
221                    delivered.set(delivered.get().saturating_add(1));
222                }
223            }
224        });
225        Self {
226            _subscription: subscription,
227            delivered_routes,
228        }
229    }
230
231    #[must_use]
232    pub fn delivered_routes(&self) -> u64 {
233        self.delivered_routes.get()
234    }
235}