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