Skip to content

Commit 080b5e0

Browse files
authored
Merge pull request #661 from cmrschwarz/avoid_context_clone_v2
Avoid cloning the Render Context
2 parents 5848265 + 1adbc0a commit 080b5e0

File tree

3 files changed

+165
-111
lines changed

3 files changed

+165
-111
lines changed

src/partial.rs

Lines changed: 86 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use std::borrow::Cow;
21
use std::collections::HashMap;
32

43
use serde_json::value::Value as Json;
@@ -15,22 +14,26 @@ use crate::RenderErrorReason;
1514

1615
pub(crate) const PARTIAL_BLOCK: &str = "@partial-block";
1716

18-
fn find_partial<'reg: 'rc, 'rc: 'a, 'a>(
19-
rc: &'a RenderContext<'reg, 'rc>,
17+
fn find_partial<'reg: 'rc, 'rc>(
18+
rc: &RenderContext<'reg, 'rc>,
2019
r: &'reg Registry<'reg>,
2120
d: &Decorator<'rc>,
2221
name: &str,
23-
) -> Result<Option<Cow<'a, Template>>, RenderError> {
22+
) -> Result<Option<&'rc Template>, RenderError> {
2423
if let Some(partial) = rc.get_partial(name) {
25-
return Ok(Some(Cow::Borrowed(partial)));
24+
return Ok(Some(partial));
2625
}
2726

28-
if let Some(tpl) = r.get_or_load_template_optional(name) {
29-
return tpl.map(Option::Some);
27+
if let Some(t) = rc.get_dev_mode_template(name) {
28+
return Ok(Some(t));
29+
}
30+
31+
if let Some(t) = r.get_template(name) {
32+
return Ok(Some(t));
3033
}
3134

3235
if let Some(tpl) = d.template() {
33-
return Ok(Some(Cow::Borrowed(tpl)));
36+
return Ok(Some(tpl));
3437
}
3538

3639
Ok(None)
@@ -49,114 +52,103 @@ pub fn expand_partial<'reg: 'rc, 'rc>(
4952
}
5053

5154
let tname = d.name();
55+
56+
let current_template_before = rc.get_current_template_name();
57+
let indent_before = rc.get_indent_string().cloned();
58+
5259
if rc.is_current_template(tname) {
5360
return Err(RenderErrorReason::CannotIncludeSelf.into());
5461
}
5562

5663
let partial = find_partial(rc, r, d, tname)?;
5764

