Skip to main content

性能、测试与跨平台发布

GPUI 的底层性能很强,但应用写法仍然会决定真实体验。最后一章把三个工程问题合在一起:如何避免卡顿,如何测试关键逻辑,如何把应用交付给用户。

性能原则

GPUI 的 render 会在需要绘制时运行。你应该让它尽量接近“纯展示”:

  • 不做网络和文件 IO。
  • 不解析大型 JSON。
  • 不在列表渲染中做复杂排序和过滤。
  • 不无意义 clone 大字符串。
  • 不滥用顶层 cx.notify()

错误示例:

fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
let pretty = serde_json::to_string_pretty(
&serde_json::from_str::<serde_json::Value>(&self.response_body).unwrap(),
).unwrap();

div().child(pretty)
}

正确方向是预计算或按需计算。

脏标记模式

HiPoster 用 dirty_response 避免每帧格式化 JSON:

pub struct ApiTab {
pub response: Option<HttpResponse>,
pub dirty_response: bool,
pub active_response_view: ResponseView,
}

收到响应:

self.response = Some(response);
self.dirty_response = true;
cx.notify();

切换 Pretty / Raw:

if self.active_response_view != view {
self.active_response_view = view;
self.dirty_response = true;
cx.notify();
}

渲染前检查:

if self.dirty_response {
self.update_response_display(window, cx);
}

这个模式可以推广到:

  • URL 与 Params 双向同步。
  • 过滤后的历史记录。
  • 语法高亮输入内容。
  • 大表格的排序结果。
  • Markdown 或 HTML 渲染结果。

大列表

少量历史记录可以直接 .children(...)。如果列表可能增长到几千、几万行,应使用虚拟列表。Zed 主仓的 GPUI examples 中有 uniform_listdata_tablelist_example,适合作为最新写法参考。

经验规则:

  • 50 条以内:普通 children 足够。
  • 几百条:先确认渲染成本,必要时分页或过滤。
  • 几千条以上:使用虚拟列表或只渲染可见区域。

单元测试

最稳定、收益最高的是测试纯逻辑:

  • HttpRequest 序列化。
  • HttpBody 枚举行为。
  • URL 编码、解码、参数解析。
  • JSON 格式化。
  • 主题颜色转换。
  • HTTP builder 的 Header、Auth、Body 构造。

示例:

#[test]
fn test_http_request_serialization() {
let request = HttpRequest {
method: HttpMethod::POST,
url: "https://example.com".to_string(),
body: HttpBody::Raw("{\"key\":\"value\"}".to_string()),
content_type: "application/json".to_string(),
..Default::default()
};

let serialized = serde_json::to_value(&request).unwrap();
assert_eq!(serialized["method"], "POST");
assert_eq!(serialized["url"], "https://example.com");
}

这类测试不依赖窗口,运行快,也最不脆弱。

GPUI 测试

Zed 主仓最新 examples 包含 testing.rs,展示了 #[gpui::test]TestAppContextVisualTestContext、Action、Focus 和异步任务测试。

实体测试:

#[gpui::test]
fn test_counter_state(cx: &mut TestAppContext) {
let counter = cx.new(|cx| Counter::new(cx));

counter.update(cx, |counter, _| {
counter.count = 42;
});

let count = counter.read_with(cx, |counter, _| counter.count);
assert_eq!(count, 42);
}

窗口测试:

#[gpui::test]
fn test_counter_in_window(cx: &mut TestAppContext) {
let window = cx.update(|cx| {
cx.open_window(Default::default(), |_, cx| cx.new(|cx| Counter::new(cx)))
.unwrap()
});

let mut cx = VisualTestContext::from_window(window.into(), cx);
let counter = window.root(&mut cx).unwrap();

let count = counter.read_with(&cx, |counter, _| counter.count);
assert_eq!(count, 0);
}

使用这些测试前,需要启用 GPUI 的 test-support feature,并以当前 Zed main 的 example 为准调整 API。

网络测试

不要让普通测试依赖公网。HiPoster 当前有一个 httpbin.org 冒烟测试,这对手动验证有帮助,但 CI 里容易受网络波动影响。

更稳的做法:

  • 用本地 mock HTTP server。
  • 把 HTTP builder 拆成可测试函数。
  • 网络错误测试使用无效 URL 或 mock client。
  • 公网测试加 #[ignore],手动运行。

构建脚本

HiPoster 已经为三个平台准备脚本:

scripts/
├── build_macos.sh
├── build_windows.ps1
└── build_linux.sh

macOS 脚本做了:

  • 安装/检查 x86_64-apple-darwinaarch64-apple-darwin target。
  • 分别构建 Intel 与 Apple Silicon。
  • lipo 合并 Universal Binary。
  • 创建 .app 目录结构。
  • 复制 Info.plist.icns
  • ad-hoc signing。
  • 打包 .dmg

Windows 脚本做了:

  • 检查 cl.exe
  • 设置 MSVC toolchain。
  • cargo build --release
  • 复制 .exe 到发布目录。

Linux 脚本做了:

  • 根据架构选择 Rust target。
  • 构建 release binary。
  • 创建 Debian package 目录。
  • 复制图标和 .desktop
  • 生成 control 文件。
  • 调用 dpkg-deb 打包。

平台注意事项

macOS:

  • 需要 Xcode 和命令行工具。
  • 正式分发需要 Developer ID 签名与 notarization。
  • Universal Binary 会显著增加包体积。

Windows:

  • 必须使用 MSVC,不要用 GNU toolchain。
  • build.rs 可注入图标并隐藏控制台。
  • 正式分发要考虑代码签名和安装器。

Linux:

  • Wayland / X11 feature 要匹配目标环境。
  • .deb 之外还可能需要 AppImage、Flatpak 或 tarball。
  • 系统图形库和字体依赖要在发行说明中写清楚。

升级检查清单

当你把 GPUI 升级到 Zed 最新 main 时,按这个顺序检查:

  1. Cargo.tomlgpuigpui_platformgpui-component 是否拉到兼容 revision。
  2. gpui_platform features 是否符合目标平台。
  3. Applicationopen_windowWindowOptions 是否与最新 example 一致。
  4. Context<T>AsyncAppcx.spawn 写法是否变化。
  5. MenuMenuItemKeyBinding、Action 注册是否变化。
  6. GPUI Component 的 RootInputStateButtonTabBarTheme API 是否变化。
  7. cargo testcargo build --release 和至少一个平台打包脚本是否通过。

GPUI 最大的魅力是把高性能 UI 和 Rust 类型系统放在一起;最大的挑战也在这里:它还年轻,升级时要像对待底层库一样认真。把边界拆清楚、测试补上、依赖锁住,应用就能稳稳向前。