dioxus_core/suspense/
mod.rs1mod component;
27pub use component::*;
28
29use crate::innerlude::*;
30use std::{
31 cell::{Cell, Ref, RefCell},
32 fmt::Debug,
33 rc::Rc,
34};
35
36#[derive(Clone, PartialEq, Debug)]
38pub struct SuspendedFuture {
39 origin: ScopeId,
40 task: Task,
41 pub(crate) placeholder: VNode,
42}
43
44impl SuspendedFuture {
45 pub fn new(task: Task) -> Self {
47 Self {
48 task,
49 origin: current_scope_id().unwrap_or_else(|e| panic!("{}", e)),
50 placeholder: VNode::placeholder(),
51 }
52 }
53
54 pub fn suspense_placeholder(&self) -> Option<VNode> {
56 if self.placeholder == VNode::placeholder() {
57 None
58 } else {
59 Some(self.placeholder.clone())
60 }
61 }
62
63 pub fn with_placeholder(mut self, placeholder: VNode) -> Self {
65 self.placeholder = placeholder;
66 self
67 }
68
69 pub fn task(&self) -> Task {
71 self.task
72 }
73
74 pub(crate) fn deep_clone(&self) -> Self {
76 Self {
77 task: self.task,
78 placeholder: self.placeholder.deep_clone(),
79 origin: self.origin,
80 }
81 }
82}
83
84#[derive(Debug, Clone)]
86pub struct SuspenseContext {
87 inner: Rc<SuspenseBoundaryInner>,
88}
89
90impl PartialEq for SuspenseContext {
91 fn eq(&self, other: &Self) -> bool {
92 Rc::ptr_eq(&self.inner, &other.inner)
93 }
94}
95
96impl SuspenseContext {
97 pub(crate) fn new() -> Self {
99 Self {
100 inner: Rc::new(SuspenseBoundaryInner {
101 suspended_tasks: RefCell::new(vec![]),
102 id: Cell::new(ScopeId::ROOT),
103 suspended_nodes: Default::default(),
104 frozen: Default::default(),
105 after_suspense_resolved: Default::default(),
106 }),
107 }
108 }
109
110 pub(crate) fn mount(&self, scope: ScopeId) {
112 self.inner.id.set(scope);
113 }
114
115 pub fn suspended_nodes(&self) -> Option<VNode> {
117 self.inner
118 .suspended_nodes
119 .borrow()
120 .as_ref()
121 .map(|node| node.clone())
122 }
123
124 pub(crate) fn set_suspended_nodes(&self, suspended_nodes: VNode) {
126 self.inner
127 .suspended_nodes
128 .borrow_mut()
129 .replace(suspended_nodes);
130 }
131
132 pub(crate) fn take_suspended_nodes(&self) -> Option<VNode> {
134 self.inner.suspended_nodes.borrow_mut().take()
135 }
136
137 pub fn frozen(&self) -> bool {
139 self.inner.frozen.get()
140 }
141
142 pub fn freeze(&self) {
144 self.inner.frozen.set(true);
145 }
146
147 pub fn has_suspended_tasks(&self) -> bool {
149 !self.inner.suspended_tasks.borrow().is_empty()
150 }
151
152 pub fn is_suspended(&self) -> bool {
154 self.inner.suspended_nodes.borrow().is_some()
155 }
156
157 pub(crate) fn add_suspended_task(&self, task: SuspendedFuture) {
159 self.inner.suspended_tasks.borrow_mut().push(task);
160 self.inner.id.get().needs_update();
161 }
162
163 pub(crate) fn remove_suspended_task(&self, task: Task) {
165 self.inner
166 .suspended_tasks
167 .borrow_mut()
168 .retain(|t| t.task != task);
169 self.inner.id.get().needs_update();
170 }
171
172 pub fn suspended_futures(&self) -> Ref<[SuspendedFuture]> {
174 Ref::map(self.inner.suspended_tasks.borrow(), |tasks| {
175 tasks.as_slice()
176 })
177 }
178
179 pub fn suspense_placeholder(&self) -> Option<Element> {
181 self.inner
182 .suspended_tasks
183 .borrow()
184 .iter()
185 .find_map(|task| task.suspense_placeholder())
186 .map(std::result::Result::Ok)
187 }
188
189 pub fn after_suspense_resolved(&self, callback: impl FnOnce() + 'static) {
191 let mut closures = self.inner.after_suspense_resolved.borrow_mut();
192 closures.push(Box::new(callback));
193 }
194
195 pub(crate) fn run_resolved_closures(&self, runtime: &Runtime) {
197 runtime.while_not_rendering(|| {
198 self.inner
199 .after_suspense_resolved
200 .borrow_mut()
201 .drain(..)
202 .for_each(|f| f());
203 })
204 }
205}
206
207pub struct SuspenseBoundaryInner {
209 suspended_tasks: RefCell<Vec<SuspendedFuture>>,
210 id: Cell<ScopeId>,
211 suspended_nodes: RefCell<Option<VNode>>,
213 frozen: Cell<bool>,
215 after_suspense_resolved: RefCell<Vec<Box<dyn FnOnce()>>>,
217}
218
219impl Debug for SuspenseBoundaryInner {
220 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
221 f.debug_struct("SuspenseBoundaryInner")
222 .field("suspended_tasks", &self.suspended_tasks)
223 .field("id", &self.id)
224 .field("suspended_nodes", &self.suspended_nodes)
225 .field("frozen", &self.frozen)
226 .finish()
227 }
228}
229
230pub trait SuspenseExtension<T>: private::Sealed {
234 fn with_loading_placeholder(
236 self,
237 display_placeholder: impl FnOnce() -> Element,
238 ) -> std::result::Result<T, RenderError>;
239}
240
241impl<T> SuspenseExtension<T> for std::result::Result<T, RenderError> {
242 fn with_loading_placeholder(
243 self,
244 display_placeholder: impl FnOnce() -> Element,
245 ) -> std::result::Result<T, RenderError> {
246 if let Err(RenderError::Suspended(suspense)) = self {
247 Err(RenderError::Suspended(suspense.with_placeholder(
248 display_placeholder().unwrap_or_default(),
249 )))
250 } else {
251 self
252 }
253 }
254}
255
256pub(crate) mod private {
257 use super::*;
258
259 pub trait Sealed {}
260
261 impl<T> Sealed for std::result::Result<T, RenderError> {}
262}