scope_arena.rs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. use crate::innerlude::{throw_error, RenderError, RenderReturn, ScopeOrder};
  2. use crate::prelude::ReactiveContext;
  3. use crate::scope_context::SuspenseLocation;
  4. use crate::{
  5. any_props::{AnyProps, BoxedAnyProps},
  6. innerlude::ScopeState,
  7. scope_context::Scope,
  8. scopes::ScopeId,
  9. virtual_dom::VirtualDom,
  10. };
  11. use crate::{Element, VNode};
  12. impl VirtualDom {
  13. pub(super) fn new_scope(
  14. &mut self,
  15. props: BoxedAnyProps,
  16. name: &'static str,
  17. ) -> &mut ScopeState {
  18. let parent_id = self.runtime.current_scope_id();
  19. let height = match parent_id.and_then(|id| self.runtime.get_state(id)) {
  20. Some(parent) => parent.height() + 1,
  21. None => 0,
  22. };
  23. let suspense_boundary = self
  24. .runtime
  25. .current_suspense_location()
  26. .unwrap_or(SuspenseLocation::NotSuspended);
  27. let entry = self.scopes.vacant_entry();
  28. let id = ScopeId(entry.key());
  29. let scope_runtime = Scope::new(name, id, parent_id, height, suspense_boundary);
  30. let reactive_context = ReactiveContext::new_for_scope(&scope_runtime, &self.runtime);
  31. let scope = entry.insert(ScopeState {
  32. runtime: self.runtime.clone(),
  33. context_id: id,
  34. props,
  35. last_rendered_node: Default::default(),
  36. reactive_context,
  37. });
  38. self.runtime.create_scope(scope_runtime);
  39. tracing::trace!("created scope {id:?} with parent {parent_id:?}");
  40. scope
  41. }
  42. /// Run a scope and return the rendered nodes. This will not modify the DOM or update the last rendered node of the scope.
  43. #[tracing::instrument(skip(self), level = "trace", name = "VirtualDom::run_scope")]
  44. pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> RenderReturn {
  45. debug_assert!(
  46. crate::Runtime::current().is_some(),
  47. "Must be in a dioxus runtime"
  48. );
  49. self.runtime.clone().with_scope_on_stack(scope_id, || {
  50. let scope = &self.scopes[scope_id.0];
  51. let output = {
  52. let scope_state = scope.state();
  53. scope_state.hook_index.set(0);
  54. // Run all pre-render hooks
  55. for pre_run in scope_state.before_render.borrow_mut().iter_mut() {
  56. pre_run();
  57. }
  58. let props: &dyn AnyProps = &*scope.props;
  59. let span = tracing::trace_span!("render", scope = %scope.state().name);
  60. span.in_scope(|| {
  61. scope.reactive_context.reset_and_run_in(|| {
  62. let mut render_return = props.render();
  63. self.handle_element_return(
  64. &mut render_return.node,
  65. scope_id,
  66. &scope.state(),
  67. );
  68. render_return
  69. })
  70. })
  71. };
  72. let scope_state = scope.state();
  73. // Run all post-render hooks
  74. for post_run in scope_state.after_render.borrow_mut().iter_mut() {
  75. post_run();
  76. }
  77. // remove this scope from dirty scopes
  78. self.dirty_scopes
  79. .remove(&ScopeOrder::new(scope_state.height, scope_id));
  80. output
  81. })
  82. }
  83. /// Insert any errors, or suspended tasks from an element return into the runtime
  84. fn handle_element_return(&self, node: &mut Element, scope_id: ScopeId, scope_state: &Scope) {
  85. match node {
  86. Err(RenderError::Aborted(e)) => {
  87. tracing::error!(
  88. "Error while rendering component `{}`:\n{e}",
  89. scope_state.name
  90. );
  91. throw_error(e.clone_mounted());
  92. e.render = VNode::placeholder();
  93. }
  94. Err(RenderError::Suspended(e)) => {
  95. let task = e.task();
  96. // Insert the task into the nearest suspense boundary if it exists
  97. let boundary = self
  98. .runtime
  99. .get_state(scope_id)
  100. .unwrap()
  101. .suspense_location();
  102. let already_suspended = self
  103. .runtime
  104. .tasks
  105. .borrow()
  106. .get(task.id)
  107. .expect("Suspended on a task that no longer exists")
  108. .suspend(boundary.clone());
  109. if !already_suspended {
  110. tracing::trace!("Suspending {:?} on {:?}", scope_id, task);
  111. // Add this task to the suspended tasks list of the boundary
  112. if let SuspenseLocation::UnderSuspense(boundary) = &boundary {
  113. boundary.add_suspended_task(e.clone());
  114. }
  115. self.runtime
  116. .suspended_tasks
  117. .set(self.runtime.suspended_tasks.get() + 1);
  118. }
  119. e.placeholder = VNode::placeholder();
  120. }
  121. Ok(_) => {
  122. // If the render was successful, we can move the render generation forward by one
  123. scope_state
  124. .render_count
  125. .set(scope_state.render_count.get() + 1);
  126. }
  127. }
  128. }
  129. }