diff --git a/examples/dog_app.rs b/examples/dog_app.rs index 5c78ed0bf..cac974b46 100644 --- a/examples/dog_app.rs +++ b/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>, } -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" } }), + } } diff --git a/examples/suspense2.rs b/examples/suspense2.rs new file mode 100644 index 000000000..b63867f0a --- /dev/null +++ b/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!" + } + }) +} diff --git a/packages/core-macro/src/inlineprops.rs b/packages/core-macro/src/inlineprops.rs index 05d9d5240..dc9c4711b 100644 --- a/packages/core-macro/src/inlineprops.rs +++ b/packages/core-macro/src/inlineprops.rs @@ -9,6 +9,7 @@ use syn::{ pub struct InlinePropsBody { pub attrs: Vec, pub vis: syn::Visibility, + pub maybe_async: Option, pub fn_token: Token![fn], pub ident: Ident, pub cx_token: Box, @@ -25,6 +26,7 @@ pub struct InlinePropsBody { impl Parse for InlinePropsBody { fn parse(input: ParseStream) -> Result { let attrs: Vec = input.call(Attribute::parse_outer)?; + let maybe_async: Option = 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; diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index c255b7113..b5baf68b4 100644 --- a/packages/core/src/create.rs +++ b/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::() + .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::() { - 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 = + 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; } } diff --git a/packages/core/src/scheduler/suspense.rs b/packages/core/src/scheduler/suspense.rs index ecdce3c93..ca0fcd3d0 100644 --- a/packages/core/src/scheduler/suspense.rs +++ b/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>; +pub type SuspenseContext = Rc; /// Essentially a fiber in React pub struct SuspenseBoundary { pub id: ScopeId, - pub waiting_on: HashSet, - pub mutations: Mutations<'static>, + pub waiting_on: RefCell>, + pub mutations: RefCell>, + pub placeholder: Cell>, } impl SuspenseBoundary { - pub fn new(id: ScopeId) -> Rc> { - Rc::new(RefCell::new(Self { + pub fn new(id: ScopeId) -> Rc { + Rc::new(Self { id, waiting_on: Default::default(), - mutations: Mutations::new(0), - })) + mutations: RefCell::new(Mutations::new(0)), + placeholder: Cell::new(None), + }) } } diff --git a/packages/core/src/scheduler/wait.rs b/packages/core/src/scheduler/wait.rs index 62462ff86..0ac312b61 100644 --- a/packages/core/src/scheduler/wait.rs +++ b/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::() .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(); + mutations.push(Mutation::ReplaceWith { + id: place_holder_id, + m: created, + }); - println!("{:#?}", mutations); + // 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"); } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 6575de6ef..8bbb0b86b 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -268,8 +268,8 @@ impl VirtualDom { !self.scopes[id.0] .consume_context::() .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::().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 diff --git a/packages/desktop/src/controller.rs b/packages/desktop/src/controller.rs index fb6d352fc..8d6ce5901 100644 --- a/packages/desktop/src/controller.rs +++ b/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(); diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index d0d9322a3..db97b6b74 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -181,12 +181,6 @@ pub fn launch_with_props(root: Component

, 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() diff --git a/packages/desktop/src/protocol.rs b/packages/desktop/src/protocol.rs index cc084b403..06dfbfc54 100644 --- a/packages/desktop/src/protocol.rs +++ b/packages/desktop/src/protocol.rs @@ -18,6 +18,8 @@ pub(super) fn desktop_handler( custom_head: Option, custom_index: Option, ) -> Result>> { + 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://", ""); diff --git a/packages/hooks/src/usefuture.rs b/packages/hooks/src/usefuture.rs index a23f09691..782c774b0 100644 --- a/packages/hooks/src/usefuture.rs +++ b/packages/hooks/src/usefuture.rs @@ -134,8 +134,11 @@ impl UseFuture { /// /// 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 { - println!("polling future"); match self.hook.values.borrow_mut().last().cloned() { Some(value) => std::task::Poll::Ready(unsafe { &*value }), None => { diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index 1a2215383..2f5c06f3c 100644 --- a/packages/interpreter/src/interpreter.js +++ b/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);