1use std::cell::{Cell, Ref, RefCell};
11use std::rc::{Rc, Weak};
12
13use crate::binding::{Binding, BindingLevel, BindingRegistry};
14use crate::widget_id::WidgetId;
15
16#[cfg(debug_assertions)]
29const SIGNAL_NOTIFY_DEPTH_LIMIT: u32 = 256;
30
31#[cfg(debug_assertions)]
32thread_local! {
33 static SIGNAL_NOTIFY_DEPTH: std::cell::Cell<u32> = const { std::cell::Cell::new(0) };
34}
35
36#[cfg(debug_assertions)]
38struct NotifyDepthGuard;
39
40#[cfg(debug_assertions)]
41impl NotifyDepthGuard {
42 fn enter() -> Self {
43 SIGNAL_NOTIFY_DEPTH.with(|d| {
44 let next = d.get() + 1;
45 assert!(
46 next <= SIGNAL_NOTIFY_DEPTH_LIMIT,
47 "Signal notification nested {next} deep (limit {SIGNAL_NOTIFY_DEPTH_LIMIT}) — \
48 almost certainly an unbounded feedback loop between observers (e.g. signal A's \
49 observer sets B and B's observer sets A). Break the cycle: guard the write with \
50 an equality check (`if sig.get() != v {{ sig.set(v) }}`), or drop one edge with \
51 a WeakSignal."
52 );
53 d.set(next);
54 });
55 NotifyDepthGuard
56 }
57}
58
59#[cfg(debug_assertions)]
60impl Drop for NotifyDepthGuard {
61 fn drop(&mut self) {
62 SIGNAL_NOTIFY_DEPTH.with(|d| d.set(d.get().saturating_sub(1)));
63 }
64}
65
66pub struct ObserverHandle {
73 _signal: Rc<dyn std::any::Any>,
75 observer_id: u64,
76 remover: Rc<dyn Fn(u64)>,
77}
78
79impl ObserverHandle {
80 pub fn new(keeper: Rc<dyn std::any::Any>, observer_id: u64, remover: Rc<dyn Fn(u64)>) -> Self {
86 Self {
87 _signal: keeper,
88 observer_id,
89 remover,
90 }
91 }
92
93 pub fn detach(self) {
95 drop(self);
97 }
98}
99
100impl Drop for ObserverHandle {
101 fn drop(&mut self) {
102 (self.remover)(self.observer_id);
103 }
104}
105
106impl std::fmt::Debug for ObserverHandle {
107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 f.debug_struct("ObserverHandle")
109 .field("observer_id", &self.observer_id)
110 .finish()
111 }
112}
113
114struct ObserverEntry<T> {
119 id: u64,
120 callback: Rc<dyn Fn(&T)>,
121}
122
123struct MutableInner<T> {
124 value: T,
125 generation: u64,
144 observers: Vec<ObserverEntry<T>>,
145 next_observer_id: u64,
146 keepalive: Vec<Box<dyn std::any::Any>>,
156}
157
158struct AnimationState {
160 pending: Option<crate::animation::AnimationRequest>,
161 target: Option<f32>,
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
165pub enum SignalAccessError {
166 #[error("signal is read-only")]
167 ReadOnly,
168 #[error("signal does not support animation")]
169 AnimationUnsupported,
170}
171
172pub struct WeakSignal<T> {
181 inner: Weak<RefCell<MutableInner<T>>>,
182 animation: Option<Weak<RefCell<AnimationState>>>,
183}
184
185impl<T> Clone for WeakSignal<T> {
186 fn clone(&self) -> Self {
187 Self {
188 inner: self.inner.clone(),
189 animation: self.animation.clone(),
190 }
191 }
192}
193
194impl<T: 'static> WeakSignal<T> {
195 pub fn upgrade(&self) -> Option<Signal<T>> {
198 let inner = self.inner.upgrade()?;
199 let animation = self.animation.as_ref().and_then(|weak| weak.upgrade());
203 Some(Signal {
204 kind: SignalKind::Mutable { inner, animation },
205 })
206 }
207}
208
209pub(crate) struct WeakAnimatedSignal {
210 inner: Weak<RefCell<MutableInner<f32>>>,
211 animation: Weak<RefCell<AnimationState>>,
212}
213
214impl WeakAnimatedSignal {
215 pub(crate) fn upgrade(&self) -> Option<Signal<f32>> {
216 Some(Signal {
217 kind: SignalKind::Mutable {
218 inner: self.inner.upgrade()?,
219 animation: Some(self.animation.upgrade()?),
220 },
221 })
222 }
223
224 pub(crate) fn same_signal(&self, signal: &Signal<f32>) -> bool {
225 match &signal.kind {
226 SignalKind::Mutable { inner, .. } => self.inner.as_ptr() == Rc::as_ptr(inner),
227 SignalKind::Derived { .. } => false,
228 }
229 }
230}
231
232#[derive(Clone)]
247struct DerivedSource {
248 generation: Rc<dyn Fn() -> u64>,
251 source_id: usize,
254}
255
256fn coalesced_source(inputs: Rc<dyn Fn() -> Vec<u64>>) -> DerivedSource {
281 let token: Rc<()> = Rc::new(());
285 let source_id = Rc::as_ptr(&token) as usize;
286 let state: Rc<(Cell<u64>, RefCell<Option<Vec<u64>>>)> =
287 Rc::new((Cell::new(0), RefCell::new(None)));
288 DerivedSource {
289 generation: Rc::new(move || {
290 let _keep = &token;
291 let now = inputs();
292 let (own, seen) = &*state;
293 let mut seen = seen.borrow_mut();
294 if seen.as_deref() != Some(now.as_slice()) {
295 *seen = Some(now);
296 own.set(own.get().wrapping_add(1));
297 }
298 own.get()
299 }),
300 source_id,
301 }
302}
303
304enum SignalKind<T> {
305 Mutable {
306 inner: Rc<RefCell<MutableInner<T>>>,
307 animation: Option<Rc<RefCell<AnimationState>>>,
308 },
309 Derived {
310 compute: Rc<dyn Fn() -> T>,
311 sources: Vec<DerivedSource>,
315 },
316}
317
318pub struct Signal<T> {
325 kind: SignalKind<T>,
326}
327
328impl<T: 'static> Signal<T> {
329 pub fn new(value: T) -> Self {
331 Self {
332 kind: SignalKind::Mutable {
333 inner: Rc::new(RefCell::new(MutableInner {
334 value,
335 generation: 0,
336 observers: Vec::new(),
337 next_observer_id: 1,
338 keepalive: Vec::new(),
339 })),
340 animation: None,
341 },
342 }
343 }
344
345 pub fn attach_keepalive<G: 'static>(&self, guard: G) {
355 if let SignalKind::Mutable { inner, .. } = &self.kind {
356 inner.borrow_mut().keepalive.push(Box::new(guard));
357 }
358 }
359
360 pub fn downgrade(&self) -> Option<WeakSignal<T>> {
368 match &self.kind {
369 SignalKind::Mutable { inner, animation } => Some(WeakSignal {
370 inner: Rc::downgrade(inner),
371 animation: animation.as_ref().map(Rc::downgrade),
372 }),
373 SignalKind::Derived { .. } => None,
374 }
375 }
376
377 pub fn observe(&self, f: impl Fn(&T) + 'static) -> ObserverHandle {
380 self.try_observe(f)
381 .expect("observe() is only supported on mutable signals")
382 }
383
384 pub fn try_observe(
385 &self,
386 f: impl Fn(&T) + 'static,
387 ) -> Result<ObserverHandle, SignalAccessError> {
388 match &self.kind {
389 SignalKind::Mutable { inner, .. } => {
390 let mut guard = inner.borrow_mut();
391 let id = guard.next_observer_id;
392 guard.next_observer_id += 1;
393 guard.observers.push(ObserverEntry {
394 id,
395 callback: Rc::new(f),
396 });
397 Ok(ObserverHandle {
398 _signal: inner.clone(),
399 observer_id: id,
400 remover: {
401 let inner = inner.clone();
402 Rc::new(move |observer_id| {
403 inner.borrow_mut().observers.retain(|e| e.id != observer_id);
404 })
405 },
406 })
407 }
408 SignalKind::Derived { .. } => Err(SignalAccessError::ReadOnly),
409 }
410 }
411
412 pub fn observer_count(&self) -> usize {
414 match &self.kind {
415 SignalKind::Mutable { inner, .. } => inner.borrow().observers.len(),
416 SignalKind::Derived { .. } => 0,
417 }
418 }
419
420 pub fn same(a: &Self, b: &Self) -> bool {
422 match (&a.kind, &b.kind) {
423 (SignalKind::Mutable { inner: a, .. }, SignalKind::Mutable { inner: b, .. }) => {
424 Rc::ptr_eq(a, b)
425 }
426 _ => false,
427 }
428 }
429}
430
431impl<T: Clone + 'static> Signal<T> {
432 pub fn set(&self, value: T) {
439 self.try_set(value)
440 .expect("cannot set() on a derived Signal — it is read-only");
441 }
442
443 pub fn set_if_changed(&self, value: T) -> bool
469 where
470 T: PartialEq,
471 {
472 if self.get() == value {
473 return false;
474 }
475 self.set(value);
476 true
477 }
478
479 pub fn try_set(&self, value: T) -> Result<(), SignalAccessError> {
502 match &self.kind {
503 SignalKind::Mutable { inner, .. } => {
504 let (snapshot, callbacks) = {
505 let mut guard = inner.borrow_mut();
506 guard.value = value;
507 guard.generation = guard.generation.wrapping_add(1);
508 let callbacks: Vec<_> =
509 guard.observers.iter().map(|e| e.callback.clone()).collect();
510 (guard.value.clone(), callbacks)
511 };
512 #[cfg(debug_assertions)]
516 let _depth = NotifyDepthGuard::enter();
517 for cb in &callbacks {
518 cb(&snapshot);
519 }
520 Ok(())
521 }
522 SignalKind::Derived { .. } => Err(SignalAccessError::ReadOnly),
523 }
524 }
525
526 pub fn get(&self) -> T {
528 match &self.kind {
529 SignalKind::Mutable { inner, .. } => inner.borrow().value.clone(),
530 SignalKind::Derived { compute, .. } => compute(),
531 }
532 }
533
534 pub fn get_ref(&self) -> Ref<'_, T> {
537 self.try_get_ref()
538 .expect("get_ref() is only supported on mutable signals")
539 }
540
541 pub fn try_get_ref(&self) -> Result<Ref<'_, T>, SignalAccessError> {
542 match &self.kind {
543 SignalKind::Mutable { inner, .. } => Ok(Ref::map(inner.borrow(), |guard| &guard.value)),
544 SignalKind::Derived { .. } => Err(SignalAccessError::ReadOnly),
545 }
546 }
547
548 pub fn map<U: Clone + 'static>(&self, f: impl Fn(&T) -> U + 'static) -> Signal<U> {
551 let compute = self.as_compute();
552 let sources = self.as_sources();
553 Signal {
554 kind: SignalKind::Derived {
555 compute: Rc::new(move || f(&compute())),
556 sources,
557 },
558 }
559 }
560
561 pub fn zip<U: Clone + 'static>(&self, other: &Signal<U>) -> Signal<(T, U)> {
575 let a = self.as_compute();
576 let b = other.as_compute();
577 let mut sources = self.as_sources();
578 merge_sources(&mut sources, other.as_sources());
579 Signal {
580 kind: SignalKind::Derived {
581 compute: Rc::new(move || (a(), b())),
582 sources,
583 },
584 }
585 }
586
587 pub fn zip3<U: Clone + 'static, V: Clone + 'static>(
589 &self,
590 b: &Signal<U>,
591 c: &Signal<V>,
592 ) -> Signal<(T, U, V)> {
593 let fa = self.as_compute();
594 let fb = b.as_compute();
595 let fc = c.as_compute();
596 let mut sources = self.as_sources();
597 merge_sources(&mut sources, b.as_sources());
598 merge_sources(&mut sources, c.as_sources());
599 Signal {
600 kind: SignalKind::Derived {
601 compute: Rc::new(move || (fa(), fb(), fc())),
602 sources,
603 },
604 }
605 }
606
607 pub fn map_coalesced<U: Clone + 'static>(&self, f: impl Fn(&T) -> U + 'static) -> Signal<U> {
624 let compute = self.as_compute();
625 let underlying = self.as_sources();
626 if underlying.len() <= 1 {
627 return self.map(f);
630 }
631 let coalesced = coalesced_source(Rc::new(move || {
632 underlying.iter().map(|s| (s.generation)()).collect()
633 }));
634 Signal {
635 kind: SignalKind::Derived {
636 compute: Rc::new(move || f(&compute())),
637 sources: vec![coalesced],
638 },
639 }
640 }
641
642 pub fn flat_map<U: Clone + 'static>(&self, f: impl Fn(&T) -> Signal<U> + 'static) -> Signal<U> {
666 let f: Rc<dyn Fn(&T) -> Signal<U>> = Rc::new(f);
667 let outer_compute = self.as_compute();
668 let outer_sources = self.as_sources();
669
670 let compute: Rc<dyn Fn() -> U> = {
672 let f = f.clone();
673 let outer_compute = outer_compute.clone();
674 Rc::new(move || f(&outer_compute()).get())
675 };
676
677 let composite = coalesced_source({
687 let f = f.clone();
688 let outer_compute = outer_compute.clone();
689 Rc::new(move || {
690 let mut gens: Vec<u64> = outer_sources.iter().map(|s| (s.generation)()).collect();
691 gens.extend(
692 f(&outer_compute())
693 .as_sources()
694 .iter()
695 .map(|s| (s.generation)()),
696 );
697 gens
698 })
699 });
700
701 Signal {
702 kind: SignalKind::Derived {
703 compute,
704 sources: vec![composite],
705 },
706 }
707 }
708
709 fn as_compute(&self) -> Rc<dyn Fn() -> T> {
713 match &self.kind {
714 SignalKind::Mutable { inner, .. } => {
715 let source = inner.clone();
716 Rc::new(move || source.borrow().value.clone())
717 }
718 SignalKind::Derived { compute, .. } => compute.clone(),
719 }
720 }
721
722 pub fn bind_to(&self, widget_id: WidgetId, registry: &BindingRegistry, level: BindingLevel) {
734 for src in self.as_sources() {
735 registry.register(Binding {
736 widget_id,
737 level,
738 generation: src.generation,
739 source_id: src.source_id,
740 });
741 }
742 }
743
744 fn as_sources(&self) -> Vec<DerivedSource> {
748 match &self.kind {
749 SignalKind::Mutable { inner, .. } => {
750 let gen_src = inner.clone();
751 let source_id = Rc::as_ptr(inner) as *const () as usize;
752 vec![DerivedSource {
753 generation: Rc::new(move || gen_src.borrow().generation),
757 source_id,
758 }]
759 }
760 SignalKind::Derived { sources, .. } => sources.clone(),
761 }
762 }
763}
764
765fn merge_sources(dst: &mut Vec<DerivedSource>, incoming: Vec<DerivedSource>) {
769 for s in incoming {
770 if !dst.iter().any(|d| d.source_id == s.source_id) {
771 dst.push(s);
772 }
773 }
774}
775
776impl<T: 'static> Signal<T> {
777 pub fn is_mutable(&self) -> bool {
778 matches!(self.kind, SignalKind::Mutable { .. })
779 }
780
781 pub fn generation(&self) -> u64 {
798 match &self.kind {
799 SignalKind::Mutable { inner, .. } => inner.borrow().generation,
800 SignalKind::Derived { sources, .. } => sources
801 .iter()
802 .map(|s| (s.generation)())
803 .fold(0u64, u64::wrapping_add),
804 }
805 }
806}
807
808impl Signal<bool> {
813 pub fn and(&self, other: &Signal<bool>) -> Signal<bool> {
818 self.zip(other).map(|(a, b)| *a && *b)
819 }
820
821 pub fn or(&self, other: &Signal<bool>) -> Signal<bool> {
824 self.zip(other).map(|(a, b)| *a || *b)
825 }
826
827 pub fn not(&self) -> Signal<bool> {
829 self.map(|b| !*b)
830 }
831}
832
833impl Signal<f32> {
838 pub fn new_animated(value: f32) -> Self {
840 Self {
841 kind: SignalKind::Mutable {
842 inner: Rc::new(RefCell::new(MutableInner {
843 value,
844 generation: 0,
845 observers: Vec::new(),
846 next_observer_id: 1,
847 keepalive: Vec::new(),
848 })),
849 animation: Some(Rc::new(RefCell::new(AnimationState {
850 pending: None,
851 target: None,
852 }))),
853 },
854 }
855 }
856
857 pub fn supports_animation(&self) -> bool {
858 matches!(
859 &self.kind,
860 SignalKind::Mutable {
861 animation: Some(_),
862 ..
863 }
864 )
865 }
866
867 pub(crate) fn weak_handle(&self) -> Option<WeakAnimatedSignal> {
868 match &self.kind {
869 SignalKind::Mutable {
870 inner,
871 animation: Some(animation),
872 } => Some(WeakAnimatedSignal {
873 inner: Rc::downgrade(inner),
874 animation: Rc::downgrade(animation),
875 }),
876 _ => None,
877 }
878 }
879
880 pub fn animate_to(
882 &self,
883 target: f32,
884 duration: std::time::Duration,
885 easing: teksilo_tokens::Easing,
886 ) {
887 self.animate_to_with_frame_interval(target, duration, easing, None);
888 }
889
890 pub fn animate_to_with_frame_interval(
891 &self,
892 target: f32,
893 duration: std::time::Duration,
894 easing: teksilo_tokens::Easing,
895 frame_interval: Option<std::time::Duration>,
896 ) {
897 self.try_animate_to_with_frame_interval(target, duration, easing, frame_interval)
898 .unwrap_or_else(|err| match err {
899 SignalAccessError::ReadOnly => {
900 panic!("animate_to is not supported on derived signals")
901 }
902 SignalAccessError::AnimationUnsupported => {
903 panic!(
904 "animate_to called on Signal<f32> without animation support; use Signal::new_animated()"
905 )
906 }
907 });
908 }
909
910 pub fn try_animate_to(
911 &self,
912 target: f32,
913 duration: std::time::Duration,
914 easing: teksilo_tokens::Easing,
915 ) -> Result<(), SignalAccessError> {
916 self.try_animate_to_with_frame_interval(target, duration, easing, None)
917 }
918
919 pub fn try_animate_to_with_frame_interval(
920 &self,
921 target: f32,
922 duration: std::time::Duration,
923 easing: teksilo_tokens::Easing,
924 frame_interval: Option<std::time::Duration>,
925 ) -> Result<(), SignalAccessError> {
926 self.try_animate_with_options(crate::animation::AnimationRequest {
927 target,
928 duration,
929 easing,
930 frame_interval,
931 looping: false,
932 epsilon: 0.0,
933 max_duration: None,
934 })
935 }
936
937 pub fn animate_looping(
941 &self,
942 target: f32,
943 period: std::time::Duration,
944 easing: teksilo_tokens::Easing,
945 frame_interval: Option<std::time::Duration>,
946 ) {
947 let _ = self.try_animate_with_options(crate::animation::AnimationRequest {
948 target,
949 duration: period,
950 easing,
951 frame_interval,
952 looping: true,
953 epsilon: 0.0,
954 max_duration: None,
955 });
956 }
957
958 pub fn try_animate_with_options(
963 &self,
964 request: crate::animation::AnimationRequest,
965 ) -> Result<(), SignalAccessError> {
966 match &self.kind {
967 SignalKind::Mutable {
968 inner,
969 animation: Some(animation),
970 } => {
971 let mut anim = animation.borrow_mut();
972 anim.target = Some(request.target);
973 anim.pending = Some(request);
974 drop(anim);
975 let mut guard = inner.borrow_mut();
979 guard.generation = guard.generation.wrapping_add(1);
980 Ok(())
981 }
982 SignalKind::Mutable {
983 animation: None, ..
984 } => Err(SignalAccessError::AnimationUnsupported),
985 SignalKind::Derived { .. } => Err(SignalAccessError::ReadOnly),
986 }
987 }
988
989 pub fn animation_target(&self) -> Option<f32> {
991 match &self.kind {
992 SignalKind::Mutable { animation, .. } => {
993 animation.as_ref().and_then(|a| a.borrow().target)
994 }
995 _ => None,
996 }
997 }
998
999 pub fn clear_animation_target(&self) {
1002 if let SignalKind::Mutable { animation, .. } = &self.kind
1003 && let Some(a) = animation
1004 {
1005 a.borrow_mut().target = None;
1006 }
1007 }
1008
1009 pub fn take_pending_animation(&self) -> Option<crate::animation::AnimationRequest> {
1011 match &self.kind {
1012 SignalKind::Mutable { animation, .. } => animation
1013 .as_ref()
1014 .and_then(|a| a.borrow_mut().pending.take()),
1015 _ => None,
1016 }
1017 }
1018
1019 pub fn has_pending_animation(&self) -> bool {
1021 match &self.kind {
1022 SignalKind::Mutable { animation, .. } => animation
1023 .as_ref()
1024 .is_some_and(|a| a.borrow().pending.is_some()),
1025 _ => false,
1026 }
1027 }
1028}
1029
1030impl<T> Clone for Signal<T> {
1035 fn clone(&self) -> Self {
1036 Self {
1037 kind: match &self.kind {
1038 SignalKind::Mutable { inner, animation } => SignalKind::Mutable {
1039 inner: inner.clone(),
1040 animation: animation.clone(),
1041 },
1042 SignalKind::Derived { compute, sources } => SignalKind::Derived {
1043 compute: compute.clone(),
1044 sources: sources.clone(),
1045 },
1046 },
1047 }
1048 }
1049}
1050
1051impl<T: std::fmt::Debug + 'static> std::fmt::Debug for Signal<T> {
1052 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1053 match &self.kind {
1054 SignalKind::Mutable { inner, .. } => f
1055 .debug_struct("Signal::Mutable")
1056 .field("value", &inner.borrow().value)
1057 .field("generation", &inner.borrow().generation)
1058 .finish(),
1059 SignalKind::Derived { .. } => f.write_str("Signal::Derived(..)"),
1060 }
1061 }
1062}
1063
1064pub enum Prop<T: Clone + 'static> {
1071 Static(T),
1073 Bound(Signal<T>),
1075}
1076
1077impl<T: Clone + 'static> Prop<T> {
1078 pub fn get(&self) -> T {
1080 match self {
1081 Prop::Static(v) => v.clone(),
1082 Prop::Bound(signal) => signal.get(),
1083 }
1084 }
1085
1086 pub fn register_if_bound(
1088 &self,
1089 widget_id: WidgetId,
1090 registry: &BindingRegistry,
1091 level: BindingLevel,
1092 ) {
1093 if let Prop::Bound(signal) = self {
1094 signal.bind_to(widget_id, registry, level);
1095 }
1096 }
1097
1098 pub fn as_signal(&self) -> Signal<T> {
1104 match self {
1105 Prop::Static(v) => Signal::new(v.clone()),
1106 Prop::Bound(signal) => signal.clone(),
1107 }
1108 }
1109}
1110
1111impl<T: Clone + 'static> Clone for Prop<T> {
1112 fn clone(&self) -> Self {
1113 match self {
1114 Prop::Static(v) => Prop::Static(v.clone()),
1115 Prop::Bound(s) => Prop::Bound(s.clone()),
1116 }
1117 }
1118}
1119
1120impl<T: Clone + std::fmt::Debug + 'static> std::fmt::Debug for Prop<T> {
1121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1122 match self {
1123 Prop::Static(v) => write!(f, "Prop::Static({:?})", v),
1124 Prop::Bound(_) => f.write_str("Prop::Bound(..)"),
1125 }
1126 }
1127}
1128
1129impl<T: Clone + 'static> From<T> for Prop<T> {
1130 fn from(value: T) -> Self {
1131 Prop::Static(value)
1132 }
1133}
1134
1135impl<T: Clone + 'static> From<Signal<T>> for Prop<T> {
1136 fn from(signal: Signal<T>) -> Self {
1137 Prop::Bound(signal)
1138 }
1139}
1140
1141impl From<&str> for Prop<String> {
1146 fn from(s: &str) -> Self {
1147 Prop::Static(s.to_owned())
1148 }
1149}
1150
1151impl From<&String> for Prop<String> {
1152 fn from(s: &String) -> Self {
1153 Prop::Static(s.clone())
1154 }
1155}
1156
1157#[cfg(test)]
1162mod tests {
1163 use super::*;
1164
1165 #[test]
1166 fn signal_get_set() {
1167 let s = Signal::new(42);
1168 assert_eq!(s.get(), 42);
1169 s.set(99);
1170 assert_eq!(s.get(), 99);
1171 }
1172
1173 #[test]
1174 fn generation_advances_on_every_write_and_never_resets() {
1175 let s = Signal::new(0);
1176 let start = s.generation();
1177 assert_eq!(s.generation(), start, "reading is not a change");
1178
1179 s.set(1);
1180 let after_one = s.generation();
1181 assert_ne!(after_one, start, "a write advances the generation");
1182
1183 s.set(1);
1184 let after_republish = s.generation();
1185 assert_ne!(
1186 after_republish, after_one,
1187 "`set` is unconditional — a republish of the same value is still \
1188 a write, and callers who want the equality guard use \
1189 `set_if_changed`"
1190 );
1191
1192 assert!(!s.set_if_changed(1), "value is unchanged");
1193 assert_eq!(
1194 s.generation(),
1195 after_republish,
1196 "`set_if_changed` with an identical value writes nothing at all"
1197 );
1198 }
1199
1200 #[test]
1205 fn observing_the_generation_does_not_consume_it() {
1206 let s = Signal::new(0);
1207 let (mut seen_a, mut seen_b) = (s.generation(), s.generation());
1208
1209 s.set(1);
1210 assert_ne!(s.generation(), seen_a);
1211 seen_a = s.generation();
1212 assert_ne!(
1213 s.generation(),
1214 seen_b,
1215 "consumer A catching up must leave consumer B behind, not clean"
1216 );
1217 seen_b = s.generation();
1218
1219 assert_eq!(s.generation(), seen_a);
1220 assert_eq!(s.generation(), seen_b);
1221 }
1222
1223 #[test]
1224 fn signal_clone_shares() {
1225 let a = Signal::new(10);
1226 let b = a.clone();
1227 a.set(20);
1228 assert_eq!(b.get(), 20);
1229 assert!(Signal::same(&a, &b));
1230 }
1231
1232 #[test]
1233 fn signal_map_derived() {
1234 let text = Signal::new(String::from("hello"));
1235 let len = text.map(|t| t.len());
1236 assert_eq!(len.get(), 5);
1237 text.set(String::from("hi"));
1238 assert_eq!(len.get(), 2);
1239 }
1240
1241 #[test]
1242 fn signal_map_chained() {
1243 let s = Signal::new(5);
1244 let doubled = s.map(|v| v * 2);
1245 let as_string = doubled.map(|v| format!("{}", v));
1246 assert_eq!(as_string.get(), "10");
1247 s.set(7);
1248 assert_eq!(as_string.get(), "14");
1249 }
1250
1251 #[test]
1252 fn signal_derived_generation_tracks_source() {
1253 let s = Signal::new(0);
1254 let derived = s.map(|v| v + 1);
1255 let seen = derived.generation();
1256 s.set(5);
1257 assert_ne!(
1258 derived.generation(),
1259 seen,
1260 "the source's write shows through"
1261 );
1262 let seen = derived.generation();
1263 assert_eq!(
1264 derived.generation(),
1265 seen,
1266 "and settles with no further write"
1267 );
1268 }
1269
1270 #[test]
1271 fn flat_map_follows_selected_inner_value() {
1272 let a = Signal::new(10);
1273 let b = Signal::new(20);
1274 let which = Signal::new(0usize);
1275 let (a2, b2) = (a.clone(), b.clone());
1276 let out = which.flat_map(move |i| if *i == 0 { a2.clone() } else { b2.clone() });
1277
1278 assert_eq!(out.get(), 10); a.set(11);
1280 assert_eq!(out.get(), 11); which.set(1);
1282 assert_eq!(out.get(), 20); b.set(21);
1284 assert_eq!(out.get(), 21);
1285 a.set(999); assert_eq!(out.get(), 21);
1287 }
1288
1289 #[test]
1290 fn flat_map_generation_tracks_outer_and_current_inner() {
1291 let a = Signal::new(0);
1292 let b = Signal::new(0);
1293 let which = Signal::new(0usize);
1294 let (a2, b2) = (a.clone(), b.clone());
1295 let out = which.flat_map(move |i| if *i == 0 { a2.clone() } else { b2.clone() });
1296 let mut seen = out.generation();
1297
1298 a.set(5);
1300 assert_ne!(out.generation(), seen);
1301 seen = out.generation();
1302 assert_eq!(out.generation(), seen);
1303
1304 b.set(7);
1306 assert_eq!(out.generation(), seen, "b is not selected");
1307
1308 which.set(1);
1310 assert_ne!(out.generation(), seen);
1311 seen = out.generation();
1312
1313 b.set(8);
1315 assert_ne!(out.generation(), seen);
1316 seen = out.generation();
1317 a.set(9);
1318 assert_eq!(out.generation(), seen, "a is no longer selected");
1319 }
1320
1321 #[test]
1328 fn flat_map_survives_an_inner_switch_that_would_cancel_out_in_a_sum() {
1329 let hot = Signal::new(0_i32);
1330 let cold = Signal::new(0_i32);
1331 hot.set(1);
1335 let (hot2, cold2) = (hot.clone(), cold.clone());
1336 let which = Signal::new(0usize);
1337 let out = which.flat_map(move |i| if *i == 0 { hot2.clone() } else { cold2.clone() });
1338
1339 assert_eq!(out.get(), 1, "starts on `hot`");
1340 let seen = out.generation();
1341
1342 which.set(1);
1343
1344 assert_eq!(out.get(), 0, "the value really did change");
1345 assert_ne!(
1346 out.generation(),
1347 seen,
1348 "and the generation says so — a plain sum of (outer + inner) \
1349 would have been unchanged here"
1350 );
1351 }
1352
1353 #[test]
1359 fn a_composite_sources_generation_is_readable_by_every_consumer() {
1360 let inner = Signal::new(0_i32);
1361 let inner2 = inner.clone();
1362 let which = Signal::new(0usize);
1363 let out = which.flat_map(move |_| inner2.clone());
1364
1365 let (window_a, window_b) = (out.generation(), out.generation());
1366 inner.set(1);
1367
1368 let a_now = out.generation();
1369 assert_ne!(a_now, window_a, "window A notices");
1370 assert_ne!(
1371 out.generation(),
1372 window_b,
1373 "and window B still notices, after A already looked"
1374 );
1375 assert_eq!(out.generation(), a_now, "both see the SAME new generation");
1376 }
1377
1378 #[test]
1379 fn flat_map_binding_rerenders_on_inner_and_outer_change() {
1380 use crate::binding::{BindingLevel, BindingRegistry};
1381 use slotmap::KeyData;
1382 let fake_id: WidgetId = KeyData::from_ffi(1).into();
1383 let inner = Signal::new(false);
1384 let which = Signal::new(0usize);
1385 let inner2 = inner.clone();
1386 let gate = which.flat_map(move |_| inner2.clone());
1387
1388 let registry = BindingRegistry::new();
1389 gate.bind_to(fake_id, ®istry, BindingLevel::Relayout);
1390 assert!(registry.flush_dirty().is_empty());
1391
1392 inner.set(true);
1394 let dirty = registry.flush_dirty();
1395 assert_eq!(dirty.len(), 1, "selected-inner change must re-render");
1396 assert_eq!(dirty[0].0, fake_id);
1397
1398 which.set(0);
1400 let dirty = registry.flush_dirty();
1401 assert_eq!(dirty.len(), 1, "outer-selector change must re-render");
1402 }
1403
1404 #[test]
1405 fn observer_called_on_set() {
1406 use std::cell::Cell;
1407 let s = Signal::new(0);
1408 let called = Rc::new(Cell::new(false));
1409 let c = called.clone();
1410 let _handle = s.observe(move |val| {
1411 assert_eq!(*val, 42);
1412 c.set(true);
1413 });
1414 s.set(42);
1415 assert!(called.get());
1416 }
1417
1418 #[test]
1422 fn set_if_changed_does_not_notify_when_the_value_is_identical() {
1423 use std::cell::Cell;
1424 let s = Signal::new(7);
1425 let calls = Rc::new(Cell::new(0));
1426 let c = calls.clone();
1427 let _handle = s.observe(move |_| c.set(c.get() + 1));
1428
1429 assert!(
1430 !s.set_if_changed(7),
1431 "an identical write must report no change"
1432 );
1433 assert_eq!(calls.get(), 0, "an identical write must not walk observers");
1434
1435 assert!(
1436 s.set_if_changed(8),
1437 "a differing write must report a change"
1438 );
1439 assert_eq!(calls.get(), 1, "a differing write must notify");
1440 assert_eq!(s.get(), 8);
1441 }
1442
1443 #[test]
1446 fn set_if_changed_settles_a_two_signal_feedback_loop() {
1447 let a = Signal::new(0);
1448 let b = Signal::new(0);
1449 let _ha = {
1450 let b = b.clone();
1451 a.observe(move |v| {
1452 b.set_if_changed(*v);
1453 })
1454 };
1455 let _hb = {
1456 let a = a.clone();
1457 b.observe(move |v| {
1458 a.set_if_changed(*v);
1459 })
1460 };
1461 a.set(5);
1463 assert_eq!(b.get(), 5);
1464 assert_eq!(a.get(), 5);
1465 }
1466
1467 #[test]
1468 fn observer_removed_on_handle_drop() {
1469 use std::cell::Cell;
1470 let s = Signal::new(0);
1471 let count = Rc::new(Cell::new(0));
1472 let c = count.clone();
1473 let handle = s.observe(move |_| {
1474 c.set(c.get() + 1);
1475 });
1476 s.set(1);
1477 assert_eq!(count.get(), 1);
1478 drop(handle);
1479 s.set(2);
1480 assert_eq!(count.get(), 1); }
1482
1483 #[test]
1484 fn multiple_observers() {
1485 use std::cell::Cell;
1486 let s = Signal::new(0);
1487 let count = Rc::new(Cell::new(0));
1488 let c1 = count.clone();
1489 let c2 = count.clone();
1490 let _h1 = s.observe(move |_| c1.set(c1.get() + 1));
1491 let _h2 = s.observe(move |_| c2.set(c2.get() + 1));
1492 s.set(10);
1493 assert_eq!(count.get(), 2);
1494 }
1495
1496 #[test]
1497 fn binding_registry_integration() {
1498 use slotmap::KeyData;
1499 let fake_id: WidgetId = KeyData::from_ffi(1).into();
1500 let registry = BindingRegistry::new();
1501 let s = Signal::new(0);
1502 s.bind_to(fake_id, ®istry, BindingLevel::RepaintOnly);
1503
1504 assert!(registry.flush_dirty().is_empty());
1505 s.set(42);
1506 let dirty = registry.flush_dirty();
1507 assert_eq!(dirty.len(), 1);
1508 assert_eq!(dirty[0].0, fake_id);
1509 assert_eq!(dirty[0].1, BindingLevel::RepaintOnly);
1510 assert!(registry.flush_dirty().is_empty());
1511 }
1512
1513 #[test]
1514 fn derived_binding_registry() {
1515 use slotmap::KeyData;
1516 let fake_id: WidgetId = KeyData::from_ffi(1).into();
1517 let registry = BindingRegistry::new();
1518 let s = Signal::new(0);
1519 let doubled = s.map(|v| v * 2);
1520 doubled.bind_to(fake_id, ®istry, BindingLevel::Relayout);
1521
1522 assert!(registry.flush_dirty().is_empty());
1523 s.set(5);
1524 let dirty = registry.flush_dirty();
1525 assert_eq!(dirty.len(), 1);
1526 assert_eq!(dirty[0].1, BindingLevel::Relayout);
1527 }
1528
1529 #[test]
1530 fn get_ref_works() {
1531 let s = Signal::new(String::from("hello"));
1532 {
1533 let r = s.get_ref();
1534 assert_eq!(&*r, "hello");
1535 }
1536 }
1537
1538 #[test]
1539 #[should_panic(expected = "cannot set() on a derived Signal")]
1540 fn set_on_derived_panics() {
1541 let s = Signal::new(0);
1542 let d = s.map(|v| v + 1);
1543 d.set(99);
1544 }
1545
1546 #[test]
1547 fn prop_static() {
1548 let p: Prop<i32> = 42.into();
1549 assert_eq!(p.get(), 42);
1550 }
1551
1552 #[test]
1553 fn prop_bound() {
1554 let s = Signal::new(10);
1555 let p: Prop<i32> = s.clone().into();
1556 assert_eq!(p.get(), 10);
1557 s.set(20);
1558 assert_eq!(p.get(), 20);
1559 }
1560
1561 #[test]
1562 fn prop_register_if_bound() {
1563 use slotmap::KeyData;
1564 let fake_id: WidgetId = KeyData::from_ffi(1).into();
1565 let registry = BindingRegistry::new();
1566
1567 let s = Signal::new(0);
1568 let p: Prop<i32> = s.clone().into();
1569 p.register_if_bound(fake_id, ®istry, BindingLevel::RepaintOnly);
1570
1571 s.set(1);
1572 let dirty = registry.flush_dirty();
1573 assert_eq!(dirty.len(), 1);
1574
1575 let p2: Prop<i32> = 42.into();
1577 p2.register_if_bound(fake_id, ®istry, BindingLevel::RepaintOnly);
1578 assert!(registry.flush_dirty().is_empty());
1579 }
1580
1581 #[test]
1584 fn zip_reads_both_sources() {
1585 let a = Signal::new(1_i32);
1586 let b = Signal::new("x".to_string());
1587 let z = a.zip(&b);
1588 assert_eq!(z.get(), (1, "x".to_string()));
1589 a.set(7);
1590 b.set("y".to_string());
1591 assert_eq!(z.get(), (7, "y".to_string()));
1592 }
1593
1594 #[test]
1595 fn zip_generation_advances_when_either_source_is_written() {
1596 let a = Signal::new(0_i32);
1597 let b = Signal::new(0_i32);
1598 let z = a.zip(&b);
1599 let mut seen = z.generation();
1600
1601 a.set(1);
1602 assert_ne!(z.generation(), seen, "a write to the first source shows");
1603 seen = z.generation();
1604
1605 b.set(2);
1606 assert_ne!(z.generation(), seen, "a write to the second source shows");
1607 seen = z.generation();
1608
1609 assert_eq!(z.generation(), seen, "and settles with no further write");
1610 }
1611
1612 #[test]
1617 fn zip_generation_reflects_writes_to_both_sources_independently() {
1618 let a = Signal::new(0_i32);
1619 let b = Signal::new(0_i32);
1620 let z = a.zip(&b);
1621
1622 let start = z.generation();
1623 a.set(1);
1624 let after_a = z.generation();
1625 b.set(1);
1626 let after_b = z.generation();
1627
1628 assert!(
1629 after_a > start && after_b > after_a,
1630 "monotone in both sources: {start} < {after_a} < {after_b}"
1631 );
1632 }
1633
1634 #[test]
1635 fn zip3_reads_three_sources() {
1636 let a = Signal::new(1_i32);
1637 let b = Signal::new(2_i32);
1638 let c = Signal::new(3_i32);
1639 let z = a.zip3(&b, &c);
1640 assert_eq!(z.get(), (1, 2, 3));
1641 c.set(30);
1642 assert_eq!(z.get(), (1, 2, 30));
1643 }
1644
1645 #[test]
1646 fn zip3_generation_advances_for_any_source() {
1647 let a = Signal::new(0_i32);
1648 let b = Signal::new(0_i32);
1649 let c = Signal::new(0_i32);
1650 let z = a.zip3(&b, &c);
1651 let mut seen = z.generation();
1652
1653 for write in [&c, &b, &a] {
1654 write.set(1);
1655 assert_ne!(z.generation(), seen);
1656 seen = z.generation();
1657 }
1658 }
1659
1660 #[test]
1661 fn and_reads_logical_and() {
1662 let a = Signal::new(true);
1663 let b = Signal::new(false);
1664 let anded = a.and(&b);
1665 assert!(!anded.get());
1666 b.set(true);
1667 assert!(anded.get());
1668 a.set(false);
1669 assert!(!anded.get());
1670 }
1671
1672 #[test]
1673 fn or_reads_logical_or() {
1674 let a = Signal::new(false);
1675 let b = Signal::new(false);
1676 let ored = a.or(&b);
1677 assert!(!ored.get());
1678 a.set(true);
1679 assert!(ored.get());
1680 a.set(false);
1681 b.set(true);
1682 assert!(ored.get());
1683 }
1684
1685 #[test]
1686 fn not_reads_logical_negation() {
1687 let a = Signal::new(true);
1688 let n = a.not();
1689 assert!(!n.get());
1690 a.set(false);
1691 assert!(n.get());
1692 }
1693
1694 #[test]
1695 fn combined_predicate_fires_binding_on_any_source() {
1696 use crate::binding::BindingRegistry;
1697 use slotmap::KeyData;
1698
1699 let reg = BindingRegistry::new();
1700 let id: WidgetId = KeyData::from_ffi(1).into();
1701
1702 let focus = Signal::new(false);
1703 let readonly = Signal::new(true);
1704 let in_editor = Signal::new(true);
1705
1706 let when = focus.and(&readonly.not()).and(&in_editor);
1709 when.bind_to(id, ®, BindingLevel::Relayout);
1710 assert!(!when.get(), "all sources start producing false");
1711
1712 focus.set(true);
1714 let dirty = reg.flush_dirty();
1715 assert_eq!(dirty.len(), 1, "focus change must fire the binding");
1716 assert_eq!(dirty[0].0, id);
1717
1718 readonly.set(false);
1720 let dirty = reg.flush_dirty();
1721 assert_eq!(dirty.len(), 1, "readonly change must fire the binding");
1722 assert!(when.get());
1724
1725 in_editor.set(false);
1727 let dirty = reg.flush_dirty();
1728 assert_eq!(dirty.len(), 1, "in_editor change must fire the binding");
1729 assert!(!when.get());
1730 }
1731
1732 #[test]
1733 fn zip_dedups_identical_source() {
1734 use crate::binding::BindingRegistry;
1738 use slotmap::KeyData;
1739
1740 let reg = BindingRegistry::new();
1741 let id: WidgetId = KeyData::from_ffi(1).into();
1742 let a = Signal::new(0_i32);
1743 let derived = a.map(|v| v + 1);
1744 let z = a.zip(&derived);
1745 z.bind_to(id, ®, BindingLevel::RepaintOnly);
1746 assert_eq!(
1747 reg.len(),
1748 1,
1749 "duplicate upstream root must register once, not twice"
1750 );
1751 }
1752
1753 #[test]
1754 fn signal_animated_f32() {
1755 let s = Signal::<f32>::new_animated(0.0);
1756 assert!(!s.has_pending_animation());
1757 s.animate_to(
1758 100.0,
1759 std::time::Duration::from_millis(200),
1760 teksilo_tokens::Easing::Linear,
1761 );
1762 assert!(s.has_pending_animation());
1763 assert_eq!(s.animation_target(), Some(100.0));
1764 let req = s.take_pending_animation().unwrap();
1765 assert_eq!(req.target, 100.0);
1766 assert!(!s.has_pending_animation());
1767 }
1768
1769 #[test]
1770 fn map_coalesced_collapses_multi_source_to_one_binding() {
1771 let a = Signal::new(1u32);
1775 let b = Signal::new(2u32);
1776 let c = Signal::new(3u32);
1777 let d = Signal::new(4u32);
1778 let composite = a
1779 .zip3(&b, &c)
1780 .zip(&d)
1781 .map_coalesced(|((x, y, z), w)| *x + *y + *z + *w);
1782 assert_eq!(composite.as_sources().len(), 1);
1784 assert_eq!(composite.get(), 10);
1785 let mut seen = composite.generation();
1788 a.set(10);
1789 assert_ne!(composite.generation(), seen);
1790 seen = composite.generation();
1791 assert_eq!(composite.generation(), seen);
1792 c.set(30);
1793 assert_ne!(composite.generation(), seen);
1794 }
1795
1796 #[test]
1800 fn map_coalesced_generation_is_readable_by_every_consumer() {
1801 let a = Signal::new(1u32);
1802 let b = Signal::new(2u32);
1803 let composite = a.zip(&b).map_coalesced(|(x, y)| *x + *y);
1804
1805 let (window_a, window_b) = (composite.generation(), composite.generation());
1806 b.set(5);
1807
1808 let a_now = composite.generation();
1809 assert_ne!(a_now, window_a);
1810 assert_ne!(
1811 composite.generation(),
1812 window_b,
1813 "the first read must not have cleared anything"
1814 );
1815 assert_eq!(composite.generation(), a_now);
1816 }
1817
1818 #[test]
1819 fn map_coalesced_with_single_source_delegates_to_map() {
1820 let a = Signal::new(7u32);
1823 let derived = a.map_coalesced(|v| *v * 2);
1824 assert_eq!(derived.get(), 14);
1825 assert_eq!(derived.as_sources().len(), 1);
1826 }
1827
1828 #[test]
1829 fn reentrant_set_in_observer_does_not_panic() {
1830 let s = Signal::new(0_i32);
1835 let s2 = s.clone();
1836 let _handle = s.observe(move |v| {
1837 if *v == 1 {
1839 s2.set(2);
1840 }
1841 });
1842 s.set(1);
1843 assert_eq!(s.get(), 2);
1844 }
1845
1846 #[cfg(debug_assertions)]
1847 #[test]
1848 #[should_panic(expected = "feedback loop")]
1849 fn unbounded_feedback_loop_panics_with_diagnostic() {
1850 let a = Signal::new(0_i32);
1854 let b = Signal::new(0_i32);
1855 let b_for_a = b.clone();
1856 let _ha = a.observe(move |v| b_for_a.set(*v + 1));
1857 let a_for_b = a.clone();
1858 let _hb = b.observe(move |v| a_for_b.set(*v + 1));
1859 a.set(1);
1860 }
1861
1862 #[test]
1863 fn detaching_observer_during_notification_does_not_panic() {
1864 use std::cell::RefCell;
1865 let s = Signal::new(0_i32);
1869 let b_slot: Rc<RefCell<Option<ObserverHandle>>> = Rc::new(RefCell::new(None));
1870 let b_slot_for_a = b_slot.clone();
1871 let _a = s.observe(move |_| {
1872 b_slot_for_a.borrow_mut().take();
1873 });
1874 let b = s.observe(|_| {});
1875 *b_slot.borrow_mut() = Some(b);
1876 s.set(1);
1877 }
1878
1879 #[test]
1880 fn registering_observer_during_notification_does_not_panic() {
1881 use std::cell::RefCell;
1882 let s = Signal::new(0_i32);
1885 let s2 = s.clone();
1886 let extra: Rc<RefCell<Option<ObserverHandle>>> = Rc::new(RefCell::new(None));
1887 let extra2 = extra.clone();
1888 let _h = s.observe(move |_| {
1889 if extra2.borrow().is_none() {
1890 *extra2.borrow_mut() = Some(s2.observe(|_| {}));
1891 }
1892 });
1893 s.set(1);
1894 }
1895}