58-
if let Some(t) = partial {
59-
// clone to avoid lifetime issue
60-
// FIXME refactor this to avoid
61-
let mut local_rc = rc.clone();
62-
63-
// if tname == PARTIAL_BLOCK
64-
let is_partial_block = tname == PARTIAL_BLOCK;
65-
66-
// add partial block depth there are consecutive partial
67-
// blocks in the stack.
68-
if is_partial_block {
69-
local_rc.inc_partial_block_depth();
70-
} else {
71-
// depth cannot be lower than 0, which is guaranted in the
72-
// `dec_partial_block_depth` method
73-
local_rc.dec_partial_block_depth();
74-
}
65+
let Some(partial) = partial else {
66+
return Err(RenderErrorReason::PartialNotFound(tname.to_owned()).into());
67+
};
68+
69+
let is_partial_block = tname == PARTIAL_BLOCK;
70+
71+
// add partial block depth there are consecutive partial
72+
// blocks in the stack.
73+
if is_partial_block {
74+
rc.inc_partial_block_depth();
75+
} else {
76+
// depth cannot be lower than 0, which is guaranted in the
77+
// `dec_partial_block_depth` method
78+
rc.dec_partial_block_depth();
79+
}
80+
81+
let mut block_created = false;
82+
83+
// create context if param given
84+
if let Some(base_path) = d.param(0).and_then(|p| p.context_path()) {
85+
// path given, update base_path
86+
let mut block_inner = BlockContext::new();
87+
*block_inner.base_path_mut() = base_path.to_vec();
7588

76-
let mut block_created = false;
89+
block_created = true;
90+
rc.push_block(block_inner);
91+
}
7792

78-
// create context if param given
79-
if let Some(base_path) = d.param(0).and_then(|p| p.context_path()) {
80-
// path given, update base_path
81-
let mut block_inner = BlockContext::new();
82-
base_path.clone_into(block_inner.base_path_mut());
93+
if !d.hash().is_empty() {
94+
// hash given, update base_value
95+
let hash_ctx = d
96+
.hash()
97+
.iter()
98+
.map(|(k, v)| (*k, v.value()))
99+
.collect::<HashMap<&str, &Json>>();
100+
101+
// create block if we didn't (no param provided for partial expression)
102+
if !block_created {
103+
let block_inner = if let Some(block) = rc.block() {
104+
// reuse current block information, including base_path and
105+
// base_value if any
106+
block.clone()
107+
} else {
108+
BlockContext::new()
109+
};
83110

84-
// because block is moved here, we need another bool variable to track
85-
// its status for later cleanup
86111
block_created = true;
87-
// clear blocks to prevent block params from parent
88-
// template to be leaked into partials
89-
// see `test_partial_context_issue_495` for the case.
90-
local_rc.clear_blocks();
91-
local_rc.push_block(block_inner);
112+
rc.push_block(block_inner);
92113
}
93114

94-
if !d.hash().is_empty() {
95-
// hash given, update base_value
96-
let hash_ctx = d
97-
.hash()
98-
.iter()
99-
.map(|(k, v)| (*k, v.value()))
100-
.collect::<HashMap<&str, &Json>>();
101-
102-
// create block if we didn't (no param provided for partial expression)
103-
if !block_created {
104-
let block_inner = if let Some(block) = local_rc.block() {
105-
// reuse current block information, including base_path and
106-
// base_value if any
107-
block.clone()
108-
} else {
109-
BlockContext::new()
110-
};
111-
112-
local_rc.clear_blocks();
113-
local_rc.push_block(block_inner);
114-
}
115-
116-
// evaluate context within current block, this includes block
117-
// context provided by partial expression parameter
118-
let merged_context = merge_json(
119-
local_rc.evaluate2(ctx, &Path::current())?.as_json(),
120-
&hash_ctx,
121-
);
122-
123-
// update the base value, there must be a block for this so it's
124-
// also safe to unwrap.
125-
if let Some(block) = local_rc.block_mut() {
126-
block.set_base_value(merged_context);
127-
}
128-
}
115+
// evaluate context within current block, this includes block
116+
// context provided by partial expression parameter
117+
let merged_context = merge_json(rc.evaluate2(ctx, &Path::current())?.as_json(), &hash_ctx);
129118

130-
// @partial-block
131-
if let Some(pb) = d.template() {
132-
local_rc.push_partial_block(pb);
119+
// update the base value, there must be a block for this so it's
120+
// also safe to unwrap.
121+
if let Some(block) = rc.block_mut() {
122+
block.set_base_value(merged_context);
133123
}
124+
}
134125

135-
// indent
136-
local_rc.set_indent_string(d.indent().cloned());
137-
138-
let result = t.render(r, ctx, &mut local_rc, out);
126+
// @partial-block
127+
if let Some(pb) = d.template() {
128+
rc.push_partial_block(pb);
129+
}
139130

140-
// cleanup
131+
// indent
132+
rc.set_indent_string(d.indent().cloned());
141133

142-
let trailing_newline = local_rc.get_trailine_newline();
134+
let result = partial.render(r, ctx, rc, out);
143135

144-
if block_created {
145-
local_rc.pop_block();
146-
}
136+
// cleanup
137+
let trailing_newline = rc.get_trailine_newline();
147138

148-
if d.template().is_some() {
149-
local_rc.pop_partial_block();
150-
}
139+
if d.template().is_some() {
140+
rc.pop_partial_block();
141+
}
151142

152-
drop(local_rc);
143+
if block_created {
144+
rc.pop_block();
145+
}
153146

154-
rc.set_trailing_newline(trailing_newline);
147+
rc.set_trailing_newline(trailing_newline);
148+
rc.set_current_template_name(current_template_before);
149+
rc.set_indent_string(indent_before);
155150

156-
result
157-
} else {
158-
Err(RenderErrorReason::PartialNotFound(tname.to_owned()).into())
159-
}
151+
result
160152
}
161153

162154
#[cfg(test)]

src/registry.rs

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use std::borrow::Cow;
2-
use std::collections::HashMap;
2+
use std::collections::{BTreeMap, HashMap};
33
use std::convert::AsRef;
44
use std::fmt::{self, Debug, Formatter};
55
use std::io::{Error as IoError, Write};
@@ -671,6 +671,52 @@ impl<'reg> Registry<'reg> {
671671
self.template_sources.clear();
672672
}
673673

674+
fn gather_dev_mode_templates(
675+
&'reg self,
676+
prebound: Option<(&str, Cow<'reg, Template>)>,
677+
) -> Result<BTreeMap<String, Cow<'reg, Template>>, RenderError> {
678+
let prebound_name = prebound.as_ref().map(|(name, _)| *name);
679+
let mut res = BTreeMap::new();
680+
for name in self.template_sources.keys() {
681+
if Some(&**name) == prebound_name {
682+
continue;
683+
}
684+
res.insert(name.clone(), self.get_or_load_template(name)?);
685+
}
686+
if let Some((name, prebound)) = prebound {
687+
res.insert(name.to_owned(), prebound);
688+
}
689+
Ok(res)
690+
}
691+
692+
fn render_resolved_template_to_output(
693+
&self,
694+
name: Option<&str>,
695+
template: Cow<'_, Template>,
696+
ctx: &Context,
697+
output: &mut impl Output,
698+
) -> Result<(), RenderError> {
699+
if !self.dev_mode {
700+
let mut render_context = RenderContext::new(template.name.as_ref());
701+
return template.render(self, ctx, &mut render_context, output);
702+
}
703+
704+
let dev_mode_templates;
705+
let template = if let Some(name) = name {
706+
dev_mode_templates = self.gather_dev_mode_templates(Some((name, template)))?;
707+
&dev_mode_templates[name]
708+
} else {
709+
dev_mode_templates = self.gather_dev_mode_templates(None)?;
710+
&template
711+
};
712+
713+
let mut render_context = RenderContext::new(template.name.as_ref());
714+
715+
render_context.set_dev_mode_templates(Some(&dev_mode_templates));
716+
717+
template.render(self, ctx, &mut render_context, output)
718+
}
719+
674720
#[inline]
675721
fn render_to_output<O>(
676722
&self,
@@ -681,10 +727,12 @@ impl<'reg> Registry<'reg> {
681727
where
682728
O: Output,
683729
{
684-
self.get_or_load_template(name).and_then(|t| {
685-
let mut render_context = RenderContext::new(t.name.as_ref());
686-
t.render(self, ctx, &mut render_context, output)
687-
})
730+
self.render_resolved_template_to_output(
731+
Some(name),
732+
self.get_or_load_template(name)?,
733+
ctx,
734+
output,
735+
)
688736
}
689737

690738
/// Render a registered template with some data into a string
@@ -762,10 +810,7 @@ impl<'reg> Registry<'reg> {
762810
.map_err(RenderError::from)?;
763811

764812
let mut out = StringOutput::new();
765-
{
766-
let mut render_context = RenderContext::new(None);
767-
tpl.render(self, ctx, &mut render_context, &mut out)?;
768-
}
813+
self.render_resolved_template_to_output(None, Cow::Owned(tpl), ctx, &mut out)?;
769814

770815
out.into_string().map_err(RenderError::from)
771816
}
@@ -789,9 +834,9 @@ impl<'reg> Registry<'reg> {
789834
},
790835
)
791836
.map_err(RenderError::from)?;
792-
let mut render_context = RenderContext::new(None);
793837
let mut out = WriteOutput::new(writer);
794-
tpl.render(self, ctx, &mut render_context, &mut out)
838+
839+
self.render_resolved_template_to_output(None, Cow::Owned(tpl), ctx, &mut out)
795840
}
796841

797842
/// Render a template string using current registry without registering it

0 commit comments

Comments
 (0)