Skip to content

Commit f3effed

Browse files
committed
wip: redesign csr
1 parent 70ae9f6 commit f3effed

File tree

9 files changed

+767
-189
lines changed

9 files changed

+767
-189
lines changed

packages/frender-csr-ext/src/lib.rs

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1-
pub use frender_html::{CsrComponent, CsrElement, RenderState};
1+
pub use frender_html::{CsrComponent, CsrElement};
22
pub use into_render_element_ext::IntoRenderElementExt;
33
pub use render_element::RenderElement;
44

55
mod render_element;
66

77
mod into_render_element_ext {
8-
use frender_html::{dom::ProvideRenderContext, CsrElement as Element, RenderHtml};
8+
use std::{future::Future, pin::pin};
9+
10+
use frender_html::{
11+
dom::ProvideRenderContext,
12+
experimental::{
13+
PinMutRenderInitStates, PinnedRenderStateKind, PinnedRenderStateKindPollRender,
14+
RenderStates,
15+
},
16+
CsrElement as Element, RenderHtml, StateUnmount,
17+
};
918

1019
pub trait IntoRenderElementExt: ProvideRenderContext {
1120
fn into_render_element<E: Element>(self, element: E) -> crate::RenderElement<Self, E>
@@ -22,6 +31,74 @@ mod into_render_element_ext {
2231
{
2332
crate::RenderElement::new(self, element)
2433
}
34+
35+
/// The caller could then unmount the ui handle or just drop it without unmounting.
36+
fn into_render_element_until_non_reactive<E: Element>(
37+
mut self,
38+
element: E,
39+
) -> impl Future<
40+
Output = <E::RenderStateKind as PinnedRenderStateKind>::PinnedUiHandle<Self::Renderer>,
41+
>
42+
where
43+
Self: Sized,
44+
Self::Renderer: RenderHtml,
45+
{
46+
async move {
47+
let mut non_reactive_state = pin!(
48+
<<E::RenderStateKind as PinnedRenderStateKind>::PinnedNonReactiveState<
49+
Self::Renderer,
50+
>>::default()
51+
);
52+
let mut reactive_state = pin!(
53+
<<E::RenderStateKind as PinnedRenderStateKind>::PinnedReactiveState>::default()
54+
);
55+
let mut ui_handle = self.provide_render_context(|render_context| {
56+
element.pinned_render_init(
57+
render_context,
58+
PinMutRenderInitStates {
59+
non_reactive_state: non_reactive_state.as_mut(),
60+
reactive_state: reactive_state.as_mut(),
61+
},
62+
)
63+
});
64+
65+
std::future::poll_fn(|cx| {
66+
<E::RenderStateKind as PinnedRenderStateKindPollRender>::pinned_poll_render(
67+
self.renderer_mut(),
68+
RenderStates {
69+
ui_handle: &mut ui_handle,
70+
non_reactive_state: non_reactive_state.as_mut(),
71+
reactive_state: reactive_state.as_mut(),
72+
},
73+
cx,
74+
)
75+
})
76+
.await;
77+
78+
// now the ui is no longer reactive
79+
80+
// The unmount order is the same as EitherElement accidentally
81+
// (This is not considered as a feature and might change in the future)
82+
non_reactive_state.set(Default::default());
83+
reactive_state.as_mut().state_unmount();
84+
reactive_state.set(Default::default());
85+
86+
ui_handle
87+
}
88+
}
89+
90+
fn render_element_until_non_reactive<E: Element>(
91+
&mut self,
92+
element: E,
93+
) -> impl Future<
94+
Output = <E::RenderStateKind as PinnedRenderStateKind>::PinnedUiHandle<Self::Renderer>,
95+
>
96+
where
97+
Self: Sized,
98+
Self::Renderer: RenderHtml,
99+
{
100+
self.into_render_element_until_non_reactive(element)
101+
}
25102
}
26103

27104
impl<R: ?Sized + ProvideRenderContext> IntoRenderElementExt for R {}
Lines changed: 99 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,76 @@
1-
use std::future::Future;
1+
use std::{future::Future, task::Poll};
22

3-
use frender_html::{dom::ProvideRenderContext, CsrElement as Element, RenderHtml, RenderState};
3+
use frender_html::{
4+
dom::{ui_handle::UiHandle as _, ProvideRenderContext},
5+
experimental::{
6+
PinMutRenderInitStates, PinnedRenderStateKind, PinnedRenderStateKindPollRender as _,
7+
RenderStates,
8+
},
9+
CsrElement, RenderHtml, StateUnmount as _,
10+
};
11+
12+
enum ElementOrUiHandle<E, UH> {
13+
Taken,
14+
Element(E),
15+
UiHandle(UH),
16+
}
17+
18+
impl<E, UH> ElementOrUiHandle<E, UH> {
19+
fn as_mut_ui_handle_or_insert(&mut self, f: impl FnOnce(E) -> UH) -> &mut UH {
20+
match self {
21+
ElementOrUiHandle::Taken => unreachable!(),
22+
ElementOrUiHandle::Element(_) => {
23+
let this = std::mem::replace(self, Self::Taken);
24+
let Self::Element(element) = this else {
25+
unreachable!()
26+
};
27+
28+
*self = Self::UiHandle(f(element));
29+
30+
if let ElementOrUiHandle::UiHandle(this) = self {
31+
this
32+
} else {
33+
unreachable!()
34+
}
35+
}
36+
ElementOrUiHandle::UiHandle(this) => this,
37+
}
38+
}
39+
40+
fn take_ui_handle(&mut self) -> UH {
41+
if let ElementOrUiHandle::UiHandle(this) = std::mem::replace(self, Self::Taken) {
42+
this
43+
} else {
44+
unreachable!()
45+
}
46+
}
47+
}
448

549
pin_project_lite::pin_project!(
6-
pub struct RenderElement<P: ProvideRenderContext, E: Element, Stop = std::future::Pending<()>>
50+
pub struct RenderElement<
51+
P: ProvideRenderContext,
52+
E: CsrElement,
53+
Stop = std::future::Pending<()>,
54+
>
755
where
856
P::Renderer: RenderHtml,
957
{
1058
p: P,
11-
element: Option<E>,
59+
element: ElementOrUiHandle<
60+
E,
61+
<E::RenderStateKind as PinnedRenderStateKind>::PinnedUiHandle<P::Renderer>,
62+
>,
63+
#[pin]
64+
non_reactive_state:
65+
<E::RenderStateKind as PinnedRenderStateKind>::PinnedNonReactiveState<P::Renderer>,
1266
#[pin]
13-
state:
14-
<E::RenderStateKind as frender_html::RenderStateKindPinned>::RenderState<P::Renderer>,
67+
reactive_state: <E::RenderStateKind as PinnedRenderStateKind>::PinnedReactiveState,
1568
#[pin]
1669
stop: Stop,
1770
}
1871
);
1972

20-
impl<P: ProvideRenderContext, E: Element> RenderElement<P, E>
73+
impl<P: ProvideRenderContext, E: CsrElement> RenderElement<P, E>
2174
where
2275
P::Renderer: RenderHtml,
2376
{
@@ -26,49 +79,69 @@ where
2679
}
2780
}
2881

29-
impl<P: ProvideRenderContext, E: Element, Stop> RenderElement<P, E, Stop>
82+
impl<P: ProvideRenderContext, E: CsrElement, Stop> RenderElement<P, E, Stop>
3083
where
3184
P::Renderer: RenderHtml,
3285
{
3386
pub fn new_with_stop(render_context: P, element: E, stop: Stop) -> Self {
3487
Self {
3588
p: render_context,
36-
element: Some(element),
37-
state: Default::default(),
89+
element: ElementOrUiHandle::Element(element),
90+
non_reactive_state: Default::default(),
91+
reactive_state: Default::default(),
3892
stop,
3993
}
4094
}
4195
}
4296

43-
impl<P: ProvideRenderContext, E: Element, Stop: Future<Output = ()>> std::future::Future
97+
impl<P: ProvideRenderContext, E: CsrElement, Stop: Future<Output = ()>> std::future::Future
4498
for RenderElement<P, E, Stop>
4599
where
46100
P::Renderer: RenderHtml,
47101
{
48102
type Output = ();
49103

50-
fn poll(
51-
self: std::pin::Pin<&mut Self>,
52-
cx: &mut std::task::Context<'_>,
53-
) -> std::task::Poll<Self::Output> {
104+
fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
54105
let mut this = self.project();
55106

56-
if let Some(element) = this.element.take() {
57-
this.p.provide_render_context(|render_context| {
58-
element.render_update(render_context, this.state.as_mut())
59-
});
107+
if let ElementOrUiHandle::Taken = this.element {
108+
return Poll::Ready(());
60109
}
61110

62-
if let std::task::Poll::Pending = this.state.as_mut().poll_render(this.p.renderer_mut(), cx)
63-
{
64-
return std::task::Poll::Pending;
111+
let ui_handle = this.element.as_mut_ui_handle_or_insert(|element| {
112+
this.p.provide_render_context(|render_context| {
113+
element.pinned_render_init(
114+
render_context,
115+
PinMutRenderInitStates {
116+
non_reactive_state: this.non_reactive_state.as_mut(),
117+
reactive_state: this.reactive_state.as_mut(),
118+
},
119+
)
120+
})
121+
});
122+
123+
if let Poll::Pending = <E::RenderStateKind>::pinned_poll_render(
124+
this.p.renderer_mut(),
125+
RenderStates {
126+
ui_handle,
127+
non_reactive_state: this.non_reactive_state.as_mut(),
128+
reactive_state: this.reactive_state.as_mut(),
129+
},
130+
cx,
131+
) {
132+
return Poll::Pending;
65133
}
66134

67-
if let std::task::Poll::Ready(()) = this.stop.poll(cx) {
68-
this.state.unmount(this.p.renderer_mut());
69-
return std::task::Poll::Ready(());
135+
if let Poll::Ready(()) = this.stop.poll(cx) {
136+
// The unmount order is the same as EitherElement accidentally
137+
// (This is not considered as a feature and might change in the future)
138+
this.non_reactive_state.set(Default::default());
139+
this.reactive_state.as_mut().state_unmount();
140+
this.reactive_state.set(Default::default());
141+
this.element.take_ui_handle().unmount(this.p.renderer_mut());
142+
return Poll::Ready(());
70143
}
71144

72-
std::task::Poll::Pending
145+
Poll::Pending
73146
}
74147
}

packages/frender-csr-web/src/renderer.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,4 +243,22 @@ impl frender_html::dom::csr::web::Renderer for Renderer {
243243

244244
node.unchecked_ref::<Removable>().remove()
245245
}
246+
247+
fn assert_cursor_if_at_node(render_context: &Self::RenderContext<'_>, node: &web_sys::Node)
248+
where
249+
Self: RenderWithContext,
250+
{
251+
debug_assert!(render_context.cursor.cursor_is_at_node(node))
252+
}
253+
254+
fn reposition_node(render_context: &mut Self::RenderContext<'_>, node: &web_sys::Node)
255+
where
256+
Self: RenderWithContext,
257+
{
258+
render_context.cursor.force_add_node(node)
259+
}
260+
261+
fn mount_node(render_context: &mut Self::RenderContext<'_>, node: &web_sys::Node) {
262+
render_context.cursor.force_add_node(node)
263+
}
246264
}

packages/frender-dom/src/csr/web.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,23 @@ impl<'a> Cursor<'a> {
177177
.map_or(false, |c| *node == c)
178178
}
179179

180+
pub fn force_add_node(&mut self, node: &web_sys::Node) {
181+
match &self.position {
182+
CursorPosition::FirstChildOf(parent) => {
183+
parent.prepend_with_node_1(node).unwrap_throw();
184+
}
185+
CursorPosition::After(pre) => {
186+
pre.parent_node()
187+
.expect_throw("the previous node should have a parent node")
188+
.insert_before(node, pre.next_sibling().as_ref())
189+
.unwrap_throw();
190+
}
191+
}
192+
193+
self.position = CursorPosition::After(Cow::Owned(node.clone()));
194+
self.skipped = false;
195+
}
196+
180197
pub fn readd_node(&mut self, node: &web_sys::Node, force_reposition: bool) {
181198
if force_reposition {
182199
// #[cfg(debug_assertions)]

packages/frender-dom/src/csr/web/cursor_place_holder.rs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,56 @@
1-
use crate::{behaviors, render::RenderWithContext};
1+
use crate::{
2+
behaviors,
3+
render::RenderWithContext,
4+
ui_handle::{UiHandle, UnmountedUiHandle},
5+
};
26

37
use super::Renderer;
48

59
#[derive(Debug)]
610
pub struct CursorPlaceholder(web_sys::Comment);
711

12+
impl<R: ?Sized + Renderer> UnmountedUiHandle<R> for CursorPlaceholder {
13+
type Mounted = Self;
14+
15+
fn mount(self, render_context: &mut <R>::RenderContext<'_>) -> Self::Mounted
16+
where
17+
R: crate::render::RenderWithContext,
18+
{
19+
R::mount_node(render_context, &self.0);
20+
self
21+
}
22+
}
23+
24+
impl<R: ?Sized + Renderer> UiHandle<R> for CursorPlaceholder {
25+
type Unmounted = Self;
26+
27+
fn unmount(self, renderer: &mut R) -> Self::Unmounted {
28+
renderer.remove_node(&self.0);
29+
self
30+
}
31+
32+
fn reposition(&mut self, render_context: &mut <R>::RenderContext<'_>)
33+
where
34+
R: crate::render::RenderWithContext,
35+
{
36+
R::reposition_node(render_context, &self.0)
37+
}
38+
39+
fn check_and_move_cursor(&self, render_context: &mut <R>::RenderContext<'_>)
40+
where
41+
R: crate::render::RenderWithContext,
42+
{
43+
R::check_and_move_cursor_after_node(render_context, &self.0)
44+
}
45+
46+
fn assert_cursor_if_at_self(&self, render_context: &<R>::RenderContext<'_>)
47+
where
48+
R: crate::render::RenderWithContext,
49+
{
50+
R::assert_cursor_if_at_node(render_context, &self.0)
51+
}
52+
}
53+
854
impl<R: ?Sized + Renderer> behaviors::Node<R> for CursorPlaceholder {
955
fn log_self(&self, _: &mut R) {
1056
web_sys::console::log_2(&"CursorPlaceholder".into(), &self.0);

packages/frender-render-with/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,15 @@ async-str-iter = { version = "0.1.0", path = "../async-str-iter", default-featur
88
frender-html = { version = "0.1.0", path = "../frender-html" }
99
frender-ssr = { version = "0.1.0", path = "../frender-ssr" }
1010

11+
[dev-dependencies]
12+
frender-html = { version = "0.1.0", path = "../frender-html", features = [
13+
"web",
14+
] }
15+
frender-common = { version = "0.1.0", path = "../frender-common" }
16+
frender-elements = { version = "0.1.0", path = "../frender-elements" }
17+
hooks = { version = "3.0.0-alpha", default-features = false, features = [
18+
"Signal",
19+
] }
20+
1121
[features]
1222
nightly = []

0 commit comments

Comments
 (0)