1use std::cell::Cell;
14use std::rc::Rc;
15use std::sync::atomic::{AtomicU64, Ordering};
16
17use super::computed::Computed;
18use super::effect::Effect;
19use super::signal::Signal;
20
21pub type BindingId = u64;
27
28static BINDING_COUNTER: AtomicU64 = AtomicU64::new(1);
29
30fn next_binding_id() -> BindingId {
32 BINDING_COUNTER.fetch_add(1, Ordering::Relaxed)
33}
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub enum BindingDirection {
42 OneWay,
44 TwoWay,
46}
47
48pub trait Binding {
54 fn id(&self) -> BindingId;
56
57 fn direction(&self) -> BindingDirection;
59
60 fn is_active(&self) -> bool;
62
63 fn dispose(&self);
65}
66
67pub trait PropertySink<T> {
76 fn set_value(&self, value: &T);
78}
79
80impl<T, F: Fn(&T)> PropertySink<T> for F {
82 fn set_value(&self, value: &T) {
83 self(value);
84 }
85}
86
87pub struct OneWayBinding<T: Clone + 'static> {
111 id: BindingId,
112 effect: Effect,
113 _source: Signal<T>,
115}
116
117impl<T: Clone + 'static> OneWayBinding<T> {
118 pub fn new(source: &Signal<T>, sink: impl PropertySink<T> + 'static) -> Self {
123 let id = next_binding_id();
124 let source_clone = source.clone();
125
126 let effect = Effect::new({
127 let sig = source.clone();
128 move || {
129 let value = sig.get();
130 sink.set_value(&value);
131 }
132 });
133
134 source.subscribe(effect.as_subscriber());
136
137 Self {
138 id,
139 effect,
140 _source: source_clone,
141 }
142 }
143}
144
145impl<T: Clone + 'static> Binding for OneWayBinding<T> {
146 fn id(&self) -> BindingId {
147 self.id
148 }
149
150 fn direction(&self) -> BindingDirection {
151 BindingDirection::OneWay
152 }
153
154 fn is_active(&self) -> bool {
155 self.effect.is_active()
156 }
157
158 fn dispose(&self) {
159 self.effect.dispose();
160 }
161}
162
163pub struct TwoWayBinding<T: Clone + 'static> {
196 id: BindingId,
197 effect: Effect,
198 source: Signal<T>,
199 updating: Rc<Cell<bool>>,
201}
202
203impl<T: Clone + 'static> TwoWayBinding<T> {
204 pub fn new(source: &Signal<T>, sink: impl PropertySink<T> + 'static) -> Self {
208 let id = next_binding_id();
209 let updating = Rc::new(Cell::new(false));
210
211 let effect = Effect::new({
212 let sig = source.clone();
213 let guard = Rc::clone(&updating);
214 move || {
215 if guard.get() {
216 return;
217 }
218 let value = sig.get();
219 sink.set_value(&value);
220 }
221 });
222
223 source.subscribe(effect.as_subscriber());
224
225 Self {
226 id,
227 effect,
228 source: source.clone(),
229 updating,
230 }
231 }
232
233 pub fn write_back(&self, value: T) {
238 if !self.effect.is_active() {
239 return;
240 }
241
242 self.updating.set(true);
243 self.source.set(value);
244 self.updating.set(false);
245 }
246}
247
248impl<T: Clone + 'static> Binding for TwoWayBinding<T> {
249 fn id(&self) -> BindingId {
250 self.id
251 }
252
253 fn direction(&self) -> BindingDirection {
254 BindingDirection::TwoWay
255 }
256
257 fn is_active(&self) -> bool {
258 self.effect.is_active()
259 }
260
261 fn dispose(&self) {
262 self.effect.dispose();
263 }
264}
265
266pub struct BindingExpression<S: Clone + 'static, T: Clone + 'static> {
294 id: BindingId,
295 effect: Effect,
296 _source: Signal<S>,
298 _computed: Computed<T>,
299}
300
301impl<S: Clone + 'static, T: Clone + 'static> BindingExpression<S, T> {
302 pub fn new(
305 source: &Signal<S>,
306 transform: impl Fn(&S) -> T + 'static,
307 sink: impl PropertySink<T> + 'static,
308 ) -> Self {
309 let id = next_binding_id();
310
311 let computed = Computed::new({
313 let sig = source.clone();
314 move || {
315 let v = sig.get();
316 transform(&v)
317 }
318 });
319 source.subscribe(computed.as_subscriber());
320
321 let effect = Effect::new({
323 let comp = computed.clone();
324 move || {
325 let value = comp.get();
326 sink.set_value(&value);
327 }
328 });
329 computed.subscribe(effect.as_subscriber());
330
331 Self {
332 id,
333 effect,
334 _source: source.clone(),
335 _computed: computed,
336 }
337 }
338}
339
340impl<S: Clone + 'static, T: Clone + 'static> Binding for BindingExpression<S, T> {
341 fn id(&self) -> BindingId {
342 self.id
343 }
344
345 fn direction(&self) -> BindingDirection {
346 BindingDirection::OneWay
347 }
348
349 fn is_active(&self) -> bool {
350 self.effect.is_active()
351 }
352
353 fn dispose(&self) {
354 self.effect.dispose();
355 }
356}
357
358pub struct BindingScope {
388 bindings: Vec<Box<dyn Binding>>,
389}
390
391impl BindingScope {
392 #[must_use]
394 pub fn new() -> Self {
395 Self {
396 bindings: Vec::new(),
397 }
398 }
399
400 pub fn bind<T: Clone + 'static>(
404 &mut self,
405 source: &Signal<T>,
406 sink: impl PropertySink<T> + 'static,
407 ) -> BindingId {
408 let binding = OneWayBinding::new(source, sink);
409 let id = binding.id();
410 self.bindings.push(Box::new(binding));
411 id
412 }
413
414 pub fn bind_two_way<T: Clone + 'static>(
418 &mut self,
419 source: &Signal<T>,
420 sink: impl PropertySink<T> + 'static,
421 ) -> (TwoWayBinding<T>, BindingId) {
422 let binding = TwoWayBinding::new(source, sink);
423 let id = binding.id();
424
425 let caller_binding = TwoWayBinding {
427 id: binding.id,
428 effect: binding.effect.clone(),
429 source: binding.source.clone(),
430 updating: Rc::clone(&binding.updating),
431 };
432
433 self.bindings.push(Box::new(binding));
434 (caller_binding, id)
435 }
436
437 pub fn bind_expression<S: Clone + 'static, T: Clone + 'static>(
441 &mut self,
442 source: &Signal<S>,
443 transform: impl Fn(&S) -> T + 'static,
444 sink: impl PropertySink<T> + 'static,
445 ) -> BindingId {
446 let binding = BindingExpression::new(source, transform, sink);
447 let id = binding.id();
448 self.bindings.push(Box::new(binding));
449 id
450 }
451
452 pub fn binding_count(&self) -> usize {
454 self.bindings.len()
455 }
456
457 pub fn is_binding_active(&self, id: BindingId) -> bool {
459 self.bindings
460 .iter()
461 .find(|b| b.id() == id)
462 .is_some_and(|b| b.is_active())
463 }
464}
465
466impl Default for BindingScope {
467 fn default() -> Self {
468 Self::new()
469 }
470}
471
472impl Drop for BindingScope {
473 fn drop(&mut self) {
474 for binding in &self.bindings {
475 binding.dispose();
476 }
477 }
478}
479
480#[cfg(test)]
485#[allow(clippy::unwrap_used)]
486mod tests {
487 use super::*;
488 use crate::reactive::batch::batch;
489 use std::cell::RefCell;
490
491 #[test]
494 fn one_way_pushes_initial_value() {
495 let sig = Signal::new(42);
496 let output = Rc::new(Cell::new(0));
497
498 let _binding = OneWayBinding::new(&sig, {
499 let out = Rc::clone(&output);
500 move |v: &i32| out.set(*v)
501 });
502
503 assert_eq!(output.get(), 42);
504 }
505
506 #[test]
507 fn one_way_pushes_on_change() {
508 let sig = Signal::new(0);
509 let output = Rc::new(Cell::new(0));
510
511 let _binding = OneWayBinding::new(&sig, {
512 let out = Rc::clone(&output);
513 move |v: &i32| out.set(*v)
514 });
515
516 sig.set(10);
517 assert_eq!(output.get(), 10);
518
519 sig.set(20);
520 assert_eq!(output.get(), 20);
521 }
522
523 #[test]
524 fn one_way_stops_after_dispose() {
525 let sig = Signal::new(0);
526 let output = Rc::new(Cell::new(0));
527
528 let binding = OneWayBinding::new(&sig, {
529 let out = Rc::clone(&output);
530 move |v: &i32| out.set(*v)
531 });
532
533 sig.set(5);
534 assert_eq!(output.get(), 5);
535
536 binding.dispose();
537 assert!(!binding.is_active());
538
539 sig.set(99);
540 assert_eq!(output.get(), 5); }
542
543 #[test]
544 fn one_way_direction() {
545 let sig = Signal::new(0);
546 let binding = OneWayBinding::new(&sig, |_: &i32| {});
547 assert_eq!(binding.direction(), BindingDirection::OneWay);
548 }
549
550 #[test]
551 fn one_way_unique_ids() {
552 let sig = Signal::new(0);
553 let a = OneWayBinding::new(&sig, |_: &i32| {});
554 let b = OneWayBinding::new(&sig, |_: &i32| {});
555 assert_ne!(a.id(), b.id());
556 }
557
558 #[test]
559 fn one_way_with_string_sink() {
560 let sig = Signal::new(String::from("hello"));
561 let output = Rc::new(RefCell::new(String::new()));
562
563 let _binding = OneWayBinding::new(&sig, {
564 let out = Rc::clone(&output);
565 move |v: &String| *out.borrow_mut() = v.clone()
566 });
567
568 assert_eq!(*output.borrow(), "hello");
569
570 sig.set("world".into());
571 assert_eq!(*output.borrow(), "world");
572 }
573
574 #[test]
577 fn two_way_forward_push() {
578 let sig = Signal::new(0);
579 let output = Rc::new(Cell::new(0));
580
581 let _binding = TwoWayBinding::new(&sig, {
582 let out = Rc::clone(&output);
583 move |v: &i32| out.set(*v)
584 });
585
586 sig.set(42);
587 assert_eq!(output.get(), 42);
588 }
589
590 #[test]
591 fn two_way_write_back() {
592 let sig = Signal::new(0);
593 let output = Rc::new(Cell::new(0));
594
595 let binding = TwoWayBinding::new(&sig, {
596 let out = Rc::clone(&output);
597 move |v: &i32| out.set(*v)
598 });
599
600 binding.write_back(99);
601 assert_eq!(sig.get(), 99);
602 }
603
604 #[test]
605 fn two_way_loop_guard() {
606 let sig = Signal::new(0);
607 let push_count = Rc::new(Cell::new(0u32));
608
609 let binding = TwoWayBinding::new(&sig, {
610 let count = Rc::clone(&push_count);
611 move |_: &i32| count.set(count.get() + 1)
612 });
613
614 assert_eq!(push_count.get(), 1);
616
617 binding.write_back(42);
620 assert_eq!(push_count.get(), 1); assert_eq!(sig.get(), 42);
622
623 sig.set(100);
625 assert_eq!(push_count.get(), 2);
626 }
627
628 #[test]
629 fn two_way_disposed_write_back_ignored() {
630 let sig = Signal::new(0);
631 let binding = TwoWayBinding::new(&sig, |_: &i32| {});
632
633 binding.dispose();
634 binding.write_back(42);
635
636 assert_eq!(sig.get(), 0);
638 }
639
640 #[test]
641 fn two_way_direction() {
642 let sig = Signal::new(0);
643 let binding = TwoWayBinding::new(&sig, |_: &i32| {});
644 assert_eq!(binding.direction(), BindingDirection::TwoWay);
645 }
646
647 #[test]
650 fn expression_transforms_value() {
651 let sig = Signal::new(3);
652 let output = Rc::new(RefCell::new(String::new()));
653
654 let _binding = BindingExpression::new(&sig, |v: &i32| format!("Count: {v}"), {
655 let out = Rc::clone(&output);
656 move |v: &String| *out.borrow_mut() = v.clone()
657 });
658
659 assert_eq!(*output.borrow(), "Count: 3");
660
661 sig.set(7);
662 assert_eq!(*output.borrow(), "Count: 7");
663 }
664
665 #[test]
666 fn expression_stops_after_dispose() {
667 let sig = Signal::new(0);
668 let output = Rc::new(Cell::new(0));
669
670 let binding = BindingExpression::new(&sig, |v: &i32| v * 10, {
671 let out = Rc::clone(&output);
672 move |v: &i32| out.set(*v)
673 });
674
675 sig.set(5);
676 assert_eq!(output.get(), 50);
677
678 binding.dispose();
679 sig.set(99);
680 assert_eq!(output.get(), 50); }
682
683 #[test]
684 fn expression_direction() {
685 let sig = Signal::new(0);
686 let binding = BindingExpression::new(&sig, |v: &i32| *v, |_: &i32| {});
687 assert_eq!(binding.direction(), BindingDirection::OneWay);
688 }
689
690 #[test]
691 fn expression_type_conversion() {
692 let sig = Signal::new(42i32);
693 let output = Rc::new(Cell::new(0.0f64));
694
695 let _binding = BindingExpression::new(&sig, |v: &i32| *v as f64 * 1.5, {
696 let out = Rc::clone(&output);
697 move |v: &f64| out.set(*v)
698 });
699
700 assert!((output.get() - 63.0).abs() < f64::EPSILON);
701
702 sig.set(10);
703 assert!((output.get() - 15.0).abs() < f64::EPSILON);
704 }
705
706 #[test]
709 fn scope_bind_creates_one_way() {
710 let sig = Signal::new(0);
711 let output = Rc::new(Cell::new(0));
712
713 let mut scope = BindingScope::new();
714 let id = scope.bind(&sig, {
715 let out = Rc::clone(&output);
716 move |v: &i32| out.set(*v)
717 });
718
719 assert_eq!(scope.binding_count(), 1);
720 assert!(scope.is_binding_active(id));
721
722 sig.set(10);
723 assert_eq!(output.get(), 10);
724 }
725
726 #[test]
727 fn scope_bind_two_way() {
728 let sig = Signal::new(0);
729 let output = Rc::new(Cell::new(0));
730
731 let mut scope = BindingScope::new();
732 let (two_way, id) = scope.bind_two_way(&sig, {
733 let out = Rc::clone(&output);
734 move |v: &i32| out.set(*v)
735 });
736
737 assert_eq!(scope.binding_count(), 1);
738 assert!(scope.is_binding_active(id));
739
740 sig.set(10);
742 assert_eq!(output.get(), 10);
743
744 two_way.write_back(50);
746 assert_eq!(sig.get(), 50);
747 }
748
749 #[test]
750 fn scope_bind_expression() {
751 let sig = Signal::new(5);
752 let output = Rc::new(RefCell::new(String::new()));
753
754 let mut scope = BindingScope::new();
755 let id = scope.bind_expression(&sig, |v: &i32| format!("val={v}"), {
756 let out = Rc::clone(&output);
757 move |v: &String| *out.borrow_mut() = v.clone()
758 });
759
760 assert!(scope.is_binding_active(id));
761 assert_eq!(*output.borrow(), "val=5");
762
763 sig.set(10);
764 assert_eq!(*output.borrow(), "val=10");
765 }
766
767 #[test]
768 fn scope_disposes_bindings_on_drop() {
769 let sig = Signal::new(0);
770 let output = Rc::new(Cell::new(0));
771
772 {
773 let mut scope = BindingScope::new();
774 scope.bind(&sig, {
775 let out = Rc::clone(&output);
776 move |v: &i32| out.set(*v)
777 });
778
779 sig.set(5);
780 assert_eq!(output.get(), 5);
781 }
782 sig.set(99);
785 assert_eq!(output.get(), 5); }
787
788 #[test]
789 fn scope_multiple_bindings() {
790 let a = Signal::new(0);
791 let b = Signal::new(0);
792 let out_a = Rc::new(Cell::new(0));
793 let out_b = Rc::new(Cell::new(0));
794
795 let mut scope = BindingScope::new();
796 scope.bind(&a, {
797 let out = Rc::clone(&out_a);
798 move |v: &i32| out.set(*v)
799 });
800 scope.bind(&b, {
801 let out = Rc::clone(&out_b);
802 move |v: &i32| out.set(*v)
803 });
804
805 assert_eq!(scope.binding_count(), 2);
806
807 a.set(10);
808 b.set(20);
809 assert_eq!(out_a.get(), 10);
810 assert_eq!(out_b.get(), 20);
811 }
812
813 #[test]
814 fn scope_is_binding_active_returns_false_for_unknown_id() {
815 let scope = BindingScope::new();
816 assert!(!scope.is_binding_active(99999));
817 }
818
819 #[test]
822 fn binding_with_batch() {
823 let sig = Signal::new(0);
824 let push_count = Rc::new(Cell::new(0u32));
825
826 let _binding = OneWayBinding::new(&sig, {
827 let count = Rc::clone(&push_count);
828 move |_: &i32| count.set(count.get() + 1)
829 });
830
831 assert_eq!(push_count.get(), 1); batch(|| {
834 sig.set(1);
835 sig.set(2);
836 sig.set(3);
837 });
838
839 assert_eq!(push_count.get(), 2);
841 }
842
843 #[test]
844 fn binding_expression_with_batch() {
845 let sig = Signal::new(0);
846 let transform_count = Rc::new(Cell::new(0u32));
847 let output = Rc::new(Cell::new(0));
848
849 let _binding = BindingExpression::new(
850 &sig,
851 {
852 let count = Rc::clone(&transform_count);
853 move |v: &i32| {
854 count.set(count.get() + 1);
855 v * 2
856 }
857 },
858 {
859 let out = Rc::clone(&output);
860 move |v: &i32| out.set(*v)
861 },
862 );
863
864 assert_eq!(transform_count.get(), 1);
866 assert_eq!(output.get(), 0);
867
868 batch(|| {
869 sig.set(5);
870 sig.set(10);
871 });
872
873 assert_eq!(output.get(), 20);
875 }
876
877 #[test]
878 fn two_way_binding_round_trip() {
879 let model = Signal::new(String::from("initial"));
880 let view = Rc::new(RefCell::new(String::new()));
881
882 let binding = TwoWayBinding::new(&model, {
883 let view = Rc::clone(&view);
884 move |v: &String| *view.borrow_mut() = v.clone()
885 });
886
887 assert_eq!(*view.borrow(), "initial");
889
890 model.set("forward".into());
891 assert_eq!(*view.borrow(), "forward");
892
893 binding.write_back("reverse".into());
895 assert_eq!(model.get(), "reverse");
896
897 model.set("final".into());
899 assert_eq!(*view.borrow(), "final");
900 }
901
902 #[test]
903 fn binding_scope_with_mixed_types() {
904 let count = Signal::new(0);
905 let name = Signal::new(String::from("test"));
906
907 let out_count = Rc::new(Cell::new(0));
908 let out_name = Rc::new(RefCell::new(String::new()));
909
910 let mut scope = BindingScope::new();
911
912 scope.bind(&count, {
913 let out = Rc::clone(&out_count);
914 move |v: &i32| out.set(*v)
915 });
916
917 scope.bind(&name, {
918 let out = Rc::clone(&out_name);
919 move |v: &String| *out.borrow_mut() = v.clone()
920 });
921
922 count.set(42);
923 name.set("hello".into());
924
925 assert_eq!(out_count.get(), 42);
926 assert_eq!(*out_name.borrow(), "hello");
927 }
928
929 #[test]
930 fn chained_one_way_bindings() {
931 let source = Signal::new(1);
932 let middle = Signal::new(0);
933 let output = Rc::new(Cell::new(0));
934
935 let _binding1 = OneWayBinding::new(&source, {
937 let mid = middle.clone();
938 move |v: &i32| mid.set(*v * 2)
939 });
940
941 let _binding2 = OneWayBinding::new(&middle, {
943 let out = Rc::clone(&output);
944 move |v: &i32| out.set(*v)
945 });
946
947 assert_eq!(middle.get(), 2);
949 assert_eq!(output.get(), 2);
950
951 source.set(5);
952 assert_eq!(middle.get(), 10);
953 assert_eq!(output.get(), 10);
954 }
955
956 #[test]
957 fn scope_default_is_empty() {
958 let scope = BindingScope::default();
959 assert_eq!(scope.binding_count(), 0);
960 }
961
962 #[test]
963 fn disposed_binding_not_active_in_scope() {
964 let sig = Signal::new(0);
965 let mut scope = BindingScope::new();
966
967 let id = scope.bind(&sig, |_: &i32| {});
968 assert!(scope.is_binding_active(id));
969
970 drop(scope);
972
973 }
976
977 #[test]
978 fn stress_many_bindings() {
979 let sig = Signal::new(0);
980 let count = Rc::new(Cell::new(0u32));
981 let mut scope = BindingScope::new();
982
983 for _ in 0..50 {
984 scope.bind(&sig, {
985 let count = Rc::clone(&count);
986 move |_: &i32| count.set(count.get() + 1)
987 });
988 }
989
990 assert_eq!(count.get(), 50);
992
993 sig.set(1);
994 assert_eq!(count.get(), 100);
996
997 drop(scope);
998 sig.set(2);
999 assert_eq!(count.get(), 100);
1001 }
1002}