Skip to content

[feat] canvas: 大工程性能基线与撤销/重做卡顿治理 #153

Open

Description

@Probiotics5333

目标与范围

承接 #107「P0 画布性能与撤销/重做」。当前缺少可比较的大工程基线,反复撤销/重做会出现可感知停顿,历史操作之间夹有编辑时更明显。本 Issue 交付:可重复的大工程测量方法、基于实测的卡顿定位与修复、以及同一工程同一环境的前后对比结论。

范围限制:不重写历史机制,不改变撤销语义(只回退允许回退的工程内容,不回退远端调用与费用),不含后台渲染与多端同步议题;性能目标数值待基线数据产出后确认。

用户流程与关键规则

  • 选一个代表性大工程并记录规模:节点数、连线数、素材数、嵌入媒体体积、工程文件大小,以及测量所用机器与浏览器版本。
  • 测量覆盖打开、加载、选中、拖拽、连线、缩放、输入、提交生成、结果显示;撤销与重做分别测单次和连续 20 次。
  • 必须包含「历史操作之间夹有编辑」的场景:撤销若干步后再编辑,future 应正确清空,不得出现重做复活旧内容或历史错位。
  • 撤销/重做期间界面保持可交互;更新后保留选中、焦点、正在编辑的文本、播放头与未提交输入。
  • 状态校验以服务端快照为准:反复撤销/重做、刷新、重开工程、后台任务并发完成后,画布与工程快照一致。
  • 定位结论要区分前端渲染、快照与版本载荷、后端历史回放三段耗时,不把服务端排队时间算作界面卡顿。

实现影响

  • 前端本地历史 src/canvas/history.ts 保留最多 80 份整份文档快照,sameContent 逐节点逐边比较;拖拽走 transaction 提交,是比较与拷贝开销的主要来源。
  • src/backend/creationClient.ts 的 undo() 每次手势都会重新拉取完整工程快照、再读 history,并携带全部 canvas_item、generation_spec、reference、editor_document 版本提交命令;大工程下这是 O(n) 载荷与往返。
  • src/canvas/CanvasWorkspace.tsx 负责撤销编排队列(historyReplaying)和整份文档状态,撤销后的重渲染范围决定停顿感知。
  • 后端 packages/core/src/opendirector/application/creation_history.py 与命令日志读取负责回放;若耗时在服务端,应在此修复而不是只用前端规避。
  • 测量脚本放 tests/browser/,沿用 tests/browser/reference_drag_performance.mjs 的隔离 API 与 CPU profile 方式;回归用例可扩展 paste_undo.mjs、canvas_selection_history.mjs 与 tests/test_creation_history_service.py。

验收标准

  • 提供可重复执行的测量脚本与环境说明,在大工程上输出各项耗时并记录工程规模。
  • 复现反复撤销/重做的停顿并给出定位证据(CPU profile 或分段耗时),说明瓶颈落在哪一段。
  • 修复后同一工程、同一环境的连续 20 次撤销/重做不再出现采集到的长停顿,并给出前后数值对比。
  • 撤销后再编辑的场景有回归用例覆盖:future 正确清空,不复活旧内容,历史步数不串位。
  • 反复撤销/重做、刷新、重开、后台并发生成完成后,画布与工程快照一致,选中、焦点、编辑中文本、播放头与未提交输入保留。
  • 现有前后端历史测试与浏览器用例通过;新增用户可见文案同时提供中英词条并通过 npm run check:i18n。
  • 剩余限制与未覆盖场景记录在本 Issue 评论中。

待确认

  • 代表性大工程的规模与参考硬件,作为对外比较的固定口径。
  • 性能目标形态:仅记录数值,还是设定阈值并纳入 CI 门禁(需基线数据后再定)。
  • 若瓶颈来自每次撤销的全量快照拉取,是否接受按需增量读取的改造。

Activity

  1. kunkunblueberry commented on Nov 10, 2026

    @kunkunblueberry
    Collaborator

    Taking this up as the performance owner, with one sequencing note before work starts.

    The first acceptance item is currently blocked

    提供可重复执行的测量脚本与环境说明,在大工程上输出各项耗时并记录工程规模。

    The storage side of that baseline does not produce numbers today. #260 records that the nightly cold-open and delete fixtures expire their edit session during preparation, so the lane reports SUITE/INCOMPLETE and fails instead of emitting storage.coldOpen.snapshot, storage.coldOpen.open, storage.delete.clientSnapshot and storage.delete.trash. I reproduced it locally at the configured 600-commit / 200-node scale on e1f2246a:

    ProjectError: 工程已关闭或会话已过期,请重新打开后保存。  (409)
      creation_projects.py:90  _lease()
    

    #270 renews the fixture's lease during preparation (outside the timed region) and both scenarios then produce every tracked measurement on my machine. That is the change that makes "可比较的基线" possible at all.

    So the order I propose is:

    1. Land #270, confirm the next nightly run emits all four storage measurements.
    2. Then use those numbers as the fixed reference for this issue's before/after comparison.
    3. Only then set thresholds (the issue's 待确认 item) — a threshold picked before the fixture is trustworthy would just freeze noise.

    What I can take

    • The measurement harness and environment record: extend tests/browser/reference_drag_performance.mjs's isolation/profile pattern for the canvas interactions listed here, and pin the storage side to the nightly lane that #270 restores.
    • The分段耗时 attribution: separate front-end render, snapshot/version payload, and backend history replay, so a stall is not attributed to the wrong layer.
    • The "撤销后再编辑 → future 清空" regression, which is the one correctness item hiding inside a performance issue.

    What I would rather not take unilaterally

    The rework direction in 待确认: "若瓶颈来自每次撤销的全量快照拉取,是否接受按需增量读取的改造". That changes creationClient.undo()'s contract and every caller, so it needs your call rather than a performance-driven decision.

    Also worth stating up front: src/canvas/history.ts keeps up to 80 whole-document snapshots and sameContent compares node by node, while undo() re-fetches the full project snapshot per gesture. Those two facts alone predict a large-project stall, but I will not claim them as the cause until the profile says so — the issue's own requirement is that the conclusion distinguish the three layers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area:canvasCanvas, nodes and direct manipulationtype:featNew capability or behaviour

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions