1#![forbid(unsafe_code)]
2#![deny(missing_docs)]
3
4use reactive_graph::{
243 owner::{ArenaItem, LocalStorage, Storage, SyncStorage},
244 signal::{
245 guards::{Plain, ReadGuard, WriteGuard},
246 ArcTrigger,
247 },
248 traits::{
249 DefinedAt, Dispose, IsDisposed, Notify, ReadUntracked, Track,
250 UntrackableGuard, Write,
251 },
252};
253pub use reactive_stores_macro::{Patch, Store};
254use rustc_hash::FxHashMap;
255use std::{
256 any::Any,
257 fmt::Debug,
258 hash::Hash,
259 ops::DerefMut,
260 panic::Location,
261 sync::{Arc, RwLock},
262};
263
264mod arc_field;
265mod deref;
266mod field;
267mod iter;
268mod keyed;
269mod len;
270mod option;
271mod patch;
272mod path;
273mod store_field;
274mod subfield;
275
276pub use arc_field::ArcField;
277pub use deref::*;
278pub use field::Field;
279pub use iter::*;
280pub use keyed::*;
281pub use len::Len;
282pub use option::*;
283pub use patch::*;
284pub use path::{StorePath, StorePathSegment};
285pub use store_field::StoreField;
286pub use subfield::Subfield;
287
288#[derive(Debug, Default)]
289struct TriggerMap(FxHashMap<StorePath, StoreFieldTrigger>);
290
291#[derive(Debug, Clone, Default)]
293pub struct StoreFieldTrigger {
294 pub(crate) this: ArcTrigger,
295 pub(crate) children: ArcTrigger,
296}
297
298impl StoreFieldTrigger {
299 pub fn new() -> Self {
301 Self::default()
302 }
303}
304
305impl TriggerMap {
306 fn get_or_insert(&mut self, key: StorePath) -> StoreFieldTrigger {
307 if let Some(trigger) = self.0.get(&key) {
308 trigger.clone()
309 } else {
310 let new = StoreFieldTrigger::new();
311 self.0.insert(key, new.clone());
312 new
313 }
314 }
315
316 #[allow(unused)]
317 fn remove(&mut self, key: &StorePath) -> Option<StoreFieldTrigger> {
318 self.0.remove(key)
319 }
320}
321
322pub(crate) struct FieldKeys<K> {
324 spare_keys: Vec<StorePathSegment>,
325 current_key: usize,
326 keys: FxHashMap<K, (StorePathSegment, usize)>,
327}
328
329impl<K> FieldKeys<K>
330where
331 K: Debug + Hash + PartialEq + Eq,
332{
333 pub fn new(from_keys: Vec<K>) -> Self {
335 let mut keys = FxHashMap::with_capacity_and_hasher(
336 from_keys.len(),
337 Default::default(),
338 );
339 for (idx, key) in from_keys.into_iter().enumerate() {
340 let segment = idx.into();
341 keys.insert(key, (segment, idx));
342 }
343
344 Self {
345 spare_keys: Vec::new(),
346 current_key: keys.len().saturating_sub(1),
347 keys,
348 }
349 }
350}
351
352impl<K> FieldKeys<K>
353where
354 K: Hash + PartialEq + Eq,
355{
356 fn get(&self, key: &K) -> Option<(StorePathSegment, usize)> {
357 self.keys.get(key).copied()
358 }
359
360 fn next_key(&mut self) -> StorePathSegment {
361 self.spare_keys.pop().unwrap_or_else(|| {
362 self.current_key += 1;
363 self.current_key.into()
364 })
365 }
366
367 fn update(&mut self, iter: impl IntoIterator<Item = K>) {
368 let new_keys = iter
369 .into_iter()
370 .enumerate()
371 .map(|(idx, key)| (key, idx))
372 .collect::<FxHashMap<K, usize>>();
373
374 self.keys.retain(|key, old_entry| match new_keys.get(key) {
376 Some(idx) => {
377 old_entry.1 = *idx;
378 true
379 }
380 None => {
381 self.spare_keys.push(old_entry.0);
382 false
383 }
384 });
385
386 for (key, idx) in new_keys {
388 #[allow(clippy::map_entry)]
391 if !self.keys.contains_key(&key) {
392 let path = self.next_key();
393 self.keys.insert(key, (path, idx));
394 }
395 }
396 }
397}
398
399impl<K> Default for FieldKeys<K> {
400 fn default() -> Self {
401 Self {
402 spare_keys: Default::default(),
403 current_key: Default::default(),
404 keys: Default::default(),
405 }
406 }
407}
408
409#[cfg(not(target_arch = "wasm32"))]
410type HashMap<K, V> = Arc<dashmap::DashMap<K, V>>;
411#[cfg(target_arch = "wasm32")]
412type HashMap<K, V> = send_wrapper::SendWrapper<
413 std::rc::Rc<std::cell::RefCell<std::collections::HashMap<K, V>>>,
414>;
415
416#[derive(Clone)]
418pub struct KeyMap(HashMap<StorePath, Box<dyn Any + Send + Sync>>);
419
420impl Default for KeyMap {
421 fn default() -> Self {
422 #[cfg(not(target_arch = "wasm32"))]
423 return Self(Default::default());
424 #[cfg(target_arch = "wasm32")]
425 return Self(send_wrapper::SendWrapper::new(Default::default()));
426 }
427}
428
429impl KeyMap {
430 fn with_field_keys<K, T>(
431 &self,
432 path: StorePath,
433 fun: impl FnOnce(&mut FieldKeys<K>) -> T,
434 initialize: impl FnOnce() -> Vec<K>,
435 ) -> Option<T>
436 where
437 K: Debug + Hash + PartialEq + Eq + Send + Sync + 'static,
438 {
439 #[cfg(not(target_arch = "wasm32"))]
440 let mut entry = self
441 .0
442 .entry(path)
443 .or_insert_with(|| Box::new(FieldKeys::new(initialize())));
444
445 #[cfg(target_arch = "wasm32")]
446 let entry = if !self.0.borrow().contains_key(&path) {
447 Some(Box::new(FieldKeys::new(initialize())))
448 } else {
449 None
450 };
451 #[cfg(target_arch = "wasm32")]
452 let mut map = self.0.borrow_mut();
453 #[cfg(target_arch = "wasm32")]
454 let entry = map.entry(path).or_insert_with(|| entry.unwrap());
455
456 let entry = entry.downcast_mut::<FieldKeys<K>>()?;
457 Some(fun(entry))
458 }
459}
460
461pub struct ArcStore<T> {
468 #[cfg(any(debug_assertions, leptos_debuginfo))]
469 defined_at: &'static Location<'static>,
470 pub(crate) value: Arc<RwLock<T>>,
471 signals: Arc<RwLock<TriggerMap>>,
472 keys: KeyMap,
473}
474
475impl<T> ArcStore<T> {
476 pub fn new(value: T) -> Self {
478 Self {
479 #[cfg(any(debug_assertions, leptos_debuginfo))]
480 defined_at: Location::caller(),
481 value: Arc::new(RwLock::new(value)),
482 signals: Default::default(),
483 keys: Default::default(),
484 }
485 }
486}
487
488impl<T: Default> Default for ArcStore<T> {
489 fn default() -> Self {
490 Self::new(T::default())
491 }
492}
493
494impl<T: Debug> Debug for ArcStore<T> {
495 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
496 let mut f = f.debug_struct("ArcStore");
497 #[cfg(any(debug_assertions, leptos_debuginfo))]
498 let f = f.field("defined_at", &self.defined_at);
499 f.field("value", &self.value)
500 .field("signals", &self.signals)
501 .finish()
502 }
503}
504
505impl<T> Clone for ArcStore<T> {
506 fn clone(&self) -> Self {
507 Self {
508 #[cfg(any(debug_assertions, leptos_debuginfo))]
509 defined_at: self.defined_at,
510 value: Arc::clone(&self.value),
511 signals: Arc::clone(&self.signals),
512 keys: self.keys.clone(),
513 }
514 }
515}
516
517impl<T> DefinedAt for ArcStore<T> {
518 fn defined_at(&self) -> Option<&'static Location<'static>> {
519 #[cfg(any(debug_assertions, leptos_debuginfo))]
520 {
521 Some(self.defined_at)
522 }
523 #[cfg(not(any(debug_assertions, leptos_debuginfo)))]
524 {
525 None
526 }
527 }
528}
529
530impl<T> IsDisposed for ArcStore<T> {
531 #[inline(always)]
532 fn is_disposed(&self) -> bool {
533 false
534 }
535}
536
537impl<T> ReadUntracked for ArcStore<T>
538where
539 T: 'static,
540{
541 type Value = ReadGuard<T, Plain<T>>;
542
543 fn try_read_untracked(&self) -> Option<Self::Value> {
544 Plain::try_new(Arc::clone(&self.value)).map(ReadGuard::new)
545 }
546}
547
548impl<T> Write for ArcStore<T>
549where
550 T: 'static,
551{
552 type Value = T;
553
554 fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
555 self.writer()
556 .map(|writer| WriteGuard::new(self.clone(), writer))
557 }
558
559 fn try_write_untracked(
560 &self,
561 ) -> Option<impl DerefMut<Target = Self::Value>> {
562 let mut writer = self.writer()?;
563 writer.untrack();
564 Some(writer)
565 }
566}
567
568impl<T: 'static> Track for ArcStore<T> {
569 fn track(&self) {
570 self.track_field();
571 }
572}
573
574impl<T: 'static> Notify for ArcStore<T> {
575 fn notify(&self) {
576 let trigger = self.get_trigger(self.path().into_iter().collect());
577 trigger.this.notify();
578 trigger.children.notify();
579 }
580}
581
582pub struct Store<T, S = SyncStorage> {
592 #[cfg(any(debug_assertions, leptos_debuginfo))]
593 defined_at: &'static Location<'static>,
594 inner: ArenaItem<ArcStore<T>, S>,
595}
596
597impl<T> Store<T>
598where
599 T: Send + Sync + 'static,
600{
601 pub fn new(value: T) -> Self {
603 Self {
604 #[cfg(any(debug_assertions, leptos_debuginfo))]
605 defined_at: Location::caller(),
606 inner: ArenaItem::new_with_storage(ArcStore::new(value)),
607 }
608 }
609}
610
611impl<T, S> PartialEq for Store<T, S> {
612 fn eq(&self, other: &Self) -> bool {
613 self.inner == other.inner
614 }
615}
616
617impl<T, S> Eq for Store<T, S> {}
618
619impl<T> Store<T, LocalStorage>
620where
621 T: 'static,
622{
623 pub fn new_local(value: T) -> Self {
627 Self {
628 #[cfg(any(debug_assertions, leptos_debuginfo))]
629 defined_at: Location::caller(),
630 inner: ArenaItem::new_with_storage(ArcStore::new(value)),
631 }
632 }
633}
634
635impl<T> Default for Store<T>
636where
637 T: Default + Send + Sync + 'static,
638{
639 fn default() -> Self {
640 Self::new(T::default())
641 }
642}
643
644impl<T> Default for Store<T, LocalStorage>
645where
646 T: Default + 'static,
647{
648 fn default() -> Self {
649 Self::new_local(T::default())
650 }
651}
652
653impl<T: Debug, S> Debug for Store<T, S>
654where
655 S: Debug,
656{
657 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
658 let mut f = f.debug_struct("Store");
659 #[cfg(any(debug_assertions, leptos_debuginfo))]
660 let f = f.field("defined_at", &self.defined_at);
661 f.field("inner", &self.inner).finish()
662 }
663}
664
665impl<T, S> Clone for Store<T, S> {
666 fn clone(&self) -> Self {
667 *self
668 }
669}
670
671impl<T, S> Copy for Store<T, S> {}
672
673impl<T, S> DefinedAt for Store<T, S> {
674 fn defined_at(&self) -> Option<&'static Location<'static>> {
675 #[cfg(any(debug_assertions, leptos_debuginfo))]
676 {
677 Some(self.defined_at)
678 }
679 #[cfg(not(any(debug_assertions, leptos_debuginfo)))]
680 {
681 None
682 }
683 }
684}
685
686impl<T, S> IsDisposed for Store<T, S>
687where
688 T: 'static,
689{
690 #[inline(always)]
691 fn is_disposed(&self) -> bool {
692 self.inner.is_disposed()
693 }
694}
695
696impl<T, S> Dispose for Store<T, S>
697where
698 T: 'static,
699{
700 fn dispose(self) {
701 self.inner.dispose();
702 }
703}
704
705impl<T, S> ReadUntracked for Store<T, S>
706where
707 T: 'static,
708 S: Storage<ArcStore<T>>,
709{
710 type Value = ReadGuard<T, Plain<T>>;
711
712 fn try_read_untracked(&self) -> Option<Self::Value> {
713 self.inner
714 .try_get_value()
715 .and_then(|inner| inner.try_read_untracked())
716 }
717}
718
719impl<T, S> Write for Store<T, S>
720where
721 T: 'static,
722 S: Storage<ArcStore<T>>,
723{
724 type Value = T;
725
726 fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
727 self.writer().map(|writer| WriteGuard::new(*self, writer))
728 }
729
730 fn try_write_untracked(
731 &self,
732 ) -> Option<impl DerefMut<Target = Self::Value>> {
733 let mut writer = self.writer()?;
734 writer.untrack();
735 Some(writer)
736 }
737}
738
739impl<T, S> Track for Store<T, S>
740where
741 T: 'static,
742 S: Storage<ArcStore<T>>,
743{
744 fn track(&self) {
745 if let Some(inner) = self.inner.try_get_value() {
746 inner.track();
747 }
748 }
749}
750
751impl<T, S> Notify for Store<T, S>
752where
753 T: 'static,
754 S: Storage<ArcStore<T>>,
755{
756 fn notify(&self) {
757 if let Some(inner) = self.inner.try_get_value() {
758 inner.notify();
759 }
760 }
761}
762
763impl<T, S> From<ArcStore<T>> for Store<T, S>
764where
765 T: 'static,
766 S: Storage<ArcStore<T>>,
767{
768 fn from(value: ArcStore<T>) -> Self {
769 Self {
770 #[cfg(any(debug_assertions, leptos_debuginfo))]
771 defined_at: value.defined_at,
772 inner: ArenaItem::new_with_storage(value),
773 }
774 }
775}
776
777#[cfg(test)]
778mod tests {
779 use crate::{self as reactive_stores, Patch, Store, StoreFieldIterator};
780 use reactive_graph::{
781 effect::Effect,
782 owner::StoredValue,
783 traits::{Read, ReadUntracked, Set, Update, Write},
784 };
785 use std::sync::{
786 atomic::{AtomicUsize, Ordering},
787 Arc,
788 };
789
790 pub async fn tick() {
791 tokio::time::sleep(std::time::Duration::from_micros(1)).await;
792 }
793
794 #[derive(Debug, Store, Patch, Default)]
795 struct Todos {
796 user: String,
797 todos: Vec<Todo>,
798 }
799
800 #[derive(Debug, Store, Patch, Default)]
801 struct Todo {
802 label: String,
803 completed: bool,
804 }
805
806 impl Todo {
807 pub fn new(label: impl ToString) -> Self {
808 Self {
809 label: label.to_string(),
810 completed: false,
811 }
812 }
813 }
814
815 fn data() -> Todos {
816 Todos {
817 user: "Bob".to_string(),
818 todos: vec![
819 Todo {
820 label: "Create reactive store".to_string(),
821 completed: true,
822 },
823 Todo {
824 label: "???".to_string(),
825 completed: false,
826 },
827 Todo {
828 label: "Profit".to_string(),
829 completed: false,
830 },
831 ],
832 }
833 }
834
835 #[tokio::test]
836 async fn mutating_field_triggers_effect() {
837 _ = any_spawner::Executor::init_tokio();
838
839 let combined_count = Arc::new(AtomicUsize::new(0));
840
841 let store = Store::new(data());
842 assert_eq!(store.read_untracked().todos.len(), 3);
843 assert_eq!(store.user().read_untracked().as_str(), "Bob");
844 Effect::new_sync({
845 let combined_count = Arc::clone(&combined_count);
846 move |prev: Option<()>| {
847 if prev.is_none() {
848 println!("first run");
849 } else {
850 println!("next run");
851 }
852 println!("{:?}", *store.user().read());
853 combined_count.fetch_add(1, Ordering::Relaxed);
854 }
855 });
856 tick().await;
857 tick().await;
858 store.user().set("Greg".into());
859 tick().await;
860 store.user().set("Carol".into());
861 tick().await;
862 store.user().update(|name| name.push_str("!!!"));
863 tick().await;
864 assert_eq!(combined_count.load(Ordering::Relaxed), 4);
866 }
867
868 #[tokio::test]
869 async fn other_field_does_not_notify() {
870 _ = any_spawner::Executor::init_tokio();
871
872 let combined_count = Arc::new(AtomicUsize::new(0));
873
874 let store = Store::new(data());
875
876 Effect::new_sync({
877 let combined_count = Arc::clone(&combined_count);
878 move |prev: Option<()>| {
879 if prev.is_none() {
880 println!("first run");
881 } else {
882 println!("next run");
883 }
884 println!("{:?}", *store.todos().read());
885 combined_count.fetch_add(1, Ordering::Relaxed);
886 }
887 });
888 tick().await;
889 store.user().set("Greg".into());
890 tick().await;
891 store.user().set("Carol".into());
892 tick().await;
893 store.user().update(|name| name.push_str("!!!"));
894 tick().await;
895 assert_eq!(combined_count.load(Ordering::Relaxed), 1);
897 }
898
899 #[tokio::test]
900 async fn parent_does_notify() {
901 _ = any_spawner::Executor::init_tokio();
902
903 let combined_count = Arc::new(AtomicUsize::new(0));
904
905 let store = Store::new(data());
906
907 Effect::new_sync({
908 let combined_count = Arc::clone(&combined_count);
909 move |prev: Option<()>| {
910 if prev.is_none() {
911 println!("first run");
912 } else {
913 println!("next run");
914 }
915 println!("{:?}", *store.todos().read());
916 combined_count.fetch_add(1, Ordering::Relaxed);
917 }
918 });
919 tick().await;
920 tick().await;
921 store.set(Todos::default());
922 tick().await;
923 store.set(data());
924 tick().await;
925 assert_eq!(combined_count.load(Ordering::Relaxed), 3);
926 }
927
928 #[tokio::test]
929 async fn changes_do_notify_parent() {
930 _ = any_spawner::Executor::init_tokio();
931
932 let combined_count = Arc::new(AtomicUsize::new(0));
933
934 let store = Store::new(data());
935
936 Effect::new_sync({
937 let combined_count = Arc::clone(&combined_count);
938 move |prev: Option<()>| {
939 if prev.is_none() {
940 println!("first run");
941 } else {
942 println!("next run");
943 }
944 println!("{:?}", *store.read());
945 combined_count.fetch_add(1, Ordering::Relaxed);
946 }
947 });
948 tick().await;
949 tick().await;
950 store.user().set("Greg".into());
951 tick().await;
952 store.user().set("Carol".into());
953 tick().await;
954 store.user().update(|name| name.push_str("!!!"));
955 tick().await;
956 store.todos().write().clear();
957 tick().await;
958 assert_eq!(combined_count.load(Ordering::Relaxed), 5);
959 }
960
961 #[tokio::test]
962 async fn iterator_tracks_the_field() {
963 _ = any_spawner::Executor::init_tokio();
964
965 let combined_count = Arc::new(AtomicUsize::new(0));
966
967 let store = Store::new(data());
968
969 Effect::new_sync({
970 let combined_count = Arc::clone(&combined_count);
971 move |prev: Option<()>| {
972 if prev.is_none() {
973 println!("first run");
974 } else {
975 println!("next run");
976 }
977 println!(
978 "{:?}",
979 store.todos().iter_unkeyed().collect::<Vec<_>>()
980 );
981 combined_count.store(1, Ordering::Relaxed);
982 }
983 });
984
985 tick().await;
986 store
987 .todos()
988 .write()
989 .push(Todo::new("Create reactive store?"));
990 tick().await;
991 store.todos().write().push(Todo::new("???"));
992 tick().await;
993 store.todos().write().push(Todo::new("Profit!"));
994 assert_eq!(combined_count.load(Ordering::Relaxed), 1);
996 }
997
998 #[tokio::test]
999 async fn patching_only_notifies_changed_field() {
1000 _ = any_spawner::Executor::init_tokio();
1001
1002 let combined_count = Arc::new(AtomicUsize::new(0));
1003
1004 let store = Store::new(Todos {
1005 user: "Alice".into(),
1006 todos: vec![],
1007 });
1008
1009 Effect::new_sync({
1010 let combined_count = Arc::clone(&combined_count);
1011 move |prev: Option<()>| {
1012 if prev.is_none() {
1013 println!("first run");
1014 } else {
1015 println!("next run");
1016 }
1017 println!("{:?}", *store.todos().read());
1018 combined_count.fetch_add(1, Ordering::Relaxed);
1019 }
1020 });
1021 tick().await;
1022 tick().await;
1023 store.patch(Todos {
1024 user: "Bob".into(),
1025 todos: vec![],
1026 });
1027 tick().await;
1028 store.patch(Todos {
1029 user: "Carol".into(),
1030 todos: vec![],
1031 });
1032 tick().await;
1033 assert_eq!(combined_count.load(Ordering::Relaxed), 1);
1034
1035 store.patch(Todos {
1036 user: "Carol".into(),
1037 todos: vec![Todo {
1038 label: "First Todo".into(),
1039 completed: false,
1040 }],
1041 });
1042 tick().await;
1043 assert_eq!(combined_count.load(Ordering::Relaxed), 2);
1044 }
1045
1046 #[tokio::test]
1047 async fn patching_only_notifies_changed_field_with_custom_patch() {
1048 _ = any_spawner::Executor::init_tokio();
1049
1050 #[derive(Debug, Store, Patch, Default)]
1051 struct CustomTodos {
1052 #[patch(|this, new| *this = new)]
1053 user: String,
1054 todos: Vec<CustomTodo>,
1055 }
1056
1057 #[derive(Debug, Store, Patch, Default)]
1058 struct CustomTodo {
1059 label: String,
1060 completed: bool,
1061 }
1062
1063 let combined_count = Arc::new(AtomicUsize::new(0));
1064
1065 let store = Store::new(CustomTodos {
1066 user: "Alice".into(),
1067 todos: vec![],
1068 });
1069
1070 Effect::new_sync({
1071 let combined_count = Arc::clone(&combined_count);
1072 move |prev: Option<()>| {
1073 if prev.is_none() {
1074 println!("first run");
1075 } else {
1076 println!("next run");
1077 }
1078 println!("{:?}", *store.user().read());
1079 combined_count.fetch_add(1, Ordering::Relaxed);
1080 }
1081 });
1082 tick().await;
1083 tick().await;
1084 store.patch(CustomTodos {
1085 user: "Bob".into(),
1086 todos: vec![],
1087 });
1088 tick().await;
1089 assert_eq!(combined_count.load(Ordering::Relaxed), 2);
1090 store.patch(CustomTodos {
1091 user: "Carol".into(),
1092 todos: vec![],
1093 });
1094 tick().await;
1095 assert_eq!(combined_count.load(Ordering::Relaxed), 3);
1096
1097 store.patch(CustomTodos {
1098 user: "Carol".into(),
1099 todos: vec![CustomTodo {
1100 label: "First CustomTodo".into(),
1101 completed: false,
1102 }],
1103 });
1104 tick().await;
1105 assert_eq!(combined_count.load(Ordering::Relaxed), 3);
1106 }
1107
1108 #[tokio::test]
1110 async fn notifying_all_descendants() {
1111 use reactive_graph::traits::*;
1112
1113 _ = any_spawner::Executor::init_tokio();
1114
1115 #[derive(Debug, Clone, Store, Patch, Default)]
1116 struct Foo {
1117 id: i32,
1118 bar: Bar,
1119 }
1120
1121 #[derive(Debug, Clone, Store, Patch, Default)]
1122 struct Bar {
1123 bar_signature: i32,
1124 baz: Baz,
1125 }
1126
1127 #[derive(Debug, Clone, Store, Patch, Default)]
1128 struct Baz {
1129 more_data: i32,
1130 baw: Baw,
1131 }
1132
1133 #[derive(Debug, Clone, Store, Patch, Default)]
1134 struct Baw {
1135 more_data: i32,
1136 end: i32,
1137 }
1138
1139 let store = Store::new(Foo {
1140 id: 42,
1141 bar: Bar {
1142 bar_signature: 69,
1143 baz: Baz {
1144 more_data: 9999,
1145 baw: Baw {
1146 more_data: 22,
1147 end: 1112,
1148 },
1149 },
1150 },
1151 });
1152
1153 let store_runs = StoredValue::new(0);
1154 let id_runs = StoredValue::new(0);
1155 let bar_runs = StoredValue::new(0);
1156 let bar_signature_runs = StoredValue::new(0);
1157 let bar_baz_runs = StoredValue::new(0);
1158 let more_data_runs = StoredValue::new(0);
1159 let baz_baw_end_runs = StoredValue::new(0);
1160
1161 Effect::new_sync(move |_| {
1162 println!("foo: {:?}", store.get());
1163 *store_runs.write_value() += 1;
1164 });
1165
1166 Effect::new_sync(move |_| {
1167 println!("foo.id: {:?}", store.id().get());
1168 *id_runs.write_value() += 1;
1169 });
1170
1171 Effect::new_sync(move |_| {
1172 println!("foo.bar: {:?}", store.bar().get());
1173 *bar_runs.write_value() += 1;
1174 });
1175
1176 Effect::new_sync(move |_| {
1177 println!(
1178 "foo.bar.bar_signature: {:?}",
1179 store.bar().bar_signature().get()
1180 );
1181 *bar_signature_runs.write_value() += 1;
1182 });
1183
1184 Effect::new_sync(move |_| {
1185 println!("foo.bar.baz: {:?}", store.bar().baz().get());
1186 *bar_baz_runs.write_value() += 1;
1187 });
1188
1189 Effect::new_sync(move |_| {
1190 println!(
1191 "foo.bar.baz.more_data: {:?}",
1192 store.bar().baz().more_data().get()
1193 );
1194 *more_data_runs.write_value() += 1;
1195 });
1196
1197 Effect::new_sync(move |_| {
1198 println!(
1199 "foo.bar.baz.baw.end: {:?}",
1200 store.bar().baz().baw().end().get()
1201 );
1202 *baz_baw_end_runs.write_value() += 1;
1203 });
1204
1205 println!("[INITIAL EFFECT RUN]");
1206 tick().await;
1207 println!("\n\n[SETTING STORE]");
1208 store.set(Default::default());
1209 tick().await;
1210 println!("\n\n[SETTING STORE.BAR.BAZ]");
1211 store.bar().baz().set(Default::default());
1212 tick().await;
1213
1214 assert_eq!(store_runs.get_value(), 3);
1215 assert_eq!(id_runs.get_value(), 2);
1216 assert_eq!(bar_runs.get_value(), 3);
1217 assert_eq!(bar_signature_runs.get_value(), 2);
1218 assert_eq!(bar_baz_runs.get_value(), 3);
1219 assert_eq!(more_data_runs.get_value(), 3);
1220 assert_eq!(baz_baw_end_runs.get_value(), 3);
1221 }
1222}