pub(crate) mod composer;
pub(crate) mod effects;
pub(crate) mod host;
mod seam;
pub(crate) mod state;
pub use compose_rust_macros::composable;
pub use effects::{
CoroutineScope, DisposableEffectResult, delay, disposable_effect, launched_effect, on_dispose,
remember_coroutine_scope, side_effect, with_frame_nanos, with_worker,
};
pub use host::{ComposeHost, Recomposer, application, launch, recomposer_for};
pub use seam::{Batch, Runtime};
pub use state::{
DerivedState, MutableState, SnapshotMutationPolicy, State, current_design_system,
current_notification_permission, current_window_size, derived_state_of, mutable_state_of,
mutable_state_of_with_policy, never_equal_policy, on_notification_activated,
remember_node_size, structural_equality_policy,
};
use composer::{Composer, GroupId};
use std::cell::RefCell;
use std::hash::{Hash, Hasher};
use std::rc::Rc;
pub(crate) struct Composition {
pub(crate) composer: RefCell<Composer>,
}
thread_local! {
static CURRENT: RefCell<Option<Rc<Composition>>> = const { RefCell::new(None) };
}
pub(crate) struct Enter {
previous: Option<Rc<Composition>>,
}
impl Composition {
pub(crate) fn new() -> Rc<Self> {
Rc::new(Self {
composer: RefCell::new(Composer::new()),
})
}
pub(crate) fn enter(self: &Rc<Self>) -> Enter {
let previous = CURRENT.with(|current| current.borrow_mut().replace(Rc::clone(self)));
Enter { previous }
}
}
impl Drop for Enter {
fn drop(&mut self) {
let previous = self.previous.take();
let _ = CURRENT.try_with(|current| *current.borrow_mut() = previous);
}
}
pub(crate) fn with_composer<R>(f: impl FnOnce(&mut Composer) -> R) -> Option<R> {
CURRENT.with(|current| {
let current = current.borrow();
let runtime = current.as_ref()?;
let mut composer = runtime
.composer
.try_borrow_mut()
.expect("the composer was reached again while it was already in use");
Some(f(&mut composer))
})
}
pub(crate) fn with_composer_if_idle<R>(f: impl FnOnce(&mut Composer) -> R) -> Option<R> {
CURRENT
.try_with(|current| {
let current = current.try_borrow().ok()?;
let runtime = current.as_ref()?;
let mut composer = runtime.composer.try_borrow_mut().ok()?;
Some(f(&mut composer))
})
.ok()
.flatten()
}
pub(crate) fn drain_graveyard() {
loop {
let dead =
with_composer(|composer| std::mem::take(&mut composer.graveyard)).unwrap_or_default();
if dead.is_empty() {
return;
}
drop(dead);
}
}
#[must_use = "the group closes when this is dropped, so it has to be bound for the length of the block"]
pub struct GroupGuard {
active: bool,
}
impl Drop for GroupGuard {
fn drop(&mut self) {
if self.active {
with_composer_if_idle(Composer::end_group);
}
}
}
pub(crate) fn group_guard(key: u64) -> GroupGuard {
let active = with_composer_if_idle(|composer| {
if composer.composing() {
composer.start_group(key);
true
} else {
false
}
})
.unwrap_or(false);
GroupGuard { active }
}
pub fn remember<T: Clone + 'static>(init: impl FnOnce() -> T) -> T {
let found = with_composer_if_idle(|composer| {
if composer.composing() {
Some(composer.remembered::<T>())
} else {
None
}
})
.flatten();
match found {
Some(Some(value)) => value,
Some(None) => {
let value = init();
with_composer(|composer| composer.remember_new(value.clone()));
value
}
None => init(),
}
}
pub fn remember_with_key<K, T>(key: K, init: impl FnOnce() -> T) -> T
where
K: PartialEq + Clone + 'static,
T: Clone + 'static,
{
let found = with_composer_if_idle(|composer| {
if composer.composing() {
Some(composer.remembered_if::<(K, T)>(|(stored, _)| *stored == key))
} else {
None
}
})
.flatten();
match found {
Some(Some((_, value))) => value,
Some(None) => {
let value = init();
with_composer(|composer| composer.remember_new((key, value.clone())));
value
}
None => init(),
}
}
const KEY_BASE: u64 = composer::call_site("compose_rust::key", 0);
pub fn key<K: Hash, R>(key: K, content: impl FnOnce() -> R) -> R {
let mut hasher = Fnv(0xcbf2_9ce4_8422_2325);
key.hash(&mut hasher);
let _group = group_guard(composer::mix(KEY_BASE, hasher.finish()));
content()
}
struct Fnv(u64);
impl Hasher for Fnv {
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, bytes: &[u8]) {
for byte in bytes {
self.0 ^= u64::from(*byte);
self.0 = self.0.wrapping_mul(0x100_0000_01b3);
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct RecomposeScope {
group: GroupId,
generation: u32,
}
impl RecomposeScope {
pub fn invalidate(&self) {
let woke = with_composer_if_idle(|composer| {
composer.invalidate(self.group, self.generation);
});
if woke.is_some() && !crate::boundary::in_host_call() {
crate::boundary::request_frame_from_worker();
}
}
}
pub fn current_recompose_scope() -> Option<RecomposeScope> {
with_composer_if_idle(|composer| {
let group = *composer.scope_stack.last()?;
Some(RecomposeScope {
group,
generation: composer.groups[group as usize].generation,
})
})
.flatten()
}
#[doc(hidden)]
pub mod __private {
use super::composer::GroupId;
use super::{GroupGuard, group_guard, with_composer, with_composer_if_idle};
use std::rc::Rc;
pub use super::composer::call_site;
pub fn group(key: u64) -> GroupGuard {
group_guard(key)
}
#[must_use]
pub struct RestartGroup {
group: Option<GroupId>,
}
pub fn start_restart_group(key: u64) -> RestartGroup {
let group = with_composer_if_idle(|composer| {
composer
.composing()
.then(|| composer.start_restart_group(key))
})
.flatten();
RestartGroup { group }
}
impl RestartGroup {
pub fn must_run(&self) -> bool {
match self.group {
Some(group) => with_composer(|composer| composer.must_run(group)).unwrap_or(true),
None => true,
}
}
pub fn skip(&self) {
if self.group.is_some() {
with_composer(super::composer::Composer::skip_to_group_end);
}
}
pub fn wants_restart(&self) -> bool {
match self.group {
Some(group) => {
with_composer(|composer| composer.wants_restart(group)).unwrap_or(false)
}
None => false,
}
}
pub fn set_restart(&self, restart: impl Fn() + 'static) {
if let Some(group) = self.group {
let restart: Rc<dyn Fn()> = Rc::new(restart);
with_composer(|composer| composer.set_restart(group, restart));
}
}
pub fn param_changed<T: PartialEq + Clone + 'static>(
&self,
index: usize,
value: &T,
) -> bool {
match self.group {
Some(group) => {
with_composer(|composer| composer.param_changed(group, index, value))
.unwrap_or(true)
}
None => true,
}
}
}
impl Drop for RestartGroup {
fn drop(&mut self) {
if self.group.is_some() {
with_composer_if_idle(super::composer::Composer::end_group);
}
}
}
pub struct Param<'a, T>(pub &'a T);
pub trait StableParam {
type Value;
fn __compose_changed(&self, scope: &RestartGroup, index: usize) -> bool;
fn __compose_clone(&self) -> Option<Self::Value>;
}
impl<T: PartialEq + Clone + 'static> StableParam for Param<'_, T> {
type Value = T;
fn __compose_changed(&self, scope: &RestartGroup, index: usize) -> bool {
scope.param_changed(index, self.0)
}
fn __compose_clone(&self) -> Option<T> {
Some(self.0.clone())
}
}
pub trait UnstableParam {
type Value;
fn __compose_changed(&self, scope: &RestartGroup, index: usize) -> bool;
fn __compose_clone(&self) -> Option<Self::Value>;
}
impl<T> UnstableParam for &Param<'_, T> {
type Value = T;
fn __compose_changed(&self, _scope: &RestartGroup, _index: usize) -> bool {
true
}
fn __compose_clone(&self) -> Option<T> {
None
}
}
}