Compare commits

..

9 Commits
3013 ... 3024

15 changed files with 156 additions and 36 deletions

View File

@@ -38,7 +38,7 @@
if (islandFn) {
islandFn(el);
} else {
console.warn(`Could not find WASM function for the island ${l}.`);
console.warn(`Could not find WASM function for the island ${id}.`);
}
}
function hydrateIslands(entry, mod) {

View File

@@ -293,6 +293,10 @@ pub mod spawn {
pub async fn tick() {
Executor::tick().await
}
pub use reactive_graph::{
spawn_local_scoped, spawn_local_scoped_with_cancellation,
};
}
// these reexports are used in islands

View File

@@ -87,6 +87,7 @@ pub mod traits;
pub mod transition;
pub mod wrappers;
use computed::ScopedFuture;
pub use graph::untrack;
#[cfg(feature = "nightly")]
@@ -130,3 +131,36 @@ pub(crate) fn spawn(task: impl Future<Output = ()> + Send + 'static) {
any_spawner::Executor::spawn(task);
}
/// Calls [`Executor::spawn_local`], but ensures that the task runs under the current reactive [`Owner`]
/// and [`Observed`]. Does not cancel the task if the owner is cleaned up.
pub fn spawn_local_scoped(task: impl Future<Output = ()> + 'static) {
let task = ScopedFuture::new(task);
#[cfg(feature = "sandboxed-arenas")]
let task = owner::Sandboxed::new(task);
any_spawner::Executor::spawn_local(task);
}
/// Calls [`Executor::spawn_local`], but ensures that the task runs under the current reactive [`Owner`]
/// and [`Observed`]. Cancels the task if the owner is cleaned up.
pub fn spawn_local_scoped_with_cancellation(
task: impl Future<Output = ()> + 'static,
) {
use crate::owner::on_cleanup;
use futures::future::{AbortHandle, Abortable};
let (abort_handle, abort_registration) = AbortHandle::new_pair();
on_cleanup(move || abort_handle.abort());
let task = Abortable::new(task, abort_registration);
let task = ScopedFuture::new(task);
#[cfg(feature = "sandboxed-arenas")]
let task = owner::Sandboxed::new(task);
any_spawner::Executor::spawn_local(async move {
_ = task.await;
});
}

View File

@@ -1,5 +1,6 @@
use super::{ArenaItem, LocalStorage, Storage, SyncStorage};
use crate::{
signal::guards::{Plain, ReadGuard, UntrackedWriteGuard},
traits::{DefinedAt, Dispose, IsDisposed},
unwrap_signal,
};
@@ -193,6 +194,44 @@ impl<T, S: Storage<Arc<RwLock<T>>>> StoredValue<T, S> {
.unwrap_or_else(unwrap_signal!(self))
}
/// Returns a read guard to the stored data, or `None` if the owner of the reactive node has been disposed.
#[track_caller]
pub fn try_read_value(&self) -> Option<ReadGuard<T, Plain<T>>> {
self.value
.try_get_value()
.and_then(|inner| Plain::try_new(inner).map(ReadGuard::new))
}
/// Returns a read guard to the stored data.
///
/// # Panics
///
/// This function panics when called after the owner of the reactive node has been disposed.
/// See [`StoredValue::try_read_value`] for a version without panic.
#[track_caller]
pub fn read_value(&self) -> ReadGuard<T, Plain<T>> {
self.try_read_value().unwrap_or_else(unwrap_signal!(self))
}
/// Returns a write guard to the stored data, or `None` if the owner of the reactive node has been disposed.
#[track_caller]
pub fn try_write_value(&self) -> Option<UntrackedWriteGuard<T>> {
self.value
.try_get_value()
.and_then(|inner| UntrackedWriteGuard::try_new(inner))
}
/// Returns a write guard to the stored data.
///
/// # Panics
///
/// This function panics when called after the owner of the reactive node has been disposed.
/// See [`StoredValue::try_write_value`] for a version without panic.
#[track_caller]
pub fn write_value(&self) -> UntrackedWriteGuard<T> {
self.try_write_value().unwrap_or_else(unwrap_signal!(self))
}
/// Updates the current value by applying the given closure, returning the return value of the
/// closure, or `None` if the value has already been disposed.
pub fn try_update_value<U>(

View File

@@ -43,7 +43,7 @@ where
Fal: Render + 'static,
{
#[allow(clippy::type_complexity)]
view: <EitherOf3<(), Fal, <Defs::Match as MatchInterface>::View> as Render>::State,
view: <EitherOf3<(), Fal, OwnedView<<Defs::Match as MatchInterface>::View>> as Render>::State,
id: Option<RouteMatchId>,
owner: Owner,
params: ArcRwSignal<ParamsMap>,
@@ -147,7 +147,7 @@ where
provide_context(params_memo);
provide_context(url);
provide_context(Matched(ArcMemo::from(matched)));
view.choose().await
OwnedView::new(view.choose().await)
}
})
}));
@@ -292,7 +292,7 @@ where
provide_context(Matched(ArcMemo::from(
new_matched,
)));
let view =
let view = OwnedView::new(
if let Some(set_is_routing) = set_is_routing {
set_is_routing.set(true);
let value =
@@ -302,7 +302,8 @@ where
value
} else {
view.choose().await
};
},
);
// only update the route if it's still the current path
// i.e., if we've navigated away before this has loaded, do nothing
@@ -572,7 +573,7 @@ where
provide_context(params_memo);
provide_context(url);
provide_context(Matched(ArcMemo::from(matched)));
view.choose().await
OwnedView::new(view.choose().await)
}
})
}));

