Skip to content

Commit

Permalink
BufWriter.
Browse files Browse the repository at this point in the history
  • Loading branch information
futursolo committed May 22, 2022
1 parent ff0ad94 commit e5b76a7
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 107 deletions.
18 changes: 8 additions & 10 deletions packages/yew/src/html/component/scope.rs
Expand Up @@ -260,27 +260,25 @@ impl<COMP: BaseComponent> Scope<COMP> {

#[cfg(feature = "ssr")]
mod feat_ssr {
use futures::channel::mpsc::UnboundedSender;
use futures::channel::oneshot;

use super::*;
use crate::html::component::lifecycle::{
ComponentRenderState, CreateRunner, DestroyRunner, RenderRunner,
};
use crate::scheduler;
use crate::server_renderer::BufWriter;
use crate::virtual_dom::Collectable;

impl<COMP: BaseComponent> Scope<COMP> {
pub(crate) async fn render_into_stream(
&self,
tx: &mut UnboundedSender<String>,
w: &mut BufWriter,
props: Rc<COMP::Properties>,
hydratable: bool,
) {
let (html_tx, html_rx) = oneshot::channel();
let state = ComponentRenderState::Ssr {
sender: Some(html_tx),
};
let (tx, rx) = oneshot::channel();
let state = ComponentRenderState::Ssr { sender: Some(tx) };

scheduler::push_component_create(
self.id,
Expand All @@ -298,17 +296,17 @@ mod feat_ssr {
let collectable = Collectable::for_component::<COMP>();

if hydratable {
collectable.write_open_tag(tx);
collectable.write_open_tag(w);
}

let html = html_rx.await.unwrap();
let html = rx.await.unwrap();

let self_any_scope = AnyScope::from(self.clone());
html.render_into_stream(tx, &self_any_scope, hydratable)
html.render_into_stream(w, &self_any_scope, hydratable)
.await;

if hydratable {
collectable.write_close_tag(tx);
collectable.write_close_tag(w);
}

scheduler::push_component_destroy(Box::new(DestroyRunner {
Expand Down
63 changes: 60 additions & 3 deletions packages/yew/src/server_renderer.rs
@@ -1,9 +1,66 @@
use futures::channel::mpsc;
use std::borrow::Cow;

use futures::channel::mpsc::{self, UnboundedSender};
use futures::stream::{Stream, StreamExt};

use crate::html::{BaseComponent, Scope};
use crate::platform::{run_pinned, spawn_local};

pub(crate) struct BufWriter {
buf: String,
tx: UnboundedSender<String>,
}

impl BufWriter {
pub fn new() -> (Self, impl Stream<Item = String>) {
let (tx, rx) = mpsc::unbounded::<String>();

let this = Self {
buf: String::with_capacity(4096),
tx,
};

(this, rx)
}

/// Writes a string into the buffer, optionally drains the buffer.
pub fn write(&mut self, s: Cow<'_, str>) {
if s.len() > 4096 {
// if the next chunk is more than 4096, we drain the buffer and the next
// chunk.
if !self.buf.is_empty() {
let mut buf = String::with_capacity(4096);
std::mem::swap(&mut buf, &mut self.buf);
let _ = self.tx.unbounded_send(buf);
}

let _ = self.tx.unbounded_send(s.into_owned());
} else if self.buf.len() + s.len() < 4096 {
// The length of current chunk and the next part is less than 4096, we push
// it on to the buffer.
self.buf.push_str(&s);
} else {
// The length of current chunk and the next part is more than 4096, we send
// the current buffer and make the next chunk the new buffer.
let mut buf = s.into_owned();

buf.reserve(4096);
std::mem::swap(&mut buf, &mut self.buf);
let _ = self.tx.unbounded_send(buf);
}
}
}

impl Drop for BufWriter {
fn drop(&mut self) {
if !self.buf.is_empty() {
let mut buf = "".to_string();
std::mem::swap(&mut buf, &mut self.buf);
let _ = self.tx.unbounded_send(buf);
}
}
}

/// A Yew Server-side Renderer that renders on the current thread.
#[cfg_attr(documenting, doc(cfg(feature = "ssr")))]
#[derive(Debug)]
Expand Down Expand Up @@ -82,12 +139,12 @@ where
// Whilst not required to be async here, this function is async to keep the same function
// signature as the ServerRenderer.
pub async fn render_stream(self) -> impl Stream<Item = String> {
let (mut tx, rx) = mpsc::unbounded::<String>();
let (mut w, rx) = BufWriter::new();

let scope = Scope::<COMP>::new(None);
spawn_local(async move {
scope
.render_into_stream(&mut tx, self.props.into(), self.hydratable)
.render_into_stream(&mut w, self.props.into(), self.hydratable)
.await;
});

Expand Down
36 changes: 13 additions & 23 deletions packages/yew/src/virtual_dom/mod.rs
Expand Up @@ -265,46 +265,36 @@ pub(crate) use feat_ssr_hydration::*;

#[cfg(feature = "ssr")]
mod feat_ssr {
use futures::channel::mpsc::UnboundedSender;

use super::*;
use crate::server_renderer::BufWriter;

impl Collectable {
pub fn write_open_tag(&self, tx: &mut UnboundedSender<String>) {
// <!--<[]>-->
let mut w = String::with_capacity(11);

w.push_str("<!--");
w.push_str(self.open_start_mark());
pub(crate) fn write_open_tag(&self, w: &mut BufWriter) {
w.write("<!--".into());
w.write(self.open_start_mark().into());

#[cfg(debug_assertions)]
match self {
Self::Component(type_name) => w.push_str(type_name),
Self::Component(type_name) => w.write((*type_name).into()),
Self::Suspense => {}
}

w.push_str(self.end_mark());
w.push_str("-->");

let _ = tx.unbounded_send(w);
w.write(self.end_mark().into());
w.write("-->".into());
}

pub fn write_close_tag(&self, tx: &mut UnboundedSender<String>) {
// <!--</[]>-->
let mut w = String::with_capacity(12);
w.push_str("<!--");
w.push_str(self.close_start_mark());
pub(crate) fn write_close_tag(&self, w: &mut BufWriter) {
w.write("<!--".into());
w.write(self.close_start_mark().into());

#[cfg(debug_assertions)]
match self {
Self::Component(type_name) => w.push_str(type_name),
Self::Component(type_name) => w.write((*type_name).into()),
Self::Suspense => {}
}

w.push_str(self.end_mark());
w.push_str("-->");

let _ = tx.unbounded_send(w);
w.write(self.end_mark().into());
w.write("-->".into());
}
}
}
Expand Down
14 changes: 7 additions & 7 deletions packages/yew/src/virtual_dom/vcomp.rs
Expand Up @@ -4,8 +4,6 @@ use std::any::TypeId;
use std::fmt;
use std::rc::Rc;

#[cfg(feature = "ssr")]
use futures::channel::mpsc::UnboundedSender;
#[cfg(feature = "ssr")]
use futures::future::{FutureExt, LocalBoxFuture};
#[cfg(feature = "csr")]
Expand All @@ -21,6 +19,8 @@ use crate::html::Scoped;
#[cfg(any(feature = "ssr", feature = "csr"))]
use crate::html::{AnyScope, Scope};
use crate::html::{BaseComponent, NodeRef};
#[cfg(feature = "ssr")]
use crate::server_renderer::BufWriter;

/// A virtual component.
pub struct VComp {
Expand Down Expand Up @@ -71,7 +71,7 @@ pub(crate) trait Mountable {
#[cfg(feature = "ssr")]
fn render_into_stream<'a>(
&'a self,
w: &'a mut UnboundedSender<String>,
w: &'a mut BufWriter,
parent_scope: &'a AnyScope,
hydratable: bool,
) -> LocalBoxFuture<'a, ()>;
Expand Down Expand Up @@ -129,15 +129,15 @@ impl<COMP: BaseComponent> Mountable for PropsWrapper<COMP> {
#[cfg(feature = "ssr")]
fn render_into_stream<'a>(
&'a self,
tx: &'a mut UnboundedSender<String>,
w: &'a mut BufWriter,
parent_scope: &'a AnyScope,
hydratable: bool,
) -> LocalBoxFuture<'a, ()> {
let scope: Scope<COMP> = Scope::new(Some(parent_scope.clone()));

async move {
scope
.render_into_stream(tx, self.props.clone(), hydratable)
.render_into_stream(w, self.props.clone(), hydratable)
.await;
}
.boxed_local()
Expand Down Expand Up @@ -246,13 +246,13 @@ mod feat_ssr {
#[inline]
pub(crate) async fn render_into_stream(
&self,
tx: &mut UnboundedSender<String>,
w: &mut BufWriter,
parent_scope: &AnyScope,
hydratable: bool,
) {
self.mountable
.as_ref()
.render_into_stream(tx, parent_scope, hydratable)
.render_into_stream(w, parent_scope, hydratable)
.await;
}
}
Expand Down
23 changes: 12 additions & 11 deletions packages/yew/src/virtual_dom/vlist.rs
Expand Up @@ -146,23 +146,23 @@ mod test {

#[cfg(feature = "ssr")]
mod feat_ssr {
use futures::channel::mpsc::{self, UnboundedSender};
use futures::stream::{FuturesOrdered, StreamExt};

use super::*;
use crate::html::AnyScope;
use crate::server_renderer::BufWriter;

impl VList {
pub(crate) async fn render_into_stream(
&self,
tx: &mut UnboundedSender<String>,
w: &mut BufWriter,
parent_scope: &AnyScope,
hydratable: bool,
) {
if self.children.len() < 2 {
match self.children.first() {
Some(m) => {
m.render_into_stream(tx, parent_scope, hydratable).await;
m.render_into_stream(w, parent_scope, hydratable).await;
}

None => {}
Expand All @@ -176,18 +176,19 @@ mod feat_ssr {
.children
.iter()
.map(|m| async move {
let (mut tx, rx) = mpsc::unbounded();
let (mut w, rx) = BufWriter::new();

m.render_into_stream(&mut tx, parent_scope, hydratable)
.await;
drop(tx);
m.render_into_stream(&mut w, parent_scope, hydratable).await;
drop(w);

let s: String = rx.collect().await;
s
rx
})
.collect();
while let Some(m) = children.next().await {
let _ = tx.unbounded_send(m);

while let Some(mut rx) = children.next().await {
while let Some(next_chunk) = rx.next().await {
w.write(next_chunk.into());
}
}
}
}
Expand Down
20 changes: 9 additions & 11 deletions packages/yew/src/virtual_dom/vnode.rs
Expand Up @@ -147,37 +147,35 @@ impl PartialEq for VNode {

#[cfg(feature = "ssr")]
mod feat_ssr {
use futures::channel::mpsc::UnboundedSender;
use futures::future::{FutureExt, LocalBoxFuture};

use super::*;
use crate::html::AnyScope;
use crate::server_renderer::BufWriter;

impl VNode {
pub(crate) fn render_into_stream<'a>(
&'a self,
tx: &'a mut UnboundedSender<String>,
w: &'a mut BufWriter,
parent_scope: &'a AnyScope,
hydratable: bool,
) -> LocalBoxFuture<'a, ()> {
async fn render_into_stream_(
this: &VNode,
tx: &mut UnboundedSender<String>,
w: &mut BufWriter,
parent_scope: &AnyScope,
hydratable: bool,
) {
match this {
VNode::VTag(vtag) => {
vtag.render_into_stream(tx, parent_scope, hydratable).await
}
VNode::VTag(vtag) => vtag.render_into_stream(w, parent_scope, hydratable).await,
VNode::VText(vtext) => {
vtext.render_into_stream(tx, parent_scope, hydratable).await
vtext.render_into_stream(w, parent_scope, hydratable).await
}
VNode::VComp(vcomp) => {
vcomp.render_into_stream(tx, parent_scope, hydratable).await
vcomp.render_into_stream(w, parent_scope, hydratable).await
}
VNode::VList(vlist) => {
vlist.render_into_stream(tx, parent_scope, hydratable).await
vlist.render_into_stream(w, parent_scope, hydratable).await
}
// We are pretty safe here as it's not possible to get a web_sys::Node without
// DOM support in the first place.
Expand All @@ -191,13 +189,13 @@ mod feat_ssr {
VNode::VPortal(_) => {}
VNode::VSuspense(vsuspense) => {
vsuspense
.render_into_stream(tx, parent_scope, hydratable)
.render_into_stream(w, parent_scope, hydratable)
.await
}
}
}

async move { render_into_stream_(self, tx, parent_scope, hydratable).await }
async move { render_into_stream_(self, w, parent_scope, hydratable).await }
.boxed_local()
}
}
Expand Down

0 comments on commit e5b76a7

Please sign in to comment.