From 599c2fb7d90b47112e0274b6f637076c8218c5ae Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 22 Sep 2026 07:54:18 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Optimize=20property=20extra?= =?UTF-8?q?ction=20in=20Canvas=20DSL=20rendering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced multiple $O(N)$ `.iter().find()` calls with a single-pass $O(N)$ loop over the properties array in all Canvas geometric shape rendering functions (`logo`, `rings`, `emblem`, `letter`, `core`, `circuits`). This significantly reduces loop overhead during SVG string construction for the canvas DSL. Co-authored-by: Tcode-Motion <188012755+Tcode-Motion@users.noreply.github.com> --- .jules/bolt.md | 3 + stdlib/src/canvas.rs | 245 +++++++++++++++++++++---------------------- test_perf.rs | 3 + 3 files changed, 125 insertions(+), 126 deletions(-) create mode 100644 test_perf.rs diff --git a/.jules/bolt.md b/.jules/bolt.md index 817bbbb5..ff826971 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -32,3 +32,6 @@ ## 2026-09-22 - String allocation optimization in std.web DSL rendering **Learning:** Generating deep HTML structures in `std.web` heavily penalized performance because `dsl_to_html` allocated and returned a new `String` for every child DSL node. This causes `O(N)` heap allocations and redundant copying in the render tree. By passing a mutable `&mut String` buffer recursively downwards, we avoid all intermediate string heap allocations and significantly improve serialization speed. **Action:** Always prefer using a recursive builder pattern passing a single mutable `&mut String` buffer to `write!` or `push_str` when rendering nested tree structures (like HTML, JSON, or ASTs) instead of returning newly allocated strings at each layer. +## 2024-05-24 - Canvas Property Extraction Optimization +**Learning:** Extracting properties from the `RuntimeValue` DSL property lists in `canvas.rs` was inefficient because the code called `.iter().find()` multiple times for each property in `logo`, `rings`, `emblem`, `letter`, `core`, and `circuits` definitions. +**Action:** Replace multiple `.find()` calls with a single-pass `for p in &dsl.properties` loop and `match` on the property names, mutating local fallback variables. diff --git a/stdlib/src/canvas.rs b/stdlib/src/canvas.rs index 3c4b3302..ffcc7a20 100644 --- a/stdlib/src/canvas.rs +++ b/stdlib/src/canvas.rs @@ -21,27 +21,24 @@ fn render_dsl_to_svg(svg: &mut String, val: &RuntimeValue, is_dragon: bool) { if is_dragon { match dsl.kind.as_str() { "logo" => { - let text = dsl - .properties - .iter() - .find(|p| p.name == "text") - .and_then(|p| p.value.as_ref()) - .map(|v| v.to_string()) - .unwrap_or_else(|| "Logo".to_string()); - let color = dsl - .properties - .iter() - .find(|p| p.name == "color") - .and_then(|p| p.value.as_ref()) - .map(|v| v.to_string()) - .unwrap_or_else(|| "#00d4ff".to_string()); - let size = dsl - .properties - .iter() - .find(|p| p.name == "size") - .and_then(|p| p.value.as_ref()) - .and_then(|v| v.try_into_int().ok()) - .unwrap_or(48); + let mut text_val = None; + let mut color_val = None; + let mut size_val = None; + + for p in &dsl.properties { + match p.name.as_str() { + "text" => text_val = p.value.as_ref().map(|v| v.to_string()), + "color" => color_val = p.value.as_ref().map(|v| v.to_string()), + "size" => { + size_val = p.value.as_ref().and_then(|v| v.try_into_int().ok()) + } + _ => {} + } + } + + let text = text_val.unwrap_or_else(|| "Logo".to_string()); + let color = color_val.unwrap_or_else(|| "#00d4ff".to_string()); + let size = size_val.unwrap_or(48); let _ = write!( svg, r#"{}"#, @@ -82,27 +79,24 @@ fn render_dsl_to_svg(svg: &mut String, val: &RuntimeValue, is_dragon: bool) { // RENDER OLD GEOMETRIC LOGO match dsl.kind.as_str() { "logo" => { - let text = dsl - .properties - .iter() - .find(|p| p.name == "text") - .and_then(|p| p.value.as_ref()) - .map(|v| v.to_string()) - .unwrap_or_else(|| "Logo".to_string()); - let color = dsl - .properties - .iter() - .find(|p| p.name == "color") - .and_then(|p| p.value.as_ref()) - .map(|v| v.to_string()) - .unwrap_or_else(|| "#00d4ff".to_string()); - let size = dsl - .properties - .iter() - .find(|p| p.name == "size") - .and_then(|p| p.value.as_ref()) - .and_then(|v| v.try_into_int().ok()) - .unwrap_or(48); + let mut text_val = None; + let mut color_val = None; + let mut size_val = None; + + for p in &dsl.properties { + match p.name.as_str() { + "text" => text_val = p.value.as_ref().map(|v| v.to_string()), + "color" => color_val = p.value.as_ref().map(|v| v.to_string()), + "size" => { + size_val = p.value.as_ref().and_then(|v| v.try_into_int().ok()) + } + _ => {} + } + } + + let text = text_val.unwrap_or_else(|| "Logo".to_string()); + let color = color_val.unwrap_or_else(|| "#00d4ff".to_string()); + let size = size_val.unwrap_or(48); let _ = write!( svg, r#"{}"#, @@ -110,34 +104,32 @@ fn render_dsl_to_svg(svg: &mut String, val: &RuntimeValue, is_dragon: bool) { ); } "rings" => { - let count = dsl - .properties - .iter() - .find(|p| p.name == "count") - .and_then(|p| p.value.as_ref()) - .and_then(|v| v.try_into_int().ok()) - .unwrap_or(3); - let color = dsl - .properties - .iter() - .find(|p| p.name == "color") - .and_then(|p| p.value.as_ref()) - .map(|v| v.to_string()) - .unwrap_or_else(|| "#00d4ff".to_string()); - let size = dsl - .properties - .iter() - .find(|p| p.name == "size") - .and_then(|p| p.value.as_ref()) - .and_then(|v| v.try_into_int().ok()) - .unwrap_or(40); - let thickness = dsl - .properties - .iter() - .find(|p| p.name == "thickness") - .and_then(|p| p.value.as_ref()) - .and_then(|v| v.try_into_int().ok()) - .unwrap_or(3); + let mut count_val = None; + let mut color_val = None; + let mut size_val = None; + let mut thickness_val = None; + + for p in &dsl.properties { + match p.name.as_str() { + "count" => { + count_val = p.value.as_ref().and_then(|v| v.try_into_int().ok()) + } + "color" => color_val = p.value.as_ref().map(|v| v.to_string()), + "size" => { + size_val = p.value.as_ref().and_then(|v| v.try_into_int().ok()) + } + "thickness" => { + thickness_val = + p.value.as_ref().and_then(|v| v.try_into_int().ok()) + } + _ => {} + } + } + + let count = count_val.unwrap_or(3); + let color = color_val.unwrap_or_else(|| "#00d4ff".to_string()); + let size = size_val.unwrap_or(40); + let thickness = thickness_val.unwrap_or(3); for i in 0..count { let r = 80 + i as i64 * (size / 2); let opacity = 0.4 - (i as f32 * 0.08); @@ -149,20 +141,21 @@ fn render_dsl_to_svg(svg: &mut String, val: &RuntimeValue, is_dragon: bool) { } } "emblem" => { - let color = dsl - .properties - .iter() - .find(|p| p.name == "color") - .and_then(|p| p.value.as_ref()) - .map(|v| v.to_string()) - .unwrap_or_else(|| "#0088cc".to_string()); - let size = dsl - .properties - .iter() - .find(|p| p.name == "size") - .and_then(|p| p.value.as_ref()) - .and_then(|v| v.try_into_int().ok()) - .unwrap_or(120); + let mut color_val = None; + let mut size_val = None; + + for p in &dsl.properties { + match p.name.as_str() { + "color" => color_val = p.value.as_ref().map(|v| v.to_string()), + "size" => { + size_val = p.value.as_ref().and_then(|v| v.try_into_int().ok()) + } + _ => {} + } + } + + let color = color_val.unwrap_or_else(|| "#0088cc".to_string()); + let size = size_val.unwrap_or(120); let x = 250 - size / 2; let y = 180 - size / 2; let _ = write!( @@ -177,27 +170,24 @@ fn render_dsl_to_svg(svg: &mut String, val: &RuntimeValue, is_dragon: bool) { ); } "letter" => { - let ch = dsl - .properties - .iter() - .find(|p| p.name == "char") - .and_then(|p| p.value.as_ref()) - .map(|v| v.to_string()) - .unwrap_or_else(|| "T".to_string()); - let color = dsl - .properties - .iter() - .find(|p| p.name == "color") - .and_then(|p| p.value.as_ref()) - .map(|v| v.to_string()) - .unwrap_or_else(|| "#0a0e27".to_string()); - let size = dsl - .properties - .iter() - .find(|p| p.name == "size") - .and_then(|p| p.value.as_ref()) - .and_then(|v| v.try_into_int().ok()) - .unwrap_or(32); + let mut ch_val = None; + let mut color_val = None; + let mut size_val = None; + + for p in &dsl.properties { + match p.name.as_str() { + "char" => ch_val = p.value.as_ref().map(|v| v.to_string()), + "color" => color_val = p.value.as_ref().map(|v| v.to_string()), + "size" => { + size_val = p.value.as_ref().and_then(|v| v.try_into_int().ok()) + } + _ => {} + } + } + + let ch = ch_val.unwrap_or_else(|| "T".to_string()); + let color = color_val.unwrap_or_else(|| "#0a0e27".to_string()); + let size = size_val.unwrap_or(32); let _ = write!( svg, r#"{}"#, @@ -205,20 +195,21 @@ fn render_dsl_to_svg(svg: &mut String, val: &RuntimeValue, is_dragon: bool) { ); } "core" => { - let color = dsl - .properties - .iter() - .find(|p| p.name == "color") - .and_then(|p| p.value.as_ref()) - .map(|v| v.to_string()) - .unwrap_or_else(|| "#66e0ff".to_string()); - let size = dsl - .properties - .iter() - .find(|p| p.name == "size") - .and_then(|p| p.value.as_ref()) - .and_then(|v| v.try_into_int().ok()) - .unwrap_or(40); + let mut color_val = None; + let mut size_val = None; + + for p in &dsl.properties { + match p.name.as_str() { + "color" => color_val = p.value.as_ref().map(|v| v.to_string()), + "size" => { + size_val = p.value.as_ref().and_then(|v| v.try_into_int().ok()) + } + _ => {} + } + } + + let color = color_val.unwrap_or_else(|| "#66e0ff".to_string()); + let size = size_val.unwrap_or(40); let _ = write!( svg, r#""#, @@ -227,13 +218,15 @@ fn render_dsl_to_svg(svg: &mut String, val: &RuntimeValue, is_dragon: bool) { ); } "circuits" => { - let color = dsl - .properties - .iter() - .find(|p| p.name == "color") - .and_then(|p| p.value.as_ref()) - .map(|v| v.to_string()) - .unwrap_or_else(|| "#00d4ff".to_string()); + let mut color_val = None; + + for p in &dsl.properties { + if p.name == "color" { + color_val = p.value.as_ref().map(|v| v.to_string()); + } + } + + let color = color_val.unwrap_or_else(|| "#00d4ff".to_string()); let _ = write!( svg, r#" diff --git a/test_perf.rs b/test_perf.rs new file mode 100644 index 00000000..8093e40b --- /dev/null +++ b/test_perf.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Testing performance..."); +}