tessera_ui_shard/
router.rs

1//! Stack-based routing utilities for shard components.
2//!
3//! Each `#[shard]` function generates a `*Destination` type that implements
4//! [`RouterDestination`]. These destinations are managed in a LIFO stack.
5//!
6//! # Responsibilities
7//!
8//! * Maintain an ordered stack (`route_stack`) of active destinations
9//! * Expose [`Router::push`] / [`Router::pop`] helpers that also manage shard state lifetimes
10//! * Remove per‑shard state from the registry when a destination whose lifecycle is
11//!   `ShardStateLifeCycle::Shard` is popped
12//! * Keep routing logic minimal; rendering happens when the top destination's
13//!   `exec_component()` is invoked every frame by `router_root`
14//!
15//! # Related
16//!
17//! * `#[shard]` macro – generates the `*Destination` structs + optional state injection
18//! * `tessera_ui::router::router_root` – executes the current top destination each frame
19use std::sync::OnceLock;
20
21use parking_lot::RwLock;
22
23use crate::{ShardRegistry, ShardStateLifeCycle};
24
25static ROUTER: OnceLock<RwLock<Router>> = OnceLock::new();
26
27pub struct Router {
28    /// Whether the router has been initialized with a default destination
29    initialized: bool,
30    /// Current route stack
31    route_stack: Vec<Box<dyn RouterDestination>>,
32}
33
34impl Router {
35    fn new() -> Self {
36        Self {
37            initialized: false,
38            route_stack: Vec::new(),
39        }
40    }
41
42    /// Initialize the router with a default destination if not already done.
43    pub fn try_init(defualt_dest: impl RouterDestination + 'static) -> bool {
44        Self::with_mut(|router| {
45            if router.initialized {
46                return false;
47            }
48            router.push(defualt_dest);
49            router.initialized = true;
50            true
51        })
52    }
53
54    /// Execute a closure with exclusive mutable access to the router.
55    pub fn with_mut<F, R>(f: F) -> R
56    where
57        F: FnOnce(&mut Self) -> R,
58    {
59        let router = ROUTER.get_or_init(|| RwLock::new(Self::new()));
60        let mut router = router.write();
61        f(&mut router)
62    }
63
64    /// Execute a closure with shared read access to the router.
65    pub fn with<F, R>(f: F) -> R
66    where
67        F: FnOnce(&Self) -> R,
68    {
69        let router = ROUTER.get_or_init(|| RwLock::new(Self::new()));
70        let router = router.read();
71        f(&router)
72    }
73
74    /// Push a new route destination onto the stack (internal helper).
75    pub fn push<T: RouterDestination + 'static>(&mut self, destination: T) {
76        self.route_stack.push(Box::new(destination));
77    }
78
79    /// Pop the top route destination from the stack.
80    ///
81    /// Returns `None` if the stack is empty.
82    pub fn pop(&mut self) -> Option<Box<dyn RouterDestination>> {
83        let dest = self.route_stack.pop()?;
84        // Decide cleanup by destination lifecycle, defaulting to Shard.
85        let life_cycle = dest.life_cycle();
86        if life_cycle == ShardStateLifeCycle::Shard {
87            // Remove per-shard state when destination is discarded
88            ShardRegistry::get().shards.remove(dest.shard_id());
89        }
90        Some(dest)
91    }
92
93    /// Whether the router is empty.
94    pub fn is_empty(&self) -> bool {
95        self.route_stack.is_empty()
96    }
97
98    /// Get the current top route destination, used for route component display.
99    pub fn last(&self) -> Option<&dyn RouterDestination> {
100        self.route_stack.last().map(|v| &**v)
101    }
102
103    /// Get the length of the route stack.
104    pub fn len(&self) -> usize {
105        self.route_stack.len()
106    }
107
108    /// Clear all routes from the stack.
109    pub fn clear(&mut self) {
110        self.route_stack.clear();
111    }
112
113    /// Clear all routes from the stack and reset initialization state.
114    ///
115    /// This allows the root destination to be initialized again.
116    pub fn reset(&mut self) {
117        self.route_stack.clear();
118        self.initialized = false;
119    }
120
121    /// Clear all routes from the stack and push a new root destination.
122    pub fn reset_with(&mut self, root_dest: impl RouterDestination + 'static) {
123        self.route_stack.clear();
124        self.push(root_dest);
125    }
126}
127
128/// A navigation destination produced automatically by the `#[shard]` macro.
129///
130/// You should not manually implement this trait. Each annotated shard function
131/// creates a `*Destination` struct that implements `RouterDestination`.
132pub trait RouterDestination: Send + Sync {
133    /// Execute the component associated with this destination.
134    fn exec_component(&self);
135    /// Stable shard identifier used for state registry lookups / cleanup.
136    fn shard_id(&self) -> &'static str;
137    /// Lifecycle policy for the shard state tied to this destination.
138    ///
139    /// Default is `Shard`, which means the associated shard state will be
140    /// removed from the registry when this destination is popped.
141    /// Override in generated implementations to persist for the whole app.
142    fn life_cycle(&self) -> ShardStateLifeCycle {
143        ShardStateLifeCycle::Shard
144    }
145}