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