1use crate::geometric::{FromGeometric, IntoGeometric, GA3};
11use std::cell::RefCell;
12use std::rc::Rc;
13
14#[allow(clippy::type_complexity)]
16pub struct Subscription {
17 #[allow(dead_code)] id: usize,
19 unsubscribe: Rc<RefCell<Option<Box<dyn Fn()>>>>,
20}
21
22impl Subscription {
23 pub fn unsubscribe(self) {
25 if let Some(unsub) = self.unsubscribe.borrow_mut().take() {
26 unsub();
27 }
28 }
29}
30
31#[allow(clippy::type_complexity)]
52pub struct Behavior<T> {
53 state: Rc<RefCell<GA3>>,
55
56 cache: Rc<RefCell<T>>,
58
59 subscribers: Rc<RefCell<Vec<(usize, Box<dyn Fn(&T)>)>>>,
61
62 next_id: Rc<RefCell<usize>>,
64}
65
66impl<T> Clone for Behavior<T> {
67 fn clone(&self) -> Self {
68 Self {
69 state: Rc::clone(&self.state),
70 cache: Rc::clone(&self.cache),
71 subscribers: Rc::clone(&self.subscribers),
72 next_id: Rc::clone(&self.next_id),
73 }
74 }
75}
76
77impl<T: IntoGeometric + FromGeometric + Clone + 'static> Behavior<T> {
78 pub fn new(initial: T) -> Self {
80 let mv = initial.clone().into_geometric();
81 Self {
82 state: Rc::new(RefCell::new(mv)),
83 cache: Rc::new(RefCell::new(initial)),
84 subscribers: Rc::new(RefCell::new(Vec::new())),
85 next_id: Rc::new(RefCell::new(0)),
86 }
87 }
88
89 pub fn sample(&self) -> T {
91 self.cache.borrow().clone()
92 }
93
94 pub fn update<F>(&self, f: F)
99 where
100 F: FnOnce(T) -> T,
101 {
102 let current = self.cache.borrow().clone();
103 let new_value = f(current);
104 let new_mv = new_value.clone().into_geometric();
105
106 *self.state.borrow_mut() = new_mv;
107 *self.cache.borrow_mut() = new_value;
108
109 let cache = self.cache.borrow();
111 for (_, callback) in self.subscribers.borrow().iter() {
112 callback(&cache);
113 }
114 }
115
116 pub fn set(&self, value: T) {
118 self.update(|_| value);
119 }
120
121 pub fn subscribe<F>(&self, callback: F) -> Subscription
125 where
126 F: Fn(&T) + 'static,
127 {
128 let id = {
129 let mut next = self.next_id.borrow_mut();
130 let id = *next;
131 *next += 1;
132 id
133 };
134
135 self.subscribers.borrow_mut().push((id, Box::new(callback)));
136
137 let subscribers = Rc::clone(&self.subscribers);
138 let unsubscribe = Rc::new(RefCell::new(Some(Box::new(move || {
139 subscribers.borrow_mut().retain(|(i, _)| *i != id);
140 }) as Box<dyn Fn()>)));
141
142 Subscription { id, unsubscribe }
143 }
144
145 pub fn map<U, F>(&self, f: F) -> Behavior<U>
149 where
150 U: IntoGeometric + FromGeometric + Clone + 'static,
151 F: Fn(T) -> U + 'static,
152 {
153 let initial = f(self.sample());
154 let derived = Behavior::new(initial);
155
156 let derived_clone = derived.clone();
158 self.subscribe(move |value| {
159 let new_value = f(value.clone());
160 derived_clone.set(new_value);
161 });
162
163 derived
164 }
165
166 pub fn combine<U, V, F>(&self, other: &Behavior<U>, f: F) -> Behavior<V>
168 where
169 U: IntoGeometric + FromGeometric + Clone + 'static,
170 V: IntoGeometric + FromGeometric + Clone + 'static,
171 F: Fn(T, U) -> V + Clone + 'static,
172 {
173 let initial = f(self.sample(), other.sample());
174 let combined = Behavior::new(initial);
175
176 let combined_clone = combined.clone();
178 let other_clone = other.clone();
179 let f_clone = f.clone();
180 self.subscribe(move |a| {
181 let b = other_clone.sample();
182 combined_clone.set(f_clone(a.clone(), b));
183 });
184
185 let combined_clone = combined.clone();
187 let self_clone = self.clone();
188 other.subscribe(move |b| {
189 let a = self_clone.sample();
190 combined_clone.set(f(a, b.clone()));
191 });
192
193 combined
194 }
195
196 pub fn geometric_state(&self) -> GA3 {
198 self.state.borrow().clone()
199 }
200
201 pub fn apply_geometric<F>(&self, transform: F)
203 where
204 F: FnOnce(&GA3) -> GA3,
205 {
206 let current = self.state.borrow().clone();
207 let new_mv = transform(¤t);
208 *self.state.borrow_mut() = new_mv.clone();
209 *self.cache.borrow_mut() = T::from_geometric(&new_mv);
210
211 let cache = self.cache.borrow();
213 for (_, callback) in self.subscribers.borrow().iter() {
214 callback(&cache);
215 }
216 }
217}
218
219pub fn behavior<T: IntoGeometric + FromGeometric + Clone + 'static>(initial: T) -> Behavior<T> {
231 Behavior::new(initial)
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237 use std::cell::Cell;
238
239 #[test]
240 fn test_behavior_new_and_sample() {
241 let b = behavior(42i32);
242 assert_eq!(b.sample(), 42);
243 }
244
245 #[test]
246 fn test_behavior_update() {
247 let b = behavior(0i32);
248 b.update(|n| n + 1);
249 assert_eq!(b.sample(), 1);
250
251 b.update(|n| n * 2);
252 assert_eq!(b.sample(), 2);
253 }
254
255 #[test]
256 fn test_behavior_set() {
257 let b = behavior(0i32);
258 b.set(100);
259 assert_eq!(b.sample(), 100);
260 }
261
262 #[test]
263 fn test_behavior_subscribe() {
264 let b = behavior(0i32);
265 let called = Rc::new(Cell::new(0));
266 let called_clone = Rc::clone(&called);
267
268 let _sub = b.subscribe(move |_value| {
269 called_clone.set(called_clone.get() + 1);
270 });
271
272 b.update(|n| n + 1);
273 assert_eq!(called.get(), 1);
274
275 b.update(|n| n + 1);
276 assert_eq!(called.get(), 2);
277 }
278
279 #[test]
280 fn test_behavior_unsubscribe() {
281 let b = behavior(0i32);
282 let called = Rc::new(Cell::new(0));
283 let called_clone = Rc::clone(&called);
284
285 let sub = b.subscribe(move |_value| {
286 called_clone.set(called_clone.get() + 1);
287 });
288
289 b.update(|n| n + 1);
290 assert_eq!(called.get(), 1);
291
292 sub.unsubscribe();
293
294 b.update(|n| n + 1);
295 assert_eq!(called.get(), 1); }
297
298 #[test]
299 fn test_behavior_map() {
300 let count = behavior(5i32);
301 let doubled = count.map(|n| n * 2);
302
303 assert_eq!(doubled.sample(), 10);
304
305 count.update(|n| n + 1);
306 assert_eq!(doubled.sample(), 12);
307 }
308
309 #[test]
310 fn test_behavior_combine() {
311 let a = behavior(10i32);
312 let b = behavior(20i32);
313 let sum = a.combine(&b, |x, y| x + y);
314
315 assert_eq!(sum.sample(), 30);
316
317 a.update(|n| n + 5);
318 assert_eq!(sum.sample(), 35);
319
320 b.update(|n| n + 10);
321 assert_eq!(sum.sample(), 45);
322 }
323
324 #[test]
325 fn test_behavior_with_string() {
326 let name = behavior("Alice".to_string());
327 assert_eq!(name.sample(), "Alice");
328
329 name.set("Bob".to_string());
330 assert_eq!(name.sample(), "Bob");
331 }
332
333 #[test]
334 fn test_behavior_with_bool() {
335 let active = behavior(false);
336 assert!(!active.sample());
337
338 active.update(|_| true);
339 assert!(active.sample());
340 }
341
342 #[test]
343 fn test_behavior_clone_shares_state() {
344 let a = behavior(0i32);
345 let b = a.clone();
346
347 a.update(|n| n + 1);
348 assert_eq!(b.sample(), 1);
349 }
350}