设计原则
这份文档讲的是几条反复出现的取舍。它们不只体现在架构图上,也体现在每一处「这个功能放哪边」的犹豫里。
这份文档讲的是几条反复出现的取舍。它们不只体现在架构图上,也体现在每一处「这个功能放哪边」的犹豫里。
边界切在像素上
外壳和内核之间只有一件事在流动:一块 RGBA 像素。外壳不知道 wgpu 是什么,拿不到 Device,也拿不到 DOM。它说「画这个区域,滚动到这里」,内核回一张图。
这条很贵——每次渲染都要把纹理从 GPU 读回 CPU 内存,白送一趟拷贝和一份带宽。它的回报是边界极窄。GPU 设备、字形图集、着色器、渲染目标,全部活在 Rust 里,生命周期只由内核管。外壳是个纯粹的零部件消费者,换成 Qt 之外的任何工具包都不需要动内核一行。
反过来说,如果哪天渲染改成了共享纹理或者交换链,边界就会立刻变宽:外壳要拿到 wgpu 的设备(也就是要认识 Rust 的类型系统),要在正确的时机重建表面,要在窗口销毁时按正确顺序释放。这些都是「必须知道才能用对」的知识,泄漏到外壳里就是永久负担。
网页平台的行为归内核
「地址栏里输的是路径还是网址」「相对链接怎么补全」「加载中该显示什么页面」——这些看起来像界面问题,其实是网页平台的行为。
判据很简单:这个决定,换一套外壳之后还需要重新做一遍吗? 需要,那它就该在内核里。
所以地址解析、相对链接补全、五个内置页面(起始页、空白页、说明页、加载页、错误页)全在 ysu::address 与 ysu::pages。外壳里一行 HTML 都没有,地址判断也一行都没有。
放在外壳里的代价不是多写一遍,而是两套写法迟早会飘。「什么算文件路径」这种判断尤其容易各写各的,然后同一个输入在两套外壳里得到不同结果。
每一步的产物都能单独拿出来看
管线是单向的,中间的每种产物都是公开类型:
html::Tokenizer → Token
TreeBuilder → Document
css::Parser → StyleSheet
style::Cascade → StyleMap
layout::Tree → 带几何的盒子树
paint → DisplayList
render::Renderer → 像素写测试、查问题时可以从任意一环切进去,不用整条链跑一遍。想验选择器匹配,直接喂一段 CSS 和一棵 DOM;想验渲染,直接构造一个显示列表。整条链只有在做端到端验证时才需要跑全。
这条也是「中间产物必须是有意义的类型」的理由。如果某一步的产物是「一大坨内部状态」,那它就切不开了。
不留空壳
没做完的功能不写进代码。没有 todo!(),没有返回零的函数,没有「先占个位置」的字段。
这条在执行时有个不那么舒服的推论:解析了但没人读的属性,等于没实现。float 现在连字段都没有,position 有字段但布局阶段从不读它,两种情况在使用者眼里没有区别——都是不生效。文档里也照这个口径写,「字段存在」不构成「支持」。
领域规则归内核,呈现归外壳
滚动条画不画、链接点下去光标变不变手型、工具栏上有几个按钮,归外壳。页面里某个坐标上压着什么、这段文字该在哪断行、这个盒子的外边距该怎么合并,归内核。
ysu_engine_link_at 是这条的分界线:内核回答「这个点上是哪个链接」,不回答「点下去要做什么」。同一个坐标,浏览器要导航,阅读器可能要高亮,网页截图工具可能只是记录——决定权在外壳。
按需,不按帧
渲染是事件驱动的:滚动、加载完、改尺寸、改缩放才画一次。空闲时一帧都不画。
这不只是为了省电。按需渲染让「一次渲染」成为一个明确的、可测试的动作——给定视口和滚动位置,画出确定的像素,和上一帧无关。要是改成每帧都画,测试就得处理时间。
代价是每一处改动状态的地方都得记得请求重绘。漏一处,界面就会停在那里不动。
尺寸三个口径分开算
布局坐标、逻辑像素、物理像素是三个量,混了就出错,而且错得不明显——文字会小四分之一,滚动之后上半屏会被裁掉,都是能跑但不对的那类问题。
约定是:进内核的是逻辑尺寸加设备像素比,内核内部一律按逻辑像素排版,只在成像那一步乘上像素比。滚动量用布局坐标传,缩放由内核换算。
把这条写成断言很难,写进文档和注释容易。所以每次触及这条边界,注释里都会写清楚「这里是逻辑还是物理」。
不引现成的轮子,但也不重造轮子
不自己写 TLS,不自己写文本整形,不自己写 URL 解析。rustls、cosmic-text、url、encoding_rs 各自做各自的事,做得比我们自己写的好。
HTTP 客户端、JSON、JavaScript 引擎、渲染器、HTML 与 CSS 的每一层,是自己写的。这些是项目本身要回答的问题,交给别人就等于没回答。
分界大致是:协议实现可以借,领域实现自己做。TLS 是密码学协议,谁写都一样;HTML 树构建是浏览器之所以为浏览器的部分,借不来。