1#![forbid(unsafe_code)]
2#![deny(missing_docs)]
3
4use or_poisoned::OrPoisoned;
243use reactive_graph::{
244 owner::{ArenaItem, LocalStorage, Storage, SyncStorage},
245 signal::{
246 guards::{Plain, ReadGuard, WriteGuard},
247 ArcTrigger,
248 },
249 traits::{
250 DefinedAt, Dispose, IsDisposed, Notify, ReadUntracked, Track,
251 UntrackableGuard, Write,
252 },
253};
254pub use reactive_stores_macro::{Patch, Store};
255use rustc_hash::FxHashMap;
256use std::{
257 any::Any,
258 fmt::Debug,
259 hash::Hash,
260 ops::DerefMut,
261 panic::Location,
262 sync::{Arc, RwLock},
263};
264
265mod arc_field;
266mod deref;
267mod field;
268mod iter;
269mod keyed;
270mod len;
271mod option;
272mod patch;
273mod path;
274#[cfg(feature = "serde")]
275mod serde;
276#[cfg(feature = "slotmap")]
277mod slotmap;
278mod store_field;
279mod subfield;
280
281pub use arc_field::ArcField;
282pub use deref::*;
283pub use field::Field;
284pub use iter::*;
285pub use keyed::*;
286pub use len::Len;
287pub use option::*;
288pub use patch::*;
289pub use path::{StorePath, StorePathSegment};
290pub use store_field::StoreField;
291pub use subfield::Subfield;
292
293#[derive(Debug, Default)]
294struct TriggerMap(FxHashMap<StorePath, StoreFieldTrigger>);
295
296#[derive(Debug, Clone, Default)]
298pub struct StoreFieldTrigger {
299 pub(crate) this: ArcTrigger,
300 pub(crate) children: ArcTrigger,
301}
302
303impl StoreFieldTrigger {
304 pub fn new() -> Self {
306 Self::default()
307 }
308}
309
310impl TriggerMap {
311 fn get_or_insert(&mut self, key: StorePath) -> StoreFieldTrigger {
312 if let Some(trigger) = self.0.get(&key) {
313 trigger.clone()
314 } else {
315 let new = StoreFieldTrigger::new();
316 self.0.insert(key, new.clone());
317 new
318 }
319 }
320
321 #[allow(unused)]
322 fn remove(&mut self, key: &StorePath) -> Option<StoreFieldTrigger> {
323 self.0.remove(key)
324 }
325}
326
327pub struct FieldKeys<K> {
329 spare_keys: Vec<StorePathSegment>,
330 current_key: usize,
331 keys: FxHashMap<K, (StorePathSegment, usize)>,
332}
333
334impl<K> FieldKeys<K>
335where
336 K: Debug + Hash + PartialEq + Eq,
337{
338 pub fn new(from_keys: Vec<K>) -> Self {
340 let mut keys = FxHashMap::with_capacity_and_hasher(
341 from_keys.len(),
342 Default::default(),
343 );
344 for (idx, key) in from_keys.into_iter().enumerate() {
345 let segment = idx.into();
346 keys.insert(key, (segment, idx));
347 }
348
349 Self {
350 spare_keys: Vec::new(),
351 current_key: keys.len().saturating_sub(1),
352 keys,
353 }
354 }
355}
356
357impl<K> FieldKeys<K>
358where
359 K: Hash + PartialEq + Eq,
360{
361 #[doc(hidden)]
369 pub fn get(&self, key: &K) -> Option<(StorePathSegment, usize)> {
370 self.keys.get(key).copied()
371 }
372
373 fn next_key(&mut self) -> StorePathSegment {
374 self.spare_keys.pop().unwrap_or_else(|| {
375 self.current_key += 1;
376 self.current_key.into()
377 })
378 }
379
380 fn update(
381 &mut self,
382 iter: impl IntoIterator<Item = K>,
383 ) -> Vec<(usize, StorePathSegment)> {
384 let new_keys = iter
385 .into_iter()
386 .enumerate()
387 .map(|(idx, key)| (key, idx))
388 .collect::<FxHashMap<K, usize>>();
389
390 let mut index_keys = Vec::with_capacity(new_keys.len());
391
392 self.keys.retain(|key, old_entry| match new_keys.get(key) {
394 Some(idx) => {
395 old_entry.1 = *idx;
396 true
397 }
398 None => {
399 self.spare_keys.push(old_entry.0);
400 false
401 }
402 });
403
404 for (key, idx) in new_keys {
406 match self.keys.get(&key) {
407 Some((segment, idx)) => index_keys.push((*idx, *segment)),
408 None => {
409 let path = self.next_key();
410 self.keys.insert(key, (path, idx));
411 index_keys.push((idx, path));
412 }
413 }
414 }
415
416 index_keys
417 }
418}
419
420impl<K> Default for FieldKeys<K> {
421 fn default() -> Self {
422 Self {
423 spare_keys: Default::default(),
424 current_key: Default::default(),
425 keys: Default::default(),
426 }
427 }
428}
429
430type Map<K, V> = Arc<std::sync::RwLock<std::collections::HashMap<K, V>>>;
431
432#[derive(Clone, Default)]
434pub struct KeyMap(
435 Map<StorePath, Box<dyn Any + Send + Sync>>,
437 Map<(StorePath, usize), StorePathSegment>,
439);
440
441impl KeyMap {
442 #[doc(hidden)]
487 pub fn with_field_keys<K, T>(
488 &self,
489 path: StorePath,
490 fun: impl FnOnce(&mut FieldKeys<K>) -> (T, Vec<(usize, StorePathSegment)>),
491 initialize: impl FnOnce() -> Vec<K>,
492 ) -> Option<T>
493 where
494 K: Debug + Hash + PartialEq + Eq + Send + Sync + 'static,
495 {
496 let mut guard = self.0.write().or_poisoned();
497 let entry = guard
498 .entry(path.clone())
499 .or_insert_with(|| Box::new(FieldKeys::new(initialize())));
500
501 let entry = entry.downcast_mut::<FieldKeys<K>>()?;
502 let (result, new_keys) = fun(entry);
503 if !new_keys.is_empty() {
504 for (idx, segment) in new_keys {
505 self.1
506 .write()
507 .or_poisoned()
508 .insert((path.clone(), idx), segment);
509 }
510 }
511 Some(result)
512 }
513
514 fn contains_key(&self, key: &StorePath) -> bool {
515 self.0.read().or_poisoned().contains_key(key)
516 }
517
518 fn get_key_for_index(
519 &self,
520 key: &(StorePath, usize),
521 ) -> Option<StorePathSegment> {
522 self.1.read().or_poisoned().get(key).copied()
523 }
524}
525
526pub struct ArcStore<T> {
533 #[cfg(any(debug_assertions, leptos_debuginfo))]
534 defined_at: &'static Location<'static>,
535 pub(crate) value: Arc<RwLock<T>>,
536 signals: Arc<RwLock<TriggerMap>>,
537 keys: KeyMap,
538}
539
540impl<T> ArcStore<T> {
541 pub fn new(value: T) -> Self {
543 Self {
544 #[cfg(any(debug_assertions, leptos_debuginfo))]
545 defined_at: Location::caller(),
546 value: Arc::new(RwLock::new(value)),
547 signals: Default::default(),
548 keys: Default::default(),
549 }
550 }
551}
552
553impl<T: Default> Default for ArcStore<T> {
554 fn default() -> Self {
555 Self::new(T::default())
556 }
557}
558
559impl<T: Debug> Debug for ArcStore<T> {
560 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
561 let mut f = f.debug_struct("ArcStore");
562 #[cfg(any(debug_assertions, leptos_debuginfo))]
563 let f = f.field("defined_at", &self.defined_at);
564 f.field("value", &self.value)
565 .field("signals", &self.signals)
566 .finish()
567 }
568}
569
570impl<T> Clone for ArcStore<T> {
571 fn clone(&self) -> Self {
572 Self {
573 #[cfg(any(debug_assertions, leptos_debuginfo))]
574 defined_at: self.defined_at,
575 value: Arc::clone(&self.value),
576 signals: Arc::clone(&self.signals),
577 keys: self.keys.clone(),
578 }
579 }
580}
581
582impl<T> DefinedAt for ArcStore<T> {
583 fn defined_at(&self) -> Option<&'static Location<'static>> {
584 #[cfg(any(debug_assertions, leptos_debuginfo))]
585 {
586 Some(self.defined_at)
587 }
588 #[cfg(not(any(debug_assertions, leptos_debuginfo)))]
589 {
590 None
591 }
592 }
593}
594
595impl<T> IsDisposed for ArcStore<T> {
596 #[inline(always)]
597 fn is_disposed(&self) -> bool {
598 false
599 }
600}
601
602impl<T> ReadUntracked for ArcStore<T>
603where
604 T: 'static,
605{
606 type Value = ReadGuard<T, Plain<T>>;
607
608 fn try_read_untracked(&self) -> Option<Self::Value> {
609 Plain::try_new(Arc::clone(&self.value)).map(ReadGuard::new)
610 }
611}
612
613impl<T> Write for ArcStore<T>
614where
615 T: 'static,
616{
617 type Value = T;
618
619 fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
620 self.writer()
621 .map(|writer| WriteGuard::new(self.clone(), writer))
622 }
623
624 fn try_write_untracked(
625 &self,
626 ) -> Option<impl DerefMut<Target = Self::Value>> {
627 let mut writer = self.writer()?;
628 writer.untrack();
629 Some(writer)
630 }
631}
632
633impl<T: 'static> Track for ArcStore<T> {
634 fn track(&self) {
635 self.track_field();
636 }
637}
638
639impl<T: 'static> Notify for ArcStore<T> {
640 fn notify(&self) {
641 let trigger = self.get_trigger(self.path().into_iter().collect());
642 trigger.this.notify();
643 trigger.children.notify();
644 }
645}
646
647pub struct Store<T, S = SyncStorage> {
657 #[cfg(any(debug_assertions, leptos_debuginfo))]
658 defined_at: &'static Location<'static>,
659 inner: ArenaItem<ArcStore<T>, S>,
660}
661
662impl<T> Store<T>
663where
664 T: Send + Sync + 'static,
665{
666 pub fn new(value: T) -> Self {
668 Self {
669 #[cfg(any(debug_assertions, leptos_debuginfo))]
670 defined_at: Location::caller(),
671 inner: ArenaItem::new_with_storage(ArcStore::new(value)),
672 }
673 }
674}
675
676impl<T, S> PartialEq for Store<T, S> {
677 fn eq(&self, other: &Self) -> bool {
678 self.inner == other.inner
679 }
680}
681
682impl<T, S> Eq for Store<T, S> {}
683
684impl<T> Store<T, LocalStorage>
685where
686 T: 'static,
687{
688 pub fn new_local(value: T) -> Self {
692 Self {
693 #[cfg(any(debug_assertions, leptos_debuginfo))]
694 defined_at: Location::caller(),
695 inner: ArenaItem::new_with_storage(ArcStore::new(value)),
696 }
697 }
698}
699
700impl<T> Default for Store<T>
701where
702 T: Default + Send + Sync + 'static,
703{
704 fn default() -> Self {
705 Self::new(T::default())
706 }
707}
708
709impl<T> Default for Store<T, LocalStorage>
710where
711 T: Default + 'static,
712{
713 fn default() -> Self {
714 Self::new_local(T::default())
715 }
716}
717
718impl<T: Debug, S> Debug for Store<T, S>
719where
720 S: Debug,
721{
722 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
723 let mut f = f.debug_struct("Store");
724 #[cfg(any(debug_assertions, leptos_debuginfo))]
725 let f = f.field("defined_at", &self.defined_at);
726 f.field("inner", &self.inner).finish()
727 }
728}
729
730impl<T, S> Clone for Store<T, S> {
731 fn clone(&self) -> Self {
732 *self
733 }
734}
735
736impl<T, S> Copy for Store<T, S> {}
737
738impl<T, S> DefinedAt for Store<T, S> {
739 fn defined_at(&self) -> Option<&'static Location<'static>> {
740 #[cfg(any(debug_assertions, leptos_debuginfo))]
741 {
742 Some(self.defined_at)
743 }
744 #[cfg(not(any(debug_assertions, leptos_debuginfo)))]
745 {
746 None
747 }
748 }
749}
750
751impl<T, S> IsDisposed for Store<T, S>
752where
753 T: 'static,
754{
755 #[inline(always)]
756 fn is_disposed(&self) -> bool {
757 self.inner.is_disposed()
758 }
759}
760
761impl<T, S> Dispose for Store<T, S>
762where
763 T: 'static,
764{
765 fn dispose(self) {
766 self.inner.dispose();
767 }
768}
769
770impl<T, S> ReadUntracked for Store<T, S>
771where
772 T: 'static,
773 S: Storage<ArcStore<T>>,
774{
775 type Value = ReadGuard<T, Plain<T>>;
776
777 fn try_read_untracked(&self) -> Option<Self::Value> {
778 self.inner
779 .try_get_value()
780 .and_then(|inner| inner.try_read_untracked())
781 }
782}
783
784impl<T, S> Write for Store<T, S>
785where
786 T: 'static,
787 S: Storage<ArcStore<T>>,
788{
789 type Value = T;
790
791 fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
792 self.writer().map(|writer| WriteGuard::new(*self, writer))
793 }
794
795 fn try_write_untracked(
796 &self,
797 ) -> Option<impl DerefMut<Target = Self::Value>> {
798 let mut writer = self.writer()?;
799 writer.untrack();
800 Some(writer)
801 }
802}
803
804impl<T, S> Track for Store<T, S>
805where
806 T: 'static,
807 S: Storage<ArcStore<T>>,
808{
809 fn track(&self) {
810 if let Some(inner) = self.inner.try_get_value() {
811 inner.track();
812 }
813 }
814}
815
816impl<T, S> Notify for Store<T, S>
817where
818 T: 'static,
819 S: Storage<ArcStore<T>>,
820{
821 fn notify(&self) {
822 if let Some(inner) = self.inner.try_get_value() {
823 inner.notify();
824 }
825 }
826}
827
828impl<T, S> From<ArcStore<T>> for Store<T, S>
829where
830 T: 'static,
831 S: Storage<ArcStore<T>>,
832{
833 fn from(value: ArcStore<T>) -> Self {
834 Self {
835 #[cfg(any(debug_assertions, leptos_debuginfo))]
836 defined_at: value.defined_at,
837 inner: ArenaItem::new_with_storage(value),
838 }
839 }
840}
841
842#[cfg(test)]
843mod tests {
844 use crate::{self as reactive_stores, Patch, Store, StoreFieldIterator};
845 use reactive_graph::{
846 effect::Effect,
847 owner::StoredValue,
848 traits::{Read, ReadUntracked, Set, Track, Update, Write},
849 };
850 use std::sync::{
851 atomic::{AtomicUsize, Ordering},
852 Arc,
853 };
854
855 pub async fn tick() {
856 tokio::time::sleep(std::time::Duration::from_micros(1)).await;
857 }
858
859 #[derive(Debug, Store, Patch, Default)]
860 struct Todos {
861 user: String,
862 todos: Vec<Todo>,
863 }
864
865 #[derive(Debug, Store, Patch, Default)]
866 struct Todo {
867 label: String,
868 completed: bool,
869 }
870
871 impl Todo {
872 pub fn new(label: impl ToString) -> Self {
873 Self {
874 label: label.to_string(),
875 completed: false,
876 }
877 }
878 }
879
880 fn data() -> Todos {
881 Todos {
882 user: "Bob".to_string(),
883 todos: vec![
884 Todo {
885 label: "Create reactive store".to_string(),
886 completed: true,
887 },
888 Todo {
889 label: "???".to_string(),
890 completed: false,
891 },
892 Todo {
893 label: "Profit".to_string(),
894 completed: false,
895 },
896 ],
897 }
898 }
899
900 #[derive(Debug, Clone, Store, Patch, Default)]
901 struct Foo {
902 id: i32,
903 bar: Bar,
904 }
905
906 #[derive(Debug, Clone, Store, Patch, Default)]
907 struct Bar {
908 bar_signature: i32,
909 baz: Baz,
910 }
911
912 #[derive(Debug, Clone, Store, Patch, Default)]
913 struct Baz {
914 more_data: i32,
915 baw: Baw,
916 }
917
918 #[derive(Debug, Clone, Store, Patch, Default)]
919 struct Baw {
920 more_data: i32,
921 end: i32,
922 }
923
924 #[tokio::test]
925 async fn mutating_field_triggers_effect() {
926 _ = any_spawner::Executor::init_tokio();
927
928 let combined_count = Arc::new(AtomicUsize::new(0));
929
930 let store = Store::new(data());
931 assert_eq!(store.read_untracked().todos.len(), 3);
932 assert_eq!(store.user().read_untracked().as_str(), "Bob");
933 Effect::new_sync({
934 let combined_count = Arc::clone(&combined_count);
935 move |prev: Option<()>| {
936 if prev.is_none() {
937 println!("first run");
938 } else {
939 println!("next run");
940 }
941 println!("{:?}", *store.user().read());
942 combined_count.fetch_add(1, Ordering::Relaxed);
943 }
944 });
945 tick().await;
946 tick().await;
947 store.user().set("Greg".into());
948 tick().await;
949 store.user().set("Carol".into());
950 tick().await;
951 store.user().update(|name| name.push_str("!!!"));
952 tick().await;
953 assert_eq!(combined_count.load(Ordering::Relaxed), 4);
955 }
956
957 #[tokio::test]
958 async fn other_field_does_not_notify() {
959 _ = any_spawner::Executor::init_tokio();
960
961 let combined_count = Arc::new(AtomicUsize::new(0));
962
963 let store = Store::new(data());
964
965 Effect::new_sync({
966 let combined_count = Arc::clone(&combined_count);
967 move |prev: Option<()>| {
968 if prev.is_none() {
969 println!("first run");
970 } else {
971 println!("next run");
972 }
973 println!("{:?}", *store.todos().read());
974 combined_count.fetch_add(1, Ordering::Relaxed);
975 }
976 });
977 tick().await;
978 store.user().set("Greg".into());
979 tick().await;
980 store.user().set("Carol".into());
981 tick().await;
982 store.user().update(|name| name.push_str("!!!"));
983 tick().await;
984 assert_eq!(combined_count.load(Ordering::Relaxed), 1);
986 }
987
988 #[tokio::test]
989 async fn parent_does_notify() {
990 _ = any_spawner::Executor::init_tokio();
991
992 let combined_count = Arc::new(AtomicUsize::new(0));
993
994 let store = Store::new(data());
995
996 Effect::new_sync({
997 let combined_count = Arc::clone(&combined_count);
998 move |prev: Option<()>| {
999 if prev.is_none() {
1000 println!("first run");
1001 } else {
1002 println!("next run");
1003 }
1004 println!("{:?}", *store.todos().read());
1005 combined_count.fetch_add(1, Ordering::Relaxed);
1006 }
1007 });
1008 tick().await;
1009 tick().await;
1010 store.set(Todos::default());
1011 tick().await;
1012 store.set(data());
1013 tick().await;
1014 assert_eq!(combined_count.load(Ordering::Relaxed), 3);
1015 }
1016
1017 #[tokio::test]
1018 async fn changes_do_notify_parent() {
1019 _ = any_spawner::Executor::init_tokio();
1020
1021 let combined_count = Arc::new(AtomicUsize::new(0));
1022
1023 let store = Store::new(data());
1024
1025 Effect::new_sync({
1026 let combined_count = Arc::clone(&combined_count);
1027 move |prev: Option<()>| {
1028 if prev.is_none() {
1029 println!("first run");
1030 } else {
1031 println!("next run");
1032 }
1033 println!("{:?}", *store.read());
1034 combined_count.fetch_add(1, Ordering::Relaxed);
1035 }
1036 });
1037 tick().await;
1038 tick().await;
1039 store.user().set("Greg".into());
1040 tick().await;
1041 store.user().set("Carol".into());
1042 tick().await;
1043 store.user().update(|name| name.push_str("!!!"));
1044 tick().await;
1045 store.todos().write().clear();
1046 tick().await;
1047 assert_eq!(combined_count.load(Ordering::Relaxed), 5);
1048 }
1049
1050 #[tokio::test]
1051 async fn iterator_tracks_the_field() {
1052 _ = any_spawner::Executor::init_tokio();
1053
1054 let combined_count = Arc::new(AtomicUsize::new(0));
1055
1056 let store = Store::new(data());
1057
1058 Effect::new_sync({
1059 let combined_count = Arc::clone(&combined_count);
1060 move |prev: Option<()>| {
1061 if prev.is_none() {
1062 println!("first run");
1063 } else {
1064 println!("next run");
1065 }
1066 println!(
1067 "{:?}",
1068 store.todos().iter_unkeyed().collect::<Vec<_>>()
1069 );
1070 combined_count.store(1, Ordering::Relaxed);
1071 }
1072 });
1073
1074 tick().await;
1075 store
1076 .todos()
1077 .write()
1078 .push(Todo::new("Create reactive store?"));
1079 tick().await;
1080 store.todos().write().push(Todo::new("???"));
1081 tick().await;
1082 store.todos().write().push(Todo::new("Profit!"));
1083 assert_eq!(combined_count.load(Ordering::Relaxed), 1);
1085 }
1086
1087 #[tokio::test]
1088 async fn patching_only_notifies_changed_field() {
1089 _ = any_spawner::Executor::init_tokio();
1090
1091 let combined_count = Arc::new(AtomicUsize::new(0));
1092
1093 let store = Store::new(Todos {
1094 user: "Alice".into(),
1095 todos: vec![],
1096 });
1097
1098 Effect::new_sync({
1099 let combined_count = Arc::clone(&combined_count);
1100 move |prev: Option<()>| {
1101 if prev.is_none() {
1102 println!("first run");
1103 } else {
1104 println!("next run");
1105 }
1106 println!("{:?}", *store.todos().read());
1107 combined_count.fetch_add(1, Ordering::Relaxed);
1108 }
1109 });
1110 tick().await;
1111 tick().await;
1112 store.patch(Todos {
1113 user: "Bob".into(),
1114 todos: vec![],
1115 });
1116 tick().await;
1117 store.patch(Todos {
1118 user: "Carol".into(),
1119 todos: vec![],
1120 });
1121 tick().await;
1122 assert_eq!(combined_count.load(Ordering::Relaxed), 1);
1123
1124 store.patch(Todos {
1125 user: "Carol".into(),
1126 todos: vec![Todo {
1127 label: "First Todo".into(),
1128 completed: false,
1129 }],
1130 });
1131 tick().await;
1132 assert_eq!(combined_count.load(Ordering::Relaxed), 2);
1133 }
1134
1135 #[tokio::test]
1136 async fn patching_only_notifies_changed_field_with_custom_patch() {
1137 _ = any_spawner::Executor::init_tokio();
1138
1139 #[derive(Debug, Store, Patch, Default)]
1140 struct CustomTodos {
1141 #[patch(|this, new| *this = new)]
1142 user: String,
1143 todos: Vec<CustomTodo>,
1144 }
1145
1146 #[derive(Debug, Store, Patch, Default)]
1147 struct CustomTodo {
1148 label: String,
1149 completed: bool,
1150 }
1151
1152 let combined_count = Arc::new(AtomicUsize::new(0));
1153
1154 let store = Store::new(CustomTodos {
1155 user: "Alice".into(),
1156 todos: vec![],
1157 });
1158
1159 Effect::new_sync({
1160 let combined_count = Arc::clone(&combined_count);
1161 move |prev: Option<()>| {
1162 if prev.is_none() {
1163 println!("first run");
1164 } else {
1165 println!("next run");
1166 }
1167 println!("{:?}", *store.user().read());
1168 combined_count.fetch_add(1, Ordering::Relaxed);
1169 }
1170 });
1171 tick().await;
1172 tick().await;
1173 store.patch(CustomTodos {
1174 user: "Bob".into(),
1175 todos: vec![],
1176 });
1177 tick().await;
1178 assert_eq!(combined_count.load(Ordering::Relaxed), 2);
1179 store.patch(CustomTodos {
1180 user: "Carol".into(),
1181 todos: vec![],
1182 });
1183 tick().await;
1184 assert_eq!(combined_count.load(Ordering::Relaxed), 3);
1185
1186 store.patch(CustomTodos {
1187 user: "Carol".into(),
1188 todos: vec![CustomTodo {
1189 label: "First CustomTodo".into(),
1190 completed: false,
1191 }],
1192 });
1193 tick().await;
1194 assert_eq!(combined_count.load(Ordering::Relaxed), 3);
1195 }
1196
1197 #[tokio::test]
1199 async fn notifying_all_descendants() {
1200 use reactive_graph::traits::*;
1201
1202 _ = any_spawner::Executor::init_tokio();
1203
1204 let store = Store::new(Foo {
1205 id: 42,
1206 bar: Bar {
1207 bar_signature: 69,
1208 baz: Baz {
1209 more_data: 9999,
1210 baw: Baw {
1211 more_data: 22,
1212 end: 1112,
1213 },
1214 },
1215 },
1216 });
1217
1218 let store_runs = StoredValue::new(0);
1219 let id_runs = StoredValue::new(0);
1220 let bar_runs = StoredValue::new(0);
1221 let bar_signature_runs = StoredValue::new(0);
1222 let bar_baz_runs = StoredValue::new(0);
1223 let more_data_runs = StoredValue::new(0);
1224 let baz_baw_end_runs = StoredValue::new(0);
1225
1226 Effect::new_sync(move |_| {
1227 println!("foo: {:?}", store.get());
1228 *store_runs.write_value() += 1;
1229 });
1230
1231 Effect::new_sync(move |_| {
1232 println!("foo.id: {:?}", store.id().get());
1233 *id_runs.write_value() += 1;
1234 });
1235
1236 Effect::new_sync(move |_| {
1237 println!("foo.bar: {:?}", store.bar().get());
1238 *bar_runs.write_value() += 1;
1239 });
1240
1241 Effect::new_sync(move |_| {
1242 println!(
1243 "foo.bar.bar_signature: {:?}",
1244 store.bar().bar_signature().get()
1245 );
1246 *bar_signature_runs.write_value() += 1;
1247 });
1248
1249 Effect::new_sync(move |_| {
1250 println!("foo.bar.baz: {:?}", store.bar().baz().get());
1251 *bar_baz_runs.write_value() += 1;
1252 });
1253
1254 Effect::new_sync(move |_| {
1255 println!(
1256 "foo.bar.baz.more_data: {:?}",
1257 store.bar().baz().more_data().get()
1258 );
1259 *more_data_runs.write_value() += 1;
1260 });
1261
1262 Effect::new_sync(move |_| {
1263 println!(
1264 "foo.bar.baz.baw.end: {:?}",
1265 store.bar().baz().baw().end().get()
1266 );
1267 *baz_baw_end_runs.write_value() += 1;
1268 });
1269
1270 println!("[INITIAL EFFECT RUN]");
1271 tick().await;
1272 println!("\n\n[SETTING STORE]");
1273 store.set(Default::default());
1274 tick().await;
1275 println!("\n\n[SETTING STORE.BAR.BAZ]");
1276 store.bar().baz().set(Default::default());
1277 tick().await;
1278
1279 assert_eq!(store_runs.get_value(), 3);
1280 assert_eq!(id_runs.get_value(), 2);
1281 assert_eq!(bar_runs.get_value(), 3);
1282 assert_eq!(bar_signature_runs.get_value(), 2);
1283 assert_eq!(bar_baz_runs.get_value(), 3);
1284 assert_eq!(more_data_runs.get_value(), 3);
1285 assert_eq!(baz_baw_end_runs.get_value(), 3);
1286 }
1287
1288 #[tokio::test]
1289 async fn changing_parent_notifies_subfield() {
1290 _ = any_spawner::Executor::init_tokio();
1291
1292 let combined_count = Arc::new(AtomicUsize::new(0));
1293
1294 let store = Store::new(Foo {
1295 id: 42,
1296 bar: Bar {
1297 bar_signature: 69,
1298 baz: Baz {
1299 more_data: 9999,
1300 baw: Baw {
1301 more_data: 22,
1302 end: 1112,
1303 },
1304 },
1305 },
1306 });
1307
1308 let tracked_field = store.bar().baz().more_data();
1309
1310 Effect::new_sync({
1311 let combined_count = Arc::clone(&combined_count);
1312 move |prev: Option<()>| {
1313 if prev.is_none() {
1314 println!("first run");
1315 } else {
1316 println!("next run");
1317 }
1318
1319 println!("{:?}", *tracked_field.read());
1322 combined_count.fetch_add(1, Ordering::Relaxed);
1323 }
1324 });
1325 tick().await;
1326 tick().await;
1327
1328 store.bar().baz().set(Baz {
1329 more_data: 42,
1330 baw: Baw {
1331 more_data: 11,
1332 end: 31,
1333 },
1334 });
1335 tick().await;
1336 store.bar().set(Bar {
1337 bar_signature: 23,
1338 baz: Baz {
1339 more_data: 32,
1340 baw: Baw {
1341 more_data: 432,
1342 end: 423,
1343 },
1344 },
1345 });
1346 tick().await;
1347
1348 assert_eq!(combined_count.load(Ordering::Relaxed), 3);
1349 }
1350
1351 #[tokio::test]
1352 async fn changing_parent_notifies_unkeyed_child() {
1353 _ = any_spawner::Executor::init_tokio();
1354
1355 let combined_count = Arc::new(AtomicUsize::new(0));
1356
1357 let store = Store::new(data());
1358
1359 let tracked_field = store.todos().at_unkeyed(0);
1360
1361 Effect::new_sync({
1362 let combined_count = Arc::clone(&combined_count);
1363 move |prev: Option<()>| {
1364 if prev.is_none() {
1365 println!("first run");
1366 } else {
1367 println!("next run");
1368 }
1369
1370 println!("{:?}", *tracked_field.read());
1373 combined_count.fetch_add(1, Ordering::Relaxed);
1374 }
1375 });
1376 tick().await;
1377 tick().await;
1378
1379 store.todos().write().pop();
1380 tick().await;
1381
1382 store.todos().write().push(Todo {
1383 label: "another one".into(),
1384 completed: false,
1385 });
1386 tick().await;
1387
1388 assert_eq!(combined_count.load(Ordering::Relaxed), 3);
1389 }
1390
1391 #[tokio::test]
1392 async fn untracked_write_on_subfield_shouldnt_notify() {
1393 _ = any_spawner::Executor::init_tokio();
1394
1395 let name_count = Arc::new(AtomicUsize::new(0));
1396
1397 let store = Store::new(data());
1398
1399 let tracked_field = store.user();
1400
1401 Effect::new_sync({
1402 let name_count = Arc::clone(&name_count);
1403 move |_| {
1404 tracked_field.track();
1405 name_count.fetch_add(1, Ordering::Relaxed);
1406 }
1407 });
1408
1409 tick().await;
1410 assert_eq!(name_count.load(Ordering::Relaxed), 1);
1411
1412 tracked_field.write().push('!');
1413 tick().await;
1414 assert_eq!(name_count.load(Ordering::Relaxed), 2);
1415
1416 tracked_field.write_untracked().push('!');
1417 tick().await;
1418 assert_eq!(name_count.load(Ordering::Relaxed), 2);
1419 }
1420}