1pub(crate) mod composer;
41pub(crate) mod effects;
42pub(crate) mod host;
43mod seam;
44pub(crate) mod state;
45
46pub use compose_rust_macros::composable;
47pub use effects::{
48 CoroutineScope, DisposableEffectResult, delay, disposable_effect, launched_effect, on_dispose,
49 remember_coroutine_scope, side_effect, with_frame_nanos, with_worker,
50};
51pub use host::{ComposeHost, Recomposer, application, launch, recomposer_for};
52pub use seam::{Batch, Runtime};
53pub use state::{
54 DerivedState, MutableState, SnapshotMutationPolicy, State, current_design_system,
55 current_notification_permission, current_window_size, derived_state_of, mutable_state_of,
56 mutable_state_of_with_policy, never_equal_policy, on_notification_activated,
57 remember_node_size, structural_equality_policy,
58};
59
60use composer::{Composer, GroupId};
61use std::cell::RefCell;
62use std::hash::{Hash, Hasher};
63use std::rc::Rc;
64
65pub(crate) struct Composition {
67 pub(crate) composer: RefCell<Composer>,
68}
69
70thread_local! {
71 static CURRENT: RefCell<Option<Rc<Composition>>> = const { RefCell::new(None) };
73}
74
75pub(crate) struct Enter {
78 previous: Option<Rc<Composition>>,
79}
80
81impl Composition {
82 pub(crate) fn new() -> Rc<Self> {
83 Rc::new(Self {
84 composer: RefCell::new(Composer::new()),
85 })
86 }
87
88 pub(crate) fn enter(self: &Rc<Self>) -> Enter {
89 let previous = CURRENT.with(|current| current.borrow_mut().replace(Rc::clone(self)));
90 Enter { previous }
91 }
92}
93
94impl Drop for Enter {
95 fn drop(&mut self) {
96 let previous = self.previous.take();
97 let _ = CURRENT.try_with(|current| *current.borrow_mut() = previous);
98 }
99}
100
101pub(crate) fn with_composer<R>(f: impl FnOnce(&mut Composer) -> R) -> Option<R> {
108 CURRENT.with(|current| {
109 let current = current.borrow();
110 let runtime = current.as_ref()?;
111 let mut composer = runtime
112 .composer
113 .try_borrow_mut()
114 .expect("the composer was reached again while it was already in use");
115 Some(f(&mut composer))
116 })
117}
118
119pub(crate) fn with_composer_if_idle<R>(f: impl FnOnce(&mut Composer) -> R) -> Option<R> {
123 CURRENT
124 .try_with(|current| {
125 let current = current.try_borrow().ok()?;
126 let runtime = current.as_ref()?;
127 let mut composer = runtime.composer.try_borrow_mut().ok()?;
128 Some(f(&mut composer))
129 })
130 .ok()
131 .flatten()
132}
133
134pub(crate) fn drain_graveyard() {
137 loop {
138 let dead =
139 with_composer(|composer| std::mem::take(&mut composer.graveyard)).unwrap_or_default();
140 if dead.is_empty() {
141 return;
142 }
143 drop(dead);
144 }
145}
146
147#[must_use = "the group closes when this is dropped, so it has to be bound for the length of the block"]
150pub struct GroupGuard {
151 active: bool,
152}
153
154impl Drop for GroupGuard {
155 fn drop(&mut self) {
156 if self.active {
161 with_composer_if_idle(Composer::end_group);
162 }
163 }
164}
165
166pub(crate) fn group_guard(key: u64) -> GroupGuard {
168 let active = with_composer_if_idle(|composer| {
169 if composer.composing() {
170 composer.start_group(key);
171 true
172 } else {
173 false
174 }
175 })
176 .unwrap_or(false);
177 GroupGuard { active }
178}
179
180pub fn remember<T: Clone + 'static>(init: impl FnOnce() -> T) -> T {
187 let found = with_composer_if_idle(|composer| {
188 if composer.composing() {
189 Some(composer.remembered::<T>())
190 } else {
191 None
192 }
193 })
194 .flatten();
195 match found {
196 Some(Some(value)) => value,
197 Some(None) => {
198 let value = init();
199 with_composer(|composer| composer.remember_new(value.clone()));
200 value
201 }
202 None => init(),
203 }
204}
205
206pub fn remember_with_key<K, T>(key: K, init: impl FnOnce() -> T) -> T
209where
210 K: PartialEq + Clone + 'static,
211 T: Clone + 'static,
212{
213 let found = with_composer_if_idle(|composer| {
214 if composer.composing() {
215 Some(composer.remembered_if::<(K, T)>(|(stored, _)| *stored == key))
216 } else {
217 None
218 }
219 })
220 .flatten();
221 match found {
222 Some(Some((_, value))) => value,
223 Some(None) => {
224 let value = init();
225 with_composer(|composer| composer.remember_new((key, value.clone())));
227 value
228 }
229 None => init(),
230 }
231}
232
233const KEY_BASE: u64 = composer::call_site("compose_rust::key", 0);
234
235pub fn key<K: Hash, R>(key: K, content: impl FnOnce() -> R) -> R {
247 let mut hasher = Fnv(0xcbf2_9ce4_8422_2325);
248 key.hash(&mut hasher);
249 let _group = group_guard(composer::mix(KEY_BASE, hasher.finish()));
250 content()
251}
252
253struct Fnv(u64);
256
257impl Hasher for Fnv {
258 fn finish(&self) -> u64 {
259 self.0
260 }
261
262 fn write(&mut self, bytes: &[u8]) {
263 for byte in bytes {
264 self.0 ^= u64::from(*byte);
265 self.0 = self.0.wrapping_mul(0x100_0000_01b3);
266 }
267 }
268}
269
270#[derive(Clone, Copy, Debug)]
273pub struct RecomposeScope {
274 group: GroupId,
275 generation: u32,
276}
277
278impl RecomposeScope {
279 pub fn invalidate(&self) {
282 let woke = with_composer_if_idle(|composer| {
283 composer.invalidate(self.group, self.generation);
284 });
285 if woke.is_some() && !crate::boundary::in_host_call() {
286 crate::boundary::request_frame_from_worker();
287 }
288 }
289}
290
291pub fn current_recompose_scope() -> Option<RecomposeScope> {
293 with_composer_if_idle(|composer| {
294 let group = *composer.scope_stack.last()?;
295 Some(RecomposeScope {
296 group,
297 generation: composer.groups[group as usize].generation,
298 })
299 })
300 .flatten()
301}
302
303#[doc(hidden)]
306pub mod __private {
307 use super::composer::GroupId;
308 use super::{GroupGuard, group_guard, with_composer, with_composer_if_idle};
309 use std::rc::Rc;
310
311 pub use super::composer::call_site;
312
313 pub fn group(key: u64) -> GroupGuard {
315 group_guard(key)
316 }
317
318 #[must_use]
320 pub struct RestartGroup {
321 group: Option<GroupId>,
322 }
323
324 pub fn start_restart_group(key: u64) -> RestartGroup {
325 let group = with_composer_if_idle(|composer| {
326 composer
327 .composing()
328 .then(|| composer.start_restart_group(key))
329 })
330 .flatten();
331 RestartGroup { group }
332 }
333
334 impl RestartGroup {
335 pub fn must_run(&self) -> bool {
337 match self.group {
338 Some(group) => with_composer(|composer| composer.must_run(group)).unwrap_or(true),
339 None => true,
340 }
341 }
342
343 pub fn skip(&self) {
345 if self.group.is_some() {
346 with_composer(super::composer::Composer::skip_to_group_end);
347 }
348 }
349
350 pub fn wants_restart(&self) -> bool {
353 match self.group {
354 Some(group) => {
355 with_composer(|composer| composer.wants_restart(group)).unwrap_or(false)
356 }
357 None => false,
358 }
359 }
360
361 pub fn set_restart(&self, restart: impl Fn() + 'static) {
362 if let Some(group) = self.group {
363 let restart: Rc<dyn Fn()> = Rc::new(restart);
364 with_composer(|composer| composer.set_restart(group, restart));
365 }
366 }
367
368 pub fn param_changed<T: PartialEq + Clone + 'static>(
370 &self,
371 index: usize,
372 value: &T,
373 ) -> bool {
374 match self.group {
375 Some(group) => {
376 with_composer(|composer| composer.param_changed(group, index, value))
377 .unwrap_or(true)
378 }
379 None => true,
380 }
381 }
382 }
383
384 impl Drop for RestartGroup {
385 fn drop(&mut self) {
386 if self.group.is_some() {
387 with_composer_if_idle(super::composer::Composer::end_group);
388 }
389 }
390 }
391
392 pub struct Param<'a, T>(pub &'a T);
396
397 pub trait StableParam {
398 type Value;
399 fn __compose_changed(&self, scope: &RestartGroup, index: usize) -> bool;
400 fn __compose_clone(&self) -> Option<Self::Value>;
401 }
402
403 impl<T: PartialEq + Clone + 'static> StableParam for Param<'_, T> {
404 type Value = T;
405
406 fn __compose_changed(&self, scope: &RestartGroup, index: usize) -> bool {
407 scope.param_changed(index, self.0)
408 }
409
410 fn __compose_clone(&self) -> Option<T> {
411 Some(self.0.clone())
412 }
413 }
414
415 pub trait UnstableParam {
416 type Value;
417 fn __compose_changed(&self, scope: &RestartGroup, index: usize) -> bool;
418 fn __compose_clone(&self) -> Option<Self::Value>;
419 }
420
421 impl<T> UnstableParam for &Param<'_, T> {
422 type Value = T;
423
424 fn __compose_changed(&self, _scope: &RestartGroup, _index: usize) -> bool {
425 true
426 }
427
428 fn __compose_clone(&self) -> Option<T> {
429 None
430 }
431 }
432}