1+ use std:: {
2+ cell:: Cell ,
3+ rc:: { Rc , Weak } ,
4+ } ;
5+
6+ use reactive_cache:: { Effect , Memo , Signal } ;
7+ use reactive_macros:: { memo, ref_signal, signal} ;
8+
9+ // ----------------------
10+ // Global signals
11+ // ----------------------
12+
13+ // Non-Copy global string signal
14+ ref_signal ! ( static mut GLOBAL_STR : String = "hello" . to_string( ) ; ) ;
15+
16+ // Copy global numeric signal
17+ signal ! ( static mut GLOBAL_NUM : i32 = 10 ; ) ;
18+
19+ // ----------------------
20+ // Global memos
21+ // ----------------------
22+
23+ #[ memo]
24+ pub fn get_global_number ( ) -> i32 {
25+ // Getter for the global numeric signal
26+ GLOBAL_NUM ( )
27+ }
28+
29+ #[ memo]
30+ pub fn get_global_string ( ) -> String {
31+ // Getter for the global string signal
32+ GLOBAL_STR_get ( ) . clone ( )
33+ }
34+
35+ // ----------------------
36+ // ViewModel definition
37+ // ----------------------
38+
39+ struct ViewModel {
40+ counter : Rc < Signal < i32 > > ,
41+ double : Rc < Memo < i32 > > ,
42+ effect : Rc < Effect > ,
43+ }
44+
45+ impl ViewModel {
46+ /// Create a new ViewModel with a local counter signal.
47+ /// The memo depends on the local counter and optionally reads the global numeric signal.
48+ /// The effect increments `effect_run_count` and reads the memo to establish reactive dependencies.
49+ fn new ( initial_counter : i32 , use_global_num : bool , effect_run_count : Rc < Cell < u32 > > ) -> Self {
50+ let counter = Signal :: new ( initial_counter) ;
51+
52+ // Memo depends on `counter`, optionally reading the global numeric signal
53+ let double = Memo :: new ( {
54+ let counter_clone = counter. clone ( ) ;
55+ move || {
56+ let base = * counter_clone. get ( ) ;
57+ let global_val = if use_global_num {
58+ GLOBAL_NUM ( )
59+ } else {
60+ 0
61+ } ;
62+ base * 2 + global_val
63+ }
64+ } ) ;
65+
66+ // Effect observes `double` and increments the counter
67+ let effect_run_count_clone = effect_run_count. clone ( ) ;
68+ let double_for_effect = double. clone ( ) ;
69+ let effect = Effect :: new ( move || {
70+ effect_run_count_clone. set ( effect_run_count_clone. get ( ) + 1 ) ;
71+ let _ = double_for_effect. get ( ) ; // subscribe to memo
72+ } ) ;
73+
74+ Self {
75+ counter,
76+ double,
77+ effect,
78+ }
79+ }
80+ }
81+
82+ // ----------------------
83+ // Comprehensive integration test
84+ // ----------------------
85+
86+ #[ test]
87+ fn test_global_and_vm_interaction_with_memory_cleanup ( ) {
88+ // ----------------------
89+ // Arrange: run counters
90+ // ----------------------
91+ let global_effect_runs = Rc :: new ( Cell :: new ( 0 ) ) ;
92+ let vm1_effect_runs = Rc :: new ( Cell :: new ( 0 ) ) ;
93+ let vm2_effect_runs = Rc :: new ( Cell :: new ( 0 ) ) ;
94+
95+ // ----------------------
96+ // Global effect observing global memos
97+ // ----------------------
98+ let _global_effect = Effect :: new ( {
99+ let runs = global_effect_runs. clone ( ) ;
100+ move || {
101+ runs. set ( runs. get ( ) + 1 ) ;
102+ let _ = get_global_number ( ) ;
103+ let _ = get_global_string ( ) ;
104+ }
105+ } ) ;
106+
107+ // Effect runs once upon creation
108+ assert_eq ! ( global_effect_runs. get( ) , 1 ) ;
109+
110+ // ----------------------
111+ // Create two ViewModels
112+ // vm1 uses global numeric signal in its memo, vm2 does not
113+ // ----------------------
114+ let vm1 = ViewModel :: new ( 1 , true , vm1_effect_runs. clone ( ) ) ;
115+ let vm2 = ViewModel :: new ( 2 , false , vm2_effect_runs. clone ( ) ) ;
116+
117+ // Keep weak references to check for leaks after dropping
118+ let vm1_counter_weak: Weak < Signal < i32 > > = Rc :: downgrade ( & vm1. counter ) ;
119+ let vm1_double_weak: Weak < Memo < i32 > > = Rc :: downgrade ( & vm1. double ) ;
120+ let vm1_effect_weak: Weak < Effect > = Rc :: downgrade ( & vm1. effect ) ;
121+
122+ let vm2_counter_weak: Weak < Signal < i32 > > = Rc :: downgrade ( & vm2. counter ) ;
123+ let vm2_double_weak: Weak < Memo < i32 > > = Rc :: downgrade ( & vm2. double ) ;
124+ let vm2_effect_weak: Weak < Effect > = Rc :: downgrade ( & vm2. effect ) ;
125+
126+ // Each VM effect runs once on creation
127+ assert_eq ! ( vm1_effect_runs. get( ) , 1 ) ;
128+ assert_eq ! ( vm2_effect_runs. get( ) , 1 ) ;
129+
130+ // Global effect may have run once more depending on memo initialization
131+ assert_eq ! ( global_effect_runs. get( ) , 1 ) ;
132+
133+ // ----------------------
134+ // Trigger updates and verify effects
135+ // ----------------------
136+ // Update vm1 counter -> vm1 effect should re-run
137+ vm1. counter . set ( 10 ) ;
138+ assert_eq ! ( vm1_effect_runs. get( ) , 2 ) ;
139+ assert_eq ! ( vm2_effect_runs. get( ) , 1 ) ; // vm2 unaffected
140+
141+ // Update vm2 counter -> vm2 effect should re-run
142+ vm2. counter . set ( 7 ) ;
143+ assert_eq ! ( vm2_effect_runs. get( ) , 2 ) ;
144+ assert_eq ! ( vm1_effect_runs. get( ) , 2 ) ; // vm1 unaffected
145+
146+ // Update global numeric signal -> global effect should re-run
147+ // vm1's memo depends on global numeric, so its effect should also run
148+ GLOBAL_NUM_set ( 100 ) ;
149+ assert_eq ! ( global_effect_runs. get( ) , 2 ) ;
150+ assert_eq ! ( vm1_effect_runs. get( ) , 3 ) ;
151+ assert_eq ! ( vm2_effect_runs. get( ) , 2 ) ; // vm2 does not depend on global
152+
153+ // ----------------------
154+ // Verify memo values
155+ // vm1.double = counter*2 + global_num (10*2 + 100)
156+ // vm2.double = counter*2 (7*2)
157+ // ----------------------
158+ assert_eq ! ( vm1. double. get( ) , 10 * 2 + 100 ) ;
159+ assert_eq ! ( vm2. double. get( ) , 7 * 2 ) ;
160+
161+ // ----------------------
162+ // Drop vm1 and check memory cleanup
163+ // ----------------------
164+ drop ( vm1) ;
165+ assert ! ( vm1_counter_weak. upgrade( ) . is_none( ) ) ;
166+ assert ! ( vm1_double_weak. upgrade( ) . is_none( ) ) ;
167+ assert ! ( vm1_effect_weak. upgrade( ) . is_none( ) ) ;
168+
169+ // vm2 still alive
170+ assert ! ( vm2_counter_weak. upgrade( ) . is_some( ) ) ;
171+
172+ // ----------------------
173+ // Drop vm2 and verify cleanup
174+ // ----------------------
175+ drop ( vm2) ;
176+ assert ! ( vm2_counter_weak. upgrade( ) . is_none( ) ) ;
177+ assert ! ( vm2_double_weak. upgrade( ) . is_none( ) ) ;
178+ assert ! ( vm2_effect_weak. upgrade( ) . is_none( ) ) ;
179+
180+ // ----------------------
181+ // Globals still alive after VMs are dropped
182+ // ----------------------
183+ let _n = get_global_number ( ) ;
184+ let _s = get_global_string ( ) ;
185+
186+ // Sanity check on global values
187+ assert_eq ! ( GLOBAL_NUM ( ) , 100 ) ;
188+ assert_eq ! ( get_global_number( ) , 100 ) ;
189+ assert_eq ! ( get_global_string( ) , "hello" . to_string( ) ) ;
190+ }
0 commit comments