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..."); +}