Skip to content

fix(desktop): 状态栏帧数改为界面线程可用帧数,去掉单位并收紧间距 - #1929

Merged
xiaoyumuxi merged 1 commit into
previewfrom
fix/desktop-status-bar-fps-spacing
Nov 14, 2026
Merged

xiaoyumuxi merged 1 commit into
previewfrom
fix/desktop-status-bar-fps-spacing

Conversation

@xiaoyumuxi

Copy link
Copy Markdown
Collaborator

关联 Issue / Linked issue

  • 无(用户直接反馈:状态栏太松散、帧数显示 “2 FPS” 不对、不需要单位)

PR 类型 / Type

  • feat:新功能或用户可见的改进
  • fix:缺陷修复
  • docs:文档、Agent Note、Skill 等规则文件
  • release:稳定版发布准备

改动说明 / What changes

背景 / Why

状态栏的帧数在空闲时显示 “2 FPS”。原来的计数器统计每秒重绘了几次,而 Viva 只在需要时才重绘:窗口空闲时只有光标闪烁会触发重绘,大约每秒 2 次,于是显示成 2,看起来像界面卡住了。这个数字本来是用来区分“界面线程卡住”和“后台任务还没跑完”的,统计重绘次数分不出这两种情况。另外,状态栏各项之间间距偏大,“FPS” 单位也占地方。

改动 / Changes

  • 新的帧数定义(desktop/internal/workbench/frame_meter.go,新增):显示最近一秒内界面线程来得及显示的帧数,不再统计重绘了多少次。
    • 给窗口线程上的每个回调计时(window_sessions.go 的 config() 包装了 OnWake/OnPaint/指针/键盘/滚动/文件拖放/关闭等回调,新增泛型 timedEvent)。相邻不到 1ms 的回调合成一段忙碌时间;长度为 L 的忙碌段占掉 (L*刷新率-1)/1s 帧。
    • 读数 = max(刷新率 − 最近 1 秒内结束的忙碌段(含进行中的一段)占掉的帧数, 0)。空闲时等于屏幕刷新率;界面线程卡住时下降,卡顿结束一秒后恢复;后台 goroutine 跑任务不占用窗口线程,所以不会拉低读数。
    • 刷新率:取最近 16 次连续动画重绘间隔的中位数,吸附到偏差 5% 以内最接近的标准刷新率(60/72/75/90/120/144/165/240 等);样本不足 5 个时按 60Hz 计算。只统计一次重绘的第一笔绘制,指针或点击额外触发的事件绘制不计入间隔。
    • 读数会变化时(比如卡顿那一段滑出 1 秒窗口时)由计量器自己请求一帧;计量器自己请求的绘制不计入,避免空闲时一直重绘。读数不变时不请求帧。
  • 去掉单位:状态栏只显示数字(chrome.go 的 itemFPS 用 strconv.Itoa)。悬停提示里解释这个数字的含义(新增词条 desktop.status.fpsTooltip,原来的 desktop.status.fpsValue / FPSValue 已删除)。
  • 收紧间距:新增常量 chromeStatusPad = 6、chromeStatusGap = 0。状态栏各项改为水平内边距 6、项与项之间间距 0(chrome_view.go 的状态栏区域、chromeStatusItem、notifications.go 的铃铛按钮),两项文字之间从约 20pt 缩小到 12pt,悬停高亮仍然覆盖整项。drag.go 里拖动排序时使用同一个间距,落点计算和实际布局保持一致。
  • 删除旧计数器:删除 app.go 的 statusState 类型和 status 字段;root.go 的 beginPaint() 不再传 canvas,也不再调用 a.status.frame(...);删除 runtime_messages.go 的 statusFPS。
  • i18n:shared/i18n/messages.json 和中英 locale 已更新,生成结果 shared.generated.go 由 generate-i18n.mjs 产生;messages.go、zh.go、en.go 改用 FPSTooltip;同步更新 i18n_test.go。
  • 测试:新增 frame_meter_test.go,覆盖空闲读数、后台任务不影响读数、500ms 卡顿读数为 31 并在一秒后恢复、同一次重绘的多笔绘制合并计算、计量器自己的绘制不会连续请求、事件后的绘制照常计入、120/144/75/72/59.94Hz 的刷新率测量、跳过显示器没有运行的间隔、事件绘制不影响刷新率、没有绘制时只保留最近一秒等场景。app_test.go 新增 TestStatusBarItemsSitClose(相邻状态项高亮之间间距等于 chromeStatusGap)和 TestStatusBarFrameReadoutIsTheBareRate(整窗 harness 回归测试)。

