1use std::{
2 any::TypeId,
3 cell::{Ref, RefCell, RefMut},
4 collections::{hash_map::DefaultHasher, VecDeque},
5 hash::{Hash, Hasher},
6 mem::ManuallyDrop,
7 rc::Rc,
8 rc::Weak,
9};
10
11use wasm_bindgen::{JsCast, JsValue};
12
13use crate::{
14 dom::{
15 document, dom_patch, now, program::app_context::WeakContext, util::body,
16 AnimationFrameHandle, Application, DomNode, DomPatch, IdleCallbackHandle, IdleDeadline,
17 Measurements, SkipDiff, SkipPath,
18 },
19 html::{self, attributes::class, text},
20 vdom::{self, diff, diff_recursive, Patch},
21};
22
23#[cfg(feature = "with-raf")]
24use crate::dom::request_animation_frame;
25
26#[cfg(feature = "with-ric")]
27use crate::dom::request_idle_callback;
28
29thread_local! {
30 static CANCEL_CNT: RefCell<i32> = RefCell::new(0);
31}
32
33thread_local! {
34 static UPDATE_CNT: RefCell<i32> = RefCell::new(0);
35}
36
37mod app_context;
38use self::app_context::AppContext;
39
40mod mount_procedure;
41pub use self::mount_procedure::{MountAction, MountProcedure, MountTarget};
42
43pub struct Program<APP>
45where
46 APP: Application,
47{
48 pub(crate) app_context: AppContext<APP>,
49
50 pub root_node: Rc<RefCell<Option<DomNode>>>,
52
53 pub(crate) mount_node: Rc<RefCell<Option<DomNode>>>,
55
56 pub(crate) pending_patches: Rc<RefCell<VecDeque<DomPatch>>>,
61
62 pub(crate) idle_callback_handles: Rc<RefCell<Vec<IdleCallbackHandle>>>,
64 pub(crate) animation_frame_handles: Rc<RefCell<Vec<AnimationFrameHandle>>>,
66
67 pub(crate) last_update: Rc<RefCell<Option<f64>>>,
69}
70
71pub struct WeakProgram<APP>
72where
73 APP: Application,
74{
75 pub(crate) app_context: WeakContext<APP>,
76 pub(crate) root_node: Weak<RefCell<Option<DomNode>>>,
77 mount_node: Weak<RefCell<Option<DomNode>>>,
78 pending_patches: Weak<RefCell<VecDeque<DomPatch>>>,
79 idle_callback_handles: Weak<RefCell<Vec<IdleCallbackHandle>>>,
80 animation_frame_handles: Weak<RefCell<Vec<AnimationFrameHandle>>>,
81 last_update: Weak<RefCell<Option<f64>>>,
82}
83
84impl<APP> WeakProgram<APP>
85where
86 APP: Application,
87{
88 pub fn upgrade(&self) -> Option<Program<APP>> {
90 let app_context = self.app_context.upgrade()?;
91 let root_node = self.root_node.upgrade()?;
92 let mount_node = self.mount_node.upgrade()?;
93 let pending_patches = self.pending_patches.upgrade()?;
94 let idle_callback_handles = self.idle_callback_handles.upgrade()?;
95 let animation_frame_handles = self.animation_frame_handles.upgrade()?;
96 let last_update = self.last_update.upgrade()?;
97 Some(Program {
98 app_context,
99 root_node,
100 mount_node,
101 pending_patches,
102 idle_callback_handles,
103 animation_frame_handles,
104 last_update,
105 })
106 }
107}
108
109impl<APP> Clone for WeakProgram<APP>
110where
111 APP: Application,
112{
113 fn clone(&self) -> Self {
114 WeakProgram {
115 app_context: self.app_context.clone(),
116 root_node: Weak::clone(&self.root_node),
117 mount_node: Weak::clone(&self.mount_node),
118 pending_patches: Weak::clone(&self.pending_patches),
119 idle_callback_handles: Weak::clone(&self.idle_callback_handles),
120 animation_frame_handles: Weak::clone(&self.animation_frame_handles),
121 last_update: Weak::clone(&self.last_update),
122 }
123 }
124}
125
126impl<APP> Program<APP>
127where
128 APP: Application,
129{
130 pub fn downgrade(&self) -> WeakProgram<APP> {
132 WeakProgram {
133 app_context: AppContext::downgrade(&self.app_context),
134 root_node: Rc::downgrade(&self.root_node),
135 mount_node: Rc::downgrade(&self.mount_node),
136 pending_patches: Rc::downgrade(&self.pending_patches),
137 idle_callback_handles: Rc::downgrade(&self.idle_callback_handles),
138 animation_frame_handles: Rc::downgrade(&self.animation_frame_handles),
139 last_update: Rc::downgrade(&self.last_update),
140 }
141 }
142}
143
144impl<APP> Clone for Program<APP>
145where
146 APP: Application,
147{
148 fn clone(&self) -> Self {
149 Program {
150 app_context: self.app_context.clone(),
151 root_node: Rc::clone(&self.root_node),
152 mount_node: Rc::clone(&self.mount_node),
153 pending_patches: Rc::clone(&self.pending_patches),
154 idle_callback_handles: Rc::clone(&self.idle_callback_handles),
155 animation_frame_handles: Rc::clone(&self.animation_frame_handles),
156 last_update: Rc::clone(&self.last_update),
157 }
158 }
159}
160
161impl<APP> Program<APP>
162where
163 APP: Application,
164{
165 pub fn app(&self) -> Ref<'_, APP> {
167 self.app_context.app.borrow()
168 }
169
170 pub fn app_mut(&self) -> RefMut<'_, APP> {
172 self.app_context.app.borrow_mut()
173 }
174}
175
176impl<APP> Program<APP>
177where
178 APP: Application,
179{
180 pub fn new(app: APP) -> Self {
183 Self::from_rc_app(Rc::new(RefCell::new(app)))
184 }
185
186 pub fn from_rc_app(app: Rc<RefCell<APP>>) -> Self {
188 let app_view = app.borrow().view();
189 Program {
190 app_context: AppContext {
191 app,
192 current_vdom: Rc::new(RefCell::new(app_view)),
193 pending_msgs: Rc::new(RefCell::new(VecDeque::new())),
194 pending_dispatches: Rc::new(RefCell::new(VecDeque::new())),
195 },
196 root_node: Rc::new(RefCell::new(None)),
197 mount_node: Rc::new(RefCell::new(None)),
198 pending_patches: Rc::new(RefCell::new(VecDeque::new())),
199 idle_callback_handles: Rc::new(RefCell::new(vec![])),
200 animation_frame_handles: Rc::new(RefCell::new(vec![])),
201 last_update: Rc::new(RefCell::new(None)),
202 }
203 }
204
205 fn after_mounted(&mut self) {
207 let init_cmd = self.app_context.init_app();
209
210 init_cmd.emit(self.clone());
215
216 self.inject_dynamic_style();
218
219 }
223
224 fn app_hash() -> u64 {
225 let type_id = TypeId::of::<APP>();
226 let mut hasher = DefaultHasher::new();
227 type_id.hash(&mut hasher);
228 hasher.finish()
229 }
230
231 fn inject_stylesheet(&mut self) {
232 let static_style = self.app_context.static_style();
233 if !static_style.is_empty() {
234 let class_names = format!("static {}", Self::app_hash());
235 self.inject_style(class_names, &static_style);
236 }
237 }
238
239 fn inject_dynamic_style(&mut self) {
240 let dynamic_style = self.app_context.dynamic_style();
241 if !dynamic_style.is_empty() {
242 let class_names = format!("dynamic {}", Self::app_hash());
243 self.inject_style(class_names, &dynamic_style);
244 }
245 }
246
247 pub fn append_to_mount(app: APP, mount_node: &web_sys::Node) -> ManuallyDrop<Self> {
265 let mut program = Self::new(app);
266 program.mount(mount_node, MountProcedure::append());
267 ManuallyDrop::new(program)
268 }
269
270 pub fn replace_mount(app: APP, mount_node: &web_sys::Node) -> ManuallyDrop<Self> {
288 let mut program = Self::new(app);
289 program.mount(mount_node, MountProcedure::replace());
290 ManuallyDrop::new(program)
291 }
292
293 pub fn clear_append_to_mount(app: APP, mount_node: &web_sys::Node) -> ManuallyDrop<Self> {
295 let mut program = Self::new(app);
296 program.mount(mount_node, MountProcedure::clear_append());
297 ManuallyDrop::new(program)
298 }
299
300 pub fn clear_mount(app: APP, mount_node: &web_sys::Node) -> ManuallyDrop<Self> {
302 Self::clear_append_to_mount(app, mount_node)
303 }
304
305 pub fn clear_mount_to_body(app: APP) -> ManuallyDrop<Self> {
307 Self::clear_append_to_mount(app, &body())
308 }
309
310 pub fn mount_to_body(app: APP) -> ManuallyDrop<Self> {
327 Self::append_to_mount(app, &body())
328 }
329
330 pub fn pre_mount(&mut self) {
332 self.inject_stylesheet();
333 }
334
335 #[allow(unused)]
336 pub(crate) fn create_initial_view(&self) -> DomNode {
339 let current_view = self.app_context.current_vdom();
340 let real_view = current_view.unwrap_template_ref();
341 self.create_dom_node(real_view)
342 }
343
344 pub fn mount(&mut self, mount_node: &web_sys::Node, mount_procedure: MountProcedure) {
347 let mount_node = DomNode::from(mount_node.clone());
348 *self.mount_node.borrow_mut() = Some(mount_node);
349 self.pre_mount();
350
351 let created_node = self.create_initial_view();
352
353 let mount_node: DomNode = match mount_procedure.target {
354 MountTarget::MountNode => self
355 .mount_node
356 .borrow()
357 .as_ref()
358 .expect("mount node")
359 .clone(),
360 MountTarget::ShadowRoot => {
361 let mount_element: web_sys::Element = self
362 .mount_node
363 .borrow()
364 .as_ref()
365 .expect("mount node")
366 .as_element();
367
368 mount_element
369 .attach_shadow(&web_sys::ShadowRootInit::new(web_sys::ShadowRootMode::Open))
370 .expect("unable to attached shadow");
371 let mount_shadow = mount_element.shadow_root().expect("must have a shadow");
372 let shadow_node: web_sys::Node = mount_shadow.unchecked_into();
373
374 *self.mount_node.borrow_mut() = Some(DomNode::from(shadow_node));
375 self.mount_node
376 .borrow()
377 .as_ref()
378 .expect("mount_node")
379 .clone()
380 }
381 };
382
383 match mount_procedure.action {
384 MountAction::Append => {
385 mount_node.append_children(vec![created_node.clone()]);
386 }
387 MountAction::ClearAppend => {
388 mount_node.clear_children();
389 mount_node.append_children(vec![created_node.clone()]);
390 }
391 MountAction::Replace => {
392 mount_node.replace_node(created_node.clone());
393 }
394 }
395 *self.root_node.borrow_mut() = Some(created_node);
396 self.after_mounted();
397 }
398
399 #[cfg(feature = "with-ric")]
400 fn dispatch_pending_msgs_with_ric(&mut self) -> Result<(), JsValue> {
401 let program = Program::downgrade(&self);
402 let handle = request_idle_callback(move |deadline| {
403 let mut program = program.upgrade().expect("must upgrade");
404 program
405 .dispatch_pending_msgs(Some(deadline))
406 .expect("must execute")
407 })
408 .expect("must execute");
409 self.idle_callback_handles.borrow_mut().push(handle);
410 Ok(())
411 }
412
413 fn dispatch_pending_msgs(&mut self, deadline: Option<IdleDeadline>) -> Result<(), JsValue> {
417 if !self.app_context.has_pending_msgs() {
418 return Ok(());
419 }
420 let mut did_complete = true;
421 while self.app_context.dispatch_pending_msg() {
422 if let Some(deadline) = &deadline {
424 if deadline.did_timeout() {
425 did_complete = false;
426 break;
427 }
428 }
429 }
430 if !did_complete {
431 log::info!("did not complete pending msgs in time.. dispatching the rest");
432 #[cfg(feature = "with-ric")]
433 self.dispatch_pending_msgs_with_ric()
434 .expect("must complete");
435 }
436 Ok(())
437 }
438
439 pub fn update_dom(&mut self) -> Result<(), JsValue> {
441 let t1 = now();
442 if let Some(last_update) = self.last_update.borrow().as_ref() {
444 let frame_time = (1000_f64 / 60_f64).floor(); let time_delta = t1 - last_update;
446 let remaining = frame_time - time_delta;
447 if time_delta < frame_time {
448 log::warn!("update is {remaining} too soon!... time_delta: {time_delta}, frame_time: {frame_time}");
449 let mut program = self.clone();
450 crate::dom::request_timeout_callback(
452 move || {
453 program.update_dom().unwrap();
454 },
455 remaining.round() as i32,
456 )
457 .unwrap();
458 log::info!("update is cancelled..");
459 CANCEL_CNT.with_borrow_mut(|c| *c += 1);
460 return Ok(());
461 }
462 }
463 log::info!("Doing and update...");
464 UPDATE_CNT.with_borrow_mut(|c| *c += 1);
465 log::info!(
466 "ratio(cancelled/update): {}/{}",
467 CANCEL_CNT.with_borrow(|c| *c),
468 UPDATE_CNT.with_borrow(|c| *c)
469 );
470 let view = self.app_context.view();
472 let t2 = now();
473
474 let node_count = view.node_count();
475 let skip_diff = view.skip_diff();
476
477 let dom_patches = if let Some(skip_diff) = skip_diff {
478 let current_vdom = self.app_context.current_vdom();
479 let real_current_vdom = current_vdom.unwrap_template_ref();
480 let real_view = view.unwrap_template_ref();
481 let patches =
482 self.create_patches_with_skip_diff(real_current_vdom, real_view, &skip_diff);
483 #[cfg(all(feature = "with-debug", feature = "log-patches"))]
484 {
485 log::info!("There are {} patches", patches.len());
486 log::info!("patches: {patches:#?}");
487 }
488 self.convert_patches(
489 self.root_node
490 .borrow()
491 .as_ref()
492 .expect("must have a root node"),
493 &patches,
494 )
495 .expect("must convert patches")
496 } else {
497 let current_vdom = self.app_context.current_vdom();
498 create_dom_patch(
499 &self.root_node,
500 ¤t_vdom,
501 &view,
502 self.create_ev_callback(),
503 )
504 };
505
506 let total_patches = dom_patches.len();
507
508 self.queue_dom_patches(dom_patches).expect("must not error");
510 self.app_context.set_current_dom(view);
512 let t3 = now();
513
514 let strong_count = self.app_context.strong_count();
515 let weak_count = self.app_context.weak_count();
516 let measurements = Measurements {
517 node_count,
518 build_view_took: t2 - t1,
519 total_patches,
520 dom_update_took: t3 - t2,
521 total_time: t3 - t1,
522 strong_count,
523 weak_count,
524 };
525
526 if measurements.total_time > 16.0 {
527 #[cfg(all(feature = "with-measure", feature = "with-debug"))]
528 {
529 log::warn!("dispatch took {}ms", measurements.total_time.round());
530 }
531 }
532
533 #[cfg(feature = "with-measure")]
535 self.app_context.measurements(measurements);
536
537 *self.last_update.borrow_mut() = Some(t3);
538 Ok(())
539 }
540
541 pub fn queue_dom_patches(&mut self, dom_patches: Vec<DomPatch>) -> Result<(), JsValue> {
545 self.pending_patches.borrow_mut().extend(dom_patches);
546
547 #[cfg(feature = "with-raf")]
548 self.apply_pending_patches_with_raf().expect("raf");
549
550 #[cfg(not(feature = "with-raf"))]
551 self.apply_pending_patches().expect("raf");
552
553 Ok(())
554 }
555
556 pub(crate) fn create_patches_with_skip_diff<'a>(
557 &self,
558 old_vdom: &'a vdom::Node<APP::MSG>,
559 new_vdom: &'a vdom::Node<APP::MSG>,
560 skip_diff: &SkipDiff,
561 ) -> Vec<Patch<'a, APP::MSG>> {
562 use crate::vdom::TreePath;
563 assert!(!old_vdom.is_template(), "old vdom should not be a template");
564 assert!(!new_vdom.is_template(), "new vdom should not be a template");
565 diff_recursive(
566 old_vdom,
567 new_vdom,
568 &SkipPath::new(TreePath::root(), skip_diff.clone()),
569 )
570 }
571
572 #[cfg(feature = "with-raf")]
573 fn apply_pending_patches_with_raf(&mut self) -> Result<(), JsValue> {
574 let program = Program::downgrade(&self);
575 let handle = request_animation_frame(move || {
576 let mut program = program.upgrade().expect("must upgrade");
577 program.apply_pending_patches().expect("must not error");
578 })
579 .expect("must execute");
580 self.animation_frame_handles.borrow_mut().push(handle);
581 Ok(())
582 }
583
584 fn apply_pending_patches(&mut self) -> Result<(), JsValue> {
586 if self.pending_patches.borrow().is_empty() {
587 return Ok(());
588 }
589 let dom_patches: Vec<DomPatch> = self.pending_patches.borrow_mut().drain(..).collect();
590 dom_patch::apply_dom_patches(
591 Rc::clone(&self.root_node),
592 Rc::clone(&self.mount_node),
593 dom_patches,
594 )?;
595
596 Ok(())
597 }
598
599 #[cfg(feature = "with-ric")]
600 fn dispatch_inner_with_ric(&self) {
601 let program = Program::downgrade(&self);
602 let handle = request_idle_callback(move |deadline| {
603 if let Some(mut program) = program.upgrade() {
604 program.dispatch_inner(Some(deadline));
605 } else {
606 log::warn!("unable to upgrade program.. maybe try again next time..");
607 }
608 })
609 .expect("must execute");
610 self.idle_callback_handles.borrow_mut().push(handle);
611 }
612
613 #[allow(unused)]
614 #[cfg(feature = "with-raf")]
615 fn dispatch_inner_with_raf(&self) {
616 let program = Program::downgrade(&self);
617 let handle = request_animation_frame(move || {
618 let mut program = program.upgrade().expect("must upgrade");
619 program.dispatch_inner(None);
620 })
621 .expect("must execute");
622 self.animation_frame_handles.borrow_mut().push(handle);
623 }
624
625 fn dispatch_inner_with_priority_ric(&self) {
626 #[cfg(feature = "with-ric")]
627 self.dispatch_inner_with_ric();
628 #[cfg(not(feature = "with-ric"))]
629 {
630 #[cfg(feature = "with-raf")]
631 self.dispatch_inner_with_raf();
632
633 #[cfg(not(feature = "with-raf"))]
634 {
635 let program = Program::downgrade(self);
636 wasm_bindgen_futures::spawn_local(async move {
637 if let Some(mut program) = program.upgrade() {
638 program.dispatch_inner(None);
639 } else {
640 log::warn!(
641 "unable to upgrade program here, in dispatch_inner_with_priority_ric"
642 );
643 }
644 })
645 }
646 }
647 }
648
649 fn dispatch_inner(&mut self, deadline: Option<IdleDeadline>) {
656 self.dispatch_pending_msgs(deadline)
657 .expect("must dispatch msgs");
658 #[cfg(feature = "ensure-check")]
660 if self.app_context.has_pending_msgs() {
661 log::info!(
662 "There are still: {} pending msgs",
663 self.app_context.pending_msgs_count()
664 );
665 self.dispatch_pending_msgs(None)
666 .expect("must dispatch all pending msgs");
667 }
668 #[cfg(feature = "ensure-check")]
669 if self.app_context.has_pending_msgs() {
670 panic!("Can not proceed until previous pending msgs are dispatched..");
671 }
672
673 let cmd = self.app_context.batch_pending_cmds();
674
675 if !self.pending_patches.borrow().is_empty() {
676 log::error!(
677 "BEFORE DOM updates there are still Remaining pending patches: {}",
678 self.pending_patches.borrow().len()
679 );
680 }
681
682 self.update_dom().expect("must update dom");
683
684 #[cfg(feature = "ensure-check")]
686 if !self.pending_patches.borrow().is_empty() {
687 self.apply_pending_patches()
688 .expect("applying pending patches..");
689 }
690
691 #[cfg(feature = "ensure-check")]
692 if !self.pending_patches.borrow().is_empty() {
693 log::error!(
694 "Remaining pending patches: {}",
695 self.pending_patches.borrow().len()
696 );
697 panic!(
698 "There are still pending patches.. can not emit cmd, if all pending patches
699 has not been applied yet!"
700 );
701 }
702
703 cmd.emit(self.clone());
705 }
706
707 fn inject_style(&mut self, class_names: String, style: &str) {
709 let style_node = html::tags::style([class(class_names)], [text(style)]);
710 let created_node = self.create_dom_node(&style_node);
711
712 let head = document().head().expect("must have a head");
713 let head_node: web_sys::Node = head.unchecked_into();
714 let dom_head = DomNode::from(head_node);
715 dom_head.append_children(vec![created_node]);
716 }
717
718 pub fn inject_style_to_mount(&mut self, style: &str) {
720 let style_node = html::tags::style([], [text(style)]);
721 let created_node = self.create_dom_node(&style_node);
722
723 self.mount_node
724 .borrow_mut()
725 .as_mut()
726 .expect("mount node")
727 .append_children(vec![created_node]);
728 }
729
730 pub fn dispatch_multiple(&mut self, msgs: impl IntoIterator<Item = APP::MSG>) {
732 self.app_context.push_msgs(msgs);
733 self.dispatch_inner_with_priority_ric();
734 }
735
736 pub fn dispatch(&mut self, msg: APP::MSG) {
738 self.dispatch_multiple([msg])
739 }
740
741 pub fn create_dom_patch(&self, new_vdom: &vdom::Node<APP::MSG>) -> Vec<DomPatch> {
745 create_dom_patch(
746 &self.root_node,
747 &self.app_context.current_vdom(),
748 new_vdom,
749 self.create_ev_callback(),
750 )
751 }
752}
753
754fn create_dom_patch<Msg, F>(
755 root_node: &Rc<RefCell<Option<DomNode>>>,
756 current_vdom: &vdom::Node<Msg>,
757 new_vdom: &vdom::Node<Msg>,
758 ev_callback: F,
759) -> Vec<DomPatch>
760where
761 Msg: 'static,
762 F: Fn(Msg) + 'static + Clone,
763{
764 let patches = diff(¤t_vdom, new_vdom);
765
766 #[cfg(all(feature = "with-debug", feature = "log-patches"))]
767 {
768 log::debug!("There are {} patches", patches.len());
769 log::debug!("patches: {patches:#?}");
770 }
771
772 dom_patch::convert_patches(
773 root_node.borrow().as_ref().expect("must have a root node"),
774 &patches,
775 ev_callback,
776 )
777 .expect("must convert patches")
778}
779
780impl<APP> Program<APP>
781where
782 APP: Application,
783{
784 #[cfg(feature = "test-fixtures")]
788 pub fn update_dom_with_vdom(
789 &mut self,
790 new_vdom: vdom::Node<APP::MSG>,
791 ) -> Result<usize, JsValue> {
792 let dom_patches = self.create_dom_patch(&new_vdom);
793 let total_patches = dom_patches.len();
794 self.pending_patches.borrow_mut().extend(dom_patches);
795
796 self.apply_pending_patches().expect("raf");
797
798 self.app_context.set_current_dom(new_vdom);
799 Ok(total_patches)
800 }
801}