use super::composer::Observer;
use super::{with_composer, with_composer_if_idle};
use crate::schema::{DesignSystem, NotificationPermission};
use crate::window::WindowSize;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::fmt;
use std::rc::{Rc, Weak};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, MutexGuard};
use super::composer::RuntimeSignal;
static NEXT_STATE_ID: AtomicU64 = AtomicU64::new(1);
fn next_state_id() -> u64 {
NEXT_STATE_ID.fetch_add(1, Ordering::Relaxed)
}
fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
mutex.lock().unwrap_or_else(|poison| poison.into_inner())
}
pub struct SnapshotMutationPolicy<T> {
equivalent: fn(&T, &T) -> bool,
}
impl<T> Clone for SnapshotMutationPolicy<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for SnapshotMutationPolicy<T> {}
pub fn structural_equality_policy<T: PartialEq>() -> SnapshotMutationPolicy<T> {
SnapshotMutationPolicy {
equivalent: |left, right| left == right,
}
}
pub fn never_equal_policy<T>() -> SnapshotMutationPolicy<T> {
SnapshotMutationPolicy {
equivalent: |_, _| false,
}
}
struct StateCell<T> {
id: u64,
value: Mutex<T>,
policy: SnapshotMutationPolicy<T>,
runtimes: Mutex<Vec<std::sync::Weak<RuntimeSignal>>>,
}
pub struct MutableState<T> {
cell: Arc<StateCell<T>>,
}
impl<T> Clone for MutableState<T> {
fn clone(&self) -> Self {
Self {
cell: Arc::clone(&self.cell),
}
}
}
impl<T> PartialEq for MutableState<T> {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.cell, &other.cell)
}
}
impl<T> Eq for MutableState<T> {}
impl<T: fmt::Debug> fmt::Debug for MutableState<T> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_tuple("MutableState")
.field(&*lock(&self.cell.value))
.finish()
}
}
pub fn mutable_state_of<T: PartialEq + 'static>(value: T) -> MutableState<T> {
mutable_state_of_with_policy(value, structural_equality_policy())
}
pub fn mutable_state_of_with_policy<T: 'static>(
value: T,
policy: SnapshotMutationPolicy<T>,
) -> MutableState<T> {
MutableState {
cell: Arc::new(StateCell {
id: next_state_id(),
value: Mutex::new(value),
policy,
runtimes: Mutex::new(Vec::new()),
}),
}
}
impl<T: 'static> MutableState<T> {
pub fn get(&self) -> T
where
T: Clone,
{
self.track();
lock(&self.cell.value).clone()
}
pub fn get_untracked(&self) -> T
where
T: Clone,
{
lock(&self.cell.value).clone()
}
pub fn with<R>(&self, read: impl FnOnce(&T) -> R) -> R {
self.track();
read(&lock(&self.cell.value))
}
pub fn set(&self, value: T) {
{
let mut current = lock(&self.cell.value);
if (self.cell.policy.equivalent)(¤t, &value) {
return;
}
*current = value;
}
self.notify();
}
pub fn update(&self, change: impl FnOnce(&mut T)) {
change(&mut lock(&self.cell.value));
self.notify();
}
pub fn as_state(&self) -> MutableState<T> {
self.clone()
}
fn track(&self) {
let fresh = with_composer_if_idle(|composer| {
composer
.record_read(self.cell.id)
.then(|| Arc::clone(&composer.signal))
})
.flatten();
if let Some(signal) = fresh {
let mut runtimes = lock(&self.cell.runtimes);
if !runtimes
.iter()
.any(|known| std::ptr::eq(known.as_ptr(), Arc::as_ptr(&signal)))
{
runtimes.push(Arc::downgrade(&signal));
}
}
}
fn notify(&self) {
let mut told = false;
lock(&self.cell.runtimes).retain(|runtime| match runtime.upgrade() {
Some(signal) => {
signal.push_dirty(self.cell.id);
told = true;
true
}
None => false,
});
if told && !crate::boundary::in_host_call() {
crate::boundary::request_frame_from_worker();
}
}
}
pub trait State<T> {
fn value(&self) -> T;
}
impl<T: Clone + 'static> State<T> for MutableState<T> {
fn value(&self) -> T {
self.get()
}
}
impl<T: Clone + PartialEq + 'static> State<T> for DerivedState<T> {
fn value(&self) -> T {
self.get()
}
}
pub(crate) trait DerivedNode {
fn refresh(&self) -> bool;
fn mark_dirty(&self);
}
struct DerivedCell<T> {
id: u64,
compute: Box<dyn Fn() -> T>,
value: RefCell<Option<T>>,
dirty: Cell<bool>,
}
pub struct DerivedState<T> {
cell: Rc<DerivedCell<T>>,
}
impl<T> Clone for DerivedState<T> {
fn clone(&self) -> Self {
Self {
cell: Rc::clone(&self.cell),
}
}
}
impl<T> PartialEq for DerivedState<T> {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.cell, &other.cell)
}
}
pub fn derived_state_of<T: Clone + PartialEq + 'static>(
calculation: impl Fn() -> T + 'static,
) -> DerivedState<T> {
DerivedState {
cell: Rc::new(DerivedCell {
id: next_state_id(),
compute: Box::new(calculation),
value: RefCell::new(None),
dirty: Cell::new(true),
}),
}
}
impl<T: Clone + PartialEq + 'static> DerivedState<T> {
pub fn get(&self) -> T {
let in_runtime = with_composer_if_idle(|_| ()).is_some();
if !in_runtime {
return (self.cell.compute)();
}
if self.cell.dirty.get() || self.cell.value.borrow().is_none() {
self.recompute();
}
with_composer_if_idle(|composer| composer.record_read(self.cell.id));
self.cell
.value
.borrow()
.clone()
.expect("a derived state holds a value once computed")
}
fn recompute(&self) -> bool {
let id = self.cell.id;
let node: Rc<dyn DerivedNode> = self.cell.clone();
let weak = Rc::downgrade(&node);
with_composer(|composer| {
if let Some(deps) = composer.derived_deps.remove(&id) {
for dep in deps {
composer.unobserve(dep, Observer::Derived(id));
}
}
composer.ambient.deriveds.insert(id, weak);
composer.derived_stack.push(id);
});
let computed = (self.cell.compute)();
with_composer(|composer| {
composer.derived_stack.pop();
});
self.cell.dirty.set(false);
let mut stored = self.cell.value.borrow_mut();
let changed = stored.as_ref() != Some(&computed);
*stored = Some(computed);
changed
}
}
impl<T: Clone + PartialEq + 'static> DerivedNode for DerivedCell<T> {
fn refresh(&self) -> bool {
let state = DerivedState {
cell: rc_from_cell(self),
};
state.recompute()
}
fn mark_dirty(&self) {
self.dirty.set(true);
}
}
fn rc_from_cell<T>(cell: &DerivedCell<T>) -> Rc<DerivedCell<T>> {
let pointer = cell as *const DerivedCell<T>;
unsafe {
Rc::increment_strong_count(pointer);
Rc::from_raw(pointer)
}
}
pub(crate) struct Ambient {
pub(crate) window: MutableState<WindowSize>,
pub(crate) design: MutableState<DesignSystem>,
pub(crate) permission: MutableState<NotificationPermission>,
pub(crate) deriveds: HashMap<u64, Weak<dyn DerivedNode>>,
}
impl Ambient {
pub(crate) fn new() -> Self {
Self {
window: mutable_state_of(crate::window::window_size()),
design: mutable_state_of(crate::design::design_system()),
permission: mutable_state_of(crate::notification::notification_permission()),
deriveds: HashMap::new(),
}
}
}
pub(crate) fn apply_state_changes() {
loop {
let Some(mut changed) = with_composer(|composer| {
let mut scratch = std::mem::take(&mut composer.dirty_scratch);
std::mem::swap(&mut *lock(&composer.signal.dirty), &mut scratch);
scratch
}) else {
return;
};
if changed.is_empty() {
with_composer(|composer| composer.dirty_scratch = changed);
return;
}
let mut index = 0;
while index < changed.len() {
let state = changed[index];
index += 1;
let observers = with_composer(|composer| {
composer.observers.get(&state).cloned().unwrap_or_default()
})
.unwrap_or_default();
for observer in observers {
match observer {
Observer::Scope(group, generation) => {
with_composer(|composer| composer.invalidate(group, generation));
}
Observer::Derived(derived) => {
let (node, watched) = with_composer(|composer| {
let node = composer
.ambient
.deriveds
.get(&derived)
.and_then(Weak::upgrade);
let watched = composer
.observers
.get(&derived)
.is_some_and(|list| !list.is_empty());
(node, watched)
})
.unwrap_or((None, false));
let Some(node) = node else {
continue;
};
if watched {
if node.refresh() && !changed.contains(&derived) {
changed.push(derived);
}
} else {
node.mark_dirty();
}
}
}
}
}
changed.clear();
with_composer(|composer| composer.dirty_scratch = changed);
}
}
pub fn current_window_size() -> WindowSize {
match with_composer_if_idle(|composer| composer.ambient.window.clone()) {
Some(state) => state.get(),
None => crate::window::window_size(),
}
}
pub fn current_design_system() -> DesignSystem {
match with_composer_if_idle(|composer| composer.ambient.design.clone()) {
Some(state) => state.get(),
None => crate::design::design_system(),
}
}
pub fn current_notification_permission() -> NotificationPermission {
match with_composer_if_idle(|composer| composer.ambient.permission.clone()) {
Some(state) => state.get(),
None => crate::notification::notification_permission(),
}
}
const NODE_SIZE: u64 = super::composer::call_site("compose_rust::remember_node_size", 0);
const NOTIFICATION_ACTIVATED: u64 =
super::composer::call_site("compose_rust::on_notification_activated", 0);
struct NodeSizeSlot {
token: u32,
state: MutableState<WindowSize>,
_subscription: crate::window::NodeSizeSubscription,
}
pub fn remember_node_size() -> crate::window::NodeSize {
let _group = super::group_guard(NODE_SIZE);
let found = with_composer_if_idle(|composer| {
if !composer.composing() {
return None;
}
if let Some(slot) = composer.slot_mut::<NodeSizeSlot>() {
return Some((slot.token, slot.state.clone()));
}
let token = crate::window::next_node_token();
let state = mutable_state_of(crate::window::node_size(token));
let follow = state.clone();
let subscription = crate::window::subscribe_node(
token,
std::sync::Arc::new(move || follow.set(crate::window::node_size(token))),
);
composer.remember_new(NodeSizeSlot {
token,
state: state.clone(),
_subscription: subscription,
});
Some((token, state))
})
.flatten();
match found {
Some((token, state)) => {
let _ = state.get();
crate::window::NodeSize::of(token)
}
None => crate::window::NodeSize::of(0),
}
}
type ActivationHandler = Rc<RefCell<Box<dyn FnMut(crate::NotificationActivation)>>>;
struct ActivationSlot {
handler: ActivationHandler,
_subscription: crate::notification::ActivationSubscription,
}
pub fn on_notification_activated(handler: impl FnMut(crate::NotificationActivation) + 'static) {
let _group = super::group_guard(NOTIFICATION_ACTIVATED);
let handler: Box<dyn FnMut(crate::NotificationActivation)> = Box::new(handler);
let mut handler = Some(handler);
let old = with_composer_if_idle(|composer| {
if !composer.composing() {
return None;
}
if let Some(slot) = composer.slot_mut::<ActivationSlot>() {
let new = handler.take().expect("the handler is installed once");
return Some(std::mem::replace(&mut *slot.handler.borrow_mut(), new));
}
let shared = Rc::new(RefCell::new(
handler.take().expect("the handler is installed once"),
));
let forward = Rc::clone(&shared);
let subscription = crate::notification::on_activation(Rc::new(
move |activation: crate::NotificationActivation| (*forward.borrow_mut())(activation),
));
composer.remember_new(ActivationSlot {
handler: shared,
_subscription: subscription,
});
None
})
.flatten();
drop(old);
}