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