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