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