1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
use std::rc::Rc;
use crate::{
Element, ReactiveContext,
innerlude::{BoxedAnyProps, RenderError, ScopeState},
render_driver::RenderDriver,
scope_context::{Scope, SuspenseLocation},
scopes::ScopeId,
virtual_dom::VirtualDom,
};
impl VirtualDom {
/// Create a scope rendering into the current scope's render target (the
/// root target when no scope is active). `driver` owns the scope's
/// rendering lifecycle and props; portal drivers retarget the scope
/// during their first create.
pub(super) fn new_scope(
&mut self,
name: &'static str,
driver: Rc<dyn RenderDriver>,
props: BoxedAnyProps,
) -> &mut ScopeState {
let target_id = self.runtime.current_render_target_id();
let parent_id = self.runtime.try_current_scope_id();
let height = match parent_id.and_then(|id| self.runtime.try_get_state(id)) {
Some(parent) => parent.height() + 1,
None => 0,
};
let inherited_suspense_location = self
.runtime
.current_suspense_location()
.unwrap_or(SuspenseLocation::NotSuspended);
let suspense_location = driver.suspense_location(inherited_suspense_location);
let entry = self.scopes.vacant_entry();
let id = ScopeId::new(entry.key());
let scope_runtime = Scope::new(
name,
id,
parent_id,
target_id,
height,
suspense_location,
driver,
);
let reactive_context = ReactiveContext::new_for_scope(&scope_runtime, &self.runtime);
let scope = entry.insert(ScopeState {
runtime: self.runtime.clone(),
context_id: id,
props,
last_rendered_node: Default::default(),
reactive_context,
});
self.runtime.create_scope(scope_runtime);
scope
}
/// Run a scope's body and return the rendered nodes. This will not modify the DOM or update
/// the last rendered node of the scope.
#[tracing::instrument(skip(self), level = "trace", name = "VirtualDom::run_scope")]
#[track_caller]
pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> Element {
// Ensure we are currently inside a `Runtime`.
crate::Runtime::current();
self.runtime.clone().with_scope_on_stack(scope_id, || {
let scope = &self.scopes[scope_id.index()];
let output = {
let scope_state = scope.state();
scope_state.hook_index.set(0);
// Run all pre-render hooks
for pre_run in scope_state.before_render.borrow_mut().iter_mut() {
pre_run();
}
let span = tracing::trace_span!("render", scope = %scope.state().name);
span.in_scope(|| {
scope.reactive_context.reset_and_run_in(|| {
let mut render_return = scope.props.render();
self.handle_element_return(&mut render_return, &scope.state());
render_return
})
})
};
{
let scope_state = scope.state();
// Run all post-render hooks
for post_run in scope_state.after_render.borrow_mut().iter_mut() {
post_run();
}
}
// remove this scope from dirty scopes
self.mark_clean(scope_id);
output
})
}
/// Insert any errors, or suspended tasks from an element return into the runtime
fn handle_element_return(&self, node: &mut Element, scope: &Scope) {
match node {
Err(RenderError::Error(e)) => {
tracing::error!("Error while rendering component `{}`: {e}", scope.name);
self.runtime.throw_error(scope.id, e.clone());
}
Err(RenderError::Suspended(e)) => {
let task = e.task();
// Insert the task into the nearest suspense boundary if it exists
let boundary = scope.suspense_location();
let already_suspended = self
.runtime
.tasks
.borrow()
.get(task.id)
.expect("Suspended on a task that no longer exists")
.suspend(boundary.clone());
if !already_suspended {
tracing::trace!("Suspending {:?} on {:?}", scope.id, task);
// Every user-rendered scope sits inside the implicit
// `SuspenseBoundary` from `RootScopeWrapper`, so a
// suspended scope's location always carries a boundary
// context (`UnderSuspense` or `InSuspensePlaceholder`).
boundary
.suspense_context()
.expect("suspended scope must have a SuspenseContext")
.add_suspended_task(e.clone());
self.runtime
.suspended_tasks
.set(self.runtime.suspended_tasks.get() + 1);
}
}
Ok(_) => {
// If the render was successful, we can move the render generation forward by one
scope.render_count.set(scope.render_count.get() + 1);
}
}
}
}