///|
/// 性能基准引擎:节点数 / 布局求解复杂度 / 渲染开销 / 内存估算。
///
/// Agent 生成设计后需要知道"这个设计跑得动吗?"
/// 引擎通过**复杂度分析**(而非平台相关的 wall-clock 计时)给出:
/// - 节点统计:总数 / 最大深度 / 平均子节点
/// - 布局复杂度:栈布局数 / Fill 节点数 / Hug 节点数(高开销)
/// - 渲染开销:SVG 输出长度 / 文字节点占比
/// - 内存估算:基于节点数和文本量的近似值
/// - 性能反模式检测:节点过多 / 嵌套过深 / Fill 过多
/// - 优化建议:合并节点 / 减少嵌套 / 用 Fixed 替代 Fill
// ---------------------------------------------------------------------------
// 基准数据
// ---------------------------------------------------------------------------
pub(all) struct ArtboardBenchmark {
artboard : String
node_count : Int
max_depth : Int
avg_children : Double
stack_layout_count : Int
fill_node_count : Int
hug_node_count : Int
text_node_count : Int
total_text_length : Int
svg_output_length : Int
layout_complexity : Int // 相对复杂度(操作计数)
render_complexity : Int // 渲染复杂度(字节量)
memory_estimate_bytes : Int // 内存估算
antipatterns : Array[String]
score : Int // 0-100(越高越好)
}
///|
pub(all) struct ProjectBenchmark {
artboards : Array[ArtboardBenchmark]
total_nodes : Int
total_memory : Int
avg_score : Double
grade : String // "A" | "B" | "C" | "D"
}
// ---------------------------------------------------------------------------
// 单画板基准
// ---------------------------------------------------------------------------
///|
pub fn Project::benchmark_artboard(
self : Project,
artboard~ : String,
) -> Result[ArtboardBenchmark, String] {
match self.artboards.get(artboard) {
None => Err("unknown_artboard")
Some(ab) => {
let doc = ab.doc
let layout = solve(doc)
let svg = render_svg(doc)
// 统计节点
let node_count = doc.nodes.size() - 1 // 减 root
let mut max_depth = 0
let mut total_children = 0
let mut parent_count = 0
let mut stack_count = 0
let mut fill_count = 0
let mut hug_count = 0
let mut text_count = 0
let mut text_length = 0
for id in doc.nodes.keys() {
match doc.nodes.get(id) {
Some(n) => {
// 深度计算
let depth = compute_depth(doc, id)
if depth > max_depth {
max_depth = depth
}
// 子节点
if n.children.length() > 0 {
parent_count = parent_count + 1
total_children = total_children + n.children.length()
}
// 布局统计
match n.layout {
Some(_) => stack_count = stack_count + 1
None => ()
}
// 尺寸规格
match n.width {
Fill => fill_count = fill_count + 1
Hug => hug_count = hug_count + 1
_ => ()
}
// 文字
if n.text != "" {
text_count = text_count + 1
text_length = text_length + n.text.length()
}
}
None => ()
}
}
let avg_children = if parent_count > 0 {
total_children.to_double() / parent_count.to_double()
} else {
0.0
}
// 复杂度估算
// 布局:每个节点 1 次求解 + Fill 额外 2 次 + Hug 额外 3 次
let layout_complexity = node_count +
fill_count * 2 +
hug_count * 3 +
stack_count * node_count / 4
// 渲染复杂度:SVG 字节数
let render_complexity = svg.length()
// 内存估算:每节点约 200 字节 + 文本
let memory = node_count * 200 + text_length * 2
// 反模式检测
let antipatterns : Array[String] = []
if node_count > 100 {
antipatterns.push(
"too_many_nodes: \{node_count} > 100,考虑拆分画板",
)
}
if max_depth > 8 {
antipatterns.push("too_deep: 深度 \{max_depth} > 8,扁平化嵌套")
}
if fill_count > 30 {
antipatterns.push(
"excessive_fill: \{fill_count} 个 Fill 节点,考虑用 Fixed 减少求解开销",
)
}
if hug_count > 20 {
antipatterns.push(
"excessive_hug: \{hug_count} 个 Hug 节点(最贵),用 Fixed 替代",
)
}
if avg_children > 15 {
antipatterns.push(
"wide_node: 平均 \{avg_children.to_int()} 个子节点,考虑分组",
)
}
// 评分
let node_score = if node_count <= 30 {
30
} else if node_count <= 60 {
25
} else if node_count <= 100 {
15
} else {
5
}
let depth_score = if max_depth <= 4 {
25
} else if max_depth <= 6 {
20
} else if max_depth <= 8 {
12
} else {
5
}
let fill_score = if fill_count <= 10 {
25
} else if fill_count <= 20 {
18
} else if fill_count <= 30 {
10
} else {
3
}
let hug_score = if hug_count <= 5 {
20
} else if hug_count <= 10 {
15
} else if hug_count <= 20 {
8
} else {
3
}
let score = node_score + depth_score + fill_score + hug_score
Ok({
artboard,
node_count,
max_depth,
avg_children,
stack_layout_count: stack_count,
fill_node_count: fill_count,
hug_node_count: hug_count,
text_node_count: text_count,
total_text_length: text_length,
svg_output_length: render_complexity,
layout_complexity,
render_complexity,
memory_estimate_bytes: memory,
antipatterns,
score,
})
}
}
}
// ---------------------------------------------------------------------------
// 项目级基准
// ---------------------------------------------------------------------------
///|
pub fn Project::benchmark(self : Project) -> ProjectBenchmark {
let benchmarks : Array[ArtboardBenchmark] = []
let mut total_nodes = 0
let mut total_memory = 0
let mut total_score = 0
for id in self.artboards.keys() {
match self.benchmark_artboard(artboard=id) {
Ok(b) => {
total_nodes = total_nodes + b.node_count
total_memory = total_memory + b.memory_estimate_bytes
total_score = total_score + b.score
benchmarks.push(b)
}
Err(_) => ()
}
}
let avg = if benchmarks.length() > 0 {
total_score.to_double() / benchmarks.length().to_double()
} else {
0.0
}
let grade = if avg >= 85 {
"A"
} else if avg >= 65 {
"B"
} else if avg >= 45 {
"C"
} else {
"D"
}
{ artboards: benchmarks, total_nodes, total_memory, avg_score: avg, grade, }
}
// ---------------------------------------------------------------------------
// 报告生成
// ---------------------------------------------------------------------------
///|
pub fn ArtboardBenchmark::to_json(self : ArtboardBenchmark) -> String {
let sb = StringBuilder::new()
sb.write_string("{\"artboard\":\"\{self.artboard}\"")
sb.write_string(",\"nodes\":\{self.node_count}")
sb.write_string(",\"depth\":\{self.max_depth}")
let ac = self.avg_children.to_string()
sb.write_string(",\"avg_children\":\{ac}")
sb.write_string(",\"fill\":\{self.fill_node_count}")
sb.write_string(",\"hug\":\{self.hug_node_count}")
sb.write_string(",\"layout_ops\":\{self.layout_complexity}")
sb.write_string(",\"svg_bytes\":\{self.render_complexity}")
let mem = self.memory_estimate_bytes.to_string()
sb.write_string(",\"memory\":\{mem}")
sb.write_string(",\"score\":\{self.score}")
sb.write_string(",\"antipatterns\":\{self.antipatterns.length()}")
sb.write_string("}")
sb.to_string()
}
///|
pub fn ProjectBenchmark::to_json(self : ProjectBenchmark) -> String {
let sb = StringBuilder::new()
sb.write_string("{\"total_nodes\":\{self.total_nodes}")
sb.write_string(",\"total_memory\":\{self.total_memory}")
let avg = self.avg_score.to_string()
sb.write_string(",\"avg_score\":\{avg}")
sb.write_string(",\"grade\":\"\{self.grade}\"")
sb.write_string(",\"artboards\":[")
for i in 0.. 0 {
sb.write_string(",")
}
sb.write_string(self.artboards[i].to_json())
}
sb.write_string("]}")
sb.to_string()
}
///|
/// 人类可读报告
pub fn ProjectBenchmark::report(self : ProjectBenchmark) -> String {
let sb = StringBuilder::new()
sb.write_string("Performance Benchmark Report\n")
sb.write_string("============================\n\n")
let tn = self.total_nodes.to_string()
let tm = self.total_memory.to_string()
let avg = self.avg_score.to_string()
sb.write_string("Overall: \{self.grade} (\{avg}/100)\n")
sb.write_string("Total nodes: \{tn}\n")
sb.write_string("Est. memory: \{tm} bytes\n\n")
for b in self.artboards.iter() {
sb.write_string(
" [\{b.artboard}] \{b.node_count} nodes, depth \{b.max_depth}",
)
sb.write_string(", \{b.fill_node_count} Fill, \{b.hug_node_count} Hug")
sb.write_string(", score \{b.score}/100\n")
if b.antipatterns.length() > 0 {
for a in b.antipatterns.iter() {
sb.write_string(" ⚠ \{a}\n")
}
}
}
sb.to_string()
}
///|
/// 优化建议
pub fn Project::suggest_optimizations(
self : Project,
artboard~ : String,
) -> Result[String, String] {
match self.benchmark_artboard(artboard~) {
Err(e) => Err(e)
Ok(b) => {
let suggestions : Array[String] = []
if b.node_count > 60 {
suggestions.push(
"{\"type\":\"split\",\"description\":\"节点较多(\{b.node_count}),考虑拆分为多个画板或使用组件复用\"}",
)
}
if b.max_depth > 6 {
suggestions.push(
"{\"type\":\"flatten\",\"description\":\"嵌套过深(\{b.max_depth} 层),减少不必要的容器层级\"}",
)
}
if b.fill_node_count > 15 {
suggestions.push(
"{\"type\":\"reduce_fill\",\"description\":\"Fill 节点 \{b.fill_node_count} 个,固定尺寸可减少布局求解\"}",
)
}
if b.hug_node_count > 8 {
suggestions.push(
"{\"type\":\"reduce_hug\",\"description\":\"Hug 节点 \{b.hug_node_count} 个(最高开销),改用 Fixed 尺寸\"}",
)
}
if suggestions.length() == 0 {
// 空集合键恒输出 [](AGENTS.md 红线,与 infer_missing/prototest 对齐)
Ok("{\"optimizations\":0,\"detail\":[],\"message\":\"性能良好,无需优化\"}")
} else {
let j = jstr23(suggestions)
Ok("{\"optimizations\":\{suggestions.length()},\"detail\":\{j}}")
}
}
}
}
// ---------------------------------------------------------------------------
// 辅助
// ---------------------------------------------------------------------------
///|
fn compute_depth(doc : Document, id : String) -> Int {
let mut depth = 0
let mut cur : String? = Some(id)
let mut running = true
let mut counter = 0
while running && counter < 100 {
counter = counter + 1
match cur {
Some(cid) =>
match doc.find_parent(cid) {
Some(pid) => {
depth = depth + 1
cur = Some(pid)
}
None => running = false
}
None => running = false
}
}
depth
}
///|
fn jstr23(parts : Array[String]) -> String {
let sb = StringBuilder::new()
for i in 0.. 0 {
sb.write_string(",")
}
sb.write_string(parts[i])
}
sb.to_string()
}