silkenweb_reactive/
accumulators.rs

1//! Accumulate reactive variables into a reactive total.
2//!
3//! The example below shows how to sum up `first_digit` and `second_digit` into
4//! `total`. When we drop `first_digit`, it is removed from the total.
5//!
6//!```
7//! # use silkenweb_reactive::{signal::SignalReceiver, accumulators::{SumTotal, SumElement}};
8//! # use std::mem;
9//! let total = SumTotal::<usize>::default();
10//! let first_digit = SumElement::new(&total);
11//! let second_digit = SumElement::new(&total);
12//! let total = total.read();
13//!
14//! first_digit.receive(&1);
15//! assert_eq!(1, *total.current());
16//! second_digit.receive(&10);
17//! assert_eq!(11, *total.current());
18//!
19//! mem::drop(first_digit);
20//! assert_eq!(10, *total.current());
21//! ```
22use std::cell::RefCell;
23
24use num_traits::{WrappingAdd, WrappingSub, Zero};
25
26use crate::signal::{ReadSignal, Signal, SignalReceiver, WriteSignal};
27
28/// This holds the current total of a sum.
29///
30/// See module level documentation for an example of usage.
31#[derive(Clone)]
32pub struct SumTotal<T> {
33    deltas: WriteSignal<T>,
34    total: ReadSignal<T>,
35}
36
37impl<T: 'static + Zero + Clone + WrappingAdd> Default for SumTotal<T> {
38    fn default() -> Self {
39        let deltas = Signal::new(T::zero());
40        let total = deltas.read().map_to(AccumulateSum(RefCell::new(T::zero())));
41        Self {
42            deltas: deltas.write(),
43            total,
44        }
45    }
46}
47
48impl<T: 'static> SumTotal<T> {
49    pub fn read(&self) -> ReadSignal<T> {
50        self.total.clone()
51    }
52}
53
54/// A single element of the sum.
55///
56/// See module level documentation for an example of usage.
57pub struct SumElement<T: 'static + Clone + Zero + WrappingAdd + WrappingSub> {
58    current: RefCell<T>,
59    total: SumTotal<T>,
60}
61
62impl<T: 'static + Zero + Clone + Zero + WrappingAdd + WrappingSub> SumElement<T> {
63    pub fn new(total: &SumTotal<T>) -> Self {
64        Self {
65            current: RefCell::new(T::zero()),
66            total: total.clone(),
67        }
68    }
69}
70
71impl<T: 'static + Clone + Zero + WrappingAdd + WrappingSub> SignalReceiver<T, SumHandle>
72    for SumElement<T>
73{
74    fn receive(&self, x: &T) -> SumHandle {
75        let delta = x.wrapping_sub(&self.current.borrow());
76        self.current.replace(x.clone());
77        self.total.deltas.set(delta);
78        SumHandle()
79    }
80}
81
82impl<T: 'static + Clone + Zero + WrappingAdd + WrappingSub> Drop for SumElement<T> {
83    fn drop(&mut self) {
84        self.total
85            .deltas
86            .set(T::zero().wrapping_sub(&self.current.borrow()));
87    }
88}
89
90/// A handle to keep a value in the sum.
91///
92/// See module level documentation for an example of usage.
93pub struct SumHandle();
94
95struct AccumulateSum<T>(RefCell<T>);
96
97impl<T: 'static + Clone + WrappingAdd> SignalReceiver<T, T> for AccumulateSum<T> {
98    fn receive(&self, x: &T) -> T {
99        let mut total = self.0.borrow_mut();
100        *total = total.wrapping_add(x);
101        total.clone()
102    }
103}