|
@@ -7,7 +7,9 @@ use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
|
|
use futures_util::{Future, StreamExt};
|
|
use futures_util::{Future, StreamExt};
|
|
use fxhash::FxHashSet;
|
|
use fxhash::FxHashSet;
|
|
use indexmap::IndexSet;
|
|
use indexmap::IndexSet;
|
|
|
|
+use smallvec::SmallVec;
|
|
use std::pin::Pin;
|
|
use std::pin::Pin;
|
|
|
|
+use std::sync::Arc;
|
|
use std::task::Poll;
|
|
use std::task::Poll;
|
|
use std::{any::Any, collections::VecDeque};
|
|
use std::{any::Any, collections::VecDeque};
|
|
|
|
|
|
@@ -18,7 +20,7 @@ use std::{any::Any, collections::VecDeque};
|
|
///
|
|
///
|
|
/// Components are defined as simple functions that take [`Context`] and a [`Properties`] type and return an [`Element`].
|
|
/// Components are defined as simple functions that take [`Context`] and a [`Properties`] type and return an [`Element`].
|
|
///
|
|
///
|
|
-/// ```rust
|
|
|
|
|
|
+/// ```rust, ignore
|
|
/// #[derive(Props, PartialEq)]
|
|
/// #[derive(Props, PartialEq)]
|
|
/// struct AppProps {
|
|
/// struct AppProps {
|
|
/// title: String
|
|
/// title: String
|
|
@@ -33,7 +35,7 @@ use std::{any::Any, collections::VecDeque};
|
|
///
|
|
///
|
|
/// Components may be composed to make complex apps.
|
|
/// Components may be composed to make complex apps.
|
|
///
|
|
///
|
|
-/// ```rust
|
|
|
|
|
|
+/// ```rust, ignore
|
|
/// fn App(cx: Context, props: &AppProps) -> Element {
|
|
/// fn App(cx: Context, props: &AppProps) -> Element {
|
|
/// cx.render(rsx!(
|
|
/// cx.render(rsx!(
|
|
/// NavBar { routes: ROUTES }
|
|
/// NavBar { routes: ROUTES }
|
|
@@ -46,14 +48,14 @@ use std::{any::Any, collections::VecDeque};
|
|
/// To start an app, create a [`VirtualDom`] and call [`VirtualDom::rebuild`] to get the list of edits required to
|
|
/// To start an app, create a [`VirtualDom`] and call [`VirtualDom::rebuild`] to get the list of edits required to
|
|
/// draw the UI.
|
|
/// draw the UI.
|
|
///
|
|
///
|
|
-/// ```rust
|
|
|
|
|
|
+/// ```rust, ignore
|
|
/// let mut vdom = VirtualDom::new(App);
|
|
/// let mut vdom = VirtualDom::new(App);
|
|
/// let edits = vdom.rebuild();
|
|
/// let edits = vdom.rebuild();
|
|
/// ```
|
|
/// ```
|
|
///
|
|
///
|
|
/// To inject UserEvents into the VirtualDom, call [`VirtualDom::get_scheduler_channel`] to get access to the scheduler.
|
|
/// To inject UserEvents into the VirtualDom, call [`VirtualDom::get_scheduler_channel`] to get access to the scheduler.
|
|
///
|
|
///
|
|
-/// ```rust
|
|
|
|
|
|
+/// ```rust, ignore
|
|
/// let channel = vdom.get_scheduler_channel();
|
|
/// let channel = vdom.get_scheduler_channel();
|
|
/// channel.send_unbounded(SchedulerMsg::UserEvent(UserEvent {
|
|
/// channel.send_unbounded(SchedulerMsg::UserEvent(UserEvent {
|
|
/// // ...
|
|
/// // ...
|
|
@@ -62,7 +64,7 @@ use std::{any::Any, collections::VecDeque};
|
|
///
|
|
///
|
|
/// While waiting for UserEvents to occur, call [`VirtualDom::wait_for_work`] to poll any futures inside the VirtualDom.
|
|
/// While waiting for UserEvents to occur, call [`VirtualDom::wait_for_work`] to poll any futures inside the VirtualDom.
|
|
///
|
|
///
|
|
-/// ```rust
|
|
|
|
|
|
+/// ```rust, ignore
|
|
/// vdom.wait_for_work().await;
|
|
/// vdom.wait_for_work().await;
|
|
/// ```
|
|
/// ```
|
|
///
|
|
///
|
|
@@ -70,7 +72,7 @@ use std::{any::Any, collections::VecDeque};
|
|
/// current UI trees. This will return a [`Mutations`] object that contains Edits, Effects, and NodeRefs that need to be
|
|
/// current UI trees. This will return a [`Mutations`] object that contains Edits, Effects, and NodeRefs that need to be
|
|
/// handled by the renderer.
|
|
/// handled by the renderer.
|
|
///
|
|
///
|
|
-/// ```rust
|
|
|
|
|
|
+/// ```rust, ignore
|
|
/// let mutations = vdom.work_with_deadline(|| false);
|
|
/// let mutations = vdom.work_with_deadline(|| false);
|
|
/// for edit in mutations {
|
|
/// for edit in mutations {
|
|
/// apply(edit);
|
|
/// apply(edit);
|
|
@@ -81,7 +83,7 @@ use std::{any::Any, collections::VecDeque};
|
|
///
|
|
///
|
|
/// Putting everything together, you can build an event loop around Dioxus by using the methods outlined above.
|
|
/// Putting everything together, you can build an event loop around Dioxus by using the methods outlined above.
|
|
///
|
|
///
|
|
-/// ```rust
|
|
|
|
|
|
+/// ```rust, ignore
|
|
/// fn App(cx: Context, props: &()) -> Element {
|
|
/// fn App(cx: Context, props: &()) -> Element {
|
|
/// cx.render(rsx!{
|
|
/// cx.render(rsx!{
|
|
/// div { "Hello World" }
|
|
/// div { "Hello World" }
|
|
@@ -106,7 +108,7 @@ use std::{any::Any, collections::VecDeque};
|
|
pub struct VirtualDom {
|
|
pub struct VirtualDom {
|
|
base_scope: ScopeId,
|
|
base_scope: ScopeId,
|
|
|
|
|
|
- _root_caller: *mut dyn Fn(&Scope) -> Element,
|
|
|
|
|
|
+ _root_props: Box<dyn Any>,
|
|
|
|
|
|
scopes: Box<ScopeArena>,
|
|
scopes: Box<ScopeArena>,
|
|
|
|
|
|
@@ -114,8 +116,6 @@ pub struct VirtualDom {
|
|
|
|
|
|
sender: UnboundedSender<SchedulerMsg>,
|
|
sender: UnboundedSender<SchedulerMsg>,
|
|
|
|
|
|
- pending_futures: FxHashSet<ScopeId>,
|
|
|
|
-
|
|
|
|
pending_messages: VecDeque<SchedulerMsg>,
|
|
pending_messages: VecDeque<SchedulerMsg>,
|
|
|
|
|
|
dirty_scopes: IndexSet<ScopeId>,
|
|
dirty_scopes: IndexSet<ScopeId>,
|
|
@@ -134,7 +134,7 @@ impl VirtualDom {
|
|
///
|
|
///
|
|
///
|
|
///
|
|
/// # Example
|
|
/// # Example
|
|
- /// ```
|
|
|
|
|
|
+ /// ```rust, ignore
|
|
/// fn Example(cx: Context, props: &()) -> Element {
|
|
/// fn Example(cx: Context, props: &()) -> Element {
|
|
/// cx.render(rsx!( div { "hello world" } ))
|
|
/// cx.render(rsx!( div { "hello world" } ))
|
|
/// }
|
|
/// }
|
|
@@ -158,7 +158,7 @@ impl VirtualDom {
|
|
///
|
|
///
|
|
///
|
|
///
|
|
/// # Example
|
|
/// # Example
|
|
- /// ```
|
|
|
|
|
|
+ /// ```rust, ignore
|
|
/// #[derive(PartialEq, Props)]
|
|
/// #[derive(PartialEq, Props)]
|
|
/// struct SomeProps {
|
|
/// struct SomeProps {
|
|
/// name: &'static str
|
|
/// name: &'static str
|
|
@@ -173,7 +173,7 @@ impl VirtualDom {
|
|
///
|
|
///
|
|
/// Note: the VirtualDOM is not progressed on creation. You must either "run_with_deadline" or use "rebuild" to progress it.
|
|
/// Note: the VirtualDOM is not progressed on creation. You must either "run_with_deadline" or use "rebuild" to progress it.
|
|
///
|
|
///
|
|
- /// ```rust
|
|
|
|
|
|
+ /// ```rust, ignore
|
|
/// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
|
|
/// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
|
|
/// let mutations = dom.rebuild();
|
|
/// let mutations = dom.rebuild();
|
|
/// ```
|
|
/// ```
|
|
@@ -194,9 +194,9 @@ impl VirtualDom {
|
|
) -> Self {
|
|
) -> Self {
|
|
let scopes = ScopeArena::new(sender.clone());
|
|
let scopes = ScopeArena::new(sender.clone());
|
|
|
|
|
|
- let caller = Box::new(move |scp: &Scope| -> Element { root(scp, &root_props) });
|
|
|
|
- let caller_ref: *mut dyn Fn(&Scope) -> Element = Box::into_raw(caller);
|
|
|
|
- let base_scope = scopes.new_with_key(root as _, caller_ref, None, 0, 0);
|
|
|
|
|
|
+ let mut caller = Box::new(move |scp: &Scope| -> Element { root(scp, &root_props) });
|
|
|
|
+ let caller_ref: *mut dyn Fn(&Scope) -> Element = caller.as_mut() as *mut _;
|
|
|
|
+ let base_scope = scopes.new_with_key(root as _, caller_ref, None, ElementId(0), 0, 0);
|
|
|
|
|
|
let pending_messages = VecDeque::new();
|
|
let pending_messages = VecDeque::new();
|
|
let mut dirty_scopes = IndexSet::new();
|
|
let mut dirty_scopes = IndexSet::new();
|
|
@@ -206,10 +206,8 @@ impl VirtualDom {
|
|
scopes: Box::new(scopes),
|
|
scopes: Box::new(scopes),
|
|
base_scope,
|
|
base_scope,
|
|
receiver,
|
|
receiver,
|
|
- // todo: clean this up manually?
|
|
|
|
- _root_caller: caller_ref,
|
|
|
|
|
|
+ _root_props: caller,
|
|
pending_messages,
|
|
pending_messages,
|
|
- pending_futures: Default::default(),
|
|
|
|
dirty_scopes,
|
|
dirty_scopes,
|
|
sender,
|
|
sender,
|
|
}
|
|
}
|
|
@@ -239,7 +237,7 @@ impl VirtualDom {
|
|
///
|
|
///
|
|
/// # Example
|
|
/// # Example
|
|
///
|
|
///
|
|
- /// ```rust
|
|
|
|
|
|
+ /// ```rust, ignore
|
|
///
|
|
///
|
|
///
|
|
///
|
|
/// ```
|
|
/// ```
|
|
@@ -251,7 +249,7 @@ impl VirtualDom {
|
|
///
|
|
///
|
|
/// # Example
|
|
/// # Example
|
|
///
|
|
///
|
|
- /// ```rust
|
|
|
|
|
|
+ /// ```rust, ignore
|
|
///
|
|
///
|
|
///
|
|
///
|
|
/// ```
|
|
/// ```
|
|
@@ -262,104 +260,79 @@ impl VirtualDom {
|
|
/// Waits for the scheduler to have work
|
|
/// Waits for the scheduler to have work
|
|
/// This lets us poll async tasks during idle periods without blocking the main thread.
|
|
/// This lets us poll async tasks during idle periods without blocking the main thread.
|
|
pub async fn wait_for_work(&mut self) {
|
|
pub async fn wait_for_work(&mut self) {
|
|
- // todo: poll the events once even if there is work to do to prevent starvation
|
|
|
|
-
|
|
|
|
- // if there's no futures in the virtualdom, just wait for a scheduler message and put it into the queue to be processed
|
|
|
|
- if self.pending_futures.is_empty() {
|
|
|
|
- self.pending_messages
|
|
|
|
- .push_front(self.receiver.next().await.unwrap());
|
|
|
|
- } else {
|
|
|
|
- struct PollTasks<'a> {
|
|
|
|
- pending_futures: &'a FxHashSet<ScopeId>,
|
|
|
|
- scopes: &'a ScopeArena,
|
|
|
|
|
|
+ loop {
|
|
|
|
+ if !self.dirty_scopes.is_empty() && self.pending_messages.is_empty() {
|
|
|
|
+ break;
|
|
}
|
|
}
|
|
|
|
|
|
- impl<'a> Future for PollTasks<'a> {
|
|
|
|
- type Output = ();
|
|
|
|
-
|
|
|
|
- fn poll(
|
|
|
|
- self: Pin<&mut Self>,
|
|
|
|
- cx: &mut std::task::Context<'_>,
|
|
|
|
- ) -> Poll<Self::Output> {
|
|
|
|
- let mut all_pending = true;
|
|
|
|
-
|
|
|
|
- // Poll every scope manually
|
|
|
|
- for fut in self.pending_futures.iter() {
|
|
|
|
- let scope = self
|
|
|
|
- .scopes
|
|
|
|
- .get_scope(fut)
|
|
|
|
- .expect("Scope should never be moved");
|
|
|
|
-
|
|
|
|
- let mut items = scope.items.borrow_mut();
|
|
|
|
- for task in items.tasks.iter_mut() {
|
|
|
|
- let task = task.as_mut();
|
|
|
|
-
|
|
|
|
- // todo: does this make sense?
|
|
|
|
- // I don't usually write futures by hand
|
|
|
|
- // I think the futures neeed to be pinned using bumpbox or something
|
|
|
|
- // right now, they're bump allocated so this shouldn't matter anyway - they're not going to move
|
|
|
|
- let unpinned = unsafe { Pin::new_unchecked(task) };
|
|
|
|
-
|
|
|
|
- if unpinned.poll(cx).is_ready() {
|
|
|
|
- all_pending = false
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ if self.pending_messages.is_empty() {
|
|
|
|
+ if self.scopes.pending_futures.borrow().is_empty() {
|
|
|
|
+ self.pending_messages
|
|
|
|
+ .push_front(self.receiver.next().await.unwrap());
|
|
|
|
+ } else {
|
|
|
|
+ use futures_util::future::{select, Either};
|
|
|
|
|
|
- // Resolve the future if any singular task is ready
|
|
|
|
- match all_pending {
|
|
|
|
- true => Poll::Pending,
|
|
|
|
- false => Poll::Ready(()),
|
|
|
|
|
|
+ match select(PollTasks(&mut self.scopes), self.receiver.next()).await {
|
|
|
|
+ Either::Left((_, _)) => {}
|
|
|
|
+ Either::Right((msg, _)) => self.pending_messages.push_front(msg.unwrap()),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- // Poll both the futures and the scheduler message queue simulataneously
|
|
|
|
- use futures_util::future::{select, Either};
|
|
|
|
-
|
|
|
|
- let scheduler_fut = self.receiver.next();
|
|
|
|
- let tasks_fut = PollTasks {
|
|
|
|
- pending_futures: &self.pending_futures,
|
|
|
|
- scopes: &self.scopes,
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- match select(tasks_fut, scheduler_fut).await {
|
|
|
|
- // Futures don't generate work
|
|
|
|
- Either::Left((_, _)) => {}
|
|
|
|
|
|
+ while let Ok(Some(msg)) = self.receiver.try_next() {
|
|
|
|
+ self.pending_messages.push_front(msg);
|
|
|
|
+ }
|
|
|
|
|
|
- // Save these messages in FIFO to be processed later
|
|
|
|
- Either::Right((msg, _)) => self.pending_messages.push_front(msg.unwrap()),
|
|
|
|
|
|
+ if let Some(msg) = self.pending_messages.pop_back() {
|
|
|
|
+ match msg {
|
|
|
|
+ // just keep looping, the task is now saved but we should actually poll it
|
|
|
|
+ SchedulerMsg::TaskPushed(id) => {
|
|
|
|
+ self.scopes.pending_futures.borrow_mut().insert(id);
|
|
|
|
+ }
|
|
|
|
+ SchedulerMsg::UiEvent(event) => {
|
|
|
|
+ if let Some(element) = event.element {
|
|
|
|
+ self.scopes.call_listener_with_bubbling(event, element);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ SchedulerMsg::Immediate(s) => {
|
|
|
|
+ self.dirty_scopes.insert(s);
|
|
|
|
+ }
|
|
|
|
+ SchedulerMsg::Suspended { scope } => todo!(),
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// Run the virtualdom with a deadline.
|
|
/// Run the virtualdom with a deadline.
|
|
///
|
|
///
|
|
- /// This method will progress async tasks until the deadline is reached. If tasks are completed before the deadline,
|
|
|
|
- /// and no tasks are pending, this method will return immediately. If tasks are still pending, then this method will
|
|
|
|
- /// exhaust the deadline working on them.
|
|
|
|
|
|
+ /// This method will perform any outstanding diffing work and try to return as many mutations as possible before the
|
|
|
|
+ /// deadline is reached. This method accepts a closure that returns `true` if the deadline has been reached. To wrap
|
|
|
|
+ /// your future into a deadline, consider the `now_or_never` method from `future_utils`.
|
|
///
|
|
///
|
|
- /// This method is useful when needing to schedule the virtualdom around other tasks on the main thread to prevent
|
|
|
|
- /// "jank". It will try to finish whatever work it has by the deadline to free up time for other work.
|
|
|
|
|
|
+ /// ```rust, ignore
|
|
|
|
+ /// let mut vdom = VirtualDom::new(App);
|
|
///
|
|
///
|
|
- /// Due to platform differences in how time is handled, this method accepts a future that resolves when the deadline
|
|
|
|
- /// is exceeded. However, the deadline won't be met precisely, so you might want to build some wiggle room into the
|
|
|
|
- /// deadline closure manually.
|
|
|
|
|
|
+ /// let timeout = TimeoutFuture::from_ms(16);
|
|
|
|
+ /// let deadline = || (&mut timeout).now_or_never();
|
|
///
|
|
///
|
|
- /// The deadline is polled before starting to diff components. This strikes a balance between the overhead of checking
|
|
|
|
- /// the deadline and just completing the work. However, if an individual component takes more than 16ms to render, then
|
|
|
|
- /// the screen will "jank" up. In debug, this will trigger an alert.
|
|
|
|
|
|
+ /// let mutations = vdom.work_with_deadline(deadline);
|
|
|
|
+ /// ```
|
|
|
|
+ ///
|
|
|
|
+ /// This method is useful when needing to schedule the virtualdom around other tasks on the main thread to prevent
|
|
|
|
+ /// "jank". It will try to finish whatever work it has by the deadline to free up time for other work.
|
|
///
|
|
///
|
|
- /// If there are no in-flight fibers when this method is called, it will await any possible tasks, aborting early if
|
|
|
|
- /// the provided deadline future resolves.
|
|
|
|
|
|
+ /// If the work is not finished by the deadline, Dioxus will store it for later and return when work_with_deadline
|
|
|
|
+ /// is called again. This means you can ensure some level of free time on the VirtualDom's thread during the work phase.
|
|
///
|
|
///
|
|
/// For use in the web, it is expected that this method will be called to be executed during "idle times" and the
|
|
/// For use in the web, it is expected that this method will be called to be executed during "idle times" and the
|
|
/// mutations to be applied during the "paint times" IE "animation frames". With this strategy, it is possible to craft
|
|
/// mutations to be applied during the "paint times" IE "animation frames". With this strategy, it is possible to craft
|
|
/// entirely jank-free applications that perform a ton of work.
|
|
/// entirely jank-free applications that perform a ton of work.
|
|
///
|
|
///
|
|
|
|
+ /// In general use, Dioxus is plenty fast enough to not need to worry about this.
|
|
|
|
+ ///
|
|
/// # Example
|
|
/// # Example
|
|
///
|
|
///
|
|
- /// ```no_run
|
|
|
|
|
|
+ /// ```rust, ignore
|
|
/// fn App(cx: Context, props: &()) -> Element {
|
|
/// fn App(cx: Context, props: &()) -> Element {
|
|
/// cx.render(rsx!( div {"hello"} ))
|
|
/// cx.render(rsx!( div {"hello"} ))
|
|
/// }
|
|
/// }
|
|
@@ -375,58 +348,19 @@ impl VirtualDom {
|
|
/// apply_mutations(mutations);
|
|
/// apply_mutations(mutations);
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
/// ```
|
|
- ///
|
|
|
|
- /// ## Mutations
|
|
|
|
- ///
|
|
|
|
- /// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also
|
|
|
|
- /// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has
|
|
|
|
- /// applied the edits.
|
|
|
|
- ///
|
|
|
|
- /// Mutations are the only link between the RealDOM and the VirtualDOM.
|
|
|
|
- ///
|
|
|
|
pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec<Mutations> {
|
|
pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec<Mutations> {
|
|
let mut committed_mutations = vec![];
|
|
let mut committed_mutations = vec![];
|
|
|
|
|
|
- while self.has_any_work() {
|
|
|
|
- while let Ok(Some(msg)) = self.receiver.try_next() {
|
|
|
|
- self.pending_messages.push_front(msg);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- while let Some(msg) = self.pending_messages.pop_back() {
|
|
|
|
- match msg {
|
|
|
|
- SchedulerMsg::Immediate(id) => {
|
|
|
|
- self.dirty_scopes.insert(id);
|
|
|
|
- }
|
|
|
|
- SchedulerMsg::UiEvent(event) => {
|
|
|
|
- if let Some(element) = event.mounted_dom_id {
|
|
|
|
- log::info!("Calling listener {:?}, {:?}", event.scope_id, element);
|
|
|
|
-
|
|
|
|
- if let Some(scope) = self.scopes.get_scope(&event.scope_id) {
|
|
|
|
- // TODO: bubble properly here
|
|
|
|
- scope.call_listener(event, element);
|
|
|
|
-
|
|
|
|
- while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
|
|
|
|
- self.pending_messages.push_front(dirty_scope);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- } else {
|
|
|
|
- log::debug!("User event without a targetted ElementId. Not currently supported.\nUnsure how to proceed. {:?}", event);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
|
|
+ while !self.dirty_scopes.is_empty() {
|
|
|
|
+ log::debug!("working with deadline");
|
|
let scopes = &self.scopes;
|
|
let scopes = &self.scopes;
|
|
let mut diff_state = DiffState::new(scopes);
|
|
let mut diff_state = DiffState::new(scopes);
|
|
|
|
|
|
let mut ran_scopes = FxHashSet::default();
|
|
let mut ran_scopes = FxHashSet::default();
|
|
|
|
|
|
- // todo: the 2021 version of rust will let us not have to force the borrow
|
|
|
|
- // let scopes = &self.scopes;
|
|
|
|
// Sort the scopes by height. Theoretically, we'll de-duplicate scopes by height
|
|
// Sort the scopes by height. Theoretically, we'll de-duplicate scopes by height
|
|
self.dirty_scopes
|
|
self.dirty_scopes
|
|
.retain(|id| scopes.get_scope(id).is_some());
|
|
.retain(|id| scopes.get_scope(id).is_some());
|
|
-
|
|
|
|
self.dirty_scopes.sort_by(|a, b| {
|
|
self.dirty_scopes.sort_by(|a, b| {
|
|
let h1 = scopes.get_scope(a).unwrap().height;
|
|
let h1 = scopes.get_scope(a).unwrap().height;
|
|
let h2 = scopes.get_scope(b).unwrap().height;
|
|
let h2 = scopes.get_scope(b).unwrap().height;
|
|
@@ -434,31 +368,27 @@ impl VirtualDom {
|
|
});
|
|
});
|
|
|
|
|
|
if let Some(scopeid) = self.dirty_scopes.pop() {
|
|
if let Some(scopeid) = self.dirty_scopes.pop() {
|
|
- log::info!("handling dirty scope {:?}", scopeid);
|
|
|
|
-
|
|
|
|
if !ran_scopes.contains(&scopeid) {
|
|
if !ran_scopes.contains(&scopeid) {
|
|
ran_scopes.insert(scopeid);
|
|
ran_scopes.insert(scopeid);
|
|
|
|
|
|
- log::debug!("about to run scope {:?}", scopeid);
|
|
|
|
-
|
|
|
|
if self.scopes.run_scope(&scopeid) {
|
|
if self.scopes.run_scope(&scopeid) {
|
|
let (old, new) = (
|
|
let (old, new) = (
|
|
self.scopes.wip_head(&scopeid),
|
|
self.scopes.wip_head(&scopeid),
|
|
self.scopes.fin_head(&scopeid),
|
|
self.scopes.fin_head(&scopeid),
|
|
);
|
|
);
|
|
- diff_state.stack.scope_stack.push(scopeid);
|
|
|
|
diff_state.stack.push(DiffInstruction::Diff { new, old });
|
|
diff_state.stack.push(DiffInstruction::Diff { new, old });
|
|
|
|
+ diff_state.stack.scope_stack.push(scopeid);
|
|
|
|
+
|
|
|
|
+ let scope = scopes.get_scope(&scopeid).unwrap();
|
|
|
|
+ diff_state.stack.element_stack.push(scope.container);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- let work_completed = diff_state.work(&mut deadline);
|
|
|
|
-
|
|
|
|
- if work_completed {
|
|
|
|
|
|
+ if diff_state.work(&mut deadline) {
|
|
let DiffState {
|
|
let DiffState {
|
|
mutations,
|
|
mutations,
|
|
seen_scopes,
|
|
seen_scopes,
|
|
- stack,
|
|
|
|
..
|
|
..
|
|
} = diff_state;
|
|
} = diff_state;
|
|
|
|
|
|
@@ -466,13 +396,9 @@ impl VirtualDom {
|
|
self.dirty_scopes.remove(&scope);
|
|
self.dirty_scopes.remove(&scope);
|
|
}
|
|
}
|
|
|
|
|
|
- // // I think the stack should be empty at the end of diffing?
|
|
|
|
- // debug_assert_eq!(stack.scope_stack.len(), 1);
|
|
|
|
-
|
|
|
|
committed_mutations.push(mutations);
|
|
committed_mutations.push(mutations);
|
|
} else {
|
|
} else {
|
|
// leave the work in an incomplete state
|
|
// leave the work in an incomplete state
|
|
- log::debug!("don't have a mechanism to pause work (yet)");
|
|
|
|
return committed_mutations;
|
|
return committed_mutations;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -490,7 +416,7 @@ impl VirtualDom {
|
|
/// All state stored in components will be completely wiped away.
|
|
/// All state stored in components will be completely wiped away.
|
|
///
|
|
///
|
|
/// # Example
|
|
/// # Example
|
|
- /// ```
|
|
|
|
|
|
+ /// ```rust, ignore
|
|
/// static App: FC<()> = |cx, props| cx.render(rsx!{ "hello world" });
|
|
/// static App: FC<()> = |cx, props| cx.render(rsx!{ "hello world" });
|
|
/// let mut dom = VirtualDom::new();
|
|
/// let mut dom = VirtualDom::new();
|
|
/// let edits = dom.rebuild();
|
|
/// let edits = dom.rebuild();
|
|
@@ -506,6 +432,7 @@ impl VirtualDom {
|
|
.stack
|
|
.stack
|
|
.create_node(self.scopes.fin_head(&scope_id), MountType::Append);
|
|
.create_node(self.scopes.fin_head(&scope_id), MountType::Append);
|
|
|
|
|
|
|
|
+ diff_state.stack.element_stack.push(ElementId(0));
|
|
diff_state.stack.scope_stack.push(scope_id);
|
|
diff_state.stack.scope_stack.push(scope_id);
|
|
|
|
|
|
diff_state.work(|| false);
|
|
diff_state.work(|| false);
|
|
@@ -521,9 +448,8 @@ impl VirtualDom {
|
|
/// In this case, every component will be diffed, even if their props are memoized. This method is intended to be used
|
|
/// In this case, every component will be diffed, even if their props are memoized. This method is intended to be used
|
|
/// to force an update of the DOM when the state of the app is changed outside of the app.
|
|
/// to force an update of the DOM when the state of the app is changed outside of the app.
|
|
///
|
|
///
|
|
- ///
|
|
|
|
/// # Example
|
|
/// # Example
|
|
- /// ```rust
|
|
|
|
|
|
+ /// ```rust, ignore
|
|
/// #[derive(PartialEq, Props)]
|
|
/// #[derive(PartialEq, Props)]
|
|
/// struct AppProps {
|
|
/// struct AppProps {
|
|
/// value: Shared<&'static str>,
|
|
/// value: Shared<&'static str>,
|
|
@@ -535,12 +461,7 @@ impl VirtualDom {
|
|
/// };
|
|
/// };
|
|
///
|
|
///
|
|
/// let value = Rc::new(RefCell::new("Hello"));
|
|
/// let value = Rc::new(RefCell::new("Hello"));
|
|
- /// let mut dom = VirtualDom::new_with_props(
|
|
|
|
- /// App,
|
|
|
|
- /// AppProps {
|
|
|
|
- /// value: value.clone(),
|
|
|
|
- /// },
|
|
|
|
- /// );
|
|
|
|
|
|
+ /// let mut dom = VirtualDom::new_with_props(App, AppProps { value: value.clone(), });
|
|
///
|
|
///
|
|
/// let _ = dom.rebuild();
|
|
/// let _ = dom.rebuild();
|
|
///
|
|
///
|
|
@@ -574,6 +495,7 @@ impl VirtualDom {
|
|
pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
|
|
pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
|
|
let mut machine = DiffState::new(&self.scopes);
|
|
let mut machine = DiffState::new(&self.scopes);
|
|
machine.stack.push(DiffInstruction::Diff { new, old });
|
|
machine.stack.push(DiffInstruction::Diff { new, old });
|
|
|
|
+ machine.stack.element_stack.push(ElementId(0));
|
|
machine.stack.scope_stack.push(self.base_scope);
|
|
machine.stack.scope_stack.push(self.base_scope);
|
|
machine.work(|| false);
|
|
machine.work(|| false);
|
|
machine.mutations
|
|
machine.mutations
|
|
@@ -585,6 +507,7 @@ impl VirtualDom {
|
|
pub fn create_vnodes<'a>(&'a self, left: Option<LazyNodes<'a, '_>>) -> Mutations<'a> {
|
|
pub fn create_vnodes<'a>(&'a self, left: Option<LazyNodes<'a, '_>>) -> Mutations<'a> {
|
|
let nodes = self.render_vnodes(left);
|
|
let nodes = self.render_vnodes(left);
|
|
let mut machine = DiffState::new(&self.scopes);
|
|
let mut machine = DiffState::new(&self.scopes);
|
|
|
|
+ machine.stack.element_stack.push(ElementId(0));
|
|
machine.stack.create_node(nodes, MountType::Append);
|
|
machine.stack.create_node(nodes, MountType::Append);
|
|
machine.work(|| false);
|
|
machine.work(|| false);
|
|
machine.mutations
|
|
machine.mutations
|
|
@@ -602,11 +525,13 @@ impl VirtualDom {
|
|
|
|
|
|
let mut create = DiffState::new(&self.scopes);
|
|
let mut create = DiffState::new(&self.scopes);
|
|
create.stack.scope_stack.push(self.base_scope);
|
|
create.stack.scope_stack.push(self.base_scope);
|
|
|
|
+ create.stack.element_stack.push(ElementId(0));
|
|
create.stack.create_node(old, MountType::Append);
|
|
create.stack.create_node(old, MountType::Append);
|
|
create.work(|| false);
|
|
create.work(|| false);
|
|
|
|
|
|
let mut edit = DiffState::new(&self.scopes);
|
|
let mut edit = DiffState::new(&self.scopes);
|
|
- create.stack.scope_stack.push(self.base_scope);
|
|
|
|
|
|
+ edit.stack.scope_stack.push(self.base_scope);
|
|
|
|
+ edit.stack.element_stack.push(ElementId(0));
|
|
edit.stack.push(DiffInstruction::Diff { old, new });
|
|
edit.stack.push(DiffInstruction::Diff { old, new });
|
|
edit.work(&mut || false);
|
|
edit.work(&mut || false);
|
|
|
|
|
|
@@ -620,17 +545,53 @@ pub enum SchedulerMsg {
|
|
|
|
|
|
// setstate
|
|
// setstate
|
|
Immediate(ScopeId),
|
|
Immediate(ScopeId),
|
|
|
|
+
|
|
|
|
+ // an async task pushed from an event handler (or just spawned)
|
|
|
|
+ TaskPushed(ScopeId),
|
|
|
|
+
|
|
|
|
+ Suspended { scope: ScopeId },
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+/// User Events are events that are shuttled from the renderer into the VirtualDom trhough the scheduler channel.
|
|
|
|
+///
|
|
|
|
+/// These events will be passed to the appropriate Element given by `mounted_dom_id` and then bubbled up through the tree
|
|
|
|
+/// where each listener is checked and fired if the event name matches.
|
|
|
|
+///
|
|
|
|
+/// It is the expectation that the event name matches the corresponding event listener, otherwise Dioxus will panic in
|
|
|
|
+/// attempting to downcast the event data.
|
|
|
|
+///
|
|
|
|
+/// Because Event Data is sent across threads, it must be `Send + Sync`. We are hoping to lift the `Sync` restriction but
|
|
|
|
+/// `Send` will not be lifted. The entire `UserEvent` must also be `Send + Sync` due to its use in the scheduler channel.
|
|
|
|
+///
|
|
|
|
+/// # Example
|
|
|
|
+/// ```rust
|
|
|
|
+/// fn App(cx: Context, props: &()) -> Element {
|
|
|
|
+/// rsx!(cx, div {
|
|
|
|
+/// onclick: move |_| println!("Clicked!")
|
|
|
|
+/// })
|
|
|
|
+/// }
|
|
|
|
+///
|
|
|
|
+/// let mut dom = VirtualDom::new(App);
|
|
|
|
+/// let mut scheduler = dom.get_scheduler_channel();
|
|
|
|
+/// scheduler.unbounded_send(SchedulerMsg::UiEvent(
|
|
|
|
+/// UserEvent {
|
|
|
|
+/// scope_id: None,
|
|
|
|
+/// priority: EventPriority::Medium,
|
|
|
|
+/// name: "click",
|
|
|
|
+/// element: Some(ElementId(0)),
|
|
|
|
+/// data: Arc::new(ClickEvent { .. })
|
|
|
|
+/// }
|
|
|
|
+/// )).unwrap();
|
|
|
|
+/// ```
|
|
#[derive(Debug)]
|
|
#[derive(Debug)]
|
|
pub struct UserEvent {
|
|
pub struct UserEvent {
|
|
/// The originator of the event trigger
|
|
/// The originator of the event trigger
|
|
- pub scope_id: ScopeId,
|
|
|
|
|
|
+ pub scope_id: Option<ScopeId>,
|
|
|
|
|
|
pub priority: EventPriority,
|
|
pub priority: EventPriority,
|
|
|
|
|
|
/// The optional real node associated with the trigger
|
|
/// The optional real node associated with the trigger
|
|
- pub mounted_dom_id: Option<ElementId>,
|
|
|
|
|
|
+ pub element: Option<ElementId>,
|
|
|
|
|
|
/// The event type IE "onclick" or "onmouseover"
|
|
/// The event type IE "onclick" or "onmouseover"
|
|
///
|
|
///
|
|
@@ -638,7 +599,7 @@ pub struct UserEvent {
|
|
pub name: &'static str,
|
|
pub name: &'static str,
|
|
|
|
|
|
/// Event Data
|
|
/// Event Data
|
|
- pub event: Box<dyn Any + Send>,
|
|
|
|
|
|
+ pub data: Arc<dyn Any + Send + Sync>,
|
|
}
|
|
}
|
|
|
|
|
|
/// Priority of Event Triggers.
|
|
/// Priority of Event Triggers.
|
|
@@ -692,3 +653,55 @@ pub enum EventPriority {
|
|
/// This is considered "idle" work or "background" work.
|
|
/// This is considered "idle" work or "background" work.
|
|
Low = 0,
|
|
Low = 0,
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+struct PollTasks<'a>(&'a mut ScopeArena);
|
|
|
|
+
|
|
|
|
+impl<'a> Future for PollTasks<'a> {
|
|
|
|
+ type Output = ();
|
|
|
|
+
|
|
|
|
+ fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
|
|
|
|
+ let mut all_pending = true;
|
|
|
|
+
|
|
|
|
+ let mut unfinished_tasks: SmallVec<[_; 10]> = smallvec::smallvec![];
|
|
|
|
+ let mut scopes_to_clear: SmallVec<[_; 10]> = smallvec::smallvec![];
|
|
|
|
+
|
|
|
|
+ // Poll every scope manually
|
|
|
|
+ for fut in self.0.pending_futures.borrow().iter() {
|
|
|
|
+ let scope = self.0.get_scope(fut).expect("Scope should never be moved");
|
|
|
|
+
|
|
|
|
+ let mut items = scope.items.borrow_mut();
|
|
|
|
+
|
|
|
|
+ // really this should just be retain_mut but that doesn't exist yet
|
|
|
|
+ while let Some(mut task) = items.tasks.pop() {
|
|
|
|
+ // todo: does this make sense?
|
|
|
|
+ // I don't usually write futures by hand
|
|
|
|
+ // I think the futures neeed to be pinned using bumpbox or something
|
|
|
|
+ // right now, they're bump allocated so this shouldn't matter anyway - they're not going to move
|
|
|
|
+ let task_mut = task.as_mut();
|
|
|
|
+ let unpinned = unsafe { Pin::new_unchecked(task_mut) };
|
|
|
|
+
|
|
|
|
+ if unpinned.poll(cx).is_ready() {
|
|
|
|
+ all_pending = false
|
|
|
|
+ } else {
|
|
|
|
+ unfinished_tasks.push(task);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if unfinished_tasks.is_empty() {
|
|
|
|
+ scopes_to_clear.push(*fut);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ items.tasks.extend(unfinished_tasks.drain(..));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for scope in scopes_to_clear {
|
|
|
|
+ self.0.pending_futures.borrow_mut().remove(&scope);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Resolve the future if any singular task is ready
|
|
|
|
+ match all_pending {
|
|
|
|
+ true => Poll::Pending,
|
|
|
|
+ false => Poll::Ready(()),
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|