Skip to content

架构设计 ​

@ggn/draw 是领域无关的绘制内核。它不认识 UML、系统地图或任务,只负责把文档、注册表、 主题和布局策略转换成可测试的几何与场景。

目标 ​

目标可验证结果
每个中间产物可获取parse / bind / measure / place / attach / route / build 都有独立入口
同一问题只诊断一次类型、props、marker 在 bind 收口;layout / build 不重复报
换主题不重算几何createPipeline 只重跑 build
增量稳定中间插入一个兄弟只产生一条 create
扩展是值registry、engine、router、measurer、host 都通过参数注入
主入口 headless@ggn/draw 不出现 DOM API;DOM 与 Canvas 度量走独立子路径

非目标 ​

  • 不内置 UML、系统地图、任务等语义;
  • 不做 Canvas / WebGL / PNG 输出;
  • 不做编辑器、手势和选择器;
  • 不内置 ELK;
  • 不把主题写进文档。

六段管线 ​

正在渲染图表…

主题与文档互不依赖;图中按理解顺序排列。

阶段输入 → 输出读 registry
parseunknown → DrawDocument否
bindDrawDocument + registry → BoundDocument是,且只在这里读
themeThemeInput → ResolvedTheme否
measureBoundDocument → Measured否
placeMeasured → Placement否
attachPlacement → Attached否
routeAttached → LayoutResult否
buildBoundDocument + LayoutResult + theme → Scene读 render
hostScene / patch → 介质否

四层数据 ​

层负责不允许
DrawDocument意图、引用、样式名、布局意图最终坐标(pins 仅 explicit)、SVG 属性
BoundDocument生效类型、归一化 props、容器标记、注册项快照几何、render、主题
LayoutResult盒子、端口落点、路由、标签落点画法、颜色、宿主 API
Scene绝对几何、已解析样式、稳定 key、ref领域语义、重新布局

模块边界 ​

text
contract ──→ bind ──→ layout/{measure,place,attach,route}
                 │                 │
registry ────────┘                 │
                                   ▼
theme ─────────────────────────→ build ──→ scene ──→ host-svg / host-dom
                                   ▲
pipeline ──→ bind + layout + build ┘
handle   ──→ pipeline + diff + host

规则:

  1. contract 不依赖注册表、主题或几何。
  2. registry 不依赖 theme、layout 或 build。
  3. bind 是 registry 与 layout / build 之间唯一的桥。
  4. layout 拿不到 render;build 是唯一同时使用几何与 render 的模块。
  5. scene 不依赖 document、layout 或 host。
  6. host 不依赖 document、build 或 pipeline。
  7. pipeline / handle 是组合根,不能被核心模块反向依赖。

不变量 ​

  • L1:只有 layout 把引用解析成几何;pins 是唯一显式坐标。
  • L2:render 只拿自己的 box、props、label、anchors 和 style。
  • L3:host 只读场景坐标;相机由显式 Scene.camera 或 bounds + padding + overflow 得到。
  • L4:bind 之后类型与 props 已定,下游不再判断“是否已注册”。
  • L5:DomHost.apply 先校验再提交,失败不改变介质。
  • D1:同输入、同注册表版本、同主题、同度量器 fingerprint、同引擎与路由 → 相同结果。

关键设计决策 ​

bind 独立成段 ​

未知类型、props schema、children 容器判断和 marker 是否存在,都集中在 bind。这样 layout 不需要 registry.metrics() 这种收窄视图,也不会和 build 各报一次同一问题。

layout 拆成四个原子 ​

text
measureDocument → placeDocument → attachEndpoints → routeEdges

layoutDocument 只是组合。标签尺寸在 measure 完成;端口槽位在 attach 完成;route 只决定 连线路由和边标签落点。

端口约束 ​

  • fixed:兼容 v1,side + offset 固定;
  • side:side 固定,同侧槽位自动分配;
  • free:先按对端方向选 side,再分配槽位。

场景可回指文档 ​

Scene 零件可带 SceneRef。宿主做命中、选择、拖拽时直接读 ref,不解析 key 字符串。 hitTest 使用场景几何从上到下返回 ref。

注册表不可变 ​

createRegistry() 默认带内置原语;with(entries) 返回新版本。同名冲突抛 RegistryConflictError,{ override: true } 才能覆盖。实例即缓存版本。

Pipeline 缓存 ​

Pipeline 以阶段产物为缓存边界:

  • 只换 tokens / overlay → build;
  • 只换 router → route;
  • 只换 measurer fingerprint → measure 及之后;
  • 全部相同 → 整帧命中。

当前迁移边界 ​

以下能力保留为下一阶段:

  • 平面引擎自动包装成层次引擎的 hierarchical();
  • Diagnostic 的 subject 从兼容字符串完全迁移到对象模型;
  • 异步 Standard Schema。

这些边界不影响当前公开契约;新增字段会在兼容窗口内逐步加入。

验收 ​

测试覆盖:

  • bind 未知类型和 props 诊断恰好一次;
  • 四个 layout 原子可独立调用;
  • 同侧端口槽位与边书写顺序无关;
  • 标签由 layout 产出;
  • Scene 中间插入只产生 create,重排产生 move;
  • hitTest 返回文档 id;
  • host apply 失败时介质不变;
  • Pipeline 在 tokens / overlay / router 变化时只重跑对应阶段。

MIT License