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}