1#![allow(warnings)]
2use std::cell::RefCell;
3use std::collections::{HashMap, HashSet};
4use std::rc::Rc;
5use std::sync::atomic::{AtomicUsize, Ordering};
6
7pub use serde_json;
8
9static NEXT_RUNTIME_ID: AtomicUsize = AtomicUsize::new(1);
10
11thread_local! {
12 static RUNTIME_ID: usize = NEXT_RUNTIME_ID.fetch_add(1, Ordering::Relaxed);
13 static GRAPH: RefCell<Graph> = RefCell::new(Graph::new());
14}
15
16#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
17pub struct NodeId {
18 runtime_id: usize,
19 index: usize,
20}
21
22#[derive(Clone, Copy, PartialEq, Eq, Debug)]
23enum State {
24 Clean,
25 Check,
26 Dirty,
27}
28
29type ComputeFn = Rc<RefCell<dyn FnMut() -> bool>>;
30
31struct Node {
32 id: NodeId,
33 state: State,
34 sources: HashMap<NodeId, usize>,
35 subscribers: HashSet<NodeId>,
36 compute: Option<ComputeFn>,
37 is_effect: bool,
38 version: usize,
39 value: Option<Box<dyn std::any::Any>>,
40}
41
42struct Graph {
43 nodes: Vec<Option<Node>>,
44 next_id: usize,
45 current_subscriber: Option<NodeId>,
46 pending_effects: HashSet<NodeId>,
47 pub pending_boundaries: HashSet<NodeId>,
54 is_batching: bool,
55}
56
57impl Graph {
58 fn new() -> Self {
59 Self {
60 nodes: Vec::new(),
61 next_id: 0,
62 current_subscriber: None,
63 pending_effects: HashSet::new(),
64 pending_boundaries: HashSet::new(),
65 is_batching: false,
66 }
67 }
68}
69
70impl NodeId {
71 pub fn runtime_id(&self) -> usize {
72 self.runtime_id
73 }
74
75 pub fn index(&self) -> usize {
76 self.index
77 }
78
79 pub fn test_new(runtime_id: usize, index: usize) -> Self {
80 Self { runtime_id, index }
81 }
82
83 pub fn new_empty() -> Self {
84 Self::new(false, None, None)
85 }
86
87 fn set_compute(&self, compute: ComputeFn) {
88 GRAPH.with(|g| {
89 let mut g = g.borrow_mut();
90 if let Some(node) = &mut g.nodes[self.index] {
91 node.compute = Some(compute);
92 }
93 });
94 }
95
96 fn new(is_effect: bool, compute: Option<ComputeFn>, value: Option<Box<dyn std::any::Any>>) -> Self {
97 GRAPH.with(|g| {
98 let mut g = g.borrow_mut();
99 let index = g.next_id;
100 let runtime_id = RUNTIME_ID.with(|id| *id);
101 let id = NodeId { runtime_id, index };
102 g.next_id += 1;
103 g.nodes.push(Some(Node {
104 id,
105 state: State::Clean,
106 sources: HashMap::new(),
107 subscribers: HashSet::new(),
108 compute,
109 is_effect,
110 version: 0,
111 value,
112 }));
113 id
114 })
115 }
116
117 fn record_read(&self) {
118 let current_runtime = RUNTIME_ID.with(|id| *id);
119 if self.runtime_id != current_runtime {
120 panic!(
124 "Cross-shard signal read detected! This will be handled by explicit synchronization in Phase 3."
125 );
126 }
127 GRAPH.with(|g| {
128 let mut g = g.borrow_mut();
129 if let Some(sub_id) = g.current_subscriber {
130 println!(
131 "record_read: {} is subscribing to {}",
132 sub_id.index, self.index
133 );
134 if let Some(node) = &mut g.nodes[self.index] {
135 node.subscribers.insert(sub_id);
136 }
137 let version = g.nodes[self.index].as_ref().unwrap().version;
138 if let Some(sub_node) = &mut g.nodes[sub_id.index] {
139 sub_node.sources.insert(*self, version);
140 }
141 }
142 });
143 }
144
145 fn mark_dirty(&self) {
146 println!("mark_dirty called on {}", self.index);
147 let mut stack = vec![*self];
148 while let Some(current) = stack.pop() {
149 let (is_effect, subscribers, should_push, has_compute) = GRAPH.with(|g| {
150 let mut g = g.borrow_mut();
151 if let Some(node) = g.nodes[current.index].as_mut() {
152 let state = node.state;
153 let is_effect = node.is_effect;
154 let subs = node.subscribers.iter().copied().collect::<Vec<_>>();
155
156 if current == *self || state == State::Clean {
157 if current != *self {
158 node.state = State::Check;
159 } else {
160 node.state = State::Dirty;
161 }
162 println!("node {} is now {:?} (is_effect: {}, has_compute: {})", current.index, node.state, is_effect, node.compute.is_some());
163 (is_effect, subs, true, node.compute.is_some())
164 } else {
165 (false, vec![], false, false)
166 }
167 } else {
168 (false, vec![], false, false)
169 }
170 });
171
172 if should_push {
173 if is_effect {
174 if has_compute {
175 GRAPH.with(|g| g.borrow_mut().pending_effects.insert(current));
176 } else {
177 GRAPH.with(|g| g.borrow_mut().pending_boundaries.insert(current));
178 }
179 }
180 for sub in subscribers {
181 stack.push(sub);
182 }
183 }
184 }
185 }
186
187 fn clear_sources(&self) {
188 GRAPH.with(|g| {
189 let mut g = g.borrow_mut();
190 let sources = g.nodes[self.index].as_ref().unwrap().sources.clone();
191 for (source, _) in sources {
192 if let Some(s) = &mut g.nodes[source.index] {
193 s.subscribers.remove(self);
194 }
195 }
196 if let Some(node) = &mut g.nodes[self.index] {
197 node.sources.clear();
198 }
199 });
200 }
201
202 fn update_if_necessary(&self) -> usize {
203 let state = GRAPH.with(|g| g.borrow().nodes[self.index].as_ref().unwrap().state);
204 if state == State::Clean {
205 return GRAPH.with(|g| g.borrow().nodes[self.index].as_ref().unwrap().version);
206 }
207 if state == State::Check {
208 let sources = GRAPH.with(|g| {
209 g.borrow().nodes[self.index]
210 .as_ref()
211 .unwrap()
212 .sources
213 .clone()
214 });
215 for (source, old_version) in sources {
216 let new_version = source.update_if_necessary();
217 if new_version > old_version {
218 GRAPH.with(|g| {
219 g.borrow_mut().nodes[self.index].as_mut().unwrap().state = State::Dirty
220 });
221 break;
222 }
223 }
224 }
225
226 let state = GRAPH.with(|g| g.borrow().nodes[self.index].as_ref().unwrap().state);
227
228 if state == State::Dirty {
229 let compute = GRAPH.with(|g| {
230 g.borrow().nodes[self.index]
231 .as_ref()
232 .unwrap()
233 .compute
234 .clone()
235 });
236 if let Some(compute) = compute {
237 self.clear_sources();
238 let prev_sub = GRAPH.with(|g| {
239 let mut g = g.borrow_mut();
240 let prev = g.current_subscriber;
241 g.current_subscriber = Some(*self);
242 prev
243 });
244
245 let changed = {
246 let mut c = compute.borrow_mut();
247 c()
248 };
249
250 GRAPH.with(|g| {
251 let mut g = g.borrow_mut();
252 g.current_subscriber = prev_sub;
253 let node = g.nodes[self.index].as_mut().unwrap();
254 node.state = State::Clean;
255 if changed {
256 node.version += 1;
257 }
258 });
259 } else {
260 GRAPH.with(|g| {
261 let mut g = g.borrow_mut();
262 let node = g.nodes[self.index].as_mut().unwrap();
263 node.state = State::Clean;
264 node.version += 1;
265 });
266 }
267 } else {
268 GRAPH.with(|g| g.borrow_mut().nodes[self.index].as_mut().unwrap().state = State::Clean);
269 }
270
271 GRAPH.with(|g| g.borrow().nodes[self.index].as_ref().unwrap().version)
272 }
273
274 pub fn track<R>(&self, f: impl FnOnce() -> R) -> R {
275 let prev_sub = GRAPH.with(|g| {
276 let mut g = g.borrow_mut();
277 let prev = g.current_subscriber;
278 g.current_subscriber = Some(*self);
279
280 let sources = g.nodes[self.index].as_ref().unwrap().sources.clone();
282 for (source, _) in sources {
283 if let Some(s) = &mut g.nodes[source.index] {
284 s.subscribers.remove(self);
285 }
286 }
287 if let Some(node) = &mut g.nodes[self.index] {
288 node.sources.clear();
289 }
290
291 prev
292 });
293
294 let result = f();
295
296 GRAPH.with(|g| {
297 let mut g = g.borrow_mut();
298 g.current_subscriber = prev_sub;
299 g.nodes[self.index].as_mut().unwrap().state = State::Clean;
300 });
301
302 result
303 }
304
305 pub fn is_dirty(&self) -> bool {
306 GRAPH.with(|g| {
307 let state = g.borrow().nodes[self.index].as_ref().unwrap().state;
308 state == State::Dirty || state == State::Check
309 })
310 }
311}
312
313pub fn take_pending_boundaries() -> Vec<NodeId> {
314 GRAPH.with(|g| {
315 let mut g = g.borrow_mut();
316 let boundaries: Vec<_> = g.pending_boundaries.iter().copied().collect();
317 g.pending_boundaries.clear();
318 boundaries
319 })
320}
321
322pub fn run_effects() {
323 let is_batching = GRAPH.with(|g| g.borrow().is_batching);
324 if is_batching {
325 return;
326 }
327 GRAPH.with(|g| g.borrow_mut().is_batching = true);
328
329 loop {
330 let effects: Vec<NodeId> = GRAPH.with(|g| {
331 let mut g = g.borrow_mut();
332 let effects: Vec<_> = g.pending_effects.iter().copied().collect();
333 g.pending_effects.clear();
334 effects
335 });
336
337 if effects.is_empty() {
338 break;
339 }
340
341 for effect in effects {
342 effect.update_if_necessary();
343 }
344 }
345
346 GRAPH.with(|g| g.borrow_mut().is_batching = false);
347}
348
349pub struct ReadSignal<T> {
354 id: NodeId,
355 _marker: std::marker::PhantomData<T>,
356}
357
358impl<T> Copy for ReadSignal<T> {}
359impl<T> Clone for ReadSignal<T> {
360 fn clone(&self) -> Self {
361 *self
362 }
363}
364
365pub struct WriteSignal<T> {
366 id: NodeId,
367 _marker: std::marker::PhantomData<T>,
368}
369
370impl<T> Copy for WriteSignal<T> {}
371impl<T> Clone for WriteSignal<T> {
372 fn clone(&self) -> Self {
373 *self
374 }
375}
376
377pub fn create_signal<T: Clone + 'static>(initial: T) -> (ReadSignal<T>, WriteSignal<T>) {
378 let id = NodeId::new(false, None, Some(Box::new(initial)));
379 (
380 ReadSignal {
381 id,
382 _marker: std::marker::PhantomData,
383 },
384 WriteSignal {
385 id,
386 _marker: std::marker::PhantomData,
387 },
388 )
389}
390
391impl<T: Clone + 'static> ReadSignal<T> {
392 pub fn get(&self) -> T {
393 self.id.record_read();
394 GRAPH.with(|g| {
395 let g = g.borrow();
396 let node = g.nodes[self.id.index].as_ref().unwrap();
397 node.value.as_ref().unwrap().downcast_ref::<T>().unwrap().clone()
398 })
399 }
400}
401
402impl<T: Clone + PartialEq + 'static> WriteSignal<T> {
403 pub fn set(&self, new_value: T) {
404 let changed = GRAPH.with(|g| {
405 let mut g = g.borrow_mut();
406 let node = g.nodes[self.id.index].as_mut().unwrap();
407 let val = node.value.as_mut().unwrap().downcast_mut::<T>().unwrap();
408 if *val == new_value {
409 false
410 } else {
411 *val = new_value;
412 true
413 }
414 });
415 if changed {
416 self.id.mark_dirty();
417 run_effects();
418 }
419 }
420}
421
422pub fn create_effect<F>(mut f: F)
423where
424 F: FnMut() + 'static,
425{
426 let compute: ComputeFn = Rc::new(RefCell::new(move || {
427 f();
428 true
429 }));
430
431 let id = NodeId::new(true, Some(compute.clone()), None);
432
433 id.mark_dirty();
434 run_effects();
435}
436
437pub struct Memo<T> {
438 id: NodeId,
439 _marker: std::marker::PhantomData<T>,
440}
441
442impl<T> Copy for Memo<T> {}
443impl<T> Clone for Memo<T> {
444 fn clone(&self) -> Self {
445 *self
446 }
447}
448
449pub fn create_memo<T, F>(mut f: F) -> Memo<T>
450where
451 F: FnMut() -> T + 'static,
452 T: Clone + PartialEq + 'static,
453{
454 let id = NodeId::new_empty();
455
456 GRAPH.with(|g| {
457 g.borrow_mut().nodes[id.index].as_mut().unwrap().value = Some(Box::new(None::<T>));
458 });
459
460 let compute: ComputeFn = Rc::new(RefCell::new(move || {
461 let new_value = f();
462 let changed = GRAPH.with(|g| {
463 let mut g = g.borrow_mut();
464 let node = g.nodes[id.index].as_mut().unwrap();
465 let val_any = node.value.as_mut().unwrap();
466 let val = val_any.downcast_mut::<Option<T>>().unwrap();
467 match val {
468 Some(old_value) if *old_value == new_value => false,
469 _ => {
470 *val = Some(new_value);
471 true
472 }
473 }
474 });
475 changed
476 }));
477
478 id.set_compute(compute);
479 id.mark_dirty();
480 id.update_if_necessary();
481
482 Memo { id, _marker: std::marker::PhantomData }
483}
484
485impl<T: Clone + 'static> Memo<T> {
486 pub fn get(&self) -> T {
487 self.id.update_if_necessary();
488 self.id.record_read();
489 GRAPH.with(|g| {
490 let g = g.borrow();
491 let node = g.nodes[self.id.index].as_ref().unwrap();
492 node.value.as_ref().unwrap().downcast_ref::<Option<T>>().unwrap().clone().unwrap()
493 })
494 }
495}
496
497#[derive(Clone)]
502pub enum AttributeValue {
503 String(String),
504 Bool(bool),
505 Dynamic(Rc<dyn Fn() -> AttributeValue>),
506 Event(Rc<dyn Fn()>),
507}
508
509impl std::fmt::Debug for AttributeValue {
510 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
511 match self {
512 Self::String(s) => write!(f, "String({:?})", s),
513 Self::Bool(b) => write!(f, "Bool({})", b),
514 Self::Dynamic(_) => write!(f, "Dynamic(..)"),
515 Self::Event(_) => write!(f, "Event(..)"),
516 }
517 }
518}
519
520impl From<&str> for AttributeValue {
521 fn from(s: &str) -> Self {
522 AttributeValue::String(s.to_string())
523 }
524}
525impl From<String> for AttributeValue {
526 fn from(s: String) -> Self {
527 AttributeValue::String(s)
528 }
529}
530impl From<bool> for AttributeValue {
531 fn from(b: bool) -> Self {
532 AttributeValue::Bool(b)
533 }
534}
535impl<F: Fn() -> String + 'static> From<F> for AttributeValue {
536 fn from(f: F) -> Self {
537 AttributeValue::Dynamic(Rc::new(move || AttributeValue::String(f())))
538 }
539}
540impl From<Rc<dyn Fn() -> AttributeValue>> for AttributeValue {
541 fn from(f: Rc<dyn Fn() -> AttributeValue>) -> Self {
542 AttributeValue::Dynamic(f)
543 }
544}
545
546#[derive(Clone)]
563pub struct Boundary {
564 pub id: NodeId,
565 pub compute: Rc<RefCell<dyn FnMut() -> View>>,
566}
567
568impl std::fmt::Debug for Boundary {
569 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
570 write!(
571 f,
572 "Boundary(runtime_id: {}, index: {})",
573 self.id.runtime_id, self.id.index
574 )
575 }
576}
577
578#[derive(Clone)]
579pub enum View {
580 Text(String),
581 DynamicNode(Boundary),
582 Element {
583 tag: String,
584 attrs: std::collections::HashMap<String, AttributeValue>,
585 children: Vec<View>,
586 },
587 Fragment(Vec<View>),
588 None,
589}
590
591impl std::fmt::Debug for View {
592 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
593 match self {
594 Self::Text(s) => write!(f, "Text({:?})", s),
595 Self::DynamicNode(_) => write!(f, "DynamicNode(..)"),
596 Self::Element {
597 tag,
598 attrs,
599 children,
600 } => f
601 .debug_struct("Element")
602 .field("tag", tag)
603 .field("attrs", attrs)
604 .field("children", children)
605 .finish(),
606 Self::Fragment(c) => write!(f, "Fragment({:?})", c),
607 Self::None => write!(f, "None"),
608 }
609 }
610}
611
612impl View {
613 pub fn with_attr(mut self, key: &str, value: &str) -> Self {
614 match &mut self {
615 View::Element { attrs, .. } => {
616 attrs.insert(key.to_string(), crate::AttributeValue::String(value.to_string()));
617 }
618 View::Fragment(children) => {
619 if let Some(first) = children.first_mut() {
620 *first = std::mem::replace(first, View::None).with_attr(key, value);
621 }
622 }
623 _ => {}
624 }
625 self
626 }
627}
628
629pub trait IntoView {
630 fn into_view(self) -> View;
631}
632
633impl IntoView for String {
634 fn into_view(self) -> View {
635 View::Text(self)
636 }
637}
638impl IntoView for &str {
639 fn into_view(self) -> View {
640 View::Text(self.to_string())
641 }
642}
643impl IntoView for View {
644 fn into_view(self) -> View {
645 self
646 }
647}
648impl<T: IntoView> IntoView for Vec<T> {
649 fn into_view(self) -> View {
650 View::Fragment(self.into_iter().map(|c| c.into_view()).collect())
651 }
652}
653impl<T: IntoView> IntoView for Option<T> {
654 fn into_view(self) -> View {
655 self.map(|t| t.into_view()).unwrap_or(View::None)
656 }
657}
658
659macro_rules! impl_into_view_for_display {
660 ($($t:ty),*) => {
661 $(
662 impl IntoView for $t {
663 fn into_view(self) -> View {
664 View::Text(self.to_string())
665 }
666 }
667 )*
668 }
669}
670impl_into_view_for_display!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64, bool);
671
672impl<T: IntoView + 'static, F: FnMut() -> T + 'static> IntoView for F {
673 fn into_view(mut self) -> View {
674 let id = NodeId::new(true, None, None);
675 View::DynamicNode(Boundary {
676 id,
677 compute: Rc::new(RefCell::new(move || self().into_view())),
678 })
679 }
680}
681
682pub struct ElementBuilder {
684 tag: String,
685 attrs: std::collections::HashMap<String, AttributeValue>,
686 children: Vec<View>,
687}
688
689impl ElementBuilder {
690 pub fn new(tag: impl Into<String>) -> Self {
691 Self {
692 tag: tag.into(),
693 attrs: std::collections::HashMap::new(),
694 children: vec![],
695 }
696 }
697 pub fn attr(mut self, key: impl Into<String>, value: impl Into<AttributeValue>) -> Self {
698 self.attrs.insert(key.into(), value.into());
699 self
700 }
701 pub fn on(mut self, key: impl Into<String>, f: impl Fn() + 'static) -> Self {
702 self.attrs
703 .insert(key.into(), AttributeValue::Event(Rc::new(f)));
704 self
705 }
706 pub fn child(mut self, child: impl IntoView) -> Self {
707 self.children.push(child.into_view());
708 self
709 }
710}
711
712impl IntoView for ElementBuilder {
713 fn into_view(self) -> View {
714 View::Element {
715 tag: self.tag,
716 attrs: self.attrs,
717 children: self.children,
718 }
719 }
720}
721
722pub fn element(tag: impl Into<String>) -> ElementBuilder {
723 ElementBuilder::new(tag)
724}
725pub fn text(text: impl Into<String>) -> View {
726 View::Text(text.into())
727}
728pub fn dyn_node<F: FnMut() -> View + 'static>(f: F) -> View {
729 f.into_view()
730}
731pub fn fragment(children: impl IntoIterator<Item = View>) -> View {
732 View::Fragment(children.into_iter().collect())
733}
734
735#[macro_export]
736macro_rules! create_store {
737 ($vis:vis $name:ident, $type:ty, $init:expr) => {
738 $vis struct $name;
739 impl $name {
740 fn store() -> ($crate::ReadSignal<$type>, $crate::WriteSignal<$type>) {
741 thread_local! {
742 static STORE: ($crate::ReadSignal<$type>, $crate::WriteSignal<$type>) = $crate::create_signal($init);
743 }
744 STORE.with(|s| *s)
745 }
746 $vis fn get() -> $type {
747 Self::store().0.get()
748 }
749 $vis fn set(val: $type) {
750 Self::store().1.set(val)
751 }
752 $vis fn update(f: impl FnOnce(&mut $type)) {
753 let mut val = Self::get();
754 f(&mut val);
755 Self::set(val);
756 }
757 }
758 };
759}
760
761pub struct Signal;
766impl Signal {
767 pub fn computed<T, F>(f: F) -> Memo<T>
768 where
769 F: FnMut() -> T + 'static,
770 T: Clone + PartialEq + 'static,
771 {
772 create_memo(f)
773 }
774}
775
776pub struct GlobalSignal<T: 'static> {
777 init: fn() -> T,
778}
779impl<T: Clone + PartialEq + 'static> GlobalSignal<T> {
780 pub const fn new(init: fn() -> T) -> Self {
781 Self { init }
782 }
783
784 fn get_signals(&self) -> (ReadSignal<T>, WriteSignal<T>) {
785 thread_local! {
786 static GLOBALS: RefCell<HashMap<usize, (NodeId, NodeId)>> = RefCell::new(HashMap::new());
787 }
788 let addr = self as *const _ as usize;
789 GLOBALS.with(|g| {
790 let mut g = g.borrow_mut();
791 if let Some(&(r, w)) = g.get(&addr) {
792 (ReadSignal { id: r, _marker: std::marker::PhantomData }, WriteSignal { id: w, _marker: std::marker::PhantomData })
793 } else {
794 let (read, write) = create_signal((self.init)());
795 g.insert(addr, (read.id, write.id));
796 (read, write)
797 }
798 })
799 }
800
801 pub fn get(&self) -> T {
802 self.get_signals().0.get()
803 }
804
805 pub fn set(&self, value: T) {
806 self.get_signals().1.set(value)
807 }
808
809 pub fn update(&self, f: impl FnOnce(&mut T)) {
810 let mut val = self.get();
811 f(&mut val);
812 self.set(val);
813 }
814}
815
816pub struct Action<I, O> {
817 is_loading: ReadSignal<bool>,
818 set_loading: WriteSignal<bool>,
819 func: std::rc::Rc<dyn Fn(I) -> std::pin::Pin<Box<dyn std::future::Future<Output = O>>>>,
820}
821
822impl<I: 'static, O: 'static> Clone for Action<I, O> {
823 fn clone(&self) -> Self {
824 Self {
825 is_loading: self.is_loading,
826 set_loading: self.set_loading,
827 func: self.func.clone(),
828 }
829 }
830}
831
832impl<I: 'static, O: 'static> Action<I, O> {
833 pub fn new<F, Fut>(f: F) -> Self
834 where
835 F: Fn(I) -> Fut + 'static,
836 Fut: std::future::Future<Output = O> + 'static
837 {
838 let (is_loading, set_loading) = create_signal(false);
839 let func = std::rc::Rc::new(move |i| Box::pin(f(i)) as std::pin::Pin<Box<dyn std::future::Future<Output = O>>>);
840 Self { is_loading, set_loading, func }
841 }
842
843 pub fn is_loading(&self) -> bool {
844 self.is_loading.get()
845 }
846
847 pub async fn execute(&self, input: I) -> O {
848 self.set_loading.set(true);
849 let res = (self.func)(input).await;
850 self.set_loading.set(false);
851 res
852 }
853}
854
855pub fn serialize_signal_graph() -> String {
859 "{}".to_string()
862}
863
864pub fn hydrate_signal_graph(_json: &str) {
865 }
867
868#[cfg(test)]
869mod tests {
870 use super::*;
871 use std::cell::RefCell;
872 use std::rc::Rc;
873
874 #[test]
875 fn test_diamond_problem() {
876 let (read_sig, write_sig) = create_signal(1);
877
878 let a_run_count = Rc::new(RefCell::new(0));
879 let b_run_count = Rc::new(RefCell::new(0));
880 let c_run_count = Rc::new(RefCell::new(0));
881
882 let arc = a_run_count.clone();
883 let read_a = read_sig.clone();
884 let memo_a = create_memo(move || {
885 *arc.borrow_mut() += 1;
886 read_a.get() * 2
887 });
888
889 let brc = b_run_count.clone();
890 let read_b = read_sig.clone();
891 let memo_b = create_memo(move || {
892 *brc.borrow_mut() += 1;
893 read_b.get() * 3
894 });
895
896 let crc = c_run_count.clone();
897 let memo_a_c = memo_a.clone();
898 let memo_b_c = memo_b.clone();
899 create_effect(move || {
900 *crc.borrow_mut() += 1;
901 let _: i32 = memo_a_c.get() + memo_b_c.get();
902 });
903
904 assert_eq!(*a_run_count.borrow(), 1);
905 assert_eq!(*b_run_count.borrow(), 1);
906 assert_eq!(*c_run_count.borrow(), 1);
907
908 write_sig.set(2);
909
910 assert_eq!(*a_run_count.borrow(), 2);
911 assert_eq!(*b_run_count.borrow(), 2);
912 assert_eq!(*c_run_count.borrow(), 2); }
914
915 #[test]
916 fn test_conditional_subscription() {
917 let (read_a, write_a) = create_signal(true);
918 let (read_b, write_b) = create_signal(10);
919
920 let run_count = Rc::new(RefCell::new(0));
921 let rc = run_count.clone();
922 let read_a_c = read_a.clone();
923 let read_b_c = read_b.clone();
924
925 create_effect(move || {
926 *rc.borrow_mut() += 1;
927 if read_a_c.get() {
928 let _: i32 = read_b_c.get();
929 }
930 });
931
932 assert_eq!(*run_count.borrow(), 1);
933
934 write_b.set(20);
936 assert_eq!(*run_count.borrow(), 2);
937
938 write_a.set(false);
940 assert_eq!(*run_count.borrow(), 3);
941
942 write_b.set(30);
944 assert_eq!(*run_count.borrow(), 3); write_a.set(true);
948 assert_eq!(*run_count.borrow(), 4);
949
950 write_b.set(40);
952 assert_eq!(*run_count.borrow(), 5);
953 }
954
955 #[test]
956 fn test_thread_safety_invariants() {
957 fn assert_send<T: Send>() {}
958
959 assert_send::<NodeId>();
961 }
962}
963
964#[cfg(target_arch = "wasm32")]
965pub async fn client_rpc_call<T: serde::de::DeserializeOwned>(url: &str, body: serde_json::Value) -> Result<T, String> {
966 use wasm_bindgen::JsCast;
967
968 let mut opts = web_sys::RequestInit::new();
969 opts.method("POST");
970 opts.mode(web_sys::RequestMode::Cors);
971
972 let js_body = wasm_bindgen::JsValue::from_str(&body.to_string());
973 opts.body(Some(&js_body));
974
975 let headers = web_sys::Headers::new()
976 .map_err(|e| format!("Headers::new failed: {:?}", e))?;
977 headers.set("Content-Type", "application/json")
978 .map_err(|e| format!("set Content-Type failed: {:?}", e))?;
979 headers.set("x-threadloom-route", url)
980 .map_err(|e| format!("set x-threadloom-route failed: {:?}", e))?;
981 opts.headers(&headers);
982
983 let request = web_sys::Request::new_with_str_and_init(url, &opts)
984 .map_err(|e| format!("Request::new failed: {:?}", e))?;
985
986 let window = web_sys::window().ok_or_else(|| "no window".to_string())?;
987 let resp_value = wasm_bindgen_futures::JsFuture::from(window.fetch_with_request(&request))
988 .await
989 .map_err(|e| format!("fetch failed: {:?}", e))?;
990 let resp: web_sys::Response = resp_value
991 .dyn_into()
992 .map_err(|e| format!("cast to Response failed: {:?}", e))?;
993
994 if !resp.ok() {
995 let status = resp.status();
996 let err_text = match resp.text() {
997 Ok(promise) => {
998 match wasm_bindgen_futures::JsFuture::from(promise).await {
999 Ok(js_val) => js_val.as_string().unwrap_or_default(),
1000 Err(_) => "Could not read response text".to_string()
1001 }
1002 },
1003 Err(_) => "Could not read response text promise".to_string()
1004 };
1005 return Err(format!("server returned HTTP {}: {}", status, err_text));
1006 }
1007
1008 let text_promise = resp.text().map_err(|e| format!("resp.text() failed: {:?}", e))?;
1009 let text_val = wasm_bindgen_futures::JsFuture::from(text_promise)
1010 .await
1011 .map_err(|e| format!("reading body failed: {:?}", e))?;
1012 let text = text_val.as_string().ok_or_else(|| "body is not a string".to_string())?;
1013
1014 serde_json::from_str(&text).map_err(|e| format!("deserialize failed: {} | body was: {}", e, text))
1015}