Skip to main content

cranpose_core/
composer_context.rs

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