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 create_scope(&self, context: Scope) {
121 let id = context.id;
122 let mut scopes = self.scope_states.borrow_mut();
123 if scopes.len() <= id.0 {
124 scopes.resize_with(id.0 + 1, Default::default);
125 }
126 scopes[id.0] = Some(context);
127 }
128
129 pub(crate) fn remove_scope(self: &Rc<Self>, id: ScopeId) {
130 {
131 let borrow = self.scope_states.borrow();
132 if let Some(scope) = &borrow[id.0] {
133 self.on_scope(id, || {
135 for id in scope.spawned_tasks.take() {
138 self.remove_task(id);
139 }
140
141 self.pending_effects
143 .borrow_mut()
144 .remove(&ScopeOrder::new(scope.height, scope.id));
145
146 for hook in scope.hooks.take().drain(..).rev() {
148 drop(hook);
149 }
150
151 scope.shared_contexts.take();
153 });
154 }
155 }
156 self.scope_states.borrow_mut()[id.0].take();
157 }
158
159 pub(crate) fn current_scope_id(&self) -> Result<ScopeId, RuntimeError> {
161 self.scope_stack
162 .borrow()
163 .last()
164 .copied()
165 .ok_or(RuntimeError { _priv: () })
166 }
167
168 pub fn on_scope<O>(self: &Rc<Self>, id: ScopeId, f: impl FnOnce() -> O) -> O {
172 let _runtime_guard = RuntimeGuard::new(self.clone());
173 {
174 self.push_scope(id);
175 }
176 let o = f();
177 {
178 self.pop_scope();
179 }
180 o
181 }
182
183 pub(crate) fn current_suspense_location(&self) -> Option<SuspenseLocation> {
185 self.suspense_stack.borrow().last().cloned()
186 }
187
188 pub(crate) fn with_suspense_location<O>(
190 &self,
191 suspense_location: SuspenseLocation,
192 f: impl FnOnce() -> O,
193 ) -> O {
194 self.suspense_stack.borrow_mut().push(suspense_location);
195 let o = f();
196 self.suspense_stack.borrow_mut().pop();
197 o
198 }
199
200 pub(crate) fn with_scope_on_stack<O>(&self, scope: ScopeId, f: impl FnOnce() -> O) -> O {
202 self.push_scope(scope);
203 let o = f();
204 self.pop_scope();
205 o
206 }
207
208 fn push_scope(&self, scope: ScopeId) {
210 let suspense_location = self
211 .scope_states
212 .borrow()
213 .get(scope.0)
214 .and_then(|s| s.as_ref())
215 .map(|s| s.suspense_location())
216 .unwrap_or_default();
217 self.suspense_stack.borrow_mut().push(suspense_location);
218 self.scope_stack.borrow_mut().push(scope);
219 }
220
221 fn pop_scope(&self) {
223 self.scope_stack.borrow_mut().pop();
224 self.suspense_stack.borrow_mut().pop();
225 }
226
227 pub(crate) fn get_state(&self, id: ScopeId) -> Option<Ref<'_, Scope>> {
231 Ref::filter_map(self.scope_states.borrow(), |contexts| {
232 contexts.get(id.0).and_then(|f| f.as_ref())
233 })
234 .ok()
235 }
236
237 pub(crate) fn push(runtime: Rc<Runtime>) {
239 RUNTIMES.with(|stack| stack.borrow_mut().push(runtime));
240 }
241
242 pub(crate) fn pop() {
244 RUNTIMES.with(|stack| stack.borrow_mut().pop());
245 }
246
247 pub(crate) fn with<R>(f: impl FnOnce(&Runtime) -> R) -> Result<R, RuntimeError> {
249 Self::current().map(|r| f(&r))
250 }
251
252 pub(crate) fn with_current_scope<R>(f: impl FnOnce(&Scope) -> R) -> Result<R, RuntimeError> {
254 Self::with(|rt| {
255 rt.current_scope_id()
256 .ok()
257 .and_then(|scope| rt.get_state(scope).map(|sc| f(&sc)))
258 })
259 .ok()
260 .flatten()
261 .ok_or(RuntimeError::new())
262 }
263
264 pub(crate) fn with_scope<R>(
266 scope: ScopeId,
267 f: impl FnOnce(&Scope) -> R,
268 ) -> Result<R, RuntimeError> {
269 Self::with(|rt| rt.get_state(scope).map(|sc| f(&sc)))
270 .ok()
271 .flatten()
272 .ok_or(RuntimeError::new())
273 }
274
275 pub(crate) fn finish_render(&self) {
277 if !self.pending_effects.borrow().is_empty() {
279 self.sender
280 .unbounded_send(SchedulerMsg::EffectQueued)
281 .expect("Scheduler should exist");
282 }
283 }
284
285 pub(crate) fn scope_should_render(&self, scope_id: ScopeId) -> bool {
287 if self.suspended_tasks.get() == 0 {
289 return true;
290 }
291 let scopes = self.scope_states.borrow();
293 let scope = &scopes[scope_id.0].as_ref().unwrap();
294 !matches!(scope.suspense_location(), SuspenseLocation::UnderSuspense(suspense) if suspense.is_suspended())
295 }
296
297 #[instrument(skip(self, event), level = "trace", name = "Runtime::handle_event")]
307 pub fn handle_event(self: &Rc<Self>, name: &str, event: Event<dyn Any>, element: ElementId) {
308 let _runtime = RuntimeGuard::new(self.clone());
309 let elements = self.elements.borrow();
310
311 if let Some(Some(parent_path)) = elements.get(element.0).copied() {
312 if event.propagates() {
313 self.handle_bubbling_event(parent_path, name, event);
314 } else {
315 self.handle_non_bubbling_event(parent_path, name, event);
316 }
317 }
318 }
319
320 #[instrument(
342 skip(self, uievent),
343 level = "trace",
344 name = "VirtualDom::handle_bubbling_event"
345 )]
346 fn handle_bubbling_event(&self, parent: ElementRef, name: &str, uievent: Event<dyn Any>) {
347 let mounts = self.mounts.borrow();
348
349 let mut parent = Some(parent);
352 while let Some(path) = parent {
353 let mut listeners = vec![];
354
355 let Some(mount) = mounts.get(path.mount.0) else {
356 return;
358 };
359 let el_ref = &mount.node;
360 let node_template = el_ref.template;
361 let target_path = path.path;
362
363 for (idx, this_path) in node_template.attr_paths.iter().enumerate() {
365 let attrs = &*el_ref.dynamic_attrs[idx];
366
367 for attr in attrs.iter() {
368 if attr.name.get(2..) == Some(name) && target_path.is_descendant(this_path) {
370 listeners.push(&attr.value);
371
372 if target_path == this_path {
376 break;
377 }
378 }
379 }
380 }
381
382 tracing::event!(
385 tracing::Level::TRACE,
386 "Calling {} listeners",
387 listeners.len()
388 );
389 for listener in listeners.into_iter().rev() {
390 if let AttributeValue::Listener(listener) = listener {
391 listener.call(uievent.clone());
392 let metadata = uievent.metadata.borrow();
393
394 if !metadata.propagates {
395 return;
396 }
397 }
398 }
399
400 let mount = el_ref.mount.get().as_usize();
401 parent = mount.and_then(|id| mounts.get(id).and_then(|el| el.parent));
402 }
403 }
404
405 #[instrument(
407 skip(self, uievent),
408 level = "trace",
409 name = "VirtualDom::handle_non_bubbling_event"
410 )]
411 fn handle_non_bubbling_event(&self, node: ElementRef, name: &str, uievent: Event<dyn Any>) {
412 let mounts = self.mounts.borrow();
413 let Some(mount) = mounts.get(node.mount.0) else {
414 return;
416 };
417 let el_ref = &mount.node;
418 let node_template = el_ref.template;
419 let target_path = node.path;
420
421 for (idx, this_path) in node_template.attr_paths.iter().enumerate() {
422 let attrs = &*el_ref.dynamic_attrs[idx];
423
424 for attr in attrs.iter() {
425 if attr.name.get(2..) == Some(name) && target_path == this_path {
428 if let AttributeValue::Listener(listener) = &attr.value {
429 listener.call(uievent.clone());
430 break;
431 }
432 }
433 }
434 }
435 }
436}
437
438pub struct RuntimeGuard(());
472
473impl RuntimeGuard {
474 pub fn new(runtime: Rc<Runtime>) -> Self {
476 Runtime::push(runtime);
477 Self(())
478 }
479}
480
481impl Drop for RuntimeGuard {
482 fn drop(&mut self) {
483 Runtime::pop();
484 }
485}
486
487pub struct RuntimeError {
489 _priv: (),
490}
491
492impl RuntimeError {
493 #[inline(always)]
494 pub(crate) fn new() -> Self {
495 Self { _priv: () }
496 }
497}
498
499impl fmt::Debug for RuntimeError {
500 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501 f.debug_struct("RuntimeError").finish()
502 }
503}
504
505impl fmt::Display for RuntimeError {
506 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
507 write!(
508 f,
509 "Must be called from inside a Dioxus runtime.
510
511Help: Some APIs in dioxus require a global runtime to be present.
512If you are calling one of these APIs from outside of a dioxus runtime
513(typically in a web-sys closure or dynamic library), you will need to
514grab the runtime from a scope that has it and then move it into your
515new scope with a runtime guard.
516
517For example, if you are trying to use dioxus apis from a web-sys
518closure, you can grab the runtime from the scope it is created in:
519
520```rust
521use dioxus::prelude::*;
522static COUNT: GlobalSignal<i32> = Signal::global(|| 0);
523
524#[component]
525fn MyComponent() -> Element {{
526 use_effect(|| {{
527 // Grab the runtime from the MyComponent scope
528 let runtime = Runtime::current().expect(\"Components run in the Dioxus runtime\");
529 // Move the runtime into the web-sys closure scope
530 let web_sys_closure = Closure::new(|| {{
531 // Then create a guard to provide the runtime to the closure
532 let _guard = RuntimeGuard::new(runtime);
533 // and run whatever code needs the runtime
534 tracing::info!(\"The count is: {{COUNT}}\");
535 }});
536 }})
537}}
538```"
539 )
540 }
541}
542
543impl std::error::Error for RuntimeError {}