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, StateError, 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_application_reopen,
57 on_notification_activated, on_window_requested, 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
180#[track_caller]
187pub fn remember<T: Clone + 'static>(init: impl FnOnce() -> T) -> T {
188 let site = std::panic::Location::caller();
189 let found = with_composer_if_idle(|composer| {
190 if composer.composing() {
191 Some(composer.remembered::<T>())
192 } else {
193 None
194 }
195 })
196 .flatten();
197 match found {
198 Some(Some(value)) => value,
199 Some(None) => {
200 let capture = state::Capture::begin(site);
201 let value = init();
202 let owned = capture.finish();
203 with_composer(|composer| composer.remember_new_owning(value.clone(), owned));
204 value
205 }
206 None => init(),
207 }
208}
209
210#[track_caller]
213pub fn remember_with_key<K, T>(key: K, init: impl FnOnce() -> T) -> T
214where
215 K: PartialEq + Clone + 'static,
216 T: Clone + 'static,
217{
218 let site = std::panic::Location::caller();
219 let found = with_composer_if_idle(|composer| {
220 if composer.composing() {
221 Some(composer.remembered_if::<(K, T)>(|(stored, _)| *stored == key))
222 } else {
223 None
224 }
225 })
226 .flatten();
227 match found {
228 Some(Some((_, value))) => value,
229 Some(None) => {
230 let capture = state::Capture::begin(site);
231 let value = init();
232 let owned = capture.finish();
233 with_composer(|composer| composer.remember_new_owning((key, value.clone()), owned));
235 value
236 }
237 None => init(),
238 }
239}
240
241const KEY_BASE: u64 = composer::call_site("compose_rust::key", 0);
242
243pub fn key<K: Hash, R>(key: K, content: impl FnOnce() -> R) -> R {
255 let mut hasher = Fnv(0xcbf2_9ce4_8422_2325);
256 key.hash(&mut hasher);
257 let _group = group_guard(composer::mix(KEY_BASE, hasher.finish()));
258 content()
259}
260
261struct Fnv(u64);
264
265impl Hasher for Fnv {
266 fn finish(&self) -> u64 {
267 self.0
268 }
269
270 fn write(&mut self, bytes: &[u8]) {
271 for byte in bytes {
272 self.0 ^= u64::from(*byte);
273 self.0 = self.0.wrapping_mul(0x100_0000_01b3);
274 }
275 }
276}
277
278#[derive(Clone, Copy, Debug)]
281pub struct RecomposeScope {
282 group: GroupId,
283 generation: u32,
284}
285
286impl RecomposeScope {
287 pub fn invalidate(&self) {
290 let woke = with_composer_if_idle(|composer| {
291 composer.invalidate(self.group, self.generation);
292 });
293 if woke.is_some() && !crate::boundary::in_host_call() {
294 crate::boundary::request_frame_from_worker();
295 }
296 }
297}
298
299pub fn current_recompose_scope() -> Option<RecomposeScope> {
301 with_composer_if_idle(|composer| {
302 let group = *composer.scope_stack.last()?;
303 Some(RecomposeScope {
304 group,
305 generation: composer.groups[group as usize].generation,
306 })
307 })
308 .flatten()
309}
310
311#[doc(hidden)]
314pub mod __private {
315 use super::composer::GroupId;
316 use super::{GroupGuard, group_guard, with_composer, with_composer_if_idle};
317 use std::rc::Rc;
318
319 pub use super::composer::call_site;
320
321 pub fn group(key: u64) -> GroupGuard {
323 group_guard(key)
324 }
325
326 #[must_use]
328 pub struct RestartGroup {
329 group: Option<GroupId>,
330 }
331
332 pub fn start_restart_group(key: u64) -> RestartGroup {
333 let group = with_composer_if_idle(|composer| {
334 composer
335 .composing()
336 .then(|| composer.start_restart_group(key))
337 })
338 .flatten();
339 RestartGroup { group }
340 }
341
342 impl RestartGroup {
343 pub fn must_run(&self) -> bool {
345 match self.group {
346 Some(group) => with_composer(|composer| composer.must_run(group)).unwrap_or(true),
347 None => true,
348 }
349 }
350
351 pub fn skip(&self) {
353 if self.group.is_some() {
354 with_composer(super::composer::Composer::skip_to_group_end);
355 }
356 }
357
358 pub fn wants_restart(&self) -> bool {
361 match self.group {
362 Some(group) => {
363 with_composer(|composer| composer.wants_restart(group)).unwrap_or(false)
364 }
365 None => false,
366 }
367 }
368
369 pub fn set_restart(&self, restart: impl Fn() + 'static) {
370 if let Some(group) = self.group {
371 let restart: Rc<dyn Fn()> = Rc::new(restart);
372 with_composer(|composer| composer.set_restart(group, restart));
373 }
374 }
375
376 pub fn param_changed<T: PartialEq + Clone + 'static>(
378 &self,
379 index: usize,
380 value: &T,
381 ) -> bool {
382 match self.group {
383 Some(group) => {
384 with_composer(|composer| composer.param_changed(group, index, value))
385 .unwrap_or(true)
386 }
387 None => true,
388 }
389 }
390 }
391
392 impl Drop for RestartGroup {
393 fn drop(&mut self) {
394 if self.group.is_some() {
395 with_composer_if_idle(super::composer::Composer::end_group);
396 }
397 }
398 }
399
400 pub struct Param<'a, T>(pub &'a T);
404
405 pub trait StableParam {
406 type Value;
407 fn __compose_changed(&self, scope: &RestartGroup, index: usize) -> bool;
408 fn __compose_clone(&self) -> Option<Self::Value>;
409 }
410
411 impl<T: PartialEq + Clone + 'static> StableParam for Param<'_, T> {
412 type Value = T;
413
414 fn __compose_changed(&self, scope: &RestartGroup, index: usize) -> bool {
415 scope.param_changed(index, self.0)
416 }
417
418 fn __compose_clone(&self) -> Option<T> {
419 Some(self.0.clone())
420 }
421 }
422
423 pub trait UnstableParam {
424 type Value;
425 fn __compose_changed(&self, scope: &RestartGroup, index: usize) -> bool;
426 fn __compose_clone(&self) -> Option<Self::Value>;
427 }
428
429 impl<T> UnstableParam for &Param<'_, T> {
430 type Value = T;
431
432 fn __compose_changed(&self, _scope: &RestartGroup, _index: usize) -> bool {
433 true
434 }
435
436 fn __compose_clone(&self) -> Option<T> {
437 None
438 }
439 }
440}