server_future uses use_resource

This commit is contained in:
Jonathan Kelley 2024-02-02 14:08:21 -08:00
parent 0fd7799bc2
commit f44b72f5e1
No known key found for this signature in database
GPG key ID: 1FBB50F7EB0A08BE
4 changed files with 64 additions and 99 deletions

View file

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

View file

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

View file

@ -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<T, F>(_future: impl Fn() -> F) -> UseServerFuture<T>
pub fn use_server_future<T, F>(_future: impl Fn() -> F + 'static) -> Option<Resource<T>>
where
T: Serialize + DeserializeOwned + 'static,
F: Future<Output = T> + 'static,
{
let value: Signal<Option<T>> = 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::<T>();
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::<T>() {
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<T: 'static> {
value: Signal<Option<Signal<T>>>,
}
impl<T> UseServerFuture<T> {
// /// 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<Signal<T>> {
todo!()
// Ref::map(self.value.read(), |v: &Option<T>| v.as_deref().unwrap())
}
// /// Get the ID of the future in Dioxus' internal scheduler
// pub fn task(&self) -> Option<Task> {
// self.task.get()
// }
// /// Get the current state of the future.
// pub fn reloading(&self) -> bool {
// self.task.get().is_some()
// }
}

View file

@ -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<T, F>(future: impl Fn() -> F + 'static) -> AsyncMemo<T>
pub fn use_resource<T, F>(future: impl Fn() -> F + 'static) -> Resource<T>
where
T: 'static,
F: Future<Output = T> + '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<T: 'static> {
pub struct Resource<T: 'static> {
value: Signal<Option<Signal<T>>>,
task: Signal<Task>,
state: Signal<UseResourceState>,
}
impl<T> AsyncMemo<T> {
impl<T> Resource<T> {
/// Restart the future with new dependencies.
///
/// Will not cancel the previous future, but will ignore any values that it
@ -139,8 +136,9 @@ impl<T> AsyncMemo<T> {
}
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum UseResourceState {
Pending,
Complete,
Ready,
Regenerating, // the old value
}