#![forbid(unsafe_code)]
use crate::{
create_isomorphic_effect, create_rw_signal, create_signal, queue_microtask,
signal::SignalGet, store_value, ReadSignal, RwSignal, Scope, SignalSet,
SignalUpdate, StoredValue, WriteSignal,
};
use futures::Future;
use std::{
borrow::Cow, cell::RefCell, collections::VecDeque, pin::Pin, rc::Rc,
};
#[derive(Copy, Clone, Debug)]
pub struct SuspenseContext {
pub pending_resources: ReadSignal<usize>,
set_pending_resources: WriteSignal<usize>,
pub(crate) pending_serializable_resources: RwSignal<usize>,
pub(crate) has_local_only: StoredValue<bool>,
pub(crate) should_block: StoredValue<bool>,
}
#[derive(Clone, Debug)]
pub struct GlobalSuspenseContext(Rc<RefCell<SuspenseContext>>);
impl GlobalSuspenseContext {
pub fn new(cx: Scope) -> Self {
Self(Rc::new(RefCell::new(SuspenseContext::new(cx))))
}
pub fn with_inner<T>(&self, f: impl FnOnce(&SuspenseContext) -> T) -> T {
f(&self.0.borrow())
}
pub fn reset(&self, cx: Scope) {
let mut inner = self.0.borrow_mut();
_ = std::mem::replace(&mut *inner, SuspenseContext::new(cx));
}
}
impl SuspenseContext {
pub fn has_local_only(&self) -> bool {
self.has_local_only.get_value()
}
pub fn should_block(&self) -> bool {
self.should_block.get_value()
}
pub fn to_future(&self, cx: Scope) -> impl Future<Output = ()> {
use futures::StreamExt;
let pending_resources = self.pending_resources;
let (tx, mut rx) = futures::channel::mpsc::channel(1);
let tx = RefCell::new(tx);
queue_microtask(move || {
create_isomorphic_effect(cx, move |_| {
if pending_resources.get() == 0 {
_ = tx.borrow_mut().try_send(());
}
})
});
async move {
rx.next().await;
}
}
}
impl std::hash::Hash for SuspenseContext {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.pending_resources.id.hash(state);
}
}
impl PartialEq for SuspenseContext {
fn eq(&self, other: &Self) -> bool {
self.pending_resources.id == other.pending_resources.id
}
}
impl Eq for SuspenseContext {}
impl SuspenseContext {
pub fn new(cx: Scope) -> Self {
let (pending_resources, set_pending_resources) = create_signal(cx, 0);
let pending_serializable_resources = create_rw_signal(cx, 0);
let has_local_only = store_value(cx, true);
let should_block = store_value(cx, false);
Self {
pending_resources,
set_pending_resources,
pending_serializable_resources,
has_local_only,
should_block,
}
}
pub fn increment(&self, serializable: bool) {
let setter = self.set_pending_resources;
let serializable_resources = self.pending_serializable_resources;
let has_local_only = self.has_local_only;
queue_microtask(move || {
setter.update(|n| *n += 1);
if serializable {
serializable_resources.update(|n| *n += 1);
has_local_only.set_value(false);
}
});
}
pub fn decrement(&self, serializable: bool) {
let setter = self.set_pending_resources;
let serializable_resources = self.pending_serializable_resources;
queue_microtask(move || {
setter.update(|n| {
if *n > 0 {
*n -= 1
}
});
if serializable {
serializable_resources.update(|n| {
if *n > 0 {
*n -= 1;
}
});
}
});
}
pub fn clear(&self) {
self.set_pending_resources.set(0);
self.pending_serializable_resources.set(0);
}
pub fn ready(&self) -> bool {
self.pending_resources
.try_with(|n| *n == 0)
.unwrap_or(false)
}
}
pub enum StreamChunk {
Sync(Cow<'static, str>),
Async {
chunks: Pin<Box<dyn Future<Output = VecDeque<StreamChunk>>>>,
should_block: bool,
},
}
impl std::fmt::Debug for StreamChunk {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StreamChunk::Sync(data) => write!(f, "StreamChunk::Sync({data:?})"),
StreamChunk::Async { .. } => write!(f, "StreamChunk::Async(_)"),
}
}
}