Skip to main content

compose_rust/runtime/
state.rs

1//! Observable state: what a composition reads, and what tells it to run again.
2//!
3//! A [`MutableState`] remembers which scopes read it during composition. Writing it, from
4//! the UI thread or from a worker, queues its id for the runtime that observed it and asks
5//! for a frame. At the next frame the runtime looks up who read that id and runs exactly
6//! those scopes again, and nothing else. This is Compose's snapshot invalidation without
7//! the isolation of separate snapshots: a write is visible to the next read at once, and
8//! what is deferred to the frame is the recomposition, not the value.
9//!
10//! A worker never touches the composition. It writes a value behind a lock and pushes an
11//! id onto a queue, and the UI thread does the rest inside its own Host call.
12
13use super::composer::Observer;
14use super::{with_composer, with_composer_if_idle};
15use crate::schema::{DesignSystem, NotificationPermission};
16use crate::window::WindowSize;
17use std::cell::{Cell, RefCell};
18use std::collections::HashMap;
19use std::fmt;
20use std::rc::{Rc, Weak};
21use std::sync::atomic::{AtomicU64, Ordering};
22use std::sync::{Arc, Mutex, MutexGuard};
23
24use super::composer::RuntimeSignal;
25
26/// Ids for states and derived states. Shared between the two so an observer list never
27/// confuses one with the other.
28static NEXT_STATE_ID: AtomicU64 = AtomicU64::new(1);
29
30fn next_state_id() -> u64 {
31    NEXT_STATE_ID.fetch_add(1, Ordering::Relaxed)
32}
33
34fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
35    mutex.lock().unwrap_or_else(|poison| poison.into_inner())
36}
37
38/// When a write counts as a change. Compose calls this a `SnapshotMutationPolicy`.
39pub struct SnapshotMutationPolicy<T> {
40    equivalent: fn(&T, &T) -> bool,
41}
42
43impl<T> Clone for SnapshotMutationPolicy<T> {
44    fn clone(&self) -> Self {
45        *self
46    }
47}
48
49impl<T> Copy for SnapshotMutationPolicy<T> {}
50
51/// A write of an equal value changes nothing and runs nothing again.
52pub fn structural_equality_policy<T: PartialEq>() -> SnapshotMutationPolicy<T> {
53    SnapshotMutationPolicy {
54        equivalent: |left, right| left == right,
55    }
56}
57
58/// Every write is a change, whatever it wrote.
59pub fn never_equal_policy<T>() -> SnapshotMutationPolicy<T> {
60    SnapshotMutationPolicy {
61        equivalent: |_, _| false,
62    }
63}
64
65struct StateCell<T> {
66    id: u64,
67    value: Mutex<T>,
68    policy: SnapshotMutationPolicy<T>,
69    /// The runtimes that have read this state, so a write knows whom to tell.
70    runtimes: Mutex<Vec<std::sync::Weak<RuntimeSignal>>>,
71}
72
73/// A value that composition observes. Compose's `MutableState`.
74///
75/// Cloning gives another handle to the same value. Two handles compare equal when they
76/// are the same state, which is what makes a state a stable parameter: passing the same
77/// state to a composable again does not make it run.
78///
79/// It is `Send` and `Sync` when the value is `Send`, so a worker thread can hold one and
80/// write it. The write is visible at once; the composables that read it run again on the
81/// next frame, which the write asks for.
82///
83/// Reading takes a lock for as long as the read lasts. Reading the same state again inside
84/// [`MutableState::with`] would wait on itself.
85pub struct MutableState<T> {
86    cell: Arc<StateCell<T>>,
87}
88
89impl<T> Clone for MutableState<T> {
90    fn clone(&self) -> Self {
91        Self {
92            cell: Arc::clone(&self.cell),
93        }
94    }
95}
96
97impl<T> PartialEq for MutableState<T> {
98    fn eq(&self, other: &Self) -> bool {
99        Arc::ptr_eq(&self.cell, &other.cell)
100    }
101}
102
103impl<T> Eq for MutableState<T> {}
104
105impl<T: fmt::Debug> fmt::Debug for MutableState<T> {
106    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
107        formatter
108            .debug_tuple("MutableState")
109            .field(&*lock(&self.cell.value))
110            .finish()
111    }
112}
113
114/// A state whose writes of an equal value change nothing. Compose's `mutableStateOf`.
115///
116/// ```ignore
117/// let count = remember(|| mutable_state_of(0));
118/// Text(format!("{}", count.get()));
119/// Button("More").on_click(move || count.update(|count| *count += 1));
120/// ```
121pub fn mutable_state_of<T: PartialEq + 'static>(value: T) -> MutableState<T> {
122    mutable_state_of_with_policy(value, structural_equality_policy())
123}
124
125/// A state with a policy of its own for what counts as a change.
126pub fn mutable_state_of_with_policy<T: 'static>(
127    value: T,
128    policy: SnapshotMutationPolicy<T>,
129) -> MutableState<T> {
130    MutableState {
131        cell: Arc::new(StateCell {
132            id: next_state_id(),
133            value: Mutex::new(value),
134            policy,
135            runtimes: Mutex::new(Vec::new()),
136        }),
137    }
138}
139
140impl<T: 'static> MutableState<T> {
141    /// The value, recorded as read by the scope composing now.
142    pub fn get(&self) -> T
143    where
144        T: Clone,
145    {
146        self.track();
147        lock(&self.cell.value).clone()
148    }
149
150    /// The value, without recording a read. A scope that only peeks is not run again when
151    /// the value changes.
152    pub fn get_untracked(&self) -> T
153    where
154        T: Clone,
155    {
156        lock(&self.cell.value).clone()
157    }
158
159    /// Reads the value in place, recorded as read.
160    pub fn with<R>(&self, read: impl FnOnce(&T) -> R) -> R {
161        self.track();
162        read(&lock(&self.cell.value))
163    }
164
165    /// Replaces the value. Under the default policy an equal value changes nothing.
166    pub fn set(&self, value: T) {
167        {
168            let mut current = lock(&self.cell.value);
169            if (self.cell.policy.equivalent)(&current, &value) {
170                return;
171            }
172            *current = value;
173        }
174        self.notify();
175    }
176
177    /// Changes the value in place. Always a change: there is no old value left to compare
178    /// against without a copy, and a caller who wants the comparison can use `set`.
179    pub fn update(&self, change: impl FnOnce(&mut T)) {
180        change(&mut lock(&self.cell.value));
181        self.notify();
182    }
183
184    /// This state as a read-only [`State`].
185    pub fn as_state(&self) -> MutableState<T> {
186        self.clone()
187    }
188
189    fn track(&self) {
190        let fresh = with_composer_if_idle(|composer| {
191            composer
192                .record_read(self.cell.id)
193                .then(|| Arc::clone(&composer.signal))
194        })
195        .flatten();
196        if let Some(signal) = fresh {
197            let mut runtimes = lock(&self.cell.runtimes);
198            if !runtimes
199                .iter()
200                .any(|known| std::ptr::eq(known.as_ptr(), Arc::as_ptr(&signal)))
201            {
202                runtimes.push(Arc::downgrade(&signal));
203            }
204        }
205    }
206
207    fn notify(&self) {
208        let mut told = false;
209        lock(&self.cell.runtimes).retain(|runtime| match runtime.upgrade() {
210            Some(signal) => {
211                signal.push_dirty(self.cell.id);
212                told = true;
213                true
214            }
215            None => false,
216        });
217        // Inside a Host call the change is picked up before the call returns. Anywhere
218        // else, a worker above all, nothing would look until something asked for a frame.
219        if told && !crate::boundary::in_host_call() {
220            crate::boundary::request_frame_from_worker();
221        }
222    }
223}
224
225/// Something composition can read. Compose's `State`.
226pub trait State<T> {
227    /// The value, recorded as read by the scope composing now.
228    fn value(&self) -> T;
229}
230
231impl<T: Clone + 'static> State<T> for MutableState<T> {
232    fn value(&self) -> T {
233        self.get()
234    }
235}
236
237impl<T: Clone + PartialEq + 'static> State<T> for DerivedState<T> {
238    fn value(&self) -> T {
239        self.get()
240    }
241}
242
243/// A derived state's half of change propagation.
244pub(crate) trait DerivedNode {
245    /// Computes the value again and says whether it changed.
246    fn refresh(&self) -> bool;
247    /// Notes that the value is out of date, to be computed when next read.
248    fn mark_dirty(&self);
249}
250
251struct DerivedCell<T> {
252    id: u64,
253    compute: Box<dyn Fn() -> T>,
254    value: RefCell<Option<T>>,
255    dirty: Cell<bool>,
256}
257
258/// A value computed from other states, which tells its readers only when the result
259/// changes. Compose's `derivedStateOf`.
260///
261/// A list that changes on every keystroke and a screen that only cares whether the list is
262/// empty is the case it is for: the screen observes the derived boolean, and the boolean
263/// is recomputed on every change but only runs the screen again when it flips.
264///
265/// It belongs to the UI thread. Remember it, so it is computed once per change rather than
266/// once per composition.
267pub struct DerivedState<T> {
268    cell: Rc<DerivedCell<T>>,
269}
270
271impl<T> Clone for DerivedState<T> {
272    fn clone(&self) -> Self {
273        Self {
274            cell: Rc::clone(&self.cell),
275        }
276    }
277}
278
279impl<T> PartialEq for DerivedState<T> {
280    fn eq(&self, other: &Self) -> bool {
281        Rc::ptr_eq(&self.cell, &other.cell)
282    }
283}
284
285/// A state computed from the states `calculation` reads.
286///
287/// ```ignore
288/// let empty = remember(move || derived_state_of(move || tasks.with(Vec::is_empty)));
289/// if empty.get() { Text("Nothing to do") }
290/// ```
291pub fn derived_state_of<T: Clone + PartialEq + 'static>(
292    calculation: impl Fn() -> T + 'static,
293) -> DerivedState<T> {
294    DerivedState {
295        cell: Rc::new(DerivedCell {
296            id: next_state_id(),
297            compute: Box::new(calculation),
298            value: RefCell::new(None),
299            dirty: Cell::new(true),
300        }),
301    }
302}
303
304impl<T: Clone + PartialEq + 'static> DerivedState<T> {
305    /// The value, recorded as read by the scope composing now.
306    pub fn get(&self) -> T {
307        let in_runtime = with_composer_if_idle(|_| ()).is_some();
308        if !in_runtime {
309            // Nothing outside a runtime can say when the inputs change, so nothing is
310            // cached: the value is computed fresh.
311            return (self.cell.compute)();
312        }
313        if self.cell.dirty.get() || self.cell.value.borrow().is_none() {
314            self.recompute();
315        }
316        with_composer_if_idle(|composer| composer.record_read(self.cell.id));
317        self.cell
318            .value
319            .borrow()
320            .clone()
321            .expect("a derived state holds a value once computed")
322    }
323
324    fn recompute(&self) -> bool {
325        let id = self.cell.id;
326        let node: Rc<dyn DerivedNode> = self.cell.clone();
327        let weak = Rc::downgrade(&node);
328        with_composer(|composer| {
329            if let Some(deps) = composer.derived_deps.remove(&id) {
330                for dep in deps {
331                    composer.unobserve(dep, Observer::Derived(id));
332                }
333            }
334            composer.ambient.deriveds.insert(id, weak);
335            composer.derived_stack.push(id);
336        });
337        let computed = (self.cell.compute)();
338        with_composer(|composer| {
339            composer.derived_stack.pop();
340        });
341        self.cell.dirty.set(false);
342        let mut stored = self.cell.value.borrow_mut();
343        let changed = stored.as_ref() != Some(&computed);
344        *stored = Some(computed);
345        changed
346    }
347}
348
349impl<T: Clone + PartialEq + 'static> DerivedNode for DerivedCell<T> {
350    fn refresh(&self) -> bool {
351        // The same as `DerivedState::recompute`, reached through the cell.
352        let state = DerivedState {
353            cell: rc_from_cell(self),
354        };
355        state.recompute()
356    }
357
358    fn mark_dirty(&self) {
359        self.dirty.set(true);
360    }
361}
362
363/// Recovers the `Rc` a derived cell lives in. Every cell is created inside an `Rc` by
364/// `derived_state_of` and only reached through one, so the registry's weak handle is
365/// always upgraded before this is called and the strong count is at least one.
366fn rc_from_cell<T>(cell: &DerivedCell<T>) -> Rc<DerivedCell<T>> {
367    let pointer = cell as *const DerivedCell<T>;
368    // SAFETY: `cell` is the value of an `Rc<DerivedCell<T>>` allocation (see above), and
369    // a strong reference is held by the caller for the duration of this call, so
370    // incrementing the count and rebuilding a second `Rc` is sound.
371    unsafe {
372        Rc::increment_strong_count(pointer);
373        Rc::from_raw(pointer)
374    }
375}
376
377/// Values the Renderer reports, as states, so a composable that reads one runs again when
378/// the report changes and nothing else does.
379pub(crate) struct Ambient {
380    pub(crate) window: MutableState<WindowSize>,
381    pub(crate) design: MutableState<DesignSystem>,
382    pub(crate) permission: MutableState<NotificationPermission>,
383    pub(crate) deriveds: HashMap<u64, Weak<dyn DerivedNode>>,
384}
385
386impl Ambient {
387    pub(crate) fn new() -> Self {
388        Self {
389            window: mutable_state_of(crate::window::window_size()),
390            design: mutable_state_of(crate::design::design_system()),
391            permission: mutable_state_of(crate::notification::notification_permission()),
392            deriveds: HashMap::new(),
393        }
394    }
395}
396
397/// Runs again whatever read the states written since the last look.
398///
399/// Scopes are marked invalid. A derived state that somebody reads is computed again on
400/// the spot, and its readers are marked only if its value moved; one nobody reads is
401/// marked out of date and left for its next read. Called with the composer free, because
402/// computing a derived state reads states.
403pub(crate) fn apply_state_changes() {
404    loop {
405        let Some(mut changed) = with_composer(|composer| {
406            let mut scratch = std::mem::take(&mut composer.dirty_scratch);
407            std::mem::swap(&mut *lock(&composer.signal.dirty), &mut scratch);
408            scratch
409        }) else {
410            return;
411        };
412        if changed.is_empty() {
413            with_composer(|composer| composer.dirty_scratch = changed);
414            return;
415        }
416        let mut index = 0;
417        while index < changed.len() {
418            let state = changed[index];
419            index += 1;
420            let observers = with_composer(|composer| {
421                composer.observers.get(&state).cloned().unwrap_or_default()
422            })
423            .unwrap_or_default();
424            for observer in observers {
425                match observer {
426                    Observer::Scope(group, generation) => {
427                        with_composer(|composer| composer.invalidate(group, generation));
428                    }
429                    Observer::Derived(derived) => {
430                        let (node, watched) = with_composer(|composer| {
431                            let node = composer
432                                .ambient
433                                .deriveds
434                                .get(&derived)
435                                .and_then(Weak::upgrade);
436                            let watched = composer
437                                .observers
438                                .get(&derived)
439                                .is_some_and(|list| !list.is_empty());
440                            (node, watched)
441                        })
442                        .unwrap_or((None, false));
443                        let Some(node) = node else {
444                            continue;
445                        };
446                        if watched {
447                            if node.refresh() && !changed.contains(&derived) {
448                                changed.push(derived);
449                            }
450                        } else {
451                            node.mark_dirty();
452                        }
453                    }
454                }
455            }
456        }
457        changed.clear();
458        with_composer(|composer| composer.dirty_scratch = changed);
459    }
460}
461
462/// The window's size, recorded as read: the composable runs again when the Renderer
463/// reports a different size class.
464pub fn current_window_size() -> WindowSize {
465    match with_composer_if_idle(|composer| composer.ambient.window.clone()) {
466        Some(state) => state.get(),
467        None => crate::window::window_size(),
468    }
469}
470
471/// The design system the Renderer resolved the theme to, recorded as read.
472///
473/// Not how an application chooses a design system, and not a way to paint by hand: it is
474/// for the screen that is a different screen under a different system, such as a
475/// calculator whose keys differ.
476pub fn current_design_system() -> DesignSystem {
477    match with_composer_if_idle(|composer| composer.ambient.design.clone()) {
478        Some(state) => state.get(),
479        None => crate::design::design_system(),
480    }
481}
482
483/// Whether notifications may be shown, recorded as read.
484pub fn current_notification_permission() -> NotificationPermission {
485    match with_composer_if_idle(|composer| composer.ambient.permission.clone()) {
486        Some(state) => state.get(),
487        None => crate::notification::notification_permission(),
488    }
489}
490
491const NODE_SIZE: u64 = super::composer::call_site("compose_rust::remember_node_size", 0);
492const NOTIFICATION_ACTIVATED: u64 =
493    super::composer::call_site("compose_rust::on_notification_activated", 0);
494
495/// The slot a node size lives in: the state the screen reads, and the subscription that
496/// moves it. Leaving the composition drops both, and the measurement with them.
497struct NodeSizeSlot {
498    token: u32,
499    state: MutableState<WindowSize>,
500    _subscription: crate::window::NodeSizeSubscription,
501}
502
503/// Follows one node's measured size, recorded as read. Compose has no single equivalent;
504/// it is `onSizeChanged` with the class judgement made for you.
505///
506/// Give the token to the node with `Modifier.observe_size(size.token())`. Nothing is
507/// measured until a node carries it, and the composable reading it runs again only when
508/// the node's size class changes.
509pub fn remember_node_size() -> crate::window::NodeSize {
510    let _group = super::group_guard(NODE_SIZE);
511    let found = with_composer_if_idle(|composer| {
512        if !composer.composing() {
513            return None;
514        }
515        if let Some(slot) = composer.slot_mut::<NodeSizeSlot>() {
516            return Some((slot.token, slot.state.clone()));
517        }
518        let token = crate::window::next_node_token();
519        let state = mutable_state_of(crate::window::node_size(token));
520        let follow = state.clone();
521        let subscription = crate::window::subscribe_node(
522            token,
523            std::sync::Arc::new(move || follow.set(crate::window::node_size(token))),
524        );
525        composer.remember_new(NodeSizeSlot {
526            token,
527            state: state.clone(),
528            _subscription: subscription,
529        });
530        Some((token, state))
531    })
532    .flatten();
533    match found {
534        Some((token, state)) => {
535            // Read through the state, so this scope runs again when the class changes;
536            // the value itself is the module's.
537            let _ = state.get();
538            crate::window::NodeSize::of(token)
539        }
540        None => crate::window::NodeSize::of(0),
541    }
542}
543
544/// A notification handler, shared between the slot and the registration that calls it.
545type ActivationHandler = Rc<RefCell<Box<dyn FnMut(crate::NotificationActivation)>>>;
546
547/// The slot a notification handler lives in: the latest handler, and the registration
548/// that lasts as long as the slot does.
549struct ActivationSlot {
550    handler: ActivationHandler,
551    _subscription: crate::notification::ActivationSubscription,
552}
553
554/// Calls `handler` when the user presses a notification this application posted, for as
555/// long as this call site is in the composition.
556///
557/// The handler is replaced by the latest one on every composition, so it always sees the
558/// states it captured this time.
559pub fn on_notification_activated(handler: impl FnMut(crate::NotificationActivation) + 'static) {
560    let _group = super::group_guard(NOTIFICATION_ACTIVATED);
561    let handler: Box<dyn FnMut(crate::NotificationActivation)> = Box::new(handler);
562    let mut handler = Some(handler);
563    let old = with_composer_if_idle(|composer| {
564        if !composer.composing() {
565            return None;
566        }
567        if let Some(slot) = composer.slot_mut::<ActivationSlot>() {
568            let new = handler.take().expect("the handler is installed once");
569            return Some(std::mem::replace(&mut *slot.handler.borrow_mut(), new));
570        }
571        let shared = Rc::new(RefCell::new(
572            handler.take().expect("the handler is installed once"),
573        ));
574        let forward = Rc::clone(&shared);
575        let subscription = crate::notification::on_activation(Rc::new(
576            move |activation: crate::NotificationActivation| (*forward.borrow_mut())(activation),
577        ));
578        composer.remember_new(ActivationSlot {
579            handler: shared,
580            _subscription: subscription,
581        });
582        None
583    })
584    .flatten();
585    // The handler it replaced is dropped here, with the composer free.
586    drop(old);
587}