1use super::composer::Observer;
14use super::{with_composer, with_composer_if_idle};
15use crate::schema::{DesignSystem, NotificationPermission};
16use crate::window::WindowSize;
17use std::cell::{Cell, RefCell};
18use std::collections::HashMap;
19use std::fmt;
20use std::rc::{Rc, Weak};
21use std::sync::atomic::{AtomicU64, Ordering};
22use std::sync::{Arc, Mutex, MutexGuard};
23
24use super::composer::RuntimeSignal;
25
26static NEXT_STATE_ID: AtomicU64 = AtomicU64::new(1);
29
30fn next_state_id() -> u64 {
31 NEXT_STATE_ID.fetch_add(1, Ordering::Relaxed)
32}
33
34fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
35 mutex.lock().unwrap_or_else(|poison| poison.into_inner())
36}
37
38pub struct SnapshotMutationPolicy<T> {
40 equivalent: fn(&T, &T) -> bool,
41}
42
43impl<T> Clone for SnapshotMutationPolicy<T> {
44 fn clone(&self) -> Self {
45 *self
46 }
47}
48
49impl<T> Copy for SnapshotMutationPolicy<T> {}
50
51pub fn structural_equality_policy<T: PartialEq>() -> SnapshotMutationPolicy<T> {
53 SnapshotMutationPolicy {
54 equivalent: |left, right| left == right,
55 }
56}
57
58pub fn never_equal_policy<T>() -> SnapshotMutationPolicy<T> {
60 SnapshotMutationPolicy {
61 equivalent: |_, _| false,
62 }
63}
64
65struct StateCell<T> {
66 id: u64,
67 value: Mutex<T>,
68 policy: SnapshotMutationPolicy<T>,
69 runtimes: Mutex<Vec<std::sync::Weak<RuntimeSignal>>>,
71}
72
73pub struct MutableState<T> {
86 cell: Arc<StateCell<T>>,
87}
88
89impl<T> Clone for MutableState<T> {
90 fn clone(&self) -> Self {
91 Self {
92 cell: Arc::clone(&self.cell),
93 }
94 }
95}
96
97impl<T> PartialEq for MutableState<T> {
98 fn eq(&self, other: &Self) -> bool {
99 Arc::ptr_eq(&self.cell, &other.cell)
100 }
101}
102
103impl<T> Eq for MutableState<T> {}
104
105impl<T: fmt::Debug> fmt::Debug for MutableState<T> {
106 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
107 formatter
108 .debug_tuple("MutableState")
109 .field(&*lock(&self.cell.value))
110 .finish()
111 }
112}
113
114pub fn mutable_state_of<T: PartialEq + 'static>(value: T) -> MutableState<T> {
122 mutable_state_of_with_policy(value, structural_equality_policy())
123}
124
125pub fn mutable_state_of_with_policy<T: 'static>(
127 value: T,
128 policy: SnapshotMutationPolicy<T>,
129) -> MutableState<T> {
130 MutableState {
131 cell: Arc::new(StateCell {
132 id: next_state_id(),
133 value: Mutex::new(value),
134 policy,
135 runtimes: Mutex::new(Vec::new()),
136 }),
137 }
138}
139
140impl<T: 'static> MutableState<T> {
141 pub fn get(&self) -> T
143 where
144 T: Clone,
145 {
146 self.track();
147 lock(&self.cell.value).clone()
148 }
149
150 pub fn get_untracked(&self) -> T
153 where
154 T: Clone,
155 {
156 lock(&self.cell.value).clone()
157 }
158
159 pub fn with<R>(&self, read: impl FnOnce(&T) -> R) -> R {
161 self.track();
162 read(&lock(&self.cell.value))
163 }
164
165 pub fn set(&self, value: T) {
167 {
168 let mut current = lock(&self.cell.value);
169 if (self.cell.policy.equivalent)(¤t, &value) {
170 return;
171 }
172 *current = value;
173 }
174 self.notify();
175 }
176
177 pub fn update(&self, change: impl FnOnce(&mut T)) {
180 change(&mut lock(&self.cell.value));
181 self.notify();
182 }
183
184 pub fn as_state(&self) -> MutableState<T> {
186 self.clone()
187 }
188
189 fn track(&self) {
190 let fresh = with_composer_if_idle(|composer| {
191 composer
192 .record_read(self.cell.id)
193 .then(|| Arc::clone(&composer.signal))
194 })
195 .flatten();
196 if let Some(signal) = fresh {
197 let mut runtimes = lock(&self.cell.runtimes);
198 if !runtimes
199 .iter()
200 .any(|known| std::ptr::eq(known.as_ptr(), Arc::as_ptr(&signal)))
201 {
202 runtimes.push(Arc::downgrade(&signal));
203 }
204 }
205 }
206
207 fn notify(&self) {
208 let mut told = false;
209 lock(&self.cell.runtimes).retain(|runtime| match runtime.upgrade() {
210 Some(signal) => {
211 signal.push_dirty(self.cell.id);
212 told = true;
213 true
214 }
215 None => false,
216 });
217 if told && !crate::boundary::in_host_call() {
220 crate::boundary::request_frame_from_worker();
221 }
222 }
223}
224
225pub trait State<T> {
227 fn value(&self) -> T;
229}
230
231impl<T: Clone + 'static> State<T> for MutableState<T> {
232 fn value(&self) -> T {
233 self.get()
234 }
235}
236
237impl<T: Clone + PartialEq + 'static> State<T> for DerivedState<T> {
238 fn value(&self) -> T {
239 self.get()
240 }
241}
242
243pub(crate) trait DerivedNode {
245 fn refresh(&self) -> bool;
247 fn mark_dirty(&self);
249}
250
251struct DerivedCell<T> {
252 id: u64,
253 compute: Box<dyn Fn() -> T>,
254 value: RefCell<Option<T>>,
255 dirty: Cell<bool>,
256}
257
258pub struct DerivedState<T> {
268 cell: Rc<DerivedCell<T>>,
269}
270
271impl<T> Clone for DerivedState<T> {
272 fn clone(&self) -> Self {
273 Self {
274 cell: Rc::clone(&self.cell),
275 }
276 }
277}
278
279impl<T> PartialEq for DerivedState<T> {
280 fn eq(&self, other: &Self) -> bool {
281 Rc::ptr_eq(&self.cell, &other.cell)
282 }
283}
284
285pub fn derived_state_of<T: Clone + PartialEq + 'static>(
292 calculation: impl Fn() -> T + 'static,
293) -> DerivedState<T> {
294 DerivedState {
295 cell: Rc::new(DerivedCell {
296 id: next_state_id(),
297 compute: Box::new(calculation),
298 value: RefCell::new(None),
299 dirty: Cell::new(true),
300 }),
301 }
302}
303
304impl<T: Clone + PartialEq + 'static> DerivedState<T> {
305 pub fn get(&self) -> T {
307 let in_runtime = with_composer_if_idle(|_| ()).is_some();
308 if !in_runtime {
309 return (self.cell.compute)();
312 }
313 if self.cell.dirty.get() || self.cell.value.borrow().is_none() {
314 self.recompute();
315 }
316 with_composer_if_idle(|composer| composer.record_read(self.cell.id));
317 self.cell
318 .value
319 .borrow()
320 .clone()
321 .expect("a derived state holds a value once computed")
322 }
323
324 fn recompute(&self) -> bool {
325 let id = self.cell.id;
326 let node: Rc<dyn DerivedNode> = self.cell.clone();
327 let weak = Rc::downgrade(&node);
328 with_composer(|composer| {
329 if let Some(deps) = composer.derived_deps.remove(&id) {
330 for dep in deps {
331 composer.unobserve(dep, Observer::Derived(id));
332 }
333 }
334 composer.ambient.deriveds.insert(id, weak);
335 composer.derived_stack.push(id);
336 });
337 let computed = (self.cell.compute)();
338 with_composer(|composer| {
339 composer.derived_stack.pop();
340 });
341 self.cell.dirty.set(false);
342 let mut stored = self.cell.value.borrow_mut();
343 let changed = stored.as_ref() != Some(&computed);
344 *stored = Some(computed);
345 changed
346 }
347}
348
349impl<T: Clone + PartialEq + 'static> DerivedNode for DerivedCell<T> {
350 fn refresh(&self) -> bool {
351 let state = DerivedState {
353 cell: rc_from_cell(self),
354 };
355 state.recompute()
356 }
357
358 fn mark_dirty(&self) {
359 self.dirty.set(true);
360 }
361}
362
363fn rc_from_cell<T>(cell: &DerivedCell<T>) -> Rc<DerivedCell<T>> {
367 let pointer = cell as *const DerivedCell<T>;
368 unsafe {
372 Rc::increment_strong_count(pointer);
373 Rc::from_raw(pointer)
374 }
375}
376
377pub(crate) struct Ambient {
380 pub(crate) window: MutableState<WindowSize>,
381 pub(crate) design: MutableState<DesignSystem>,
382 pub(crate) permission: MutableState<NotificationPermission>,
383 pub(crate) deriveds: HashMap<u64, Weak<dyn DerivedNode>>,
384}
385
386impl Ambient {
387 pub(crate) fn new() -> Self {
388 Self {
389 window: mutable_state_of(crate::window::window_size()),
390 design: mutable_state_of(crate::design::design_system()),
391 permission: mutable_state_of(crate::notification::notification_permission()),
392 deriveds: HashMap::new(),
393 }
394 }
395}
396
397pub(crate) fn apply_state_changes() {
404 loop {
405 let Some(mut changed) = with_composer(|composer| {
406 let mut scratch = std::mem::take(&mut composer.dirty_scratch);
407 std::mem::swap(&mut *lock(&composer.signal.dirty), &mut scratch);
408 scratch
409 }) else {
410 return;
411 };
412 if changed.is_empty() {
413 with_composer(|composer| composer.dirty_scratch = changed);
414 return;
415 }
416 let mut index = 0;
417 while index < changed.len() {
418 let state = changed[index];
419 index += 1;
420 let observers = with_composer(|composer| {
421 composer.observers.get(&state).cloned().unwrap_or_default()
422 })
423 .unwrap_or_default();
424 for observer in observers {
425 match observer {
426 Observer::Scope(group, generation) => {
427 with_composer(|composer| composer.invalidate(group, generation));
428 }
429 Observer::Derived(derived) => {
430 let (node, watched) = with_composer(|composer| {
431 let node = composer
432 .ambient
433 .deriveds
434 .get(&derived)
435 .and_then(Weak::upgrade);
436 let watched = composer
437 .observers
438 .get(&derived)
439 .is_some_and(|list| !list.is_empty());
440 (node, watched)
441 })
442 .unwrap_or((None, false));
443 let Some(node) = node else {
444 continue;
445 };
446 if watched {
447 if node.refresh() && !changed.contains(&derived) {
448 changed.push(derived);
449 }
450 } else {
451 node.mark_dirty();
452 }
453 }
454 }
455 }
456 }
457 changed.clear();
458 with_composer(|composer| composer.dirty_scratch = changed);
459 }
460}
461
462pub fn current_window_size() -> WindowSize {
465 match with_composer_if_idle(|composer| composer.ambient.window.clone()) {
466 Some(state) => state.get(),
467 None => crate::window::window_size(),
468 }
469}
470
471pub fn current_design_system() -> DesignSystem {
477 match with_composer_if_idle(|composer| composer.ambient.design.clone()) {
478 Some(state) => state.get(),
479 None => crate::design::design_system(),
480 }
481}
482
483pub fn current_notification_permission() -> NotificationPermission {
485 match with_composer_if_idle(|composer| composer.ambient.permission.clone()) {
486 Some(state) => state.get(),
487 None => crate::notification::notification_permission(),
488 }
489}
490
491const NODE_SIZE: u64 = super::composer::call_site("compose_rust::remember_node_size", 0);
492const NOTIFICATION_ACTIVATED: u64 =
493 super::composer::call_site("compose_rust::on_notification_activated", 0);
494
495struct NodeSizeSlot {
498 token: u32,
499 state: MutableState<WindowSize>,
500 _subscription: crate::window::NodeSizeSubscription,
501}
502
503pub fn remember_node_size() -> crate::window::NodeSize {
510 let _group = super::group_guard(NODE_SIZE);
511 let found = with_composer_if_idle(|composer| {
512 if !composer.composing() {
513 return None;
514 }
515 if let Some(slot) = composer.slot_mut::<NodeSizeSlot>() {
516 return Some((slot.token, slot.state.clone()));
517 }
518 let token = crate::window::next_node_token();
519 let state = mutable_state_of(crate::window::node_size(token));
520 let follow = state.clone();
521 let subscription = crate::window::subscribe_node(
522 token,
523 std::sync::Arc::new(move || follow.set(crate::window::node_size(token))),
524 );
525 composer.remember_new(NodeSizeSlot {
526 token,
527 state: state.clone(),
528 _subscription: subscription,
529 });
530 Some((token, state))
531 })
532 .flatten();
533 match found {
534 Some((token, state)) => {
535 let _ = state.get();
538 crate::window::NodeSize::of(token)
539 }
540 None => crate::window::NodeSize::of(0),
541 }
542}
543
544type ActivationHandler = Rc<RefCell<Box<dyn FnMut(crate::NotificationActivation)>>>;
546
547struct ActivationSlot {
550 handler: ActivationHandler,
551 _subscription: crate::notification::ActivationSubscription,
552}
553
554pub fn on_notification_activated(handler: impl FnMut(crate::NotificationActivation) + 'static) {
560 let _group = super::group_guard(NOTIFICATION_ACTIVATED);
561 let handler: Box<dyn FnMut(crate::NotificationActivation)> = Box::new(handler);
562 let mut handler = Some(handler);
563 let old = with_composer_if_idle(|composer| {
564 if !composer.composing() {
565 return None;
566 }
567 if let Some(slot) = composer.slot_mut::<ActivationSlot>() {
568 let new = handler.take().expect("the handler is installed once");
569 return Some(std::mem::replace(&mut *slot.handler.borrow_mut(), new));
570 }
571 let shared = Rc::new(RefCell::new(
572 handler.take().expect("the handler is installed once"),
573 ));
574 let forward = Rc::clone(&shared);
575 let subscription = crate::notification::on_activation(Rc::new(
576 move |activation: crate::NotificationActivation| (*forward.borrow_mut())(activation),
577 ));
578 composer.remember_new(ActivationSlot {
579 handler: shared,
580 _subscription: subscription,
581 });
582 None
583 })
584 .flatten();
585 drop(old);
587}