Răsfoiți Sursa

suspense works in desktop

Jonathan Kelley 2 ani în urmă
părinte
comite
642b21f125

+ 31 - 28
examples/dog_app.rs

@@ -6,7 +6,11 @@ use dioxus::prelude::*;
 use std::collections::HashMap;
 
 fn main() {
-    dioxus_desktop::launch(app);
+    dioxus_desktop::launch(|cx| {
+        cx.render(rsx! {
+            app_root {}
+        })
+    });
 }
 
 #[derive(Debug, Clone, PartialEq, serde::Deserialize)]
@@ -14,8 +18,8 @@ struct ListBreeds {
     message: HashMap<String, Vec<String>>,
 }
 
-fn app(cx: Scope) -> Element {
-    let breed = use_state(&cx, || None);
+async fn app_root(cx: Scope<'_>) -> Element {
+    let breed = use_state(&cx, || "deerhound".to_string());
 
     let breeds = use_future(&cx, (), |_| async move {
         reqwest::get("https://dog.ceo/api/breeds/list/all")
@@ -25,32 +29,26 @@ fn app(cx: Scope) -> Element {
             .await
     });
 
-    match breeds.value() {
-        Some(Ok(breeds)) => cx.render(rsx! {
-            div {
+    match breeds.await {
+        Ok(breeds) => cx.render(rsx! {
+            div { height: "500px",
                 h1 { "Select a dog breed!" }
                 div { display: "flex",
                     ul { flex: "50%",
-                        breeds.message.keys().map(|cur_breed| rsx!(
+                        breeds.message.keys().take(5).map(|cur_breed| rsx!(
                             li {
                                 button {
-                                    onclick: move |_| breed.set(Some(cur_breed.clone())),
+                                    onclick: move |_| breed.set(cur_breed.clone()),
                                     "{cur_breed}"
                                 }
                             }
                         ))
                     }
-                    div { flex: "50%",
-                        match breed.get() {
-                            Some(breed) => rsx!( Breed { breed: breed.clone() } ),
-                            None => rsx!("No Breed selected"),
-                        }
-                    }
+                    div { flex: "50%", Breed { breed: breed.to_string() } }
                 }
             }
         }),
-        Some(Err(_e)) => cx.render(rsx! { div { "Error fetching breeds" } }),
-        None => cx.render(rsx! { div { "Loading dogs..." } }),
+        Err(_e) => cx.render(rsx! { div { "Error fetching breeds" } }),
     }
 }
 
@@ -60,7 +58,9 @@ struct DogApi {
 }
 
 #[inline_props]
-fn Breed(cx: Scope, breed: String) -> Element {
+async fn Breed(cx: Scope, breed: String) -> Element {
+    println!("Rendering Breed: {}", breed);
+
     let fut = use_future(&cx, (breed,), |(breed,)| async move {
         reqwest::get(format!("https://dog.ceo/api/breed/{}/images/random", breed))
             .await
@@ -69,21 +69,24 @@ fn Breed(cx: Scope, breed: String) -> Element {
             .await
     });
 
-    cx.render(match fut.value() {
-        Some(Ok(resp)) => rsx! {
-            button {
-                onclick: move |_| fut.restart(),
-                "Click to fetch another doggo"
-            }
+    let resp = fut.await;
+
+    println!("achieved results!");
+
+    match resp {
+        Ok(resp) => cx.render(rsx! {
             div {
+                button {
+                    onclick: move |_| fut.restart(),
+                    "Click to fetch another doggo"
+                }
                 img {
+                    src: "{resp.message}",
                     max_width: "500px",
                     max_height: "500px",
-                    src: "{resp.message}",
                 }
             }
-        },
-        Some(Err(_)) => rsx! { div { "loading dogs failed" } },
-        None => rsx! { div { "loading dogs..." } },
-    })
+        }),
+        Err(e) => cx.render(rsx! { div { "loading dogs failed" } }),
+    }
 }

+ 16 - 0
examples/suspense2.rs

@@ -0,0 +1,16 @@
+use dioxus::prelude::*;
+use dioxus_desktop::{Config, LogicalSize, WindowBuilder};
+
+fn main() {
+    dioxus_desktop::launch(|cx| cx.render(rsx! { async_app {} }));
+}
+
+async fn async_app(cx: Scope<'_>) -> Element {
+    tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
+
+    cx.render(rsx! {
+        div {
+            "hi!"
+        }
+    })
+}

+ 5 - 1
packages/core-macro/src/inlineprops.rs

@@ -9,6 +9,7 @@ use syn::{
 pub struct InlinePropsBody {
     pub attrs: Vec<Attribute>,
     pub vis: syn::Visibility,
+    pub maybe_async: Option<Token![async]>,
     pub fn_token: Token![fn],
     pub ident: Ident,
     pub cx_token: Box<Pat>,
@@ -25,6 +26,7 @@ pub struct InlinePropsBody {
 impl Parse for InlinePropsBody {
     fn parse(input: ParseStream) -> Result<Self> {
         let attrs: Vec<Attribute> = input.call(Attribute::parse_outer)?;
+        let maybe_async: Option<Token![async]> = input.parse().ok();
         let vis: Visibility = input.parse()?;
 
         let fn_token = input.parse()?;
@@ -57,6 +59,7 @@ impl Parse for InlinePropsBody {
 
         Ok(Self {
             vis,
+            maybe_async,
             fn_token,
             ident,
             generics,
@@ -84,6 +87,7 @@ impl ToTokens for InlinePropsBody {
             block,
             cx_token,
             attrs,
+            maybe_async,
             ..
         } = self;
 
@@ -151,7 +155,7 @@ impl ToTokens for InlinePropsBody {
             }
 
             #(#attrs)*
-            #vis fn #ident #fn_generics (#cx_token: Scope<#scope_lifetime #struct_name #generics>) #output
+            #maybe_async #vis fn #ident #fn_generics (#cx_token: Scope<#scope_lifetime #struct_name #generics>) #output
             #where_clause
             {
                 let #struct_name { #(#field_names),* } = &cx.props;

+ 70 - 45
packages/core/src/create.rs

@@ -1,11 +1,13 @@
+use std::ops::Bound;
+
 use crate::factory::RenderReturn;
-use crate::innerlude::{Mutations, SuspenseContext};
+use crate::innerlude::{Mutations, SuspenseContext, SuspenseId};
 use crate::mutations::Mutation;
 use crate::mutations::Mutation::*;
 use crate::nodes::VNode;
 use crate::nodes::{DynamicNode, TemplateNode};
 use crate::virtual_dom::VirtualDom;
-use crate::{AttributeValue, ElementId, ScopeId, TemplateAttribute};
+use crate::{AttributeValue, ScopeId, TemplateAttribute};
 
 impl VirtualDom {
     pub(crate) fn create_scope<'a>(
@@ -50,8 +52,6 @@ impl VirtualDom {
 
         let cur_scope = self.scope_stack.last().copied().unwrap();
 
-        println!("creating template: {:#?}", template);
-
         let mut on_stack = 0;
         for (root_idx, root) in template.template.roots.iter().enumerate() {
             on_stack += match root {
@@ -64,9 +64,7 @@ impl VirtualDom {
                 }
 
                 TemplateNode::DynamicText(id) | TemplateNode::Dynamic(id) => {
-                    let dynamic_node = &template.dynamic_nodes[*id];
-
-                    match dynamic_node {
+                    match &template.dynamic_nodes[*id] {
                         DynamicNode::Fragment { .. } | DynamicNode::Component { .. } => self
                             .create_dynamic_node(
                                 mutations,
@@ -79,19 +77,16 @@ impl VirtualDom {
                         } => {
                             let id = self.next_element(template);
                             slot.set(id);
-                            mutations.push(CreateTextNode {
-                                value: value.clone(),
-                                id,
-                            });
+                            mutations.push(CreateTextNode { value, id });
                             1
                         }
-                        DynamicNode::Placeholder(id) => {
+                        DynamicNode::Placeholder(slot) => {
                             let id = self.next_element(template);
+                            slot.set(id);
                             mutations.push(CreatePlaceholder { id });
                             1
                         }
                     }
-                    // self.create_dynamic_node(mutations, template, &template.dynamic_nodes[*id], *id)
                 }
             };
 
@@ -141,31 +136,34 @@ impl VirtualDom {
                 }
             }
 
-            // todo:
-            //
-            //  we walk the roots front to back when creating nodes, bur want to fill in the dynamic nodes
-            // back to front. This is because the indices would shift around because the paths become invalid
-            //
-            // We could easily implement this without the vec by walking the indicies forward
-            let mut queued_changes = vec![];
-
             // We're on top of a node that has a dynamic child for a descendant
             // Skip any node that's a root
-            while let Some((idx, path)) =
-                dynamic_nodes.next_if(|(_, p)| p.len() > 1 && p[0] == root_idx as u8)
-            {
-                let node = &template.dynamic_nodes[idx];
-                let m = self.create_dynamic_node(mutations, template, node, idx);
-                if m > 0 {
-                    queued_changes.push(ReplacePlaceholder {
-                        m,
-                        path: &path[1..],
-                    });
+            let mut start = None;
+            let mut end = None;
+
+            while let Some((idx, p)) = dynamic_nodes.next_if(|(_, p)| p[0] == root_idx as u8) {
+                if p.len() == 1 {
+                    continue;
+                }
+
+                if start.is_none() {
+                    start = Some(idx);
                 }
+
+                end = Some(idx);
             }
 
-            for change in queued_changes.into_iter().rev() {
-                mutations.push(change);
+            if let (Some(start), Some(end)) = (start, end) {
+                for idx in start..=end {
+                    let node = &template.dynamic_nodes[idx];
+                    let m = self.create_dynamic_node(mutations, template, node, idx);
+                    if m > 0 {
+                        mutations.push(ReplacePlaceholder {
+                            m,
+                            path: &template.template.node_paths[idx][1..],
+                        });
+                    }
+                }
             }
         }
 
@@ -240,7 +238,7 @@ impl VirtualDom {
         idx: usize,
     ) -> usize {
         match &node {
-            DynamicNode::Text { id, value, inner } => {
+            DynamicNode::Text { id, value, .. } => {
                 let new_id = self.next_element(template);
                 id.set(new_id);
                 mutations.push(HydrateText {
@@ -266,14 +264,32 @@ impl VirtualDom {
                 let return_nodes = unsafe { self.run_scope(scope).extend_lifetime_ref() };
 
                 match return_nodes {
-                    RenderReturn::Sync(None) | RenderReturn::Async(_) => {
+                    RenderReturn::Sync(None) => {
+                        todo!()
+                    }
+
+                    RenderReturn::Async(_) => {
                         let new_id = self.next_element(template);
                         placeholder.set(Some(new_id));
                         self.scopes[scope.0].placeholder.set(Some(new_id));
+
                         mutations.push(AssignId {
                             id: new_id,
                             path: &template.template.node_paths[idx][1..],
                         });
+
+                        let boudary = self.scopes[scope.0]
+                            .consume_context::<SuspenseContext>()
+                            .unwrap();
+
+                        if boudary.placeholder.get().is_none() {
+                            boudary.placeholder.set(Some(new_id));
+                        }
+                        boudary
+                            .waiting_on
+                            .borrow_mut()
+                            .extend(self.collected_leaves.drain(..));
+
                         0
                     }
 
@@ -288,16 +304,6 @@ impl VirtualDom {
                             if let Some(boundary) =
                                 self.scopes[scope.0].has_context::<SuspenseContext>()
                             {
-                                let mut boundary_mut = boundary.borrow_mut();
-                                let split_off = mutations.split_off(mutations_to_this_point);
-
-                                let split_off = unsafe { std::mem::transmute(split_off) };
-
-                                boundary_mut.mutations.edits = split_off;
-                                boundary_mut
-                                    .waiting_on
-                                    .extend(self.collected_leaves.drain(..));
-
                                 // Since this is a boundary, use it as a placeholder
                                 let new_id = self.next_element(template);
                                 placeholder.set(Some(new_id));
@@ -306,6 +312,25 @@ impl VirtualDom {
                                     id: new_id,
                                     path: &template.template.node_paths[idx][1..],
                                 });
+
+                                // Now connect everything to the boundary
+                                let boundary_mut = boundary;
+                                let split_off = mutations.split_off(mutations_to_this_point);
+                                let split_off: Vec<Mutation> =
+                                    unsafe { std::mem::transmute(split_off) };
+
+                                if boundary_mut.placeholder.get().is_none() {
+                                    boundary_mut.placeholder.set(Some(new_id));
+                                }
+
+                                // In the generated edits, we want to pick off from where we left off.
+                                boundary_mut.mutations.borrow_mut().edits.extend(split_off);
+
+                                boundary_mut
+                                    .waiting_on
+                                    .borrow_mut()
+                                    .extend(self.collected_leaves.drain(..));
+
                                 created = 0;
                             }
                         }

+ 10 - 7
packages/core/src/scheduler/suspense.rs

@@ -1,4 +1,5 @@
 use super::{waker::RcWake, SchedulerMsg};
+use crate::ElementId;
 use crate::{innerlude::Mutations, Element, ScopeId};
 use std::future::Future;
 use std::{
@@ -10,22 +11,24 @@ use std::{
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 pub struct SuspenseId(pub usize);
 
-pub type SuspenseContext = Rc<RefCell<SuspenseBoundary>>;
+pub type SuspenseContext = Rc<SuspenseBoundary>;
 
 /// Essentially a fiber in React
 pub struct SuspenseBoundary {
     pub id: ScopeId,
-    pub waiting_on: HashSet<SuspenseId>,
-    pub mutations: Mutations<'static>,
+    pub waiting_on: RefCell<HashSet<SuspenseId>>,
+    pub mutations: RefCell<Mutations<'static>>,
+    pub placeholder: Cell<Option<ElementId>>,
 }
 
 impl SuspenseBoundary {
-    pub fn new(id: ScopeId) -> Rc<RefCell<Self>> {
-        Rc::new(RefCell::new(Self {
+    pub fn new(id: ScopeId) -> Rc<Self> {
+        Rc::new(Self {
             id,
             waiting_on: Default::default(),
-            mutations: Mutations::new(0),
-        }))
+            mutations: RefCell::new(Mutations::new(0)),
+            placeholder: Cell::new(None),
+        })
     }
 }
 

+ 22 - 7
packages/core/src/scheduler/wait.rs

@@ -53,14 +53,12 @@ impl VirtualDom {
         // we should attach them to that component and then render its children
         // continue rendering the tree until we hit yet another suspended component
         if let Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) {
-            let boundary = &self.scopes[leaf.scope_id.0]
+            let fiber = &self.scopes[leaf.scope_id.0]
                 .consume_context::<SuspenseContext>()
                 .unwrap();
 
             println!("ready pool");
 
-            let mut fiber = boundary.borrow_mut();
-
             println!(
                 "Existing mutations {:?}, scope {:?}",
                 fiber.mutations, fiber.id
@@ -72,16 +70,33 @@ impl VirtualDom {
             let ret = arena.bump.alloc(RenderReturn::Sync(new_nodes));
             arena.node.set(ret);
 
+            fiber.waiting_on.borrow_mut().remove(&id);
+
             if let RenderReturn::Sync(Some(template)) = ret {
-                let mutations = &mut fiber.mutations;
+                let mutations_ref = &mut fiber.mutations.borrow_mut();
+                let mutations = &mut **mutations_ref;
                 let template: &VNode = unsafe { std::mem::transmute(template) };
                 let mutations: &mut Mutations = unsafe { std::mem::transmute(mutations) };
 
+                let place_holder_id = scope.placeholder.get().unwrap();
                 self.scope_stack.push(scope_id);
-                self.create(mutations, template);
+                let created = self.create(mutations, template);
                 self.scope_stack.pop();
-
-                println!("{:#?}", mutations);
+                mutations.push(Mutation::ReplaceWith {
+                    id: place_holder_id,
+                    m: created,
+                });
+
+                // for leaf in self.collected_leaves.drain(..) {
+                //     fiber.waiting_on.borrow_mut().insert(leaf);
+                // }
+
+                if fiber.waiting_on.borrow().is_empty() {
+                    println!("fiber is finished!");
+                    self.finished_fibers.push(fiber.id);
+                } else {
+                    println!("fiber is not finished {:?}", fiber.waiting_on);
+                }
             } else {
                 println!("nodes arent right");
             }

+ 10 - 3
packages/core/src/virtual_dom.rs

@@ -268,8 +268,8 @@ impl VirtualDom {
         !self.scopes[id.0]
             .consume_context::<SuspenseContext>()
             .unwrap()
-            .borrow()
             .waiting_on
+            .borrow()
             .is_empty()
     }
 
@@ -469,8 +469,15 @@ impl VirtualDom {
             for finished_fiber in self.finished_fibers.drain(..) {
                 let scope = &mut self.scopes[finished_fiber.0];
                 let context = scope.has_context::<SuspenseContext>().unwrap();
-                let mut context = context.borrow_mut();
-                mutations.extend(context.mutations.drain(..));
+                println!("unloading suspense tree {:?}", context.mutations);
+
+                mutations.extend(context.mutations.borrow_mut().template_mutations.drain(..));
+                mutations.extend(context.mutations.borrow_mut().drain(..));
+
+                mutations.push(Mutation::ReplaceWith {
+                    id: context.placeholder.get().unwrap(),
+                    m: 1,
+                })
             }
 
             // Next, diff any dirty scopes

+ 6 - 9
packages/desktop/src/controller.rs

@@ -50,7 +50,7 @@ impl DesktopController {
 
                 let edits = dom.rebuild();
 
-                println!("got muts: {:#?}", edits);
+                // println!("got muts: {:#?}", edits);
 
                 {
                     let mut queue = edit_queue.lock().unwrap();
@@ -68,20 +68,15 @@ impl DesktopController {
 
                     let muts = dom
                         .render_with_deadline(tokio::time::sleep(
-                            tokio::time::Duration::from_millis(100),
+                            tokio::time::Duration::from_millis(16),
                         ))
                         .await;
 
                     {
                         let mut queue = edit_queue.lock().unwrap();
 
-                        for edit in muts.template_mutations {
-                            queue.push(serde_json::to_string(&edit).unwrap());
-                        }
-
-                        for edit in muts.edits {
-                            queue.push(serde_json::to_string(&edit).unwrap());
-                        }
+                        queue.push(serde_json::to_string(&muts.template_mutations).unwrap());
+                        queue.push(serde_json::to_string(&muts.edits).unwrap());
 
                         drop(queue);
                     }
@@ -118,6 +113,8 @@ impl DesktopController {
 
             let (_id, view) = self.webviews.iter_mut().next().unwrap();
 
+            println!("sending edits {:#?}", new_queue);
+
             for edit in new_queue.drain(..) {
                 view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit))
                     .unwrap();

+ 0 - 6
packages/desktop/src/lib.rs

@@ -181,12 +181,6 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
                             index_file.clone(),
                         )
                     })
-                    // passing edits via the custom protocol is faster than using eval, maybe?
-                    .with_custom_protocol(String::from("edits"), move |r| {
-                        //
-                        // Ok(Response::body())
-                        todo!()
-                    })
                     .with_file_drop_handler(move |window, evet| {
                         file_handler
                             .as_ref()

+ 2 - 0
packages/desktop/src/protocol.rs

@@ -18,6 +18,8 @@ pub(super) fn desktop_handler(
     custom_head: Option<String>,
     custom_index: Option<String>,
 ) -> Result<Response<Vec<u8>>> {
+    println!("loading request: {:?}", request.uri());
+
     // Any content that uses the `dioxus://` scheme will be shuttled through this handler as a "special case".
     // For now, we only serve two pieces of content which get included as bytes into the final binary.
     let path = request.uri().to_string().replace("dioxus://", "");

+ 5 - 3
packages/hooks/src/usefuture.rs

@@ -134,8 +134,11 @@ impl<T> UseFuture<T> {
     ///
     /// If the future has never completed, the returned value will be `None`.
     pub fn value(&self) -> Option<&T> {
-        // self.value.as_ref()
-        todo!()
+        self.values
+            .borrow_mut()
+            .last()
+            .cloned()
+            .map(|x| unsafe { &*x })
     }
 
     /// Get the ID of the future in Dioxus' internal scheduler
@@ -181,7 +184,6 @@ impl<'a, T> Future for UseFutureAwait<'a, T> {
         self: std::pin::Pin<&mut Self>,
         cx: &mut std::task::Context<'_>,
     ) -> std::task::Poll<Self::Output> {
-        println!("polling future");
         match self.hook.values.borrow_mut().last().cloned() {
             Some(value) => std::task::Poll::Ready(unsafe { &*value }),
             None => {

+ 4 - 1
packages/interpreter/src/interpreter.js

@@ -167,7 +167,10 @@ export class Interpreter {
     const name = field;
     const node = this.nodes[root];
     if (ns === "style") {
-      // @ts-ignore
+      // ????? why do we need to do this
+      if (node.style === undefined) {
+        node.style = {};
+      }
       node.style[name] = value;
     } else if (ns != null || ns != undefined) {
       node.setAttributeNS(ns, name, value);