Skip to main content

rlvgl_core/
observer.rs

1//! Value-binding `Subject<T>` for deterministic observer notifications.
2//!
3//! This module provides a typed publish/subscribe primitive that is **separate
4//! from** the LPAR-04 [`ObjectEvent`](crate::object::ObjectEvent) dispatch
5//! system.  It carries scalar data values (`i32`, `bool`, [`Color`](crate::widget::Color),
6//! `String`) between arbitrary owners through explicit subscribe/notify
7//! callbacks, without routing through `ObjectNode` or emitting `ObjectEvent`.
8//!
9//! # Relationship to the LPAR-04 event system
10//!
11//! | Dimension | LPAR-04 `ObjectEvent` | `Subject<T>` |
12//! |---|---|---|
13//! | What it carries | Semantic gestures, lifecycle signals | Scalar data values |
14//! | Routing | Tree dispatch through `ObjectNode` | Direct callback invocation |
15//! | Coupling | Requires `ObjectNode` hierarchy | Caller holds both ends |
16//! | v1 integration | Not altered | Added orthogonally |
17//!
18//! A widget MAY both receive `ObjectEvent::Clicked` (dispatched by the tree
19//! router) **and** update a `Subject<bool>` it owns.  No framework mechanism
20//! connects them; wiring is the caller's responsibility.
21//!
22//! # `no_std` notes
23//!
24//! [`Subject<T>`] requires `alloc` (it owns a `Vec<Box<dyn FnMut>>` observer
25//! list).  Targets without a heap allocator MUST NOT instantiate subjects;
26//! the type compiles under `no_std + alloc` but panics if the allocator
27//! aborts.
28//!
29//! # Reentrancy
30//!
31//! Observers receive `&T`, not `&mut Subject<T>`, so direct recursive
32//! mutation of the *same* subject from within its own observer is
33//! structurally prevented.  A second `Subject` may be mutated from within an
34//! observer โ€” this is the intended cross-subject binding pattern (see
35//! [`tests::cross_subject_observer`](crate::observer) below).
36//!
37//! A lightweight `notifying: bool` sentinel is included as a defensive guard.
38//! If a re-entrant [`Subject::notify`] call somehow occurs (e.g. via unsafe
39//! aliasing outside this module), the inner call is silently skipped rather
40//! than potentially overflowing the stack or double-borrowing.  This is
41//! documented and testable; it does **not** imply that re-entrant use is
42//! supported.
43
44extern crate alloc;
45
46use alloc::boxed::Box;
47use alloc::vec::Vec;
48
49/// Heap-allocated, mutable observer callback for a value of type `T`.
50///
51/// Factored out to satisfy `clippy::type_complexity`; the underlying type is
52/// `Box<dyn FnMut(&T)>`.
53pub type ObserverFn<T> = Box<dyn FnMut(&T)>;
54
55/// A typed value holder with an observer callback list.
56///
57/// `Subject<T>` stores a current and previous value and a list of callbacks.
58/// Calling [`set`](Subject::set) atomically updates the value and notifies
59/// all registered observers synchronously, in subscription order.
60///
61/// ## Type coverage in v1
62///
63/// `Subject<i32>`, `Subject<bool>`, `Subject<Color>`, and `Subject<String>`
64/// cover the four [`PropertyValue`](crate::property::PropertyValue) variants.
65/// Subjects are intentionally typed โ€” callers subscribe to a `Subject<i32>`,
66/// not to a `Subject<PropertyValue>`.  Bridging the two is application-level
67/// code.
68///
69/// ## Object-identity-free
70///
71/// `Subject<T>` does not require object ids, `WidgetId`, or `ObjectNode`
72/// references.  Subjects are owned values; callers hold them directly.  This
73/// satisfies the LPAR-15 ยง5.I binding invariant.
74pub struct Subject<T: Clone> {
75    /// Current value.
76    value: T,
77    /// Value before the most recent [`set`](Subject::set) call.  Equals
78    /// `value` on construction (no previous state exists yet).
79    prev_value: T,
80    /// Registered observer callbacks, called in subscription order.
81    observers: Vec<ObserverFn<T>>,
82    /// Reentrancy sentinel: `true` while [`notify`](Subject::notify) is
83    /// dispatching.  A re-entrant `notify` call skips execution rather than
84    /// recursing.  Callers MUST NOT rely on re-entrant notifications being
85    /// delivered; this guard is purely defensive.
86    notifying: bool,
87}
88
89impl<T: Clone> Subject<T> {
90    /// Create a new `Subject` with the given initial value.
91    ///
92    /// `prev_value` is initialised to a clone of `initial`; no observers are
93    /// registered.
94    ///
95    /// ```rust
96    /// use rlvgl_core::observer::Subject;
97    /// let s: Subject<i32> = Subject::new(0);
98    /// assert_eq!(*s.get(), 0);
99    /// assert_eq!(*s.prev(), 0);
100    /// ```
101    pub fn new(initial: T) -> Self {
102        Self {
103            prev_value: initial.clone(),
104            value: initial,
105            observers: Vec::new(),
106            notifying: false,
107        }
108    }
109
110    /// Borrow the current value.
111    pub fn get(&self) -> &T {
112        &self.value
113    }
114
115    /// Borrow the value from before the most recent [`set`](Subject::set) call.
116    ///
117    /// On a freshly constructed subject `prev() == get()`.
118    pub fn prev(&self) -> &T {
119        &self.prev_value
120    }
121
122    /// Store a new value, copy the old value to `prev`, then notify all
123    /// observers synchronously in subscription order.
124    ///
125    /// Observers receive a shared reference `&T` to the **new** value.  They
126    /// MUST NOT attempt to call `set` on *this same* subject; that is
127    /// structurally prevented because they do not hold a `&mut Subject`.
128    /// Cross-subject `set` (observer of A mutates B) is the intended
129    /// composition pattern and works correctly.
130    pub fn set(&mut self, value: T) {
131        self.prev_value = self.value.clone();
132        self.value = value;
133        self.notify();
134    }
135
136    /// Notify all observers with the current value without changing it.
137    ///
138    /// `prev_value` is also left unchanged.  Useful for re-broadcasting after
139    /// an external mutation or initialisation.
140    ///
141    /// If this method is already executing on the call stack (re-entrant call),
142    /// the inner invocation is silently skipped.  See the module-level
143    /// reentrancy note for rationale.
144    pub fn notify(&mut self) {
145        if self.notifying {
146            return;
147        }
148        self.notifying = true;
149        // Snapshot the current value so that observers see a consistent view.
150        let current = self.value.clone();
151        for cb in &mut self.observers {
152            cb(&current);
153        }
154        self.notifying = false;
155    }
156
157    /// Register an observer callback.
158    ///
159    /// Callbacks are invoked in subscription order on every [`set`] or
160    /// explicit [`notify`](Subject::notify) call.  There is no unsubscribe
161    /// mechanism in v1; adding one is a Specification Required amendment that
162    /// does not break existing call sites.
163    ///
164    /// `F: 'static` is required because the closure is heap-allocated behind a
165    /// `Box<dyn FnMut>` and may outlive the calling scope.
166    pub fn subscribe<F: FnMut(&T) + 'static>(&mut self, cb: F) {
167        self.observers.push(Box::new(cb));
168    }
169}
170
171// ---------------------------------------------------------------------------
172// Tests
173// ---------------------------------------------------------------------------
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178    use alloc::rc::Rc;
179    use alloc::string::String;
180    use alloc::vec;
181    use core::cell::Cell;
182    use core::cell::RefCell;
183
184    // -----------------------------------------------------------------------
185    // Basic get / prev after construction
186    // -----------------------------------------------------------------------
187
188    #[test]
189    fn new_get_and_prev_equal_initial() {
190        let s: Subject<i32> = Subject::new(42);
191        assert_eq!(*s.get(), 42);
192        assert_eq!(*s.prev(), 42);
193    }
194
195    // -----------------------------------------------------------------------
196    // set() notifies observers and updates prev
197    // -----------------------------------------------------------------------
198
199    #[test]
200    fn set_updates_value_and_prev() {
201        let mut s: Subject<i32> = Subject::new(10);
202        s.set(20);
203        assert_eq!(*s.get(), 20);
204        assert_eq!(*s.prev(), 10);
205    }
206
207    #[test]
208    fn set_notifies_single_observer() {
209        let mut s: Subject<i32> = Subject::new(0);
210        let received = Rc::new(Cell::new(-1_i32));
211        let received_clone = received.clone();
212        s.subscribe(move |v| received_clone.set(*v));
213        s.set(7);
214        assert_eq!(received.get(), 7);
215    }
216
217    #[test]
218    fn set_notifies_multiple_observers_in_order() {
219        let mut s: Subject<i32> = Subject::new(0);
220        let log = Rc::new(RefCell::new(Vec::<(usize, i32)>::new()));
221
222        for idx in 0..3 {
223            let log_clone = log.clone();
224            s.subscribe(move |v| log_clone.borrow_mut().push((idx, *v)));
225        }
226
227        s.set(5);
228
229        let entries = log.borrow();
230        assert_eq!(*entries, vec![(0, 5), (1, 5), (2, 5)]);
231    }
232
233    // -----------------------------------------------------------------------
234    // notify() fires without changing value or prev
235    // -----------------------------------------------------------------------
236
237    #[test]
238    fn notify_fires_without_changing_value() {
239        let mut s: Subject<i32> = Subject::new(3);
240        s.set(9); // prev = 3, value = 9
241        let call_count = Rc::new(Cell::new(0_u32));
242        let call_count_clone = call_count.clone();
243        s.subscribe(move |_| call_count_clone.set(call_count_clone.get() + 1));
244
245        s.notify();
246        assert_eq!(*s.get(), 9, "value unchanged by notify");
247        assert_eq!(*s.prev(), 3, "prev unchanged by notify");
248        assert_eq!(call_count.get(), 1, "observer fired once");
249    }
250
251    // -----------------------------------------------------------------------
252    // Multiple sequential set() calls
253    // -----------------------------------------------------------------------
254
255    #[test]
256    fn multiple_set_calls_track_prev_correctly() {
257        let mut s: Subject<i32> = Subject::new(1);
258        s.set(2);
259        assert_eq!(*s.prev(), 1);
260        assert_eq!(*s.get(), 2);
261        s.set(3);
262        assert_eq!(*s.prev(), 2);
263        assert_eq!(*s.get(), 3);
264    }
265
266    // -----------------------------------------------------------------------
267    // Cross-subject observer: observer of A updates B
268    // -----------------------------------------------------------------------
269
270    #[test]
271    fn cross_subject_observer() {
272        let mut a: Subject<i32> = Subject::new(0);
273        let b = Rc::new(RefCell::new(Subject::<i32>::new(0)));
274
275        let b_clone = b.clone();
276        a.subscribe(move |v| {
277            b_clone.borrow_mut().set(*v * 2);
278        });
279
280        a.set(5);
281
282        assert_eq!(*a.get(), 5);
283        assert_eq!(*b.borrow().get(), 10);
284        assert_eq!(*b.borrow().prev(), 0);
285    }
286
287    // -----------------------------------------------------------------------
288    // Reentrancy sentinel: re-entrant notify is skipped, no panic/recursion
289    // -----------------------------------------------------------------------
290
291    #[test]
292    fn reentrant_notify_is_skipped_no_panic() {
293        // We use a raw pointer trick to demonstrate the sentinel: we capture a
294        // pointer to the subject and attempt a notify inside the observer.  The
295        // sentinel `notifying = true` prevents recursion.  This is intentionally
296        // unsafe in the test only to exercise the guard; production code cannot
297        // trigger this path because observers receive `&T`, not `&mut Subject`.
298        let mut s: Subject<i32> = Subject::new(0);
299        let call_count = Rc::new(Cell::new(0_u32));
300        let call_count_clone = call_count.clone();
301
302        // SAFETY (test-only): we hold a raw pointer to `s` solely to invoke
303        // `notify` inside the observer and exercise the reentrancy guard.
304        // `s` outlives the closure; the closure is dropped before `s`.
305        // No other aliasing occurs; the sentinel prevents actual re-entry.
306        let s_ptr = &mut s as *mut Subject<i32>;
307        s.subscribe(move |_| {
308            call_count_clone.set(call_count_clone.get() + 1);
309            // Attempt a re-entrant notify
310            // SAFETY: see above.
311            unsafe { (*s_ptr).notify() };
312        });
313
314        s.set(1);
315
316        // The outer notify fires the observer once.  The inner notify is
317        // skipped by the sentinel, so the observer is NOT called a second time.
318        assert_eq!(call_count.get(), 1, "re-entrant notify must be skipped");
319        assert_eq!(*s.get(), 1);
320    }
321
322    // -----------------------------------------------------------------------
323    // Subject<bool>
324    // -----------------------------------------------------------------------
325
326    #[test]
327    fn bool_subject_set_and_notify() {
328        let mut s: Subject<bool> = Subject::new(false);
329        let seen = Rc::new(Cell::new(false));
330        let seen_clone = seen.clone();
331        s.subscribe(move |v| seen_clone.set(*v));
332        s.set(true);
333        assert!(seen.get());
334    }
335
336    // -----------------------------------------------------------------------
337    // Subject<String>
338    // -----------------------------------------------------------------------
339
340    #[test]
341    fn string_subject_tracks_value() {
342        let mut s: Subject<String> = Subject::new("hello".into());
343        s.set("world".into());
344        assert_eq!(s.get().as_str(), "world");
345        assert_eq!(s.prev().as_str(), "hello");
346    }
347
348    // -----------------------------------------------------------------------
349    // Zero observers โ€” set is a no-op for notifications
350    // -----------------------------------------------------------------------
351
352    #[test]
353    fn set_with_no_observers_does_not_panic() {
354        let mut s: Subject<i32> = Subject::new(0);
355        s.set(42); // must not panic
356        assert_eq!(*s.get(), 42);
357    }
358}