fix async context issues, add flat routing

This commit is contained in:
Greg Johnston 2024-03-20 20:34:13 -04:00
parent 6d9906111d
commit a12c707f3f
7 changed files with 419 additions and 14 deletions

View File

@ -1774,8 +1774,13 @@ where
let mut router = self;
// register server functions first to allow for wildcard router path
// register server functions
println!(
"server fn paths are {:?}",
server_fn::axum::server_fn_paths().collect::<Vec<_>>()
);
for (path, method) in server_fn::axum::server_fn_paths() {
println!("registering {path}");
let cx_with_state = cx_with_state.clone();
let handler = move |req: Request<Body>| async move {
handle_server_fns_with_context(cx_with_state, req).await

View File

@ -18,7 +18,9 @@
#![forbid(unsafe_code)]
#![deny(missing_docs)]
use std::sync::{PoisonError, RwLockReadGuard, RwLockWriteGuard};
use std::sync::{
LockResult, MutexGuard, PoisonError, RwLockReadGuard, RwLockWriteGuard,
};
/// Unwraps a lock.
pub trait OrPoisoned {
@ -52,3 +54,11 @@ impl<'a, T> OrPoisoned
self.expect("lock poisoned")
}
}
impl<'a, T> OrPoisoned for LockResult<MutexGuard<'a, T>> {
type Inner = MutexGuard<'a, T>;
fn or_poisoned(self) -> Self::Inner {
self.expect("lock poisoned")
}
}

View File

@ -40,7 +40,7 @@ impl Owner {
let (parent, shared_context) = OWNER
.with(|o| {
o.borrow().as_ref().map(|o| {
(Some(Arc::clone(&o.inner)), o.shared_context.clone())
(Some(Arc::downgrade(&o.inner)), o.shared_context.clone())
})
})
.unwrap_or((None, None));
@ -73,7 +73,7 @@ impl Owner {
}
pub fn child(&self) -> Self {
let parent = Some(Arc::clone(&self.inner));
let parent = Some(Arc::downgrade(&self.inner));
Self {
inner: Arc::new(RwLock::new(OwnerInner {
parent,
@ -147,7 +147,7 @@ impl Owner {
#[derive(Default)]
pub(crate) struct OwnerInner {
pub parent: Option<Arc<RwLock<OwnerInner>>>,
pub parent: Option<Weak<RwLock<OwnerInner>>>,
nodes: Vec<NodeId>,
pub contexts: FxHashMap<TypeId, Box<dyn Any + Send + Sync>>,
pub cleanups: Vec<Box<dyn FnOnce() + Send + Sync>>,

View File

@ -14,7 +14,7 @@ impl Owner {
fn use_context<T: Clone + 'static>(&self) -> Option<T> {
let ty = TypeId::of::<T>();
let inner = self.inner.read().or_poisoned();
let mut parent = inner.parent.as_ref().map(|p| p.clone());
let mut parent = inner.parent.as_ref().and_then(|p| p.upgrade());
let contexts = &self.inner.read().or_poisoned().contexts;
if let Some(context) = contexts.get(&ty) {
context.downcast_ref::<T>().cloned()
@ -28,7 +28,8 @@ impl Owner {
if let Some(value) = downcast {
return Some(value);
} else {
parent = this_parent.parent.as_ref().map(|p| p.clone());
parent =
this_parent.parent.as_ref().and_then(|p| p.upgrade());
}
}
None

View File

@ -181,12 +181,18 @@ pub mod read {
fn try_get_untracked(&self) -> Option<Self::Value> {
self.inner
.with_value(|inner| match &inner {
// cloning here clones the inner Arc and releases the lock, in case anything inside needs to take it
// this happens particularly in derived signals, because they need to to access the
// global arena again
//
// note that .read() multiple times in the same thread on a std RwLock can deadlock
// to avoid writer starvation, which is why this happens
.with_value(|inner| inner.clone())
.and_then(|inner| match &inner {
SignalTypes::ReadSignal(i) => i.try_get_untracked(),
SignalTypes::Memo(i) => i.try_get_untracked(),
SignalTypes::DerivedSignal(i) => Some(untrack(|| i())),
})
.flatten()
}
}

View File

@ -107,9 +107,6 @@ where
FallbackFn: Fn() -> Fallback + 'static,
Fallback: Render<Rndr>,
Children: MatchNestedRoutes<Rndr> + 'static,
//for<'a> <Children::Match<'a> as MatchInterface<Rndr, View = View>>,
/*View: Render<Rndr> + IntoAny<Rndr> + 'static,
View::State: 'static,*/
Fallback::State: 'static,
Rndr: Renderer + 'static,
Children::Match: std::fmt::Debug,
@ -1016,3 +1013,388 @@ where
self
}
}
#[derive(Debug)]
pub struct FlatRouter<Rndr, Loc, Children, FallbackFn> {
base: Option<Cow<'static, str>>,
location: PhantomData<Loc>,
pub routes: Routes<Children, Rndr>,
fallback: FallbackFn,
}
impl<Rndr, Loc, Children, FallbackFn, Fallback>
FlatRouter<Rndr, Loc, Children, FallbackFn>
where
Loc: Location,
Rndr: Renderer,
FallbackFn: Fn() -> Fallback,
{
pub fn new(
routes: Routes<Children, Rndr>,
fallback: FallbackFn,
) -> FlatRouter<Rndr, Loc, Children, FallbackFn> {
Self {
base: None,
location: PhantomData,
routes,
fallback,
}
}
pub fn new_with_base(
base: impl Into<Cow<'static, str>>,
routes: Routes<Children, Rndr>,
fallback: FallbackFn,
) -> FlatRouter<Rndr, Loc, Children, FallbackFn> {
Self {
base: Some(base.into()),
location: PhantomData,
routes,
fallback,
}
}
}
impl<Rndr, Loc, FallbackFn, Fallback, Children> Render<Rndr>
for FlatRouter<Rndr, Loc, Children, FallbackFn>
where
Loc: Location,
FallbackFn: Fn() -> Fallback + 'static,
Fallback: Render<Rndr>,
Children: MatchNestedRoutes<Rndr> + 'static,
Fallback::State: 'static,
Rndr: Renderer + 'static,
{
type State =
RenderEffect<
EitherState<
<<Children::Match as MatchInterface<Rndr>>::View as Render<
Rndr,
>>::State,
<Fallback as Render<Rndr>>::State,
Rndr,
>,
>;
type FallibleState = Self::State;
fn build(self) -> Self::State {
let location = Loc::new().unwrap(); // TODO
location.init(self.base);
let url = location.as_url().clone();
let path = ArcMemo::new({
let url = url.clone();
move |_| url.read().path().to_string()
});
let search_params = ArcMemo::new({
let url = url.clone();
move |_| url.read().search_params().clone()
});
let outer_owner =
Owner::current().expect("creating Router, but no Owner was found");
RenderEffect::new(move |prev: Option<EitherState<_, _, _>>| {
let path = path.read();
let new_match = self.routes.match_route(&path);
if let Some(mut prev) = prev {
if let Some(new_match) = new_match {
let params = ArcRwSignal::new(
new_match.to_params().into_iter().collect(),
);
#[allow(unused)]
let (view, child) = new_match.into_view_and_child();
#[cfg(debug_assertions)]
if child.is_some() {
panic!(
"FlatRouter should not be used with a route that \
has a child."
);
}
let route_data = RouteData {
params: ArcMemo::new({
let params = params.clone();
move |_| params.get()
}),
outlet: Default::default(),
};
let view = outer_owner.with(|| view.choose(route_data));
Either::Left::<_, Fallback>(view).rebuild(&mut prev);
} else {
Either::<<Children::Match as MatchInterface<Rndr>>::View, _>::Right((self.fallback)()).rebuild(&mut prev);
}
prev
} else {
match new_match {
Some(matched) => {
let params = ArcRwSignal::new(
matched.to_params().into_iter().collect(),
);
#[allow(unused)]
let (view, child) = matched.into_view_and_child();
#[cfg(debug_assertions)]
if child.is_some() {
panic!(
"FlatRouter should not be used with a route \
that has a child."
);
}
let route_data = RouteData {
params: ArcMemo::new({
let params = params.clone();
move |_| params.get()
}),
outlet: Default::default(),
};
let view = outer_owner.with(|| view.choose(route_data));
Either::Left(view)
}
_ => Either::Right((self.fallback)()),
}
.build()
}
})
}
fn rebuild(self, state: &mut Self::State) {}
fn try_build(self) -> tachys::error::Result<Self::FallibleState> {
todo!()
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> tachys::error::Result<()> {
todo!()
}
}
impl<Rndr, Loc, FallbackFn, Fallback, Children> RenderHtml<Rndr>
for FlatRouter<Rndr, Loc, Children, FallbackFn>
where
Loc: Location,
FallbackFn: Fn() -> Fallback + 'static,
Fallback: RenderHtml<Rndr>,
Children: MatchNestedRoutes<Rndr> + 'static,
Fallback::State: 'static,
Rndr: Renderer + 'static,
{
const MIN_LENGTH: usize =
<Children::Match as MatchInterface<Rndr>>::View::MIN_LENGTH;
fn to_html_with_buf(self, buf: &mut String, position: &mut Position) {
// if this is being run on the server for the first time, generating all possible routes
if RouteList::is_generating() {
// add routes
let (base, routes) = self.routes.generate_routes();
let mut routes = routes
.into_iter()
.map(|segments| {
let path = base
.into_iter()
.flat_map(|base| {
iter::once(PathSegment::Static(
base.to_string().into(),
))
})
.chain(segments)
.collect::<Vec<_>>();
// TODO add non-defaults for mode, etc.
RouteListing::new(
path,
SsrMode::OutOfOrder,
[Method::Get],
None,
)
})
.collect::<Vec<_>>();
// add fallback
// TODO fix: causes overlapping route issues on Axum
/*routes.push(RouteListing::new(
[PathSegment::Static(
base.unwrap_or_default().to_string().into(),
)],
SsrMode::Async,
[
Method::Get,
Method::Post,
Method::Put,
Method::Patch,
Method::Delete,
],
None,
));*/
RouteList::register(RouteList::from(routes));
} else {
let outer_owner = Owner::current()
.expect("creating Router, but no Owner was found");
let url = use_context::<RequestUrl>()
.expect("could not find request URL in context");
// TODO base
let url =
RequestUrl::parse(url.as_ref()).expect("could not parse URL");
// TODO query params
match self.routes.match_route(url.path()) {
Some(new_match) => {
let params = ArcRwSignal::new(
new_match.to_params().into_iter().collect(),
);
#[allow(unused)]
let (view, child) = new_match.into_view_and_child();
#[cfg(debug_assertions)]
if child.is_some() {
panic!(
"FlatRouter should not be used with a route that \
has a child."
);
}
let route_data = RouteData {
params: ArcMemo::new({
let params = params.clone();
move |_| params.get()
}),
outlet: Default::default(),
};
let view = outer_owner.with(|| view.choose(route_data));
Either::Left(view)
}
None => Either::Right((self.fallback)()),
}
.to_html_with_buf(buf, position)
}
}
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
self,
buf: &mut StreamBuilder,
position: &mut Position,
) where
Self: Sized,
{
let outer_owner =
Owner::current().expect("creating Router, but no Owner was found");
let url = use_context::<RequestUrl>()
.expect("could not find request URL in context");
// TODO base
let url = RequestUrl::parse(url.as_ref()).expect("could not parse URL");
// TODO query params
match self.routes.match_route(url.path()) {
Some(new_match) => {
let params = ArcRwSignal::new(
new_match.to_params().into_iter().collect(),
);
#[allow(unused)]
let (view, child) = new_match.into_view_and_child();
#[cfg(debug_assertions)]
if child.is_some() {
panic!(
"FlatRouter should not be used with a route that has \
a child."
);
}
let route_data = RouteData {
params: ArcMemo::new({
let params = params.clone();
move |_| params.get()
}),
outlet: Default::default(),
};
let view = outer_owner.with(|| view.choose(route_data));
Either::Left(view)
}
None => Either::Right((self.fallback)()),
}
.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position)
}
fn hydrate<const FROM_SERVER: bool>(
self,
cursor: &Cursor<Rndr>,
position: &PositionState,
) -> Self::State {
let location = Loc::new().unwrap(); // TODO
location.init(self.base);
let url = location.as_url().clone();
let path = ArcMemo::new({
let url = url.clone();
move |_| url.read().path().to_string()
});
let search_params = ArcMemo::new({
let url = url.clone();
move |_| url.read().search_params().clone()
});
let outer_owner =
Owner::current().expect("creating Router, but no Owner was found");
let cursor = cursor.clone();
let position = position.clone();
RenderEffect::new(move |prev: Option<EitherState<_, _, _>>| {
let path = path.read();
let new_match = self.routes.match_route(&path);
if let Some(mut prev) = prev {
if let Some(new_match) = new_match {
let params = ArcRwSignal::new(
new_match.to_params().into_iter().collect(),
);
#[allow(unused)]
let (view, child) = new_match.into_view_and_child();
#[cfg(debug_assertions)]
if child.is_some() {
panic!(
"FlatRouter should not be used with a route that \
has a child."
);
}
let route_data = RouteData {
params: ArcMemo::new({
let params = params.clone();
move |_| params.get()
}),
outlet: Default::default(),
};
let view = outer_owner.with(|| view.choose(route_data));
Either::Left::<_, Fallback>(view).rebuild(&mut prev);
} else {
Either::<<Children::Match as MatchInterface<Rndr>>::View, _>::Right((self.fallback)()).rebuild(&mut prev);
}
prev
} else {
match new_match {
Some(matched) => {
let params = ArcRwSignal::new(
matched.to_params().into_iter().collect(),
);
#[allow(unused)]
let (view, child) = matched.into_view_and_child();
#[cfg(debug_assertions)]
if child.is_some() {
panic!(
"FlatRouter should not be used with a route \
that has a child."
);
}
let route_data = RouteData {
params: ArcMemo::new({
let params = params.clone();
move |_| params.get()
}),
outlet: Default::default(),
};
let view = outer_owner.with(|| view.choose(route_data));
Either::Left(view)
}
_ => Either::Right((self.fallback)()),
}
.hydrate::<FROM_SERVER>(&cursor, &position)
}
})
}
}

View File

@ -107,8 +107,9 @@ where
where
Self: Sized,
{
let capacity = self.html_len();
let mut builder = StreamBuilder::with_capacity(capacity, Some(vec![0]));
//let capacity = self.html_len();
let mut builder =
StreamBuilder::with_capacity(self.html_len(), Some(vec![0]));
self.to_html_async_with_buf::<true>(
&mut builder,