#!/usr/bin/env python3 # -*- coding: utf-8 -*- """生成 docs/images/就绪屏障.svg —— 启动就绪屏障(Event Flags)依赖图(纯标准库)。 为什么不用 Mermaid:原图 9 条边全挤在单一中心节点上,画成交叉的"意大利面"。 本脚本手工排布为三栏: 左 = 生产者(set) 中 = 事件组 sys_evtHandle 右 = 消费者(wait) 任务卡与事件位各自对齐到同一水平行,箭头基本水平/正交;bit1 → ftpTask 的 "与"依赖用汇合点(All)表达,避免跨行连线。 用法(仓库根目录,任意 python3): python tools/arch_diagram/gen_barrier_svg.py 输出: docs/images/就绪屏障.svg 改依赖时:编辑本文件顶部的 BITS / PRODUCERS / CONSUMERS 数据后重跑。 """ import html import math import os ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) OUT = os.path.join(ROOT, "docs", "images", "就绪屏障.svg") # ----------------------------- 画布与尺寸 ----------------------------- W = 1400 PAD = 40 TITLE_FONT = 20 SUB_FONT = 13 CARD_TITLE_FONT = 15 CARD_NOTE_FONT = 12 BIT_TITLE_FONT = 14 BIT_NOTE_FONT = 12 LABEL_FONT = 11.5 FOOT_FONT = 12 H_CARD = 84 # 任务卡 / 事件位行高 GAP = 26 # 行间距 MAIN_Y = 200 # 首行 y CARD_W_L = 290 # 生产者卡宽 CARD_W_R = 450 # 消费者卡宽 PANEL_X, PANEL_W = 400, 420 PROD_X = 40 CONS_X = 910 TAP_X = 832 # bit1 → All 汇合点的下引线 x BUS_X = 862 # bit0 分发总线 x ARROW_W = 2.2 FONT = "Microsoft YaHei, Segoe UI, PingFang SC, sans-serif" C_PROD, BG_PROD = "#2563eb", "#eff6ff" C_CONS, BG_CONS = "#7c3aed", "#f5f3ff" # ----------------------------- 数据(改这里) ----------------------------- # 事件位(中间栏),自上而下 = 时序先后 BITS = [ dict(bit="bit1", flag="STORAGE_READY", color="#16a34a", bg="#f0fdf4", note="defaultTask 置位(挂载+配置)· netTask / ftpTask 等待"), dict(bit="bit0", flag="NET_READY", color="#0d9488", bg="#f0fdfa", note="netTask 置位 · 4 个任务等待"), dict(bit="bit2", flag="FTP_READY", color="#d97706", bg="#fffbeb", note="ftpTask 置位 · 预留消费者"), dict(bit="bit3", flag="TIMESYNC_READY", color="#94a3b8", bg="#f8fafc", note="预留(尚未接入调度)"), ] # 生产者(左栏):row = 其置位的 bit 行 PRODUCERS = [ dict(name="defaultTask", note="卷挂载 + net_cfg_load(f_mount)", row=0, label="set bit1"), dict(name="netTask", note="net_stack_bring_up()", row=1, label="set bit0"), dict(name="ftpTask", note='lftpd_start("/", 21)', row=2, label="set bit2"), ] # 消费者(右栏):row = 卡片排布行(前两行与 bit1 / bit0 对齐) CONSUMERS = [ dict(name="netTask", note="wait bit1 · ≤10s", row=0), dict(name="ftpTask", note="wait bit0 + bit1 · All", row=1), dict(name="uiTask", note="wait bit0 · Forever", row=2), dict(name="timeSyncTask", note="wait bit0 · Forever", row=3), dict(name="testTask", note="wait bit0 · NET 模式", row=4), ] FOOT = ("生产者与消费者互不知晓,只经事件组解耦;新增子系统 = 新 bit + 各自 wait/set," "不改已有任务。详见 docs/架构设计/项目启动顺序管理.md") # ----------------------------- 工具函数 ----------------------------- def esc(s): return html.escape(s, quote=True) def text_w(s, fs): """粗略估算文本像素宽:CJK/全角 ≈ 1.0em,其余 ≈ 0.58em(宁可偏大)。""" w = 0.0 for ch in s: w += fs * (1.0 if ord(ch) >= 0x2E80 else 0.58) return w # ----------------------------- 生成 ----------------------------- def build(): out = [] add = out.append def row_y(r): return MAIN_Y + r * (H_CARD + GAP) def row_cy(r): return row_y(r) + H_CARD / 2.0 def poly(points, color, dash=False, head=True, width=ARROW_W): pts = " ".join("%.1f,%.1f" % p for p in points) d = ' stroke-dasharray="7 5"' if dash else '' add('' % (pts, color, width, d)) if head: (x1, y1), (x2, y2) = points[-2], points[-1] ang = math.atan2(y2 - y1, x2 - x1) hl, hw = 11.0, 5.5 bx, by = x2 - hl * math.cos(ang), y2 - hl * math.sin(ang) px, py = -math.sin(ang) * hw, math.cos(ang) * hw add('' % (x2, y2, bx + px, by + py, bx - px, by - py, color)) def card(x, y, w, title, note, color, bg): add('' % (x, y, w, H_CARD, bg, color)) add('%s' % (x + 16, y + 34, CARD_TITLE_FONT, esc(title))) add('%s' % (x + 16, y + 58, CARD_NOTE_FONT, esc(note))) # 行/画布尺寸 n_rows = max(len(CONSUMERS), len(BITS)) main_bottom = row_y(n_rows - 1) + H_CARD total_h = main_bottom + 78 add('' % (W, total_h)) # ---- 标题 ---- add('%s' % (PAD, TITLE_FONT, esc("就绪屏障(Readiness Barrier)· 启动依赖链"))) add('%s' % (PAD, SUB_FONT, esc("CMSIS-RTOS2 Event Flags · 生产者置位声明「我好了」,消费者阻塞等待"))) # ---- 图例 ---- lx, ly = PAD, 108 for kind, color, label in [ ("rect", C_PROD, "生产者(set)"), ("rect", C_CONS, "消费者(wait)"), ("rect", "#16a34a", "事件位(Event Flag)"), ("dot", "#0d9488", "All 汇合(等全部位)"), ("dash", "#94a3b8", "预留 / 未接入"), ]: if kind == "rect": add('' % (lx, ly - 12, color)) elif kind == "dot": add('' % (lx + 8, ly - 4, color)) else: add('' % (lx, ly - 4, lx + 16, ly - 4, color)) lx += 24 add('%s' % (lx, ly, esc(label))) lx += text_w(label, 12) + 34 # ---- 事件组面板 ---- py0 = MAIN_Y - 34 py1 = row_y(len(BITS) - 1) + H_CARD + 14 add('' % (PANEL_X, py0, PANEL_W, py1 - py0)) head = "sys_evtHandle(Event Flags 事件组)" add('%s' % (PANEL_X + 16, py0 + 26, esc(head))) for i, b in enumerate(BITS): by = row_y(i) add('' % (PANEL_X + 14, by, PANEL_W - 28, H_CARD, b["bg"], b["color"])) add('' % (PANEL_X + 28, by + (H_CARD - 30) / 2, b["color"])) add('%s' % (PANEL_X + 43, by + H_CARD / 2 + 5, esc(b["bit"][3]))) tx = PANEL_X + 70 add('%s' % (tx, by + 36, BIT_TITLE_FONT, b["color"], esc(b["bit"] + " · " + b["flag"]))) add('%s' % (tx, by + 58, BIT_NOTE_FONT, esc(b["note"]))) # ---- 生产者卡 + set 箭头 ---- for p in PRODUCERS: y, cy = row_y(p["row"]), row_cy(p["row"]) card(PROD_X, y, CARD_W_L, p["name"], p["note"], C_PROD, BG_PROD) poly([(PROD_X + CARD_W_L, cy), (PANEL_X, cy)], BITS[p["row"]]["color"]) add('%s' % ((PROD_X + CARD_W_L + PANEL_X) / 2.0, cy - 8, LABEL_FONT, BITS[p["row"]]["color"], esc(p["label"]))) # ---- 消费者卡 ---- for c in CONSUMERS: card(CONS_X, row_y(c["row"]), CARD_W_R, c["name"], c["note"], C_CONS, BG_CONS) # ---- wait 连线:bit1 → netTask(水平) ---- c0, c1 = row_cy(0), row_cy(1) col1, col0 = BITS[0]["color"], BITS[1]["color"] poly([(PANEL_X + PANEL_W, c0), (CONS_X, c0)], col1) add('%s' % ((PANEL_X + PANEL_W + CONS_X) / 2.0, c0 - 8, LABEL_FONT, col1, esc("wait"))) # ---- wait 连线:bit1 → All 汇合点(下引,与 bit0 相与) ---- poly([(TAP_X, c0), (TAP_X, c1)], col1, head=False) add('' % (TAP_X, c1, col0)) add('All' % (TAP_X + 9, c1 - 8, LABEL_FONT, col0)) # ---- wait 连线:bit0 → 总线 → 4 个消费者 ---- c_last = row_cy(len(CONSUMERS) - 1) poly([(PANEL_X + PANEL_W, c1), (BUS_X, c1)], col0) poly([(BUS_X, c1), (BUS_X, c_last)], col0, head=False) for c in CONSUMERS[1:]: poly([(BUS_X, row_cy(c["row"])), (CONS_X, row_cy(c["row"]))], col0) mid_y = (c1 + c_last) / 2.0 add('%s' % (BUS_X - 12, mid_y, BUS_X - 12, mid_y, LABEL_FONT, col0, esc("wait bit0"))) add('%s' % (PAD, total_h - 18, FOOT_FONT, esc(FOOT))) svg = ('\n%s\n\n' % (W, total_h, W, total_h, FONT, "\n".join(out))) return svg, total_h def main(): svg, h = build() with open(OUT, "w", encoding="utf-8", newline="\n") as f: f.write(svg) import xml.etree.ElementTree as ET ET.fromstring(svg) print("OK %d bytes, canvas %dx%d -> %s" % (len(svg.encode("utf-8")), W, h, OUT)) if __name__ == "__main__": main()