extern crate alloc;
use alloc::boxed::Box;
use alloc::vec::Vec;
pub type ObserverFn<T> = Box<dyn FnMut(&T)>;
pub struct Subject<T: Clone> {
value: T,
prev_value: T,
observers: Vec<ObserverFn<T>>,
notifying: bool,
}
impl<T: Clone> Subject<T> {
pub fn new(initial: T) -> Self {
Self {
prev_value: initial.clone(),
value: initial,
observers: Vec::new(),
notifying: false,
}
}
pub fn get(&self) -> &T {
&self.value
}
pub fn prev(&self) -> &T {
&self.prev_value
}
pub fn set(&mut self, value: T) {
self.prev_value = self.value.clone();
self.value = value;
self.notify();
}
pub fn notify(&mut self) {
if self.notifying {
return;
}
self.notifying = true;
let current = self.value.clone();
for cb in &mut self.observers {
cb(¤t);
}
self.notifying = false;
}
pub fn subscribe<F: FnMut(&T) + 'static>(&mut self, cb: F) {
self.observers.push(Box::new(cb));
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::rc::Rc;
use alloc::string::String;
use alloc::vec;
use core::cell::Cell;
use core::cell::RefCell;
#[test]
fn new_get_and_prev_equal_initial() {
let s: Subject<i32> = Subject::new(42);
assert_eq!(*s.get(), 42);
assert_eq!(*s.prev(), 42);
}
#[test]
fn set_updates_value_and_prev() {
let mut s: Subject<i32> = Subject::new(10);
s.set(20);
assert_eq!(*s.get(), 20);
assert_eq!(*s.prev(), 10);
}
#[test]
fn set_notifies_single_observer() {
let mut s: Subject<i32> = Subject::new(0);
let received = Rc::new(Cell::new(-1_i32));
let received_clone = received.clone();
s.subscribe(move |v| received_clone.set(*v));
s.set(7);
assert_eq!(received.get(), 7);
}
#[test]
fn set_notifies_multiple_observers_in_order() {
let mut s: Subject<i32> = Subject::new(0);
let log = Rc::new(RefCell::new(Vec::<(usize, i32)>::new()));
for idx in 0..3 {
let log_clone = log.clone();
s.subscribe(move |v| log_clone.borrow_mut().push((idx, *v)));
}
s.set(5);
let entries = log.borrow();
assert_eq!(*entries, vec![(0, 5), (1, 5), (2, 5)]);
}
#[test]
fn notify_fires_without_changing_value() {
let mut s: Subject<i32> = Subject::new(3);
s.set(9); let call_count = Rc::new(Cell::new(0_u32));
let call_count_clone = call_count.clone();
s.subscribe(move |_| call_count_clone.set(call_count_clone.get() + 1));
s.notify();
assert_eq!(*s.get(), 9, "value unchanged by notify");
assert_eq!(*s.prev(), 3, "prev unchanged by notify");
assert_eq!(call_count.get(), 1, "observer fired once");
}
#[test]
fn multiple_set_calls_track_prev_correctly() {
let mut s: Subject<i32> = Subject::new(1);
s.set(2);
assert_eq!(*s.prev(), 1);
assert_eq!(*s.get(), 2);
s.set(3);
assert_eq!(*s.prev(), 2);
assert_eq!(*s.get(), 3);
}
#[test]
fn cross_subject_observer() {
let mut a: Subject<i32> = Subject::new(0);
let b = Rc::new(RefCell::new(Subject::<i32>::new(0)));
let b_clone = b.clone();
a.subscribe(move |v| {
b_clone.borrow_mut().set(*v * 2);
});
a.set(5);
assert_eq!(*a.get(), 5);
assert_eq!(*b.borrow().get(), 10);
assert_eq!(*b.borrow().prev(), 0);
}
#[test]
fn reentrant_notify_is_skipped_no_panic() {
let mut s: Subject<i32> = Subject::new(0);
let call_count = Rc::new(Cell::new(0_u32));
let call_count_clone = call_count.clone();
let s_ptr = &mut s as *mut Subject<i32>;
s.subscribe(move |_| {
call_count_clone.set(call_count_clone.get() + 1);
unsafe { (*s_ptr).notify() };
});
s.set(1);
assert_eq!(call_count.get(), 1, "re-entrant notify must be skipped");
assert_eq!(*s.get(), 1);
}
#[test]
fn bool_subject_set_and_notify() {
let mut s: Subject<bool> = Subject::new(false);
let seen = Rc::new(Cell::new(false));
let seen_clone = seen.clone();
s.subscribe(move |v| seen_clone.set(*v));
s.set(true);
assert!(seen.get());
}
#[test]
fn string_subject_tracks_value() {
let mut s: Subject<String> = Subject::new("hello".into());
s.set("world".into());
assert_eq!(s.get().as_str(), "world");
assert_eq!(s.prev().as_str(), "hello");
}
#[test]
fn set_with_no_observers_does_not_panic() {
let mut s: Subject<i32> = Subject::new(0);
s.set(42); assert_eq!(*s.get(), 42);
}
}