Rip out async components

This commit is contained in:
Jonathan Kelley 2023-07-14 16:15:20 -07:00
parent 4147498041
commit cbd88bbcc3
No known key found for this signature in database
GPG Key ID: 1FBB50F7EB0A08BE
14 changed files with 62 additions and 300 deletions

View File

@ -10,7 +10,7 @@ struct ListBreeds {
message: HashMap<String, Vec<String>>,
}
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 {

View File

@ -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<A>,
}
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(),
}
}
}

View File

@ -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>,

View File

@ -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

View File

@ -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<Output = Element<'a>> + 'a>),
}
impl<'a> Default for RenderReturn<'a> {
@ -688,32 +684,6 @@ impl<T: Any + PartialEq + 'static> 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<Output = Element<'a>> + 'a,
{
fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a> {
let f: &mut dyn Future<Output = Element<'a>> = 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) }

View File

@ -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()
}

View File

@ -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<Slab<LocalTask>>,
/// Async components
pub leaves: RefCell<Slab<SuspenseLeaf>>,
}
impl Scheduler {
@ -40,7 +34,6 @@ impl Scheduler {
Rc::new(Scheduler {
sender,
tasks: RefCell::new(Slab::new()),
leaves: RefCell::new(Slab::new()),
})
}
}

View File

@ -36,23 +36,3 @@ impl SuspenseContext {
}
}
}
pub(crate) struct SuspenseLeaf {
pub(crate) scope_id: ScopeId,
pub(crate) notified: Cell<bool>,
pub(crate) task: *mut dyn Future<Output = Element<'static>>,
pub(crate) waker: Waker,
}
pub struct SuspenseHandle {
pub(crate) id: SuspenseId,
pub(crate) tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
}
impl ArcWake for SuspenseHandle {
fn wake_by_ref(arc_self: &Arc<Self>) {
_ = arc_self
.tx
.unbounded_send(SchedulerMsg::SuspenseNotified(arc_self.id));
}
}

View File

@ -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::<Rc<SuspenseContext>>()
.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);
}
}
}
}
}

View File

@ -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

View File

@ -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<Props>) -> Element;
/// async fn(Scope<Props<'_>>) -> Element;
/// ```
pub fn component<P, A, F: ComponentReturn<'src, A>>(
pub fn component<P>(
&'src self,
component: fn(Scope<'src, P>) -> F,
component: fn(Scope<'src, P>) -> Element<'src>,
props: P,
fn_name: &'static str,
) -> DynamicNode<'src>

View File

@ -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();

View File

@ -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::<Rc<SuspenseContext>>().unwrap();
// // Ensure the right types are found
// cx.has_context::<Rc<SuspenseContext>>().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}" } ))
}

View File

@ -167,6 +167,10 @@ impl<T> UseFuture<T> {
(Some(_), None) => UseFutureState::Pending,
}
}
pub fn suspend(&self) -> Option<&T> {
todo!()
}
}
impl<'a, T> IntoFuture for &'a UseFuture<T> {