Skip to main content

incular_runtime/
route_tasks.rs

1//! Binds task scopes to navigation route lifetimes.
2//!
3//! The binding is deliberately one-directional: the navigation crate knows
4//! nothing about tasks, and this module adds no executor or cancellation
5//! engine. It couples one existing [`TaskScope`] child to one neutral
6//! [`RouteLifetime`](incular_navigation::RouteLifetime), so permanent route
7//! removal cancels route-associated work through the scheduler machinery
8//! that already discards completions of cancelled scopes.
9
10use incular_navigation::{RouteLifetime, RouteLifetimeSubscription};
11
12use super::tasks::TaskScope;
13
14/// Couples one task scope to one navigation route lifetime.
15///
16/// [`RouteTaskBinding::bind`] creates a scope bounded by `parent` and
17/// cancels it when the lifetime ends. Deactivation, keyed reorder, and
18/// retained updates leave the scope alone because the lifetime survives
19/// them; only permanent removal ends it. Cancelling `parent` — window
20/// close, application shutdown — still cancels through the existing
21/// parent-to-child ownership, and a binding made on an already-ended
22/// lifetime yields an immediately cancelled scope.
23///
24/// Typical use: keep the binding beside the route's controller, spawn with
25/// [`RuntimeSpawner::spawn_into_in`](super::tasks::RuntimeSpawner::spawn_into_in)
26/// on [`Self::scope`], and let removal discard late completions instead of
27/// updating a gone route.
28///
29/// Disposal policy: dropping the binding unregisters its removal callback,
30/// so a later removal no longer cancels through this binding (detached),
31/// while the scope itself follows ordinary [`TaskScope`] ownership —
32/// including last-owner-drop cancellation. Retain the binding while the
33/// route is mounted. Repeated removal and repeated [`TaskScope::cancel`]
34/// are harmless no-ops through the idempotent lifetime and scope flags.
35#[must_use]
36pub struct RouteTaskBinding {
37    scope: TaskScope,
38    lifetime: RouteLifetime,
39    _subscription: RouteLifetimeSubscription,
40}
41
42impl RouteTaskBinding {
43    /// Binds a fresh scope bounded by `parent` to `lifetime`.
44    pub fn bind(parent: &TaskScope, lifetime: &RouteLifetime) -> Self {
45        let scope = parent.child();
46        let scope_for_callback = scope.clone();
47        let subscription = lifetime.on_ended(move || scope_for_callback.cancel());
48        Self {
49            scope,
50            lifetime: lifetime.clone(),
51            _subscription: subscription,
52        }
53    }
54
55    /// Returns the route-bounded scope to spawn work in.
56    #[must_use]
57    pub fn scope(&self) -> &TaskScope {
58        &self.scope
59    }
60
61    /// Returns the bound route lifetime.
62    #[must_use]
63    pub fn lifetime(&self) -> &RouteLifetime {
64        &self.lifetime
65    }
66}