1use crate::arena::ElementRef;
2use crate::innerlude::{DirtyTasks, Effect};
3use crate::nodes::VNodeMount;
4use crate::scheduler::ScopeOrder;
5use crate::scope_context::SuspenseLocation;
6use crate::{
7 innerlude::{LocalTask, SchedulerMsg},
8 scope_context::Scope,
9 scopes::ScopeId,
10 Task,
11};
12use crate::{AttributeValue, ElementId, Event};
13use slab::Slab;
14use slotmap::DefaultKey;
15use std::any::Any;
16use std::collections::BTreeSet;
17use std::fmt;
18use std::{
19 cell::{Cell, Ref, RefCell},
20 rc::Rc,
21};
22use tracing::instrument;
23
24thread_local! {
25 static RUNTIMES: RefCell<Vec<Rc<Runtime>>> = const { RefCell::new(vec![]) };
26}
27
28pub struct Runtime {
30 pub(crate) scope_states: RefCell<Vec<Option<Scope>>>,
31
32 scope_stack: RefCell<Vec<ScopeId>>,
35
36 suspense_stack: RefCell<Vec<SuspenseLocation>>,
39
40 pub(crate) current_task: Cell<Option<Task>>,
42
43 pub(crate) tasks: RefCell<slotmap::SlotMap<DefaultKey, Rc<LocalTask>>>,
45
46 pub(crate) suspended_tasks: Cell<usize>,
48
49 pub(crate) rendering: Cell<bool>,
50
51 pub(crate) sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
52
53 pub(crate) pending_effects: RefCell<BTreeSet<Effect>>,
55
56 pub(crate) dirty_tasks: RefCell<BTreeSet<DirtyTasks>>,
58
59 pub(crate) elements: RefCell<Slab<Option<ElementRef>>>,
62
63 pub(crate) mounts: RefCell<Slab<VNodeMount>>,
67}
68
69impl Runtime {
70 pub(crate) fn new(sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>) -> Rc<Self> {
71 let mut elements = Slab::default();
72 elements.insert(None);
74
75 Rc::new(Self {
76 sender,
77 rendering: Cell::new(false),
78 scope_states: Default::default(),
79 scope_stack: Default::default(),
80 suspense_stack: Default::default(),
81 current_task: Default::default(),
82 tasks: Default::default(),
83 suspended_tasks: Default::default(),
84 pending_effects: Default::default(),
85 dirty_tasks: Default::default(),
86 elements: RefCell::new(elements),
87 mounts: Default::default(),
88 })
89 }
90
91 pub fn current() -> Result<Rc<Self>, RuntimeError> {
93 RUNTIMES
94 .with(|stack| stack.borrow().last().cloned())
95 .ok_or(RuntimeError::new())
96 }
97
98 pub fn wrap_closure<'a, I, O>(f: impl Fn(I) -> O + 'a) -> impl Fn(I) -> O + 'a {
100 let current_runtime = Self::current().unwrap();
101 let current_scope = current_runtime.current_scope_id().ok();
102 move |input| match current_scope {
103 Some(scope) => current_runtime.on_scope(scope, || f(input)),
104 None => {
105 let _runtime_guard = RuntimeGuard::new(current_runtime.clone());
106 f(input)
107 }
108 }
109 }
110
111 pub(crate) fn while_rendering<T>(&self, f: impl FnOnce() -> T) -> T {
113 self.rendering.set(true);
114 let result = f();
115 self.rendering.set(false);
116 result
117 }
118
119 pub(crate) fn while_not_rendering<T>(&self, f: impl FnOnce() -> T) -> T {
121 let previous = self.rendering.get();
122 self.rendering.set(false);
123 let result = f();
124 self.rendering.set(previous);
125 result
126 }
127
128 pub(crate) fn create_scope(&self, context: Scope) {
130 let id = context.id;
131 let mut scopes = self.scope_states.borrow_mut();
132 if scopes.len() <= id.0 {
133 scopes.resize_with(id.0 + 1, Default::default);
134 }
135 scopes[id.0] = Some(context);
136 }
137
138 pub(crate) fn remove_scope(self: &Rc<Self>, id: ScopeId) {
139 {
140 let borrow = self.scope_states.borrow();
141 if let Some(scope) = &borrow[id.0] {
142 self.on_scope(id, || {
144 for id in scope.spawned_tasks.take() {
147 self.remove_task(id);
148 }
149
150 self.pending_effects
152 .borrow_mut()
153 .remove(&ScopeOrder::new(scope.height, scope.id));
154
155 for hook in scope.hooks.take().drain(..).rev() {
157 drop(hook);
158 }
159
160 scope.shared_contexts.take();
162 });
163 }
164 }
165 self.scope_states.borrow_mut()[id.0].take();
166 }
167
168 pub(crate) fn current_scope_id(&self) -> Result<ScopeId, RuntimeError> {
170 self.scope_stack
171 .borrow()
172 .last()
173 .copied()
174 .ok_or(RuntimeError { _priv: () })
175 }
176
177 pub fn on_scope<O>(self: &Rc<Self>, id: ScopeId, f: impl FnOnce() -> O) -> O {
181 let _runtime_guard = RuntimeGuard::new(self.clone());
182 {
183 self.push_scope(id);
184 }
185 let o = f();
186 {
187 self.pop_scope();
188 }
189 o
190 }
191
192 pub(crate) fn current_suspense_location(&self) -> Option<SuspenseLocation> {
194 self.suspense_stack.borrow().last().cloned()
195 }
196
197 pub(crate) fn with_suspense_location<O>(
199 &self,
200 suspense_location: SuspenseLocation,
201 f: impl FnOnce() -> O,
202 ) -> O {
203 self.suspense_stack.borrow_mut().push(suspense_location);
204 let o = f();
205 self.suspense_stack.borrow_mut().pop();
206 o
207 }
208
209 pub(crate) fn with_scope_on_stack<O>(&self, scope: ScopeId, f: impl FnOnce() -> O) -> O {
211 self.push_scope(scope);
212 let o = f();
213 self.pop_scope();
214 o
215 }
216
217 fn push_scope(&self, scope: ScopeId) {
219 let suspense_location = self
220 .scope_states
221 .borrow()
222 .get(scope.0)
223 .and_then(|s| s.as_ref())
224 .map(|s| s.suspense_location())
225 .unwrap_or_default();
226 self.suspense_stack.borrow_mut().push(suspense_location);
227 self.scope_stack.borrow_mut().push(scope);
228 }
229
230 fn pop_scope(&self) {
232 self.scope_stack.borrow_mut().pop();
233 self.suspense_stack.borrow_mut().pop();
234 }
235
236 pub(crate) fn get_state(&self, id: ScopeId) -> Option<Ref<'_, Scope>> {
240 Ref::filter_map(self.scope_states.borrow(), |contexts| {
241 contexts.get(id.0).and_then(|f| f.as_ref())
242 })
243 .ok()
244 }
245
246 pub(crate) fn push(runtime: Rc<Runtime>) {
248 RUNTIMES.with(|stack| stack.borrow_mut().push(runtime));
249 }
250
251 pub(crate) fn pop() {
253 RUNTIMES.with(|stack| stack.borrow_mut().pop());
254 }
255
256 pub(crate) fn with<R>(f: impl FnOnce(&Runtime) -> R) -> Result<R, RuntimeError> {
258 Self::current().map(|r| f(&r))
259 }
260
261 pub(crate) fn with_current_scope<R>(f: impl FnOnce(&Scope) -> R) -> Result<R, RuntimeError> {
263 Self::with(|rt| {
264 rt.current_scope_id()
265 .ok()
266 .and_then(|scope| rt.get_state(scope).map(|sc| f(&sc)))
267 })
268 .ok()
269 .flatten()
270 .ok_or(RuntimeError::new())
271 }
272
273 pub(crate) fn with_scope<R>(
275 scope: ScopeId,
276 f: impl FnOnce(&Scope) -> R,
277 ) -> Result<R, RuntimeError> {
278 Self::with(|rt| rt.get_state(scope).map(|sc| f(&sc)))
279 .ok()
280 .flatten()
281 .ok_or(RuntimeError::new())
282 }
283
284 pub(crate) fn finish_render(&self) {
286 if !self.pending_effects.borrow().is_empty() {
288 self.sender
289 .unbounded_send(SchedulerMsg::EffectQueued)
290 .expect("Scheduler should exist");
291 }
292 }
293
294 pub(crate) fn scope_should_render(&self, scope_id: ScopeId) -> bool {
296 if self.suspended_tasks.get() == 0 {
298 return true;
299 }
300 let scopes = self.scope_states.borrow();
302 let scope = &scopes[scope_id.0].as_ref().unwrap();
303 !matches!(scope.suspense_location(), SuspenseLocation::UnderSuspense(suspense) if suspense.is_suspended())
304 }
305
306 #[instrument(skip(self, event), level = "trace", name = "Runtime::handle_event")]
316 pub fn handle_event(self: &Rc<Self>, name: &str, event: Event<dyn Any>, element: ElementId) {
317 let _runtime = RuntimeGuard::new(self.clone());
318 let elements = self.elements.borrow();
319
320 if let Some(Some(parent_path)) = elements.get(element.0).copied() {
321 if event.propagates() {
322 self.handle_bubbling_event(parent_path, name, event);
323 } else {
324 self.handle_non_bubbling_event(parent_path, name, event);
325 }
326 }
327 }
328
329 #[instrument(
351 skip(self, uievent),
352 level = "trace",
353 name = "VirtualDom::handle_bubbling_event"
354 )]
355 fn handle_bubbling_event(&self, parent: ElementRef, name: &str, uievent: Event<dyn Any>) {
356 let mounts = self.mounts.borrow();
357
358 let mut parent = Some(parent);
361 while let Some(path) = parent {
362 let mut listeners = vec![];
363
364 let Some(mount) = mounts.get(path.mount.0) else {
365 return;
367 };
368 let el_ref = &mount.node;
369 let node_template = el_ref.template;
370 let target_path = path.path;
371
372 for (idx, this_path) in node_template.attr_paths.iter().enumerate() {
374 let attrs = &*el_ref.dynamic_attrs[idx];
375
376 for attr in attrs.iter() {
377 if attr.name.get(2..) == Some(name) && target_path.is_descendant(this_path) {
379 listeners.push(&attr.value);
380
381 if target_path == this_path {
385 break;
386 }
387 }
388 }
389 }
390
391 tracing::event!(
394 tracing::Level::TRACE,
395 "Calling {} listeners",
396 listeners.len()
397 );
398 for listener in listeners.into_iter().rev() {
399 if let AttributeValue::Listener(listener) = listener {
400 listener.call(uievent.clone());
401 let metadata = uievent.metadata.borrow();
402
403 if !metadata.propagates {
404 return;
405 }
406 }
407 }
408
409 let mount = el_ref.mount.get().as_usize();
410 parent = mount.and_then(|id| mounts.get(id).and_then(|el| el.parent));
411 }
412 }
413
414 #[instrument(
416 skip(self, uievent),
417 level = "trace",
418 name = "VirtualDom::handle_non_bubbling_event"
419 )]
420 fn handle_non_bubbling_event(&self, node: ElementRef, name: &str, uievent: Event<dyn Any>) {
421 let mounts = self.mounts.borrow();
422 let Some(mount) = mounts.get(node.mount.0) else {
423 return;
425 };
426 let el_ref = &mount.node;
427 let node_template = el_ref.template;
428 let target_path = node.path;
429
430 for (idx, this_path) in node_template.attr_paths.iter().enumerate() {
431 let attrs = &*el_ref.dynamic_attrs[idx];
432
433 for attr in attrs.iter() {
434 if attr.name.get(2..) == Some(name) && target_path == this_path {
437 if let AttributeValue::Listener(listener) = &attr.value {
438 listener.call(uievent.clone());
439 break;
440 }
441 }
442 }
443 }
444 }
445}
446
447pub struct RuntimeGuard(());
482
483impl RuntimeGuard {
484 pub fn new(runtime: Rc<Runtime>) -> Self {
486 Runtime::push(runtime);
487 Self(())
488 }
489}
490
491impl Drop for RuntimeGuard {
492 fn drop(&mut self) {
493 Runtime::pop();
494 }
495}
496
497pub struct RuntimeError {
499 _priv: (),
500}
501
502impl RuntimeError {
503 #[inline(always)]
504 pub(crate) fn new() -> Self {
505 Self { _priv: () }
506 }
507}
508
509impl fmt::Debug for RuntimeError {
510 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
511 f.debug_struct("RuntimeError").finish()
512 }
513}
514
515impl fmt::Display for RuntimeError {
516 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
517 write!(
518 f,
519 "Must be called from inside a Dioxus runtime.
520
521Help: Some APIs in dioxus require a global runtime to be present.
522If you are calling one of these APIs from outside of a dioxus runtime
523(typically in a web-sys closure or dynamic library), you will need to
524grab the runtime from a scope that has it and then move it into your
525new scope with a runtime guard.
526
527For example, if you are trying to use dioxus apis from a web-sys
528closure, you can grab the runtime from the scope it is created in:
529
530```rust
531use dioxus::prelude::*;
532static COUNT: GlobalSignal<i32> = Signal::global(|| 0);
533
534#[component]
535fn MyComponent() -> Element {{
536 use_effect(|| {{
537 // Grab the runtime from the MyComponent scope
538 let runtime = Runtime::current().expect(\"Components run in the Dioxus runtime\");
539 // Move the runtime into the web-sys closure scope
540 let web_sys_closure = Closure::new(|| {{
541 // Then create a guard to provide the runtime to the closure
542 let _guard = RuntimeGuard::new(runtime);
543 // and run whatever code needs the runtime
544 tracing::info!(\"The count is: {{COUNT}}\");
545 }});
546 }})
547}}
548```"
549 )
550 }
551}
552
553impl std::error::Error for RuntimeError {}