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.
14#[inline]
15fn any_composer_installed() -> bool {
16    INSTALLED_COMPOSERS.load(Ordering::Relaxed) != 0
17}
18
19/// Restores the previous composer when its scope ends.
20#[must_use = "ComposerScopeGuard restores the previous composer on drop"]
21pub struct ComposerScopeGuard {
22    previous: Option<Rc<ComposerCore>>,
23}
24
25impl Drop for ComposerScopeGuard {
26    fn drop(&mut self) {
27        replace_current(self.previous.take());
28    }
29}
30
31/// Sets the current thread's composer for the duration of the scope.
32/// Returns a guard that restores the previous composer on drop.
33pub fn enter(composer: &Composer) -> ComposerScopeGuard {
34    ComposerScopeGuard {
35        previous: replace_current(Some(composer.clone_core())),
36    }
37}
38
39fn replace_current(composer: Option<Rc<ComposerCore>>) -> Option<Rc<ComposerCore>> {
40    let installs = composer.is_some();
41    let previous = thread_state::runtimes(|state| state.current_composer.replace(composer));
42    match (previous.is_some(), installs) {
43        (false, true) => {
44            INSTALLED_COMPOSERS.fetch_add(1, Ordering::Relaxed);
45        }
46        (true, false) => {
47            INSTALLED_COMPOSERS.fetch_sub(1, Ordering::Relaxed);
48        }
49        _ => {}
50    }
51    previous
52}
53
54pub(crate) fn without_composer<R>(f: impl FnOnce() -> R) -> R {
55    let _suspended = ComposerScopeGuard {
56        previous: replace_current(None),
57    };
58    f()
59}
60
61/// Access the current thread's composer.
62///
63/// # Panics
64/// Panics if there is no active composer.
65pub fn with_composer<R>(f: impl FnOnce(&Composer) -> R) -> R {
66    thread_state::runtimes(|state| {
67        let core = state
68            .current_composer
69            .borrow()
70            .as_ref()
71            .expect("with_composer: no active composer")
72            .clone();
73        let composer = Composer::from_core(core);
74        f(&composer)
75    })
76}
77
78/// The current thread's composer, for the code `#[composable]` expands to.
79///
80/// # Panics
81/// Panics if there is no active composer.
82#[doc(hidden)]
83pub fn __current_composer() -> Composer {
84    thread_state::runtimes(|state| {
85        Composer::from_core(
86            state
87                .current_composer
88                .borrow()
89                .as_ref()
90                .expect("with_composer: no active composer")
91                .clone(),
92        )
93    })
94}
95
96/// Runs `f` with the current thread's composer core, `None` without one.
97/// The check that no thread has one is inlined where it is asked: a draw
98/// closure's guards run it many times a draw, and a function holding the
99/// lookup too saved its registers before it checked.
100#[inline]
101pub(crate) fn with_current_core<R>(f: impl FnOnce(&ComposerCore) -> R) -> Option<R> {
102    if !any_composer_installed() {
103        return None;
104    }
105    with_installed_core(f)
106}
107
108#[inline(never)]
109fn with_installed_core<R>(f: impl FnOnce(&ComposerCore) -> R) -> Option<R> {
110    thread_state::try_runtimes(|state| state.current_composer.borrow().as_deref().map(f)).flatten()
111}
112
113/// Return the current thread's composer.
114pub fn current_composer() -> Option<Composer> {
115    if !any_composer_installed() {
116        return None;
117    }
118    thread_state::try_runtimes(|state| {
119        let core = state.current_composer.borrow().as_ref()?.clone();
120        Some(Composer::from_core(core))
121    })
122    .flatten()
123}
124
125/// Registers `host`, the slot table of a subcomposition, as nested in the active slot table of the
126/// current composer, so that forgetting effects in the outer table reaches `host` too. Does nothing
127/// without a current composer.
128pub fn note_nested_slots_host(host: &std::rc::Rc<crate::SlotsHost>) {
129    let Some(composer) = current_composer() else {
130        return;
131    };
132    let holder = composer.active_slots_host();
133    if std::rc::Rc::ptr_eq(&holder, host) {
134        return;
135    }
136    holder.note_nested_host(host);
137}
138
139/// Try to access the current thread's composer.
140/// Returns None if there is no active composer.
141pub fn try_with_composer<R>(f: impl FnOnce(&Composer) -> R) -> Option<R> {
142    current_composer().map(|composer| f(&composer))
143}