use interning to leak less memory

This commit is contained in:
Evan Almloff committed 2022-12-10 16:21:31 -06:00
1 parent 2131e5658b
commit 6b19229b53
2 files changed
+49 -31

No files matched your search

+1
View File
@@ -12,3 +12,4 @@ syn = { version = "1.0", features = ["full", "extra-traits"] }
quote = { version = "1.0" } quote = { version = "1.0" }
dioxus-core = { path = "../core", features = ["serialize"] } dioxus-core = { path = "../core", features = ["serialize"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
internment = "0.7.0"
+48 -31
View File
@@ -19,12 +19,15 @@ mod hot_reloading_context;
mod ifmt; mod ifmt;
mod node; mod node;
use std::{borrow::Borrow, hash::Hash};
// Re-export the namespaces into each other // Re-export the namespaces into each other
pub use component::*; pub use component::*;
use dioxus_core::{Template, TemplateAttribute, TemplateNode}; use dioxus_core::{Template, TemplateAttribute, TemplateNode};
pub use element::*; pub use element::*;
use hot_reloading_context::{Empty, HotReloadingContext}; use hot_reloading_context::{Empty, HotReloadingContext};
pub use ifmt::*; pub use ifmt::*;
use internment::Intern;
pub use node::*; pub use node::*;
// imports // imports
@@ -35,6 +38,13 @@ use syn::{
Result, Token, Result, Token,
}; };
// interns a object into a static object, resusing the value if it already exists
fn intern<'a, T: Eq + Hash + Send + Sync + ?Sized + 'static>(
s: impl Into<Intern<T>>,
) -> &'static T {
s.into().as_ref()
}
/// Fundametnally, every CallBody is a template /// Fundametnally, every CallBody is a template
#[derive(Default)] #[derive(Default)]
pub struct CallBody<Ctx: HotReloadingContext = Empty> { pub struct CallBody<Ctx: HotReloadingContext = Empty> {
@@ -47,15 +57,20 @@ pub struct CallBody<Ctx: HotReloadingContext = Empty> {
} }
impl<Ctx: HotReloadingContext> CallBody<Ctx> { impl<Ctx: HotReloadingContext> CallBody<Ctx> {
/// This will try to create a new template from the current body and the previous body. This will return None if the rsx has some dynamic part that has changed.
/// This function intentionally leaks memory to create a static template. /// This function intentionally leaks memory to create a static template.
/// Keeping the template static allows us to simplify the core of dioxus and leaking memory in dev mode is less of an issue. /// Keeping the template static allows us to simplify the core of dioxus and leaking memory in dev mode is less of an issue.
/// the previous_location is the location of the previous template at the time the template was originally compiled. /// the previous_location is the location of the previous template at the time the template was originally compiled.
pub fn leak_template(&self, previous_location: &'static str) -> Template { pub fn update_template(
&self,
template: Option<&CallBody<Ctx>>,
location: &'static str,
) -> Option<Template> {
let mut renderer: TemplateRenderer<Ctx> = TemplateRenderer { let mut renderer: TemplateRenderer<Ctx> = TemplateRenderer {
roots: &self.roots, roots: &self.roots,
phantom: std::marker::PhantomData, phantom: std::marker::PhantomData,
}; };
renderer.leak_template(previous_location) renderer.update_template(template, location)
} }
} }
@@ -112,7 +127,11 @@ pub struct TemplateRenderer<'a, Ctx: HotReloadingContext = Empty> {
} }
impl<'a, Ctx: HotReloadingContext> TemplateRenderer<'a, Ctx> { impl<'a, Ctx: HotReloadingContext> TemplateRenderer<'a, Ctx> {
fn leak_template(&mut self, previous_location: &'static str) -> Template<'static> { fn update_template(
&mut self,
previous_call: Option<&CallBody<Ctx>>,
location: &'static str,
) -> Option<Template<'static>> {
let mut context: DynamicContext<Ctx> = DynamicContext::default(); let mut context: DynamicContext<Ctx> = DynamicContext::default();
let roots: Vec<_> = self let roots: Vec<_> = self
@@ -121,32 +140,32 @@ impl<'a, Ctx: HotReloadingContext> TemplateRenderer<'a, Ctx> {
.enumerate() .enumerate()
.map(|(idx, root)| { .map(|(idx, root)| {
context.current_path.push(idx as u8); context.current_path.push(idx as u8);
let out = context.leak_node(root); let out = context.update_node(root);
context.current_path.pop(); context.current_path.pop();
out out
}) })
.collect(); .collect();
Template { Some(Template {
name: previous_location, name: location,
roots: Box::leak(roots.into_boxed_slice()), roots: intern(roots.as_slice()),
node_paths: Box::leak( node_paths: intern(
context context
.node_paths .node_paths
.into_iter() .into_iter()
.map(|path| &*Box::leak(path.into_boxed_slice())) .map(|path| intern(path.as_slice()))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.into_boxed_slice(), .as_slice(),
), ),
attr_paths: Box::leak( attr_paths: intern(
context context
.attr_paths .attr_paths
.into_iter() .into_iter()
.map(|path| &*Box::leak(path.into_boxed_slice())) .map(|path| intern(path.as_slice()))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.into_boxed_slice(), .as_slice(),
), ),
} })
} }
} }
@@ -209,8 +228,9 @@ impl<'a, Ctx: HotReloadingContext> ToTokens for TemplateRenderer<'a, Ctx> {
}); });
} }
} }
// As we print out the dynamic nodes, we want to keep track of them in a linear fashion
// We'll use the size of the vecs to determine the index of the dynamic node in the final // As we create the dynamic nodes, we want to keep track of them in a linear fashion
// We'll use the size of the vecs to determine the index of the dynamic node in the final output
pub struct DynamicContext<'a, Ctx: HotReloadingContext> { pub struct DynamicContext<'a, Ctx: HotReloadingContext> {
dynamic_nodes: Vec<&'a BodyNode>, dynamic_nodes: Vec<&'a BodyNode>,
dynamic_attributes: Vec<&'a ElementAttrNamed>, dynamic_attributes: Vec<&'a ElementAttrNamed>,
@@ -236,7 +256,7 @@ impl<'a, Ctx: HotReloadingContext> Default for DynamicContext<'a, Ctx> {
} }
impl<'a, Ctx: HotReloadingContext> DynamicContext<'a, Ctx> { impl<'a, Ctx: HotReloadingContext> DynamicContext<'a, Ctx> {
fn leak_node(&mut self, root: &'a BodyNode) -> TemplateNode<'static> { fn update_node(&mut self, root: &'a BodyNode) -> TemplateNode<'static> {
match root { match root {
BodyNode::Element(el) => { BodyNode::Element(el) => {
// dynamic attributes // dynamic attributes
@@ -250,7 +270,7 @@ impl<'a, Ctx: HotReloadingContext> DynamicContext<'a, Ctx> {
let element_name_rust = el.name.to_string(); let element_name_rust = el.name.to_string();
let static_attrs: Vec<_> = el let static_attrs: Vec<TemplateAttribute<'static>> = el
.attributes .attributes
.iter() .iter()
.map(|attr| match &attr.attr { .map(|attr| match &attr.attr {
@@ -259,14 +279,11 @@ impl<'a, Ctx: HotReloadingContext> DynamicContext<'a, Ctx> {
let attribute_name_rust = name.to_string(); let attribute_name_rust = name.to_string();
let (name, namespace) = let (name, namespace) =
Ctx::map_attribute(&element_name_rust, &attribute_name_rust) Ctx::map_attribute(&element_name_rust, &attribute_name_rust)
.unwrap_or(( .unwrap_or((intern(attribute_name_rust.as_str()), None));
Box::leak(attribute_name_rust.into_boxed_str()),
None,
));
TemplateAttribute::Static { TemplateAttribute::Static {
name, name,
namespace, namespace,
value: Box::leak(value.value().into_boxed_str()), value: intern(value.value().as_str()),
// name: dioxus_elements::#el_name::#name.0, // name: dioxus_elements::#el_name::#name.0,
// namespace: dioxus_elements::#el_name::#name.1, // namespace: dioxus_elements::#el_name::#name.1,
// value: #value, // value: #value,
@@ -279,9 +296,9 @@ impl<'a, Ctx: HotReloadingContext> DynamicContext<'a, Ctx> {
ElementAttr::CustomAttrText { name, value } if value.is_static() => { ElementAttr::CustomAttrText { name, value } if value.is_static() => {
let value = value.source.as_ref().unwrap(); let value = value.source.as_ref().unwrap();
TemplateAttribute::Static { TemplateAttribute::Static {
name: Box::leak(name.value().into_boxed_str()), name: intern(name.value().as_str()),
namespace: None, namespace: None,
value: Box::leak(value.value().into_boxed_str()), value: intern(value.value().as_str()),
// todo: we don't diff these so we never apply the volatile flag // todo: we don't diff these so we never apply the volatile flag
// volatile: dioxus_elements::#el_name::#name.2, // volatile: dioxus_elements::#el_name::#name.2,
} }
@@ -306,26 +323,26 @@ impl<'a, Ctx: HotReloadingContext> DynamicContext<'a, Ctx> {
.enumerate() .enumerate()
.map(|(idx, root)| { .map(|(idx, root)| {
self.current_path.push(idx as u8); self.current_path.push(idx as u8);
let out = self.leak_node(root); let out = self.update_node(root);
self.current_path.pop(); self.current_path.pop();
out out
}) })
.collect(); .collect();
let (tag, namespace) = Ctx::map_element(&element_name_rust) let (tag, namespace) = Ctx::map_element(&element_name_rust)
.unwrap_or((Box::leak(element_name_rust.into_boxed_str()), None)); .unwrap_or((intern(element_name_rust.as_str()), None));
TemplateNode::Element { TemplateNode::Element {
tag, tag,
namespace, namespace,
attrs: Box::leak(static_attrs.into_boxed_slice()), attrs: intern(static_attrs.into_boxed_slice()),
children: Box::leak(children.into_boxed_slice()), children: intern(children.as_slice()),
} }
} }
BodyNode::Text(text) if text.is_static() => { BodyNode::Text(text) if text.is_static() => {
let text = text.source.as_ref().unwrap(); let text = text.source.as_ref().unwrap();
TemplateNode::Text { TemplateNode::Text {
text: Box::leak(text.value().into_boxed_str()), text: intern(text.value().as_str()),
} }
} }
@@ -490,7 +507,7 @@ fn template() {
let call_body: CallBody<Mock> = syn::parse2(input).unwrap(); let call_body: CallBody<Mock> = syn::parse2(input).unwrap();
let template = call_body.leak_template("testing"); let template = call_body.update_template(None, "testing").unwrap();
dbg!(template); dbg!(template);