use std::{hash::Hash, rc::Rc};
use crate::{
composer_context,
owned::Owned,
runtime,
state::{
DerivedState, MutableState, OwnedMutableState, SnapshotStateList, SnapshotStateMap, State,
StructuralEqual,
},
};
#[track_caller]
pub fn remember<T: 'static>(init: impl FnOnce() -> T) -> Owned<T> {
let source = crate::caller_location_key();
composer_context::with_composer(|composer| composer.remember_at(source, init))
}
#[expect(non_snake_case, reason = "names follow Compose's casing")]
#[track_caller]
pub fn rememberKeyed<K, T>(key: K, init: impl FnOnce(&K) -> T) -> T
where
K: PartialEq + 'static,
T: Clone + 'static,
{
let slot = remember(|| std::cell::RefCell::new(None::<(K, T)>));
slot.with(|cell| {
let mut stored = cell.borrow_mut();
match &*stored {
Some((stored_key, value)) if *stored_key == key => value.clone(),
_ => {
let value = init(&key);
*stored = Some((key, value.clone()));
value
}
}
})
}
#[expect(non_snake_case, reason = "names follow Compose's casing")]
#[track_caller]
pub fn rememberUpdatedState<T: Clone + 'static>(value: T) -> MutableState<T> {
let source = crate::caller_location_key();
composer_context::with_composer(|composer| {
let runtime = composer.runtime_handle();
let state = composer.remember_at(source, || {
OwnedMutableState::with_runtime(value.clone(), runtime)
});
state.with(|s| {
s.set(value);
s.handle()
})
})
}
#[expect(non_snake_case, reason = "names follow Compose's casing")]
pub fn mutableStateOf<T: Clone + PartialEq + 'static>(initial: T) -> MutableState<T> {
current_runtime("mutableStateOf")
.alloc_persistent_state_with_policy(initial, Rc::new(StructuralEqual))
}
#[expect(non_snake_case, reason = "names follow Compose's casing")]
pub fn mutableStateOfNeverEqual<T: Clone + 'static>(initial: T) -> MutableState<T> {
current_runtime("mutableStateOfNeverEqual").alloc_persistent_state(initial)
}
#[expect(non_snake_case, reason = "names follow Compose's casing")]
pub fn ownedMutableStateOf<T: Clone + PartialEq + 'static>(initial: T) -> OwnedMutableState<T> {
OwnedMutableState::with_runtime_structural_eq(initial, current_runtime("ownedMutableStateOf"))
}
#[expect(non_snake_case, reason = "names follow Compose's casing")]
pub fn ownedMutableStateOfNeverEqual<T: Clone + 'static>(initial: T) -> OwnedMutableState<T> {
OwnedMutableState::with_runtime(initial, current_runtime("ownedMutableStateOfNeverEqual"))
}
pub(crate) fn current_runtime(what: &str) -> runtime::RuntimeHandle {
composer_context::try_with_composer(super::composer::Composer::runtime_handle)
.or_else(runtime::current_runtime_handle)
.unwrap_or_else(|| {
panic!(
"{what} requires an active runtime. Create state inside a composition or after a Runtime is created."
)
})
}
#[expect(non_snake_case, reason = "names follow Compose's casing")]
pub fn try_mutableStateOf<T: Clone + PartialEq + 'static>(initial: T) -> Option<MutableState<T>> {
let runtime = composer_context::try_with_composer(super::composer::Composer::runtime_handle)
.or_else(runtime::current_runtime_handle)?;
Some(runtime.alloc_persistent_state_with_policy(initial, Rc::new(StructuralEqual)))
}
#[expect(non_snake_case, reason = "names follow Compose's casing")]
pub fn mutableStateListOf<T, I>(values: I) -> SnapshotStateList<T>
where
T: Clone + 'static,
I: IntoIterator<Item = T>,
{
composer_context::with_composer(move |composer| composer.mutable_state_list_of(values))
}
#[expect(non_snake_case, reason = "names follow Compose's casing")]
pub fn mutableStateList<T: Clone + 'static>() -> SnapshotStateList<T> {
mutableStateListOf(std::iter::empty::<T>())
}
#[expect(non_snake_case, reason = "names follow Compose's casing")]
pub fn mutableStateMapOf<K, V, I>(pairs: I) -> SnapshotStateMap<K, V>
where
K: Clone + Eq + Hash + 'static,
V: Clone + 'static,
I: IntoIterator<Item = (K, V)>,
{
composer_context::with_composer(move |composer| composer.mutable_state_map_of(pairs))
}
#[expect(non_snake_case, reason = "names follow Compose's casing")]
pub fn mutableStateMap<K, V>() -> SnapshotStateMap<K, V>
where
K: Clone + Eq + Hash + 'static,
V: Clone + 'static,
{
mutableStateMapOf(std::iter::empty::<(K, V)>())
}
#[expect(non_snake_case, reason = "names follow Compose's casing")]
#[track_caller]
pub fn rememberMutableStateOf<T: Clone + PartialEq + 'static>(
init: impl FnOnce() -> T,
) -> MutableState<T> {
let source = crate::caller_location_key();
composer_context::with_composer(|composer| {
let runtime = composer.runtime_handle();
composer
.remember_at(source, || {
OwnedMutableState::with_runtime_structural_eq(init(), runtime)
})
.with(OwnedMutableState::handle)
})
}
#[expect(non_snake_case, reason = "names follow Compose's casing")]
#[track_caller]
pub fn rememberMutableStateOfNeverEqual<T: Clone + 'static>(
init: impl FnOnce() -> T,
) -> MutableState<T> {
let source = crate::caller_location_key();
composer_context::with_composer(|composer| {
let runtime = composer.runtime_handle();
composer
.remember_at(source, || OwnedMutableState::with_runtime(init(), runtime))
.with(OwnedMutableState::handle)
})
}
#[expect(non_snake_case, reason = "names follow Compose's casing")]
#[track_caller]
pub fn derivedStateOf<T: 'static + Clone + PartialEq>(
compute: impl Fn() -> T + 'static,
) -> State<T> {
let source = crate::caller_location_key();
let compute: Rc<dyn Fn() -> T> = Rc::new(compute);
composer_context::with_composer(|composer| {
composer.with_group(source, |composer| {
let derivation = composer.current_recompose_scope();
if let Some(scope) = &derivation
&& !scope.is_derivation()
{
scope.mark_derivation();
composer.set_recompose_fn(|_| {});
}
let runtime = composer.runtime_handle();
let (state, rerun) = composer
.remember_at(source, || None::<DerivedState<T>>)
.update(|slot| {
let derived = match slot.take() {
Some(derived) => {
derived.set_compute(compute);
if restarted_for(composer, &derived) {
if let Some(scope) = &derivation {
scope.keep_reads();
}
} else {
derived.recompute();
}
derived
}
None => DerivedState::new(runtime, compute, derivation.as_ref()),
};
let state = derived.state();
let rerun = derived.rerun();
*slot = Some(derived);
(state, rerun)
});
composer.set_recompose_callback(rerun);
state
})
})
}
fn restarted_for<T: Clone + PartialEq + 'static>(
composer: &crate::Composer,
derived: &DerivedState<T>,
) -> bool {
composer
.restarted_scope()
.is_some_and(|scope| scope.invalidated_only_by_state(derived.state_id()))
}