View File

@@ -29,6 +29,7 @@ where
const SELF_CLOSING: bool = false;
const ESCAPE_CHILDREN: bool = true;
const TAG: &'static str = "";
const NAMESPACE: Option<&'static str> = None;
fn tag(&self) -> &str {
self.0.as_ref()

View File

@@ -72,6 +72,7 @@ macro_rules! html_element_inner {
const TAG: &'static str = stringify!($tag);
const SELF_CLOSING: bool = false;
const ESCAPE_CHILDREN: bool = $escape;
const NAMESPACE: Option<&'static str> = None;
#[inline(always)]
fn tag(&self) -> &str {
@@ -170,6 +171,7 @@ macro_rules! html_self_closing_elements {
const TAG: &'static str = stringify!($tag);
const SELF_CLOSING: bool = true;
const ESCAPE_CHILDREN: bool = $escape;
const NAMESPACE: Option<&'static str> = None;
#[inline(always)]
fn tag(&self) -> &str {

View File

@@ -138,6 +138,8 @@ pub trait ElementType: Send {
/// like `<style>` and `<script>`, which include other languages that should not use HTML
/// entity escaping.
const ESCAPE_CHILDREN: bool;
/// The element's namespace, if it is not HTML.
const NAMESPACE: Option<&'static str>;
/// The element's tag.
fn tag(&self) -> &str;
@@ -177,7 +179,7 @@ where
}
fn build(self) -> Self::State {
let el = Rndr::create_element(self.tag.tag());
let el = Rndr::create_element(self.tag.tag(), E::NAMESPACE);
let attrs = self.attributes.build(&el);
let children = if E::SELF_CLOSING {

View File

@@ -101,6 +101,7 @@ macro_rules! mathml_elements {
const TAG: &'static str = stringify!($tag);
const SELF_CLOSING: bool = false;
const ESCAPE_CHILDREN: bool = true;
const NAMESPACE: Option<&'static str> = Some("http://www.w3.org/1998/Math/MathML");
#[inline(always)]
fn tag(&self) -> &str {

View File

@@ -1,6 +1,5 @@
use super::{ReactiveFunction, SharedReactiveFunction, Suspend};
use crate::{html::class::IntoClass, renderer::Rndr};
use any_spawner::Executor;
use futures::FutureExt;
use reactive_graph::{effect::RenderEffect, signal::guards::ReadGuard};
use std::{
@@ -738,7 +737,7 @@ where
) -> Self::State {
let el = el.to_owned();
let state = Rc::new(RefCell::new(None));
Executor::spawn_local({
reactive_graph::spawn_local_scoped({
let state = Rc::clone(&state);
async move {
*state.borrow_mut() =
@@ -752,7 +751,7 @@ where
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
let el = el.to_owned();
let state = Rc::new(RefCell::new(None));
Executor::spawn_local({
reactive_graph::spawn_local_scoped({
let state = Rc::clone(&state);
async move {
*state.borrow_mut() = Some(self.inner.await.build(&el));
@@ -763,7 +762,7 @@ where
}
fn rebuild(self, state: &mut Self::State) {
Executor::spawn_local({
reactive_graph::spawn_local_scoped({
let state = Rc::clone(state);
async move {
let value = self.inner.await;

View File

@@ -8,7 +8,6 @@ use crate::{
RenderHtml, ToTemplate,
},
};
use any_spawner::Executor;
use reactive_graph::effect::RenderEffect;
use std::{
cell::RefCell,
@@ -391,7 +390,7 @@ where
let key = key.to_owned();
let el = el.to_owned();
let state = Rc::new(RefCell::new(None));
Executor::spawn_local({
reactive_graph::spawn_local_scoped({
let state = Rc::clone(&state);
async move {
*state.borrow_mut() =
@@ -410,7 +409,7 @@ where
let key = key.to_owned();
let el = el.to_owned();
let state = Rc::new(RefCell::new(None));
Executor::spawn_local({
reactive_graph::spawn_local_scoped({
let state = Rc::clone(&state);
async move {
*state.borrow_mut() = Some(self.inner.await.build(&el, &key));
@@ -422,7 +421,7 @@ where
fn rebuild(self, key: &str, state: &mut Self::State) {
let key = key.to_owned();
Executor::spawn_local({
reactive_graph::spawn_local_scoped({
let state = Rc::clone(state);
async move {
let value = self.inner.await;

View File

@@ -1,6 +1,5 @@
use super::{ReactiveFunction, SharedReactiveFunction, Suspend};
use crate::{html::style::IntoStyle, renderer::Rndr};
use any_spawner::Executor;
use futures::FutureExt;
use reactive_graph::effect::RenderEffect;
use std::{borrow::Cow, cell::RefCell, future::Future, rc::Rc};
@@ -463,7 +462,7 @@ where
) -> Self::State {
let el = el.to_owned();
let state = Rc::new(RefCell::new(None));
Executor::spawn_local({
reactive_graph::spawn_local_scoped({
let state = Rc::clone(&state);
async move {
*state.borrow_mut() =
@@ -477,7 +476,7 @@ where
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
let el = el.to_owned();
let state = Rc::new(RefCell::new(None));
Executor::spawn_local({
reactive_graph::spawn_local_scoped({
let state = Rc::clone(&state);
async move {
*state.borrow_mut() = Some(self.inner.await.build(&el));
@@ -488,7 +487,7 @@ where
}
fn rebuild(self, state: &mut Self::State) {
Executor::spawn_local({
reactive_graph::spawn_local_scoped({
let state = Rc::clone(state);
async move {
let value = self.inner.await;

View File

@@ -8,7 +8,10 @@ use crate::{
},
};
use any_spawner::Executor;
use futures::{select, FutureExt};
use futures::{
future::{AbortHandle, Abortable},
select, FutureExt,
};
use or_poisoned::OrPoisoned;
use reactive_graph::{
computed::{
@@ -19,7 +22,7 @@ use reactive_graph::{
AnySource, AnySubscriber, Observer, ReactiveNode, Source, Subscriber,
ToAnySubscriber, WithObserver,
},
owner::{provide_context, use_context},
owner::{on_cleanup, provide_context, use_context},
};
use std::{
cell::RefCell,
@@ -164,15 +167,20 @@ where
{
type State = SuspendState<Fut::Output>;
// TODO cancelation if it fires multiple times
fn build(self) -> Self::State {
let Self { subscriber, inner } = self;
// create a Future that will be aborted on on_cleanup
// this prevents trying to access signals or other resources inside the Suspend, after the
// await, if they have already been cleaned up
let (abort_handle, abort_registration) = AbortHandle::new_pair();
let mut fut = Box::pin(Abortable::new(inner, abort_registration));
on_cleanup(move || abort_handle.abort());
// poll the future once immediately
// if it's already available, start in the ready state
// otherwise, start with the fallback
let mut fut = Box::pin(inner);
let initial = fut.as_mut().now_or_never();
let initial = fut.as_mut().now_or_never().and_then(Result::ok);
let initially_pending = initial.is_none();
let inner = Rc::new(RefCell::new(initial.build()));
@@ -184,7 +192,7 @@ where
// spawning immediately means that our now_or_never poll result isn't lost
// if it wasn't pending at first, we don't need to poll the Future again
if initially_pending {
Executor::spawn_local({
reactive_graph::spawn_local_scoped({
let state = Rc::clone(&inner);
async move {
let _guard = error_hook.as_ref().map(|hook| {
@@ -193,7 +201,10 @@ where
let value = fut.as_mut().await;
drop(id);
Some(value).rebuild(&mut *state.borrow_mut());
if let Ok(value) = value {
Some(value).rebuild(&mut *state.borrow_mut());
}
subscriber.forward();
}
@@ -208,13 +219,19 @@ where
fn rebuild(self, state: &mut Self::State) {
let Self { subscriber, inner } = self;
// create a Future that will be aborted on on_cleanup
// this prevents trying to access signals or other resources inside the Suspend, after the
// await, if they have already been cleaned up
let (abort_handle, abort_registration) = AbortHandle::new_pair();
let fut = Abortable::new(inner, abort_registration);
on_cleanup(move || abort_handle.abort());
// get a unique ID if there's a SuspenseContext
let fut = inner;
let id = use_context::<SuspenseContext>().map(|sc| sc.task_id());
let error_hook = use_context::<Arc<dyn ErrorHook>>();
// spawn the future, and rebuild the state when it resolves
Executor::spawn_local({
reactive_graph::spawn_local_scoped({
let state = Rc::clone(&state.inner);
async move {
let _guard = error_hook
@@ -223,11 +240,14 @@ where
let value = fut.await;
drop(id);
// waiting a tick here allows Suspense to remount if necessary, which prevents some
// edge cases in which a rebuild can't happen while unmounted because the DOM node
// has no parent
any_spawner::Executor::tick().await;
Some(value).rebuild(&mut *state.borrow_mut());
Executor::tick().await;
if let Ok(value) = value {
Some(value).rebuild(&mut *state.borrow_mut());
}
subscriber.forward();
}
@@ -367,11 +387,17 @@ where
) -> Self::State {
let Self { subscriber, inner } = self;
// create a Future that will be aborted on on_cleanup
// this prevents trying to access signals or other resources inside the Suspend, after the
// await, if they have already been cleaned up
let (abort_handle, abort_registration) = AbortHandle::new_pair();
let mut fut = Box::pin(Abortable::new(inner, abort_registration));
on_cleanup(move || abort_handle.abort());
// poll the future once immediately
// if it's already available, start in the ready state
// otherwise, start with the fallback
let mut fut = Box::pin(inner);
let initial = fut.as_mut().now_or_never();
let initial = fut.as_mut().now_or_never().and_then(Result::ok);
let initially_pending = initial.is_none();
let inner = Rc::new(RefCell::new(
initial.hydrate::<FROM_SERVER>(cursor, position),
@@ -385,7 +411,7 @@ where
// spawning immediately means that our now_or_never poll result isn't lost
// if it wasn't pending at first, we don't need to poll the Future again
if initially_pending {
Executor::spawn_local({
reactive_graph::spawn_local_scoped({
let state = Rc::clone(&inner);
async move {
let _guard = error_hook.as_ref().map(|hook| {
@@ -394,7 +420,10 @@ where
let value = fut.as_mut().await;
drop(id);
Some(value).rebuild(&mut *state.borrow_mut());
if let Ok(value) = value {
Some(value).rebuild(&mut *state.borrow_mut());
}
subscriber.forward();
}

View File

@@ -33,8 +33,17 @@ impl Dom {
intern(text)
}
pub fn create_element(tag: &str) -> Element {
document().create_element(tag).unwrap()
pub fn create_element(tag: &str, namespace: Option<&str>) -> Element {
if let Some(namespace) = namespace {
document()
.create_element_ns(
Some(Self::intern(namespace)),
Self::intern(tag),
)
.unwrap()
} else {
document().create_element(Self::intern(tag)).unwrap()
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace"))]

View File

@@ -69,6 +69,7 @@ macro_rules! svg_elements {
const TAG: &'static str = stringify!($tag);
const SELF_CLOSING: bool = false;
const ESCAPE_CHILDREN: bool = true;
const NAMESPACE: Option<&'static str> = Some("http://www.w3.org/2000/svg");
#[inline(always)]
fn tag(&self) -> &str {