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    /// What each window of a multi-window application last measured, as a state per
384    /// window, made when a composable in that window first reads it.
385    pub(crate) windows: HashMap<u32, WindowAmbient>,
386    pub(crate) deriveds: HashMap<u64, Weak<dyn DerivedNode>>,
387}
388
389/// One window's size as a state, and the subscription that moves it.
390pub(crate) struct WindowAmbient {
391    state: MutableState<WindowSize>,
392    _subscription: crate::window::WindowSubscription,
393}
394
395impl Ambient {
396    pub(crate) fn new() -> Self {
397        Self {
398            window: mutable_state_of(crate::window::window_size()),
399            design: mutable_state_of(crate::design::design_system()),
400            permission: mutable_state_of(crate::notification::notification_permission()),
401            windows: HashMap::new(),
402            deriveds: HashMap::new(),
403        }
404    }
405}
406
407/// Runs again whatever read the states written since the last look.
408///
409/// Scopes are marked invalid. A derived state that somebody reads is computed again on
410/// the spot, and its readers are marked only if its value moved; one nobody reads is
411/// marked out of date and left for its next read. Called with the composer free, because
412/// computing a derived state reads states.
413pub(crate) fn apply_state_changes() {
414    loop {
415        let Some(mut changed) = with_composer(|composer| {
416            let mut scratch = std::mem::take(&mut composer.dirty_scratch);
417            std::mem::swap(&mut *lock(&composer.signal.dirty), &mut scratch);
418            scratch
419        }) else {
420            return;
421        };
422        if changed.is_empty() {
423            with_composer(|composer| composer.dirty_scratch = changed);
424            return;
425        }
426        let mut index = 0;
427        while index < changed.len() {
428            let state = changed[index];
429            index += 1;
430            let observers = with_composer(|composer| {
431                composer.observers.get(&state).cloned().unwrap_or_default()
432            })
433            .unwrap_or_default();
434            for observer in observers {
435                match observer {
436                    Observer::Scope(group, generation) => {
437                        with_composer(|composer| composer.invalidate(group, generation));
438                    }
439                    Observer::Derived(derived) => {
440                        let (node, watched) = with_composer(|composer| {
441                            let node = composer
442                                .ambient
443                                .deriveds
444                                .get(&derived)
445                                .and_then(Weak::upgrade);
446                            let watched = composer
447                                .observers
448                                .get(&derived)
449                                .is_some_and(|list| !list.is_empty());
450                            (node, watched)
451                        })
452                        .unwrap_or((None, false));
453                        let Some(node) = node else {
454                            continue;
455                        };
456                        if watched {
457                            if node.refresh() && !changed.contains(&derived) {
458                                changed.push(derived);
459                            }
460                        } else {
461                            node.mark_dirty();
462                        }
463                    }
464                }
465            }
466        }
467        changed.clear();
468        with_composer(|composer| composer.dirty_scratch = changed);
469    }
470}
471
472/// The window's size, recorded as read: the composable runs again when the Renderer
473/// reports a different size class.
474///
475/// In a multi-window application it is the size of the window the composable is in,
476/// and the composable runs again only when that window's class changes.
477pub fn current_window_size() -> WindowSize {
478    let window = crate::windows::current_window_id();
479    if window != 0 {
480        if let Some(state) = window_state(window) {
481            return state.get();
482        }
483    }
484    match with_composer_if_idle(|composer| composer.ambient.window.clone()) {
485        Some(state) => state.get(),
486        None => crate::window::window_size(),
487    }
488}
489
490/// The state that follows one window's size, made on first use.
491fn window_state(window: u32) -> Option<MutableState<WindowSize>> {
492    with_composer_if_idle(|composer| {
493        if let Some(entry) = composer.ambient.windows.get(&window) {
494            return entry.state.clone();
495        }
496        let state = mutable_state_of(crate::window::window_size_of(window));
497        let follow = state.clone();
498        let subscription = crate::window::subscribe_window(
499            window,
500            Arc::new(move || follow.set(crate::window::window_size_of(window))),
501        );
502        composer.ambient.windows.insert(
503            window,
504            WindowAmbient {
505                state: state.clone(),
506                _subscription: subscription,
507            },
508        );
509        state
510    })
511}
512
513/// The design system the Renderer resolved the theme to, recorded as read.
514///
515/// Not how an application chooses a design system, and not a way to paint by hand: it is
516/// for the screen that is a different screen under a different system, such as a
517/// calculator whose keys differ.
518pub fn current_design_system() -> DesignSystem {
519    match with_composer_if_idle(|composer| composer.ambient.design.clone()) {
520        Some(state) => state.get(),
521        None => crate::design::design_system(),
522    }
523}
524
525/// Whether notifications may be shown, recorded as read.
526pub fn current_notification_permission() -> NotificationPermission {
527    match with_composer_if_idle(|composer| composer.ambient.permission.clone()) {
528        Some(state) => state.get(),
529        None => crate::notification::notification_permission(),
530    }
531}
532
533const NODE_SIZE: u64 = super::composer::call_site("compose_rust::remember_node_size", 0);
534const NOTIFICATION_ACTIVATED: u64 =
535    super::composer::call_site("compose_rust::on_notification_activated", 0);
536
537/// The slot a node size lives in: the state the screen reads, and the subscription that
538/// moves it. Leaving the composition drops both, and the measurement with them.
539struct NodeSizeSlot {
540    token: u32,
541    state: MutableState<WindowSize>,
542    _subscription: crate::window::NodeSizeSubscription,
543}
544
545/// Follows one node's measured size, recorded as read. Compose has no single equivalent;
546/// it is `onSizeChanged` with the class judgement made for you.
547///
548/// Give the token to the node with `Modifier.observe_size(size.token())`. Nothing is
549/// measured until a node carries it, and the composable reading it runs again only when
550/// the node's size class changes.
551pub fn remember_node_size() -> crate::window::NodeSize {
552    let _group = super::group_guard(NODE_SIZE);
553    let found = with_composer_if_idle(|composer| {
554        if !composer.composing() {
555            return None;
556        }
557        if let Some(slot) = composer.slot_mut::<NodeSizeSlot>() {
558            return Some((slot.token, slot.state.clone()));
559        }
560        let token = crate::window::next_node_token();
561        let state = mutable_state_of(crate::window::node_size(token));
562        let follow = state.clone();
563        let subscription = crate::window::subscribe_node(
564            token,
565            std::sync::Arc::new(move || follow.set(crate::window::node_size(token))),
566        );
567        composer.remember_new(NodeSizeSlot {
568            token,
569            state: state.clone(),
570            _subscription: subscription,
571        });
572        Some((token, state))
573    })
574    .flatten();
575    match found {
576        Some((token, state)) => {
577            // Read through the state, so this scope runs again when the class changes;
578            // the value itself is the module's.
579            let _ = state.get();
580            crate::window::NodeSize::of(token)
581        }
582        None => crate::window::NodeSize::of(0),
583    }
584}
585
586/// A notification handler, shared between the slot and the registration that calls it.
587type ActivationHandler = Rc<RefCell<Box<dyn FnMut(crate::NotificationActivation)>>>;
588
589/// The slot a notification handler lives in: the latest handler, and the registration
590/// that lasts as long as the slot does.
591struct ActivationSlot {
592    handler: ActivationHandler,
593    _subscription: crate::notification::ActivationSubscription,
594}
595
596/// Calls `handler` when the user presses a notification this application posted, for as
597/// long as this call site is in the composition.
598///
599/// The handler is replaced by the latest one on every composition, so it always sees the
600/// states it captured this time.
601pub fn on_notification_activated(handler: impl FnMut(crate::NotificationActivation) + 'static) {
602    let _group = super::group_guard(NOTIFICATION_ACTIVATED);
603    let handler: Box<dyn FnMut(crate::NotificationActivation)> = Box::new(handler);
604    let mut handler = Some(handler);
605    let old = with_composer_if_idle(|composer| {
606        if !composer.composing() {
607            return None;
608        }
609        if let Some(slot) = composer.slot_mut::<ActivationSlot>() {
610            let new = handler.take().expect("the handler is installed once");
611            return Some(std::mem::replace(&mut *slot.handler.borrow_mut(), new));
612        }
613        let shared = Rc::new(RefCell::new(
614            handler.take().expect("the handler is installed once"),
615        ));
616        let forward = Rc::clone(&shared);
617        let subscription = crate::notification::on_activation(Rc::new(
618            move |activation: crate::NotificationActivation| (*forward.borrow_mut())(activation),
619        ));
620        composer.remember_new(ActivationSlot {
621            handler: shared,
622            _subscription: subscription,
623        });
624        None
625    })
626    .flatten();
627    // The handler it replaced is dropped here, with the composer free.
628    drop(old);
629}
630
631const WINDOW_REQUESTED: u64 = super::composer::call_site("compose_rust::on_window_requested", 0);
632const APPLICATION_REOPEN: u64 =
633    super::composer::call_site("compose_rust::on_application_reopen", 0);
634
635/// The slot a registered handler lives in: the latest handler, and the registration that
636/// lasts as long as the slot does.
637type SharedHandler<A> = Rc<RefCell<Box<dyn FnMut(A)>>>;
638
639struct HandlerSlot<A: 'static> {
640    handler: SharedHandler<A>,
641    _subscription: Box<dyn std::any::Any>,
642}
643
644/// Registers `subscribe`'s handler once for this call site and replaces the closure it
645/// calls on every composition, so it always sees the states it captured this time. The
646/// registration ends when the call site leaves the composition.
647fn install_handler<A: 'static>(
648    key: u64,
649    handler: Box<dyn FnMut(A)>,
650    subscribe: impl FnOnce(Rc<dyn Fn(A)>) -> Box<dyn std::any::Any>,
651) {
652    let _group = super::group_guard(key);
653    let mut handler = Some(handler);
654    let mut subscribe = Some(subscribe);
655    let old = with_composer_if_idle(|composer| {
656        if !composer.composing() {
657            return None;
658        }
659        if let Some(slot) = composer.slot_mut::<HandlerSlot<A>>() {
660            let new = handler.take().expect("the handler is installed once");
661            return Some(std::mem::replace(&mut *slot.handler.borrow_mut(), new));
662        }
663        let shared = Rc::new(RefCell::new(
664            handler.take().expect("the handler is installed once"),
665        ));
666        let forward = Rc::clone(&shared);
667        let subscription = (subscribe.take().expect("the handler is installed once"))(Rc::new(
668            move |argument: A| (*forward.borrow_mut())(argument),
669        ));
670        composer.remember_new(HandlerSlot {
671            handler: shared,
672            _subscription: subscription,
673        });
674        None
675    })
676    .flatten();
677    // The handler it replaced is dropped here, with the composer free.
678    drop(old);
679}
680
681/// Calls `handler` for each window the system or the user made that this application has
682/// no `Window` node for, for as long as this call site is in the composition.
683/// The handler puts a node with that `RestoreKey` into the tree, or the Renderer closes the
684/// window.
685pub fn on_window_requested(handler: impl FnMut(crate::windows::WindowRequest) + 'static) {
686    install_handler::<crate::windows::WindowRequest>(
687        WINDOW_REQUESTED,
688        Box::new(handler),
689        |forward| Box::new(crate::windows::on_window_request(forward)),
690    );
691}
692
693/// Calls `handler` when the user opens the application again while it has no window, which
694/// is the Dock icon on macOS. The handler puts a `Window` node into the tree.
695pub fn on_application_reopen(handler: impl FnMut() + 'static) {
696    let mut handler = handler;
697    install_handler::<()>(
698        APPLICATION_REOPEN,
699        Box::new(move |(): ()| handler()),
700        |forward| {
701            Box::new(crate::windows::on_application_reopen_handler(Rc::new(
702                move || forward(()),
703            )))
704        },
705    );
706}