不在本 PR 范围内 / Out of scope

  • Viva 内部的 publish/present 工作不在应用回调里,没有计时;它卡住时读数不会下降。要覆盖这部分,需要 Viva 提供帧呈现回调。
  • IME TextInput 的处理函数没有包装计时。
  • 没有动画样本之前,刷新率按 60Hz 计算,高刷屏启动后要等出现连续动画才会显示真实刷新率。
  • 多个长忙碌段之间只隔极短空隙时,占用帧数是近似计算。

类型专属:fix

复现与根因

  • 复现步骤、实际结果、期望结果:打开工作区,窗口空闲不操作,观察状态栏帧数。实际显示 “2 FPS”;期望空闲时显示屏幕刷新率(如 60),只有界面线程卡住时才下降,并且不带单位。
  • 根因(引入该行为的提交或 PR,用 git log -S 或 git blame 查):0faecba49(Epic(0.7.0): 基于 Viva 框架的跨平台方案的迁移(#1794) #1799,Viva 迁移)引入的 statusState.frame 统计每秒 paint 次数;Viva 按需重绘,空闲时只有光标闪烁触发重绘。
  • 影响平台:macOS、Windows(共用 Viva 桌面端代码)。

检查项

  • 已先写回归测试,确认它在修复前失败、修复后通过;测试路径与修复前的失败输出:desktop/internal/workbench/app_test.go 的 TestStatusBarFrameReadoutIsTheBareRate,修复前失败,输出为读数 "2 FPS",期望 "60"
  • [N/A] 无法自动化时,已写明手动复现与验证步骤:已有自动化测试
  • 已搜索被修改函数、类型、契约字段的全部调用方(含另一平台、插件、SDK、脚本和按字符串分发的调用):statusState、FPSValue、statusFPS、fpsValue、beginPaint 已全部更新或删除
  • 已检查另一平台是否有同类缺陷(有则本 PR 一并修复,或已提交 Issue):两端共用同一份 Viva 代码,本 PR 一并修复
  • 只改了修复所需的代码,没有夹带重构;修复过程中发现的其他问题已提交 Issue:无新发现
  • [N/A] 修复改变了行为约束或容易复发,已在 .agents/notes/implemented/bug-fix/ 记录:读数的定义和取舍已写在 frame_meter.go 的注释和测试里,不需要单独的 Note

范围检查 / Scope checks

功能矩阵 / Feature matrix

  • [N/A] 已更新 shared/platform-feature-matrix/features/<id>.json:没有新增能力,状态栏帧数项本来就存在,两端行为一致
  • 已运行 ./scripts/platform-feature-matrix/verify-platform-feature-matrix.sh(189 个能力通过);改了平台实现路径的,还运行了 ./scripts/platform-feature-matrix/verify-platform-feature-matrix-change.sh origin/preview HEAD(matrix change gate passed)
  • 没有提交 .artifacts/ 下生成的矩阵视图
  • 没有更新矩阵的,理由:修正已有状态栏项的读数和间距,没有新增或改变跨平台能力

插件归属 / Plugin ownership

  • 已在仓库根目录运行 develop-lithe 第三节“插件归属与宿主零依赖”的自查命令,结果为空:只改宿主状态栏,不涉及任何插件
  • 新增或修改的测试只使用测试内联的合成插件、Core 契约 fixture 或插件市场索引快照;没有读取具体插件的插件包、仓库检出、构建产物或清单,没有使用真实插件 ID,没有“缺少插件时跳过”的测试
  • 没有为具体插件提供的能力新建或更新宿主功能矩阵条目
  • 插件文案没有写进 shared/i18n/;插件构建步骤、资源路径和补丁没有写进宿主资源说明
  • [N/A] 修改了通用扩展点、可能影响已发布插件的,已在受影响插件的仓库运行它的测试:没有修改扩展点

统一日志系统 / Structured logging

N/A:没有新增或改动生命周期里程碑、终态、降级或失败路径;计量器在高频路径上,不写日志。

界面文案 / i18n

  • 宿主文案都走共享 i18n:在 shared/i18n/ 补齐中英词条 desktop.status.fpsTooltip,运行 node scripts/i18n/generate-i18n.mjs 并提交生成结果,没有手改生成文件;Viva 代码通过 desktop/internal/i18n/ 的接口读取
  • [N/A] 插件提供的文字由插件自己翻译:不涉及插件
  • 已运行 node scripts/i18n/verify-app-language-localization.mjs 和 node scripts/i18n/generate-i18n.mjs --check,没有放宽脚本或扩大豁免名单

逻辑归属 / Logic placement

  • 可共享的逻辑在 Rust Core;平台层只做原生 I/O 和呈现:计量器测量的是 Viva 窗口线程上回调的耗时和显示器节奏,属于窗口呈现层的观测,Core 看不到窗口线程,所以留在平台层
  • [N/A] 修改平台层已有的可共享逻辑时已迁入 Core:被替换的旧计数器同样只是呈现层观测
  • 工具或运行时专属的知识放在插件或成熟上游,宿主和 Core 没有按工具名加分支

性能与内存 / Performance and memory

  • 没有新增空闲轮询:读数不变时不请求帧,只有卡顿段滑出 1 秒窗口时才请求一帧,而且计量器自己请求的绘制不会再触发请求;忙碌段记录只保留最近 1 秒,间隔样本固定 16 个
  • 主线程没有文件读写、目录遍历、进程启动:每个回调多两次单调时钟读取
  • [N/A] 性能基线:没有改动 desktop/internal/editor/**、editorview/**,不涉及启动、打开项目、输入、滚动、大文件、长输出或搜索的处理路径

设置 / Settings

N/A:没有新增或修改设置项。

插件契约 / Plugin contract

N/A:没有改动插件契约、能力版本、插件函数协议、宿主方法或通知。

发行包与资源 / Bundle and resources

N/A:没有新增运行时资源、缓存、下载物或构建资源步骤。

无用代码与重构 / Dead code and refactoring

  • 本次改动导致变成无用的代码已在本 PR 删除:statusState、status 字段、statusFPS、FPSValue/desktop.status.fpsValue
  • [N/A] 本次之前就存在的无用代码或可重构之处,已提交带 refactor 标签的 Issue:没有发现

验证 / Verification

命令或操作 平台 结果
scripts/desktop/desktop-go.sh vet ./internal/workbench/... ./internal/i18n/... macOS 通过
scripts/desktop/desktop-go.sh test ./internal/workbench/... ./internal/i18n/... macOS 全部通过(workbench 166.7s)
go test -race -run TestFrameMeter ./internal/workbench/ macOS 通过
变基到 origin/preview(b08c3ecab)后重跑 vet 和 TestFrameMeter、TestStatusBar、TestWorkbenchDefaultLayout、TestStatusItem、Drag、TestTerminalAndStatus、HTML/Preview 相关测试 macOS 通过
node scripts/i18n/verify-app-language-localization.mjs macOS 通过
node scripts/i18n/generate-i18n.mjs --check macOS 通过
./scripts/platform-feature-matrix/verify-platform-feature-matrix.sh macOS 通过(189 个能力)
./scripts/platform-feature-matrix/verify-platform-feature-matrix-change.sh origin/preview HEAD macOS matrix change gate passed
gofmt -l 改动文件 macOS 无输出

未能执行的检查和原因:

  • 没有做整机手动验收和截图:本次只在测试 harness 中验证状态栏读数和布局,没有启动应用对比明暗主题和中英文的显示效果。ready 前需要在 macOS 上启动应用确认空闲时显示 60(或屏幕刷新率),并截图。
  • Windows 没有实机验证:代码与 macOS 共用,等 CI 的 Windows 检查和后续实机验证。

提交前确认 / Final checks

  • 分支从最新的 preview 创建(已变基到 b08c3ec);目标分支:preview
  • 以 Draft 提交,本地验证通过后才标记 ready
  • 没有无关改动,没有提交 .build/、target/、dist/、.artifacts/ 等产物,没有凭据、个人路径和未脱敏的日志或截图
  • 本次构建、测试或预览启动的 Lithe 应用和子进程已全部关闭,没有进程残留(本次没有启动应用;主检出里原有的 Lithe 进程不是本次启动的,未触碰)

- 帧数原来统计每秒重绘次数,Viva 只在内容变化时重绘,空闲窗口只为光标闪烁重绘,所以显示 2。
  改为测量界面线程:给窗口的每个回调计时,超过一帧的连续工作按跨过的帧数扣除,
  读数 = 屏幕刷新率 - 最近一秒被占用的帧数。空闲或后台任务运行时等于刷新率,界面线程卡住时下降。
- 刷新率取动画连续帧间隔的中位数,并贴近到常见刷新率;测到之前按 60 Hz 计算。
- 只在读数会变化时请求补帧;测量器自己请求的那一帧不计入,空闲时不会自己循环重绘。
- 状态栏只显示数字,悬停提示说明含义(新增 desktop.status.fpsTooltip,删除 fpsValue)。
- 状态栏项内边距 8 改为 6,项间距 4 改为 0,相邻读数间隔从 20pt 收紧到 12pt;拖动插入线和通知铃铛同步。
@ghfind-review ghfind-review Bot added the review: high ghfind author score; see https://ghfind.com label Nov 14, 2026
@xiaoyumuxi
xiaoyumuxi marked this pull request as ready for review November 14, 2026 13:48
@xiaoyumuxi
xiaoyumuxi merged commit 7f2449a into preview Nov 14, 2026
9 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review: high ghfind author score; see https://ghfind.com

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant