diff --git a/examples/dog_app.rs b/examples/dog_app.rs index 107251cd..510550b9 100644 --- a/examples/dog_app.rs +++ b/examples/dog_app.rs @@ -10,7 +10,7 @@ struct ListBreeds { message: HashMap>, } -async fn app_root(cx: Scope<'_>) -> Element { +fn app_root(cx: Scope<'_>) -> Element { let breed = use_state(cx, || "deerhound".to_string()); let breeds = use_future!(cx, || async move { @@ -21,13 +21,13 @@ async fn app_root(cx: Scope<'_>) -> Element { .await }); - match breeds.await { - Ok(breeds) => cx.render(rsx! { + match breeds.suspend()? { + Ok(breed_list) => cx.render(rsx! { div { height: "500px", h1 { "Select a dog breed!" } div { display: "flex", ul { flex: "50%", - for cur_breed in breeds.message.keys().take(10) { + for cur_breed in breed_list.message.keys().take(10) { li { key: "{cur_breed}", button { onclick: move |_| breed.set(cur_breed.clone()), @@ -50,7 +50,7 @@ struct DogApi { } #[inline_props] -async fn breed_pic(cx: Scope, breed: String) -> Element { +fn breed_pic(cx: Scope, breed: String) -> Element { let fut = use_future!(cx, |breed| async move { reqwest::get(format!("https://dog.ceo/api/breed/{breed}/images/random")) .await @@ -59,7 +59,7 @@ async fn breed_pic(cx: Scope, breed: String) -> Element { .await }); - match fut.await { + match fut.suspend()? { Ok(resp) => render! { div { button { diff --git a/packages/core/src/any_props.rs b/packages/core/src/any_props.rs index 7014a277..e3a3bdae 100644 --- a/packages/core/src/any_props.rs +++ b/packages/core/src/any_props.rs @@ -1,8 +1,8 @@ -use std::{marker::PhantomData, panic::AssertUnwindSafe}; +use std::{ panic::AssertUnwindSafe}; use crate::{ innerlude::Scoped, - nodes::{ComponentReturn, RenderReturn}, + nodes::RenderReturn, scopes::{Scope, ScopeState}, Element, }; @@ -18,19 +18,15 @@ pub(crate) unsafe trait AnyProps<'a> { unsafe fn memoize(&self, other: &dyn AnyProps) -> bool; } -pub(crate) struct VProps<'a, P, A, F: ComponentReturn<'a, A> = Element<'a>> { - pub render_fn: fn(Scope<'a, P>) -> F, +pub(crate) struct VProps<'a, P> { + pub render_fn: fn(Scope<'a, P>) -> Element<'a>, pub memo: unsafe fn(&P, &P) -> bool, pub props: P, - _marker: PhantomData, } -impl<'a, P, A, F> VProps<'a, P, A, F> -where - F: ComponentReturn<'a, A>, -{ +impl<'a, P> VProps<'a, P> { pub(crate) fn new( - render_fn: fn(Scope<'a, P>) -> F, + render_fn: fn(Scope<'a, P>) -> Element<'a>, memo: unsafe fn(&P, &P) -> bool, props: P, ) -> Self { @@ -38,15 +34,11 @@ where render_fn, memo, props, - _marker: PhantomData, } } } -unsafe impl<'a, P, A, F> AnyProps<'a> for VProps<'a, P, A, F> -where - F: ComponentReturn<'a, A>, -{ +unsafe impl<'a, P> AnyProps<'a> for VProps<'a, P> { fn props_ptr(&self) -> *const () { &self.props as *const _ as *const () } @@ -69,12 +61,12 @@ where scope: cx, }); - (self.render_fn)(scope).into_return(cx) + (self.render_fn)(scope) })); match res { - Ok(e) => e, - Err(_) => RenderReturn::default(), + Ok(Some(e)) => RenderReturn::Ready(e), + _ => RenderReturn::default(), } } } diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index 80561e93..d31e5ab4 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -514,7 +514,6 @@ impl<'b> VirtualDom { match unsafe { self.run_scope(scope).extend_lifetime_ref() } { Ready(t) => self.mount_component(scope, template, t, idx), Aborted(t) => self.mount_aborted(template, t), - Pending(_) => self.mount_async(template, idx, scope), } } @@ -591,24 +590,6 @@ impl<'b> VirtualDom { 1 } - /// Take the rendered nodes from a component and handle them if they were async - /// - /// IE simply assign an ID to the placeholder - fn mount_async(&mut self, template: &VNode, idx: usize, scope: ScopeId) -> usize { - let new_id = self.next_element(template, template.template.get().node_paths[idx]); - - // Set the placeholder of the scope - self.scopes[scope].placeholder.set(Some(new_id)); - - // Since the placeholder is already in the DOM, we don't create any new nodes - self.mutations.push(AssignId { - id: new_id, - path: &template.template.get().node_paths[idx][1..], - }); - - 0 - } - fn set_slot( &mut self, template: &'b VNode<'b>, diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index a8b91344..c06e46d3 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -30,7 +30,7 @@ impl<'b> VirtualDom { .try_load_node() .expect("Call rebuild before diffing"); - use RenderReturn::{Aborted, Pending, Ready}; + use RenderReturn::{Aborted, Ready}; match (old, new) { // Normal pathway @@ -42,29 +42,14 @@ impl<'b> VirtualDom { // Just move over the placeholder (Aborted(l), Aborted(r)) => r.id.set(l.id.get()), - // Becomes async, do nothing while we wait - (Ready(_nodes), Pending(_fut)) => self.diff_ok_to_async(_nodes, scope), - // Placeholder becomes something // We should also clear the error now (Aborted(l), Ready(r)) => self.replace_placeholder(l, [r]), - - (Aborted(_), Pending(_)) => todo!("async should not resolve here"), - (Pending(_), Ready(_)) => todo!("async should not resolve here"), - (Pending(_), Aborted(_)) => todo!("async should not resolve here"), - (Pending(_), Pending(_)) => { - // All suspense should resolve before we diff it again - panic!("Should not roll from suspense to suspense."); - } }; } self.scope_stack.pop(); } - fn diff_ok_to_async(&mut self, _new: &'b VNode<'b>, _scope: ScopeId) { - // - } - fn diff_ok_to_err(&mut self, l: &'b VNode<'b>, p: &'b VPlaceholder) { let id = self.next_null(); p.id.set(Some(id)); @@ -735,7 +720,6 @@ impl<'b> VirtualDom { match unsafe { self.scopes[scope].root_node().extend_lifetime_ref() } { RenderReturn::Ready(node) => self.push_all_real_nodes(node), RenderReturn::Aborted(_node) => todo!(), - _ => todo!(), } } } @@ -937,7 +921,6 @@ impl<'b> VirtualDom { match unsafe { self.scopes[scope].root_node().extend_lifetime_ref() } { RenderReturn::Ready(t) => self.remove_node(t, gen_muts), RenderReturn::Aborted(placeholder) => self.remove_placeholder(placeholder, gen_muts), - _ => todo!(), }; // Restore the props back to the vcomponent in case it gets rendered again diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index dabb479c..209c37c7 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -7,7 +7,6 @@ use std::{ any::{Any, TypeId}, cell::{Cell, RefCell, UnsafeCell}, fmt::{Arguments, Debug}, - future::Future, }; pub type TemplateId = &'static str; @@ -28,9 +27,6 @@ pub enum RenderReturn<'a> { /// In its place we've produced a placeholder to locate its spot in the dom when /// it recovers. Aborted(VPlaceholder), - - /// An ongoing future that will resolve to a [`Element`] - Pending(BumpBox<'a, dyn Future> + 'a>), } impl<'a> Default for RenderReturn<'a> { @@ -688,32 +684,6 @@ impl AnyValue for T { } } -#[doc(hidden)] -pub trait ComponentReturn<'a, A = ()> { - fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a>; -} - -impl<'a> ComponentReturn<'a> for Element<'a> { - fn into_return(self, _cx: &ScopeState) -> RenderReturn<'a> { - match self { - Some(node) => RenderReturn::Ready(node), - None => RenderReturn::default(), - } - } -} - -#[doc(hidden)] -pub struct AsyncMarker; -impl<'a, F> ComponentReturn<'a, AsyncMarker> for F -where - F: Future> + 'a, -{ - fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a> { - let f: &mut dyn Future> = cx.bump().alloc(self); - RenderReturn::Pending(unsafe { BumpBox::from_raw(f) }) - } -} - impl<'a> RenderReturn<'a> { pub(crate) unsafe fn extend_lifetime_ref<'c>(&self) -> &'c RenderReturn<'c> { unsafe { std::mem::transmute(self) } diff --git a/packages/core/src/properties.rs b/packages/core/src/properties.rs index f8be8a0d..834b0cab 100644 --- a/packages/core/src/properties.rs +++ b/packages/core/src/properties.rs @@ -70,6 +70,6 @@ impl EmptyBuilder { /// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern /// to initialize a component's props. -pub fn fc_to_builder<'a, A, T: Properties + 'a>(_: fn(Scope<'a, T>) -> A) -> T::Builder { +pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element<'a>) -> T::Builder { T::builder() } diff --git a/packages/core/src/scheduler/mod.rs b/packages/core/src/scheduler/mod.rs index 8fb476fb..f5c540dd 100644 --- a/packages/core/src/scheduler/mod.rs +++ b/packages/core/src/scheduler/mod.rs @@ -18,9 +18,6 @@ pub(crate) enum SchedulerMsg { /// A task has woken and needs to be progressed TaskNotified(TaskId), - - /// A task has woken and needs to be progressed - SuspenseNotified(SuspenseId), } use std::{cell::RefCell, rc::Rc}; @@ -30,9 +27,6 @@ pub(crate) struct Scheduler { /// Tasks created with cx.spawn pub tasks: RefCell>, - - /// Async components - pub leaves: RefCell>, } impl Scheduler { @@ -40,7 +34,6 @@ impl Scheduler { Rc::new(Scheduler { sender, tasks: RefCell::new(Slab::new()), - leaves: RefCell::new(Slab::new()), }) } } diff --git a/packages/core/src/scheduler/suspense.rs b/packages/core/src/scheduler/suspense.rs index 39e40419..ad3d525b 100644 --- a/packages/core/src/scheduler/suspense.rs +++ b/packages/core/src/scheduler/suspense.rs @@ -36,23 +36,3 @@ impl SuspenseContext { } } } - -pub(crate) struct SuspenseLeaf { - pub(crate) scope_id: ScopeId, - pub(crate) notified: Cell, - pub(crate) task: *mut dyn Future>, - pub(crate) waker: Waker, -} - -pub struct SuspenseHandle { - pub(crate) id: SuspenseId, - pub(crate) tx: futures_channel::mpsc::UnboundedSender, -} - -impl ArcWake for SuspenseHandle { - fn wake_by_ref(arc_self: &Arc) { - _ = arc_self - .tx - .unbounded_send(SchedulerMsg::SuspenseNotified(arc_self.id)); - } -} diff --git a/packages/core/src/scheduler/wait.rs b/packages/core/src/scheduler/wait.rs index 77bea3a1..4e56f50f 100644 --- a/packages/core/src/scheduler/wait.rs +++ b/packages/core/src/scheduler/wait.rs @@ -1,16 +1,5 @@ -use futures_util::FutureExt; -use std::{ - rc::Rc, - task::{Context, Poll}, -}; - -use crate::{ - innerlude::{Mutation, Mutations, SuspenseContext}, - nodes::RenderReturn, - ScopeId, TaskId, VNode, VirtualDom, -}; - -use super::SuspenseId; +use crate::{innerlude::SuspenseContext, ScopeId, TaskId, VirtualDom}; +use std::{rc::Rc, task::Context}; impl VirtualDom { /// Handle notifications by tasks inside the scheduler @@ -44,68 +33,4 @@ impl VirtualDom { .consume_context::>() .unwrap() } - - pub(crate) fn handle_suspense_wakeup(&mut self, id: SuspenseId) { - let leaves = self.scheduler.leaves.borrow_mut(); - let leaf = leaves.get(id.0).unwrap(); - - let scope_id = leaf.scope_id; - - // todo: cache the waker - let mut cx = Context::from_waker(&leaf.waker); - - // Safety: the future is always pinned to the bump arena - let mut pinned = unsafe { std::pin::Pin::new_unchecked(&mut *leaf.task) }; - let as_pinned_mut = &mut pinned; - - // the component finished rendering and gave us nodes - // 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 fiber = self.acquire_suspense_boundary(leaf.scope_id); - - let scope = &self.scopes[scope_id]; - let arena = scope.current_frame(); - - let ret = arena.bump().alloc(match new_nodes { - Some(new) => RenderReturn::Ready(new), - None => RenderReturn::default(), - }); - - arena.node.set(ret); - - fiber.waiting_on.borrow_mut().remove(&id); - - if let RenderReturn::Ready(template) = ret { - 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) }; - - std::mem::swap(&mut self.mutations, mutations); - - let place_holder_id = scope.placeholder.get().unwrap(); - self.scope_stack.push(scope_id); - - drop(leaves); - - let created = self.create(template); - self.scope_stack.pop(); - mutations.push(Mutation::ReplaceWith { - id: place_holder_id, - m: created, - }); - - for leaf in self.collected_leaves.drain(..) { - fiber.waiting_on.borrow_mut().insert(leaf); - } - - std::mem::swap(&mut self.mutations, mutations); - - if fiber.waiting_on.borrow().is_empty() { - self.finished_fibers.push(fiber.id); - } - } - } - } } diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index a8a7319a..96bf8e08 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -2,18 +2,10 @@ use crate::{ any_props::AnyProps, bump_frame::BumpFrame, innerlude::DirtyScope, - innerlude::{SuspenseHandle, SuspenseId, SuspenseLeaf}, nodes::RenderReturn, scopes::{ScopeId, ScopeState}, virtual_dom::VirtualDom, }; -use futures_util::FutureExt; -use std::{ - mem, - pin::Pin, - sync::Arc, - task::{Context, Poll}, -}; impl VirtualDom { pub(super) fn new_scope( @@ -58,7 +50,7 @@ impl VirtualDom { // Remove all the outdated listeners self.ensure_drop_safety(scope_id); - let mut new_nodes = unsafe { + let new_nodes = unsafe { self.scopes[scope_id].previous_frame().bump_mut().reset(); let scope = &self.scopes[scope_id]; @@ -67,65 +59,11 @@ impl VirtualDom { // safety: due to how we traverse the tree, we know that the scope is not currently aliased let props: &dyn AnyProps = scope.props.as_ref().unwrap().as_ref(); - let props: &dyn AnyProps = mem::transmute(props); + let props: &dyn AnyProps = std::mem::transmute(props); props.render(scope).extend_lifetime() }; - // immediately resolve futures that can be resolved - if let RenderReturn::Pending(task) = &mut new_nodes { - let mut leaves = self.scheduler.leaves.borrow_mut(); - - let entry = leaves.vacant_entry(); - let suspense_id = SuspenseId(entry.key()); - - let leaf = SuspenseLeaf { - scope_id, - task: task.as_mut(), - notified: Default::default(), - waker: futures_util::task::waker(Arc::new(SuspenseHandle { - id: suspense_id, - tx: self.scheduler.sender.clone(), - })), - }; - - let mut cx = Context::from_waker(&leaf.waker); - - // safety: the task is already pinned in the bump arena - let mut pinned = unsafe { Pin::new_unchecked(task.as_mut()) }; - - // Keep polling until either we get a value or the future is not ready - loop { - match pinned.poll_unpin(&mut cx) { - // If nodes are produced, then set it and we can break - Poll::Ready(nodes) => { - new_nodes = match nodes { - Some(nodes) => RenderReturn::Ready(nodes), - None => RenderReturn::default(), - }; - - break; - } - - // If no nodes are produced but the future woke up immediately, then try polling it again - // This circumvents things like yield_now, but is important is important when rendering - // components that are just a stream of immediately ready futures - _ if leaf.notified.get() => { - leaf.notified.set(false); - continue; - } - - // If no nodes are produced, then we need to wait for the future to be woken up - // Insert the future into fiber leaves and break - _ => { - entry.insert(leaf); - self.collected_leaves.push(suspense_id); - break; - } - }; - } - }; - let scope = &self.scopes[scope_id]; // We write on top of the previous frame and then make it the current by pushing the generation forward diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index ff94c456..02b9bb65 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -6,7 +6,7 @@ use crate::{ innerlude::{DynamicNode, EventHandler, VComponent, VText}, innerlude::{ErrorBoundary, Scheduler, SchedulerMsg}, lazynodes::LazyNodes, - nodes::{ComponentReturn, IntoAttributeValue, IntoDynNode, RenderReturn}, + nodes::{IntoAttributeValue, IntoDynNode, RenderReturn}, AnyValue, Attribute, AttributeValue, Element, Event, Properties, TaskId, }; use bumpalo::{boxed::Box as BumpBox, Bump}; @@ -574,9 +574,9 @@ impl<'src> ScopeState { /// fn(Scope) -> Element; /// async fn(Scope>) -> Element; /// ``` - pub fn component>( + pub fn component

( &'src self, - component: fn(Scope<'src, P>) -> F, + component: fn(Scope<'src, P>) -> Element<'src>, props: P, fn_name: &'static str, ) -> DynamicNode<'src> diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index b118cd59..96c935d1 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -335,7 +335,8 @@ impl VirtualDom { /// Determine if the tree is at all suspended. Used by SSR and other outside mechanisms to determine if the tree is /// ready to be rendered. pub fn has_suspended_work(&self) -> bool { - !self.scheduler.leaves.borrow().is_empty() + todo!() + // !self.scheduler.leaves.borrow().is_empty() } /// Call a listener inside the VirtualDom with data from outside the VirtualDom. @@ -485,7 +486,6 @@ impl VirtualDom { Some(msg) => match msg { SchedulerMsg::Immediate(id) => self.mark_dirty(id), SchedulerMsg::TaskNotified(task) => self.handle_task_wakeup(task), - SchedulerMsg::SuspenseNotified(id) => self.handle_suspense_wakeup(id), }, // If they're not ready, then we should wait for them to be ready @@ -513,7 +513,6 @@ impl VirtualDom { match msg { SchedulerMsg::Immediate(id) => self.mark_dirty(id), SchedulerMsg::TaskNotified(task) => self.handle_task_wakeup(task), - SchedulerMsg::SuspenseNotified(id) => self.handle_suspense_wakeup(id), } } } @@ -574,7 +573,6 @@ impl VirtualDom { } // If an error occurs, we should try to render the default error component and context where the error occured RenderReturn::Aborted(_placeholder) => panic!("Cannot catch errors during rebuild"), - RenderReturn::Pending(_) => unreachable!("Root scope cannot be an async component"), } self.finalize() @@ -680,11 +678,6 @@ impl VirtualDom { continue; } - // If there's no pending suspense, then we have no reason to wait for anything - if self.scheduler.leaves.borrow().is_empty() { - return self.finalize(); - } - // Poll the suspense leaves in the meantime let mut work = self.wait_for_work(); diff --git a/packages/core/tests/suspense.rs b/packages/core/tests/suspense.rs index 06bb7b1a..708494dd 100644 --- a/packages/core/tests/suspense.rs +++ b/packages/core/tests/suspense.rs @@ -59,42 +59,45 @@ fn app(cx: Scope) -> Element { } fn suspense_boundary(cx: Scope) -> Element { - cx.use_hook(|| { - cx.provide_context(Rc::new(SuspenseContext::new(cx.scope_id()))); - }); + todo!() + // cx.use_hook(|| { + // cx.provide_context(Rc::new(SuspenseContext::new(cx.scope_id()))); + // }); - // Ensure the right types are found - cx.has_context::>().unwrap(); + // // Ensure the right types are found + // cx.has_context::>().unwrap(); - cx.render(rsx!(async_child {})) + // cx.render(rsx!(async_child {})) } -async fn async_child(cx: Scope<'_>) -> Element { - use_future!(cx, || tokio::time::sleep(Duration::from_millis(10))).await; - cx.render(rsx!(async_text {})) +fn async_child(cx: Scope<'_>) -> Element { + todo!() + // use_future!(cx, || tokio::time::sleep(Duration::from_millis(10))).await; + // cx.render(rsx!(async_text {})) } -async fn async_text(cx: Scope<'_>) -> Element { - let username = use_future!(cx, || async { - tokio::time::sleep(std::time::Duration::from_secs(1)).await; - "async child 1" - }); +fn async_text(cx: Scope<'_>) -> Element { + todo!() + // let username = use_future!(cx, || async { + // tokio::time::sleep(std::time::Duration::from_secs(1)).await; + // "async child 1" + // }); - let age = use_future!(cx, || async { - tokio::time::sleep(std::time::Duration::from_secs(2)).await; - 1234 - }); + // let age = use_future!(cx, || async { + // tokio::time::sleep(std::time::Duration::from_secs(2)).await; + // 1234 + // }); - let (_user, _age) = use_future!(cx, || async { - tokio::join!( - tokio::time::sleep(std::time::Duration::from_secs(1)), - tokio::time::sleep(std::time::Duration::from_secs(2)) - ); - ("async child 1", 1234) - }) - .await; + // let (_user, _age) = use_future!(cx, || async { + // tokio::join!( + // tokio::time::sleep(std::time::Duration::from_secs(1)), + // tokio::time::sleep(std::time::Duration::from_secs(2)) + // ); + // ("async child 1", 1234) + // }) + // .await; - let (username, age) = tokio::join!(username.into_future(), age.into_future()); + // let (username, age) = tokio::join!(username.into_future(), age.into_future()); - cx.render(rsx!( div { "Hello! {username}, you are {age}, {_user} {_age}" } )) + // cx.render(rsx!( div { "Hello! {username}, you are {age}, {_user} {_age}" } )) } diff --git a/packages/hooks/src/usefuture.rs b/packages/hooks/src/usefuture.rs index 18bf5031..9d39e82d 100644 --- a/packages/hooks/src/usefuture.rs +++ b/packages/hooks/src/usefuture.rs @@ -167,6 +167,10 @@ impl UseFuture { (Some(_), None) => UseFutureState::Pending, } } + + pub fn suspend(&self) -> Option<&T> { + todo!() + } } impl<'a, T> IntoFuture for &'a UseFuture {