From f44b72f5e1b563f10c5838ef9aac980d6c76dbef Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 2 Feb 2024 14:08:21 -0800 Subject: [PATCH] server_future uses use_resource --- packages/core/src/tasks.rs | 19 ++- packages/core/src/virtual_dom.rs | 4 +- packages/fullstack/src/hooks/server_future.rs | 122 ++++++------------ packages/hooks/src/use_resource.rs | 18 ++- 4 files changed, 64 insertions(+), 99 deletions(-) diff --git a/packages/core/src/tasks.rs b/packages/core/src/tasks.rs index f508ebcae..1e0d60162 100644 --- a/packages/core/src/tasks.rs +++ b/packages/core/src/tasks.rs @@ -1,11 +1,11 @@ use crate::innerlude::{remove_future, spawn, Runtime}; use crate::ScopeId; use futures_util::task::ArcWake; -use std::pin::Pin; use std::sync::Arc; use std::task::Waker; use std::{cell::Cell, future::Future}; use std::{cell::RefCell, rc::Rc}; +use std::{pin::Pin, task::Poll}; /// A task's unique identifier. /// @@ -50,6 +50,11 @@ impl Task { Runtime::with(|rt| _ = rt.sender.unbounded_send(SchedulerMsg::TaskNotified(*self))); } + /// Poll the task immediately. + pub fn poll_now(&self) -> Poll<()> { + Runtime::with(|rt| rt.handle_task_wakeup(*self)).unwrap() + } + /// Set the task as active or paused. pub fn set_active(&self, active: bool) { Runtime::with(|rt| rt.tasks.borrow()[self.0].active.set(active)); @@ -122,19 +127,19 @@ impl Runtime { self.tasks.borrow().get(task.0)?.parent } - pub(crate) fn handle_task_wakeup(&self, id: Task) { + pub(crate) fn handle_task_wakeup(&self, id: Task) -> Poll<()> { debug_assert!(Runtime::current().is_some(), "Must be in a dioxus runtime"); let task = self.tasks.borrow().get(id.0).cloned(); // The task was removed from the scheduler, so we can just ignore it let Some(task) = task else { - return; + return Poll::Ready(()); }; // If a task woke up but is paused, we can just ignore it if !task.active.get() { - return; + return Poll::Pending; } let mut cx = std::task::Context::from_waker(&task.waker); @@ -144,7 +149,9 @@ impl Runtime { self.rendering.set(false); self.current_task.set(Some(id)); - if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() { + let poll_result = task.task.borrow_mut().as_mut().poll(&mut cx); + + if poll_result.is_ready() { // Remove it from the scope so we dont try to double drop it when the scope dropes self.get_state(task.scope) .unwrap() @@ -160,6 +167,8 @@ impl Runtime { self.scope_stack.borrow_mut().pop(); self.rendering.set(true); self.current_task.set(None); + + poll_result } /// Drop the future with the given TaskId diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 011e21944..96e001e25 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -454,7 +454,7 @@ impl VirtualDom { match self.rx.next().await.expect("channel should never close") { SchedulerMsg::Immediate(id) => self.mark_dirty(id), - SchedulerMsg::TaskNotified(id) => self.runtime.handle_task_wakeup(id), + SchedulerMsg::TaskNotified(id) => _ = self.runtime.handle_task_wakeup(id), }; } } @@ -467,7 +467,7 @@ impl VirtualDom { while let Ok(Some(msg)) = self.rx.try_next() { match msg { SchedulerMsg::Immediate(id) => self.mark_dirty(id), - SchedulerMsg::TaskNotified(task) => self.runtime.handle_task_wakeup(task), + SchedulerMsg::TaskNotified(task) => _ = self.runtime.handle_task_wakeup(task), } } } diff --git a/packages/fullstack/src/hooks/server_future.rs b/packages/fullstack/src/hooks/server_future.rs index 8f7284956..a34929231 100644 --- a/packages/fullstack/src/hooks/server_future.rs +++ b/packages/fullstack/src/hooks/server_future.rs @@ -1,99 +1,57 @@ use dioxus_lib::prelude::*; use serde::{de::DeserializeOwned, Serialize}; -use std::cell::Cell; -use std::cell::Ref; -use std::cell::RefCell; -use std::fmt::Debug; use std::future::Future; -use std::rc::Rc; -use std::sync::Arc; /// A future that resolves to a value. -/// -/// -/// -/// ```rust -/// fn User(id: String) -> Element { -/// let data = use_sever_future(move || fetch_user(id)).suspend()?; -/// -/// -/// } -/// -/// ``` #[must_use = "Consider using `cx.spawn` to run a future without reading its value"] -pub fn use_server_future(_future: impl Fn() -> F) -> UseServerFuture +pub fn use_server_future(_future: impl Fn() -> F + 'static) -> Option> where T: Serialize + DeserializeOwned + 'static, F: Future + 'static, { - let value: Signal> = use_signal(|| { - // Doesn't this need to be keyed by something? - // We should try and link these IDs across the server and client - // Just the file/line/col span should be fine (or byte index) - #[cfg(feature = "ssr")] - return crate::html_storage::deserialize::take_server_data::(); + let mut cb = use_callback(_future); + let mut gen = use_hook(|| CopyValue::new(0)); - #[cfg(not(feature = "ssr"))] - return None; + let resource = use_resource(move || { + async move { + // this is going to subscribe this resource to any reactivity given to use in the callback + // We're doing this regardless so inputs get tracked, even if we drop the future before polling it + let user_fut = cb.call(); + + // If this is the first run, the data might be cached + if gen() == 0 { + #[cfg(not(feature = "web"))] + if let Some(o) = crate::html_storage::deserialize::take_server_data::() { + gen.set(1); + return o; + } + } + + // Otherwise just run the future itself + let out = user_fut.await; + + // and push the gen forward + gen.set(1); + + out + } }); - // Run the callback regardless, giving us the future without actually polling it - // This is where use_server_future gets its reactivity from - // If the client is using signals to drive the future itself, (say, via args to the server_fn), then we need to know - // what signals are being used - use_future(move || async move { - // watch the reactive context - // if it changes, restart the future - // - // if let Err(err) = crate::prelude::server_context().push_html_data(&data) { - // tracing::error!("Failed to push HTML data: {}", err); - // }; + // On the first run, force this task to be polled right away in case its value is ready + use_hook(|| { + let _ = resource.task().unwrap().poll_now(); }); - // if there's no value ready, mark this component as suspended and return early - if value.peek().is_none() { - suspend(); + // Suspend if the value isn't ready + match resource.state() { + UseResourceState::Pending => { + suspend(); + None + } + UseResourceState::Regenerating => { + suspend(); + Some(resource) + } + UseResourceState::Ready => Some(resource), } - - todo!() -} - -pub struct UseServerFuture { - value: Signal>>, -} - -impl UseServerFuture { - // /// Restart the future with new dependencies. - // /// - // /// Will not cancel the previous future, but will ignore any values that it - // /// generates. - // pub fn restart(&self) { - // self.needs_regen.set(true); - // (self.update)(); - // } - - // /// Forcefully cancel a future - // pub fn cancel(&self) { - // if let Some(task) = self.task.take() { - // remove_future(task); - // } - // } - - /// Return any value, even old values if the future has not yet resolved. - /// - /// If the future has never completed, the returned value will be `None`. - pub fn value(&self) -> Option> { - todo!() - // Ref::map(self.value.read(), |v: &Option| v.as_deref().unwrap()) - } - - // /// Get the ID of the future in Dioxus' internal scheduler - // pub fn task(&self) -> Option { - // self.task.get() - // } - - // /// Get the current state of the future. - // pub fn reloading(&self) -> bool { - // self.task.get().is_some() - // } } diff --git a/packages/hooks/src/use_resource.rs b/packages/hooks/src/use_resource.rs index 1e70ccfe0..92c81bbe4 100644 --- a/packages/hooks/src/use_resource.rs +++ b/packages/hooks/src/use_resource.rs @@ -11,9 +11,9 @@ use std::future::Future; /// A memo that resolve to a value asynchronously. /// -/// Regular memos are synchronous and resolve immediately. However, you might want to resolve a memo +/// This runs on the server #[must_use = "Consider using `cx.spawn` to run a future without reading its value"] -pub fn use_resource(future: impl Fn() -> F + 'static) -> AsyncMemo +pub fn use_resource(future: impl Fn() -> F + 'static) -> Resource where T: 'static, F: Future + 'static, @@ -28,9 +28,6 @@ where // Spawn a wrapper task that polls the innner future and watch its dependencies spawn(async move { - // Wait for the dom the be finished with sync work - flush_sync().await; - // move the future here and pin it so we can poll it let fut = fut; pin_mut!(fut); @@ -40,7 +37,7 @@ where let res = future::poll_fn(|cx| rc.run_in(|| fut.poll_unpin(cx))).await; // Set the value and state - state.set(UseResourceState::Complete); + state.set(UseResourceState::Ready); value.set(Some(Signal::new(res))); }) }); @@ -62,17 +59,17 @@ where }) }); - AsyncMemo { task, value, state } + Resource { task, value, state } } #[allow(unused)] -pub struct AsyncMemo { +pub struct Resource { value: Signal>>, task: Signal, state: Signal, } -impl AsyncMemo { +impl Resource { /// Restart the future with new dependencies. /// /// Will not cancel the previous future, but will ignore any values that it @@ -139,8 +136,9 @@ impl AsyncMemo { } } +#[derive(PartialEq, Eq, Clone, Copy, Debug)] pub enum UseResourceState { Pending, - Complete, + Ready, Regenerating, // the old value }