Skip to main content

cranpose_core/
composer_context.rs

1use std::{
2    rc::Rc,
3    sync::atomic::{AtomicUsize, Ordering},
4};
5
6use crate::{Composer, ComposerCore, thread_state};
7
8/// How many threads have a composer installed. Code outside any
9/// composition, as a draw closure's branch guards are, reads this instead of
10/// the thread-local, which costs a call into the platform on Android.
11static INSTALLED_COMPOSERS: AtomicUsize = AtomicUsize::new(0);
12
13/// Whether any thread may have a composer: `false` means this one has none.
14fn any_composer_installed() -> bool {
15    INSTALLED_COMPOSERS.load(Ordering::Relaxed) != 0
16}
17
18/// Restores the previous composer when its scope ends.
19#[must_use = "ComposerScopeGuard restores the previous composer on drop"]
20pub struct ComposerScopeGuard {
21    previous: Option<Rc<ComposerCore>>,
22}
23
24impl Drop for ComposerScopeGuard {
25    fn drop(&mut self) {
26        replace_current(self.previous.take());
27    }
28}
29
30/// Sets the current thread's composer for the duration of the scope.
31/// Returns a guard that restores the previous composer on drop.
32pub fn enter(composer: &Composer) -> ComposerScopeGuard {
33    ComposerScopeGuard {
34        previous: replace_current(Some(composer.clone_core())),
35    }
36}
37
38fn replace_current(composer: Option<Rc<ComposerCore>>) -> Option<Rc<ComposerCore>> {
39    let installs = composer.is_some();
40    let previous = thread_state::runtimes(|state| state.current_composer.replace(composer));
41    match (previous.is_some(), installs) {
42        (false, true) => {
43            INSTALLED_COMPOSERS.fetch_add(1, Ordering::Relaxed);
44        }
45        (true, false) => {
46            INSTALLED_COMPOSERS.fetch_sub(1, Ordering::Relaxed);
47        }
48        _ => {}
49    }
50    previous
51}
52
53pub(crate) fn without_composer<R>(f: impl FnOnce() -> R) -> R {
54    let _suspended = ComposerScopeGuard {
55        previous: replace_current(None),
56    };
57    f()
58}
59
60/// Access the current thread's composer.
61///
62/// # Panics
63/// Panics if there is no active composer.
64pub fn with_composer<R>(f: impl FnOnce(&Composer) -> R) -> R {
65    thread_state::runtimes(|state| {
66        let core = state
67            .current_composer
68            .borrow()
69            .as_ref()
70            .expect("with_composer: no active composer")
71            .clone();
72        let composer = Composer::from_core(core);
73        f(&composer)
74    })
75}
76
77/// The current thread's composer, for the code `#[composable]` expands to.
78///
79/// # Panics
80/// Panics if there is no active composer.
81#[doc(hidden)]
82pub fn __current_composer() -> Composer {
83    thread_state::runtimes(|state| {
84        Composer::from_core(
85            state
86                .current_composer
87                .borrow()
88                .as_ref()
89                .expect("with_composer: no active composer")
90                .clone(),
91        )
92    })
93}
94
95pub(crate) fn with_current_core<R>(f: impl FnOnce(&ComposerCore) -> R) -> Option<R> {
96    if !any_composer_installed() {
97        return None;
98    }
99    thread_state::try_runtimes(|state| state.current_composer.borrow().as_deref().map(f)).flatten()
100}
101
102/// Return the current thread's composer.
103pub fn current_composer() -> Option<Composer> {
104    if !any_composer_installed() {
105        return None;
106    }
107    thread_state::try_runtimes(|state| {
108        let core = state.current_composer.borrow().as_ref()?.clone();
109        Some(Composer::from_core(core))
110    })
111    .flatten()
112}
113
114pub fn note_nested_slots_host(host: &std::rc::Rc<crate::SlotsHost>) {
115    let Some(composer) = current_composer() else {
116        return;
117    };
118    let holder = composer.active_slots_host();
119    if std::rc::Rc::ptr_eq(&holder, host) {
120        return;
121    }
122    holder.note_nested_host(host);
123}
124
125/// Try to access the current thread's composer.
126/// Returns None if there is no active composer.
127pub fn try_with_composer<R>(f: impl FnOnce(&Composer) -> R) -> Option<R> {
128    current_composer().map(|composer| f(&composer))
129}