1use crate::nodes::VNodeMount;
2use crate::scheduler::ScopeOrder;
3use crate::scope_context::SuspenseLocation;
4use crate::{arena::ElementRef, CapturedError};
5use crate::{
6 innerlude::{DirtyTasks, Effect},
7 SuspenseContext,
8};
9use crate::{
10 innerlude::{LocalTask, SchedulerMsg},
11 scope_context::Scope,
12 scopes::ScopeId,
13 Task,
14};
15use crate::{AttributeValue, ElementId, Event};
16use generational_box::{AnyStorage, Owner};
17use slab::Slab;
18use slotmap::DefaultKey;
19use std::any::Any;
20use std::collections::BTreeSet;
21use std::{
22 cell::{Cell, Ref, RefCell},
23 rc::Rc,
24};
25use tracing::instrument;
26
27thread_local! {
28 static RUNTIMES: RefCell<Vec<Rc<Runtime>>> = const { RefCell::new(vec![]) };
29}
30
31pub struct Runtime {
33 scope_stack: RefCell<Vec<ScopeId>>,
36
37 suspense_stack: RefCell<Vec<SuspenseLocation>>,
40
41 pub(crate) scope_states: RefCell<Vec<Option<Scope>>>,
43
44 pub(crate) current_task: Cell<Option<Task>>,
46
47 pub(crate) tasks: RefCell<slotmap::SlotMap<DefaultKey, Rc<LocalTask>>>,
49
50 pub(crate) suspended_tasks: Cell<usize>,
52
53 pub(crate) rendering: Cell<bool>,
54
55 pub(crate) sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
56
57 pub(crate) pending_effects: RefCell<BTreeSet<Effect>>,
59
60 pub(crate) dirty_tasks: RefCell<BTreeSet<DirtyTasks>>,
62
63 pub(crate) elements: RefCell<Slab<Option<ElementRef>>>,
66
67 pub(crate) mounts: RefCell<Slab<VNodeMount>>,
71}
72
73impl Runtime {
74 pub(crate) fn new(sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>) -> Rc<Self> {
75 let mut elements = Slab::default();
76 elements.insert(None);
78
79 Rc::new(Self {
80 sender,
81 rendering: Cell::new(false),
82 scope_states: Default::default(),
83 scope_stack: Default::default(),
84 suspense_stack: Default::default(),
85 current_task: Default::default(),
86 tasks: Default::default(),
87 suspended_tasks: Default::default(),
88 pending_effects: Default::default(),
89 dirty_tasks: Default::default(),
90 elements: RefCell::new(elements),
91 mounts: Default::default(),
92 })
93 }
94
95 pub fn current() -> Rc<Self> {
97 RUNTIMES
98 .with(|stack| stack.borrow().last().cloned())
99 .unwrap_or_else(|| {
100 panic!(
101 "Must be called from inside a Dioxus runtime.
102
103Help: Some APIs in dioxus require a global runtime to be present.
104If you are calling one of these APIs from outside of a dioxus runtime
105(typically in a web-sys closure or dynamic library), you will need to
106grab the runtime from a scope that has it and then move it into your
107new scope with a runtime guard.
108
109For example, if you are trying to use dioxus apis from a web-sys
110closure, you can grab the runtime from the scope it is created in:
111
112```rust
113use dioxus::prelude::*;
114static COUNT: GlobalSignal<i32> = Signal::global(|| 0);
115
116#[component]
117fn MyComponent() -> Element {{
118 use_effect(|| {{
119 // Grab the runtime from the MyComponent scope
120 let runtime = Runtime::current().expect(\"Components run in the Dioxus runtime\");
121 // Move the runtime into the web-sys closure scope
122 let web_sys_closure = Closure::new(|| {{
123 // Then create a guard to provide the runtime to the closure
124 let _guard = RuntimeGuard::new(runtime);
125 // and run whatever code needs the runtime
126 tracing::info!(\"The count is: {{COUNT}}\");
127 }});
128 }})
129}}
130```"
131 )
132 })
133 }
134
135 pub fn try_current() -> Option<Rc<Self>> {
137 RUNTIMES.with(|stack| stack.borrow().last().cloned())
138 }
139
140 pub fn wrap_closure<'a, I, O>(f: impl Fn(I) -> O + 'a) -> impl Fn(I) -> O + 'a {
142 let current_runtime = Self::current();
143 move |input| match current_runtime.try_current_scope_id() {
144 Some(scope) => current_runtime.in_scope(scope, || f(input)),
145 None => {
146 let _runtime_guard = RuntimeGuard::new(current_runtime.clone());
147 f(input)
148 }
149 }
150 }
151
152 pub(crate) fn while_rendering<T>(&self, f: impl FnOnce() -> T) -> T {
154 self.rendering.set(true);
155 let result = f();
156 self.rendering.set(false);
157 result
158 }
159
160 pub(crate) fn while_not_rendering<T>(&self, f: impl FnOnce() -> T) -> T {
162 let previous = self.rendering.get();
163 self.rendering.set(false);
164 let result = f();
165 self.rendering.set(previous);
166 result
167 }
168
169 pub(crate) fn create_scope(&self, context: Scope) {
171 let id = context.id;
172 let mut scopes = self.scope_states.borrow_mut();
173 if scopes.len() <= id.0 {
174 scopes.resize_with(id.0 + 1, Default::default);
175 }
176 scopes[id.0] = Some(context);
177 }
178
179 pub(crate) fn remove_scope(self: &Rc<Self>, id: ScopeId) {
180 {
181 let borrow = self.scope_states.borrow();
182 if let Some(scope) = &borrow[id.0] {
183 self.in_scope(id, || {
185 for id in scope.spawned_tasks.take() {
188 self.remove_task(id);
189 }
190
191 self.pending_effects
193 .borrow_mut()
194 .remove(&ScopeOrder::new(scope.height, scope.id));
195
196 for hook in scope.hooks.take().drain(..).rev() {
198 drop(hook);
199 }
200
201 scope.shared_contexts.take();
203 });
204 }
205 }
206 self.scope_states.borrow_mut()[id.0].take();
207 }
208
209 #[track_caller]
211 pub fn current_owner<S: AnyStorage>(&self) -> Owner<S> {
212 self.get_state(self.current_scope_id()).owner()
213 }
214
215 #[track_caller]
217 pub fn scope_owner<S: AnyStorage>(&self, scope: ScopeId) -> Owner<S> {
218 self.get_state(scope).owner()
219 }
220
221 pub fn current_scope_id(&self) -> ScopeId {
223 self.scope_stack.borrow().last().copied().unwrap()
224 }
225
226 pub fn try_current_scope_id(&self) -> Option<ScopeId> {
228 self.scope_stack.borrow().last().copied()
229 }
230
231 #[track_caller]
233 pub fn in_scope<O>(self: &Rc<Self>, id: ScopeId, f: impl FnOnce() -> O) -> O {
234 let _runtime_guard = RuntimeGuard::new(self.clone());
235 {
236 self.push_scope(id);
237 }
238 let o = f();
239 {
240 self.pop_scope();
241 }
242 o
243 }
244
245 pub(crate) fn current_suspense_location(&self) -> Option<SuspenseLocation> {
247 self.suspense_stack.borrow().last().cloned()
248 }
249
250 pub(crate) fn with_suspense_location<O>(
252 &self,
253 suspense_location: SuspenseLocation,
254 f: impl FnOnce() -> O,
255 ) -> O {
256 self.suspense_stack.borrow_mut().push(suspense_location);
257 let o = f();
258 self.suspense_stack.borrow_mut().pop();
259 o
260 }
261
262 pub(crate) fn with_scope_on_stack<O>(&self, scope: ScopeId, f: impl FnOnce() -> O) -> O {
264 self.push_scope(scope);
265 let o = f();
266 self.pop_scope();
267 o
268 }
269
270 fn push_scope(&self, scope: ScopeId) {
272 let suspense_location = self
273 .scope_states
274 .borrow()
275 .get(scope.0)
276 .and_then(|s| s.as_ref())
277 .map(|s| s.suspense_location())
278 .unwrap_or_default();
279 self.suspense_stack.borrow_mut().push(suspense_location);
280 self.scope_stack.borrow_mut().push(scope);
281 }
282
283 fn pop_scope(&self) {
285 self.scope_stack.borrow_mut().pop();
286 self.suspense_stack.borrow_mut().pop();
287 }
288
289 pub(crate) fn get_state(&self, id: ScopeId) -> Ref<'_, Scope> {
293 Ref::filter_map(self.scope_states.borrow(), |scopes| {
294 scopes.get(id.0).and_then(|f| f.as_ref())
295 })
296 .ok()
297 .unwrap()
298 }
299
300 pub(crate) fn try_get_state(&self, id: ScopeId) -> Option<Ref<'_, Scope>> {
304 Ref::filter_map(self.scope_states.borrow(), |contexts| {
305 contexts.get(id.0).and_then(|f| f.as_ref())
306 })
307 .ok()
308 }
309
310 pub(crate) fn push(runtime: Rc<Runtime>) {
312 RUNTIMES.with(|stack| stack.borrow_mut().push(runtime));
313 }
314
315 pub(crate) fn pop() {
317 RUNTIMES.with(|stack| stack.borrow_mut().pop().unwrap());
318 }
319
320 pub(crate) fn with<R>(callback: impl FnOnce(&Runtime) -> R) -> R {
322 callback(&Self::current())
323 }
324
325 pub(crate) fn with_current_scope<R>(callback: impl FnOnce(&Scope) -> R) -> R {
327 Self::with(|rt| Self::with_scope(rt.current_scope_id(), callback))
328 }
329
330 pub(crate) fn with_scope<R>(scope: ScopeId, callback: impl FnOnce(&Scope) -> R) -> R {
332 let rt = Runtime::current();
333 Self::in_scope(&rt, scope, || callback(&rt.get_state(scope)))
334 }
335
336 pub(crate) fn finish_render(&self) {
338 if !self.pending_effects.borrow().is_empty() {
340 self.sender
341 .unbounded_send(SchedulerMsg::EffectQueued)
342 .expect("Scheduler should exist");
343 }
344 }
345
346 pub(crate) fn scope_should_render(&self, scope_id: ScopeId) -> bool {
348 if self.suspended_tasks.get() == 0 {
350 return true;
351 }
352
353 let scopes = self.scope_states.borrow();
355 let scope = &scopes[scope_id.0].as_ref().unwrap();
356 !matches!(scope.suspense_location(), SuspenseLocation::UnderSuspense(suspense) if suspense.is_suspended())
357 }
358
359 #[instrument(skip(self, event), level = "trace", name = "Runtime::handle_event")]
369 pub fn handle_event(self: &Rc<Self>, name: &str, event: Event<dyn Any>, element: ElementId) {
370 let _runtime = RuntimeGuard::new(self.clone());
371 let elements = self.elements.borrow();
372
373 if let Some(Some(parent_path)) = elements.get(element.0).copied() {
374 if event.propagates() {
375 self.handle_bubbling_event(parent_path, name, event);
376 } else {
377 self.handle_non_bubbling_event(parent_path, name, event);
378 }
379 }
380 }
381
382 #[instrument(
404 skip(self, uievent),
405 level = "trace",
406 name = "VirtualDom::handle_bubbling_event"
407 )]
408 fn handle_bubbling_event(&self, parent: ElementRef, name: &str, uievent: Event<dyn Any>) {
409 let mounts = self.mounts.borrow();
410
411 let mut parent = Some(parent);
414 while let Some(path) = parent {
415 let mut listeners = vec![];
416
417 let Some(mount) = mounts.get(path.mount.0) else {
418 return;
420 };
421 let el_ref = &mount.node;
422 let node_template = el_ref.template;
423 let target_path = path.path;
424
425 for (idx, this_path) in node_template.attr_paths.iter().enumerate() {
427 let attrs = &*el_ref.dynamic_attrs[idx];
428
429 for attr in attrs.iter() {
430 if attr.name.get(2..) == Some(name) && target_path.is_descendant(this_path) {
432 listeners.push(&attr.value);
433
434 if target_path == this_path {
438 break;
439 }
440 }
441 }
442 }
443
444 tracing::event!(
447 tracing::Level::TRACE,
448 "Calling {} listeners",
449 listeners.len()
450 );
451 for listener in listeners.into_iter().rev() {
452 if let AttributeValue::Listener(listener) = listener {
453 listener.call(uievent.clone());
454 let metadata = uievent.metadata.borrow();
455
456 if !metadata.propagates {
457 return;
458 }
459 }
460 }
461
462 let mount = el_ref.mount.get().as_usize();
463 parent = mount.and_then(|id| mounts.get(id).and_then(|el| el.parent));
464 }
465 }
466
467 #[instrument(
469 skip(self, uievent),
470 level = "trace",
471 name = "VirtualDom::handle_non_bubbling_event"
472 )]
473 fn handle_non_bubbling_event(&self, node: ElementRef, name: &str, uievent: Event<dyn Any>) {
474 let mounts = self.mounts.borrow();
475 let Some(mount) = mounts.get(node.mount.0) else {
476 return;
478 };
479 let el_ref = &mount.node;
480 let node_template = el_ref.template;
481 let target_path = node.path;
482
483 for (idx, this_path) in node_template.attr_paths.iter().enumerate() {
484 let attrs = &*el_ref.dynamic_attrs[idx];
485
486 for attr in attrs.iter() {
487 if attr.name.get(2..) == Some(name) && target_path == this_path {
490 if let AttributeValue::Listener(listener) = &attr.value {
491 listener.call(uievent.clone());
492 break;
493 }
494 }
495 }
496 }
497 }
498
499 pub fn consume_context<T: 'static + Clone>(&self, id: ScopeId) -> Option<T> {
501 self.get_state(id).consume_context::<T>()
502 }
503
504 pub fn consume_context_from_scope<T: 'static + Clone>(&self, scope_id: ScopeId) -> Option<T> {
506 self.get_state(scope_id).consume_context::<T>()
507 }
508
509 pub fn has_context<T: 'static + Clone>(&self, id: ScopeId) -> Option<T> {
511 self.get_state(id).has_context::<T>()
512 }
513
514 pub fn provide_context<T: 'static + Clone>(&self, id: ScopeId, value: T) -> T {
516 self.get_state(id).provide_context(value)
517 }
518
519 pub fn parent_scope(&self, scope: ScopeId) -> Option<ScopeId> {
521 self.get_state(scope).parent_id()
522 }
523
524 pub fn is_descendant_of(&self, us: ScopeId, other: ScopeId) -> bool {
526 let mut current = us;
527 while let Some(parent) = self.parent_scope(current) {
528 if parent == other {
529 return true;
530 }
531 current = parent;
532 }
533 false
534 }
535
536 pub fn needs_update(&self, scope: ScopeId) {
538 self.get_state(scope).needs_update();
539 }
540
541 pub fn height(&self, id: ScopeId) -> u32 {
543 self.get_state(id).height
544 }
545
546 pub fn throw_error(&self, id: ScopeId, error: impl Into<CapturedError> + 'static) {
564 let error = error.into();
565 if let Some(cx) = self.consume_context::<crate::ErrorContext>(id) {
566 cx.insert_error(error)
567 } else {
568 tracing::error!(
569 "Tried to throw an error into an error boundary, but failed to locate a boundary: {:?}",
570 error
571 )
572 }
573 }
574
575 pub fn suspense_context(&self) -> Option<SuspenseContext> {
577 self.get_state(self.current_scope_id())
578 .suspense_location()
579 .suspense_context()
580 .cloned()
581 }
582
583 pub fn force_all_dirty(&self) {
588 self.scope_states.borrow_mut().iter().for_each(|state| {
589 if let Some(scope) = state.as_ref() {
590 scope.needs_update();
591 }
592 });
593 }
594
595 pub fn vdom_is_rendering(&self) -> bool {
597 self.rendering.get()
598 }
599}
600
601pub struct RuntimeGuard(());
636
637impl RuntimeGuard {
638 pub fn new(runtime: Rc<Runtime>) -> Self {
640 Runtime::push(runtime);
641 Self(())
642 }
643}
644
645impl Drop for RuntimeGuard {
646 fn drop(&mut self) {
647 Runtime::pop();
648 }
649}