refactor(core)!: 统一 Layer 绘制契约并退役 RendererPlugin - #277
Merged
Merged
Conversation
旧式 RendererPlugin 经 createLayerFromPlugin 适配为 Layer,Scene 无法直接消化 帧上下文,渲染器也要自己重复实现 pane 过滤与异常隔离。改为单一 Layer 契约: - Layer<TFrame>/Scene<TFrame> 泛型化;Layer.pane 用具体 paneId(或 LAYER_PANE_GLOBAL)取代丢弃 id 的 paneRole,sub pane 归属精确到 pane。 - Layer.paint 直接接收帧上下文(DrawContext<TFrame, TSceneRenderer>), 业务侧实例化为 LayerDrawContext(RenderContext + sceneRenderer)。 - 横切关注点上移 Scene:pane 匹配、可见性、role 过滤、z 排序、逐 Layer try/catch 异常隔离统一由 createScene 负责,删除 createLayerFromPlugin。 - Scene.paint(frame) 一次画完所有 pane,取代逐 pane 的 paintPane。 - ChartRenderer 累加本帧 framePanes 后一次性交给 Scene。 - Chart 渲染器 API 改 Layer 语义(useRenderer(layer)/getRenderer/removeRenderer/ setRendererEnabled),删除 RendererPluginManager 字段与 updateRendererConfig。 - 图例可见指标改由注入 reader 读 kernel 信号,参数变化原子重建 Layer, 渲染器不再持有 config;修复 indicatorDefinitionRegistry 覆盖 scaleRendererFactory 的缺陷。 - 新增设计决策文档 docs/design/retire-renderer-plugin-contract.md,记录后续 分批迁移 106 个渲染器并最终删除 RendererPlugin 的计划。 BREAKING CHANGE: RendererPlugin/RendererPluginWithHost 契约与 Scene.paintPane 将在后续收尾提交中删除;当前 RendererPlugin 仅作为迁移期桥接保留。
RendererPlugin/RendererPluginWithHost 及其适配层是迁移期桥接:渲染器经 createLayerFromPlugin 包装成 Layer,Scene 无法直接消化帧上下文,渲染器还要 自行 pane 过滤、异常隔离与 config 持有。一次性退役,渲染器统一为原生 Layer。 - 全部渲染器工厂改为返回 Layer<RenderContext>:主图/副图/分时/markers/轴/ 指标(~70 个)与指标 scale;业务 draw 体保持不变,仅换外壳。 - 新增指标接缝 Indicator/shared/indicatorRendererLayer.ts,集中 id/role/pane/z 组装;状态统一经 context.indicatorStateReader 读取,删除 onInstall/ getDeclaredNamespaces/getConfig/setConfig 与 PluginHost 依赖。 - Scene 注入 sceneRenderer;Layer.pane 用具体 paneId 取代 paneRole; Scene.paint(frame) 一次画完所有 pane 并逐 Layer 异常隔离。 - 图例可见指标改由注入 reader 读 kernel 信号;参数变化原子重建 Layer。 - Chart 渲染器 API 改 Layer 语义,删除 RendererPluginManager、 updateRendererConfig、createLayerFromPlugin/wrapRendererAsLayer。 - 删除 RendererPlugin/RendererPluginWithHost 类型与 RendererPluginManager; 更新 rendering/README.md 与设计决策文档。 BREAKING CHANGE: 删除公开的 RendererPlugin/RendererPluginWithHost 契约与 Chart.useRenderer(plugin) 语义;宿主与扩展改用 Layer,随 major 版本发布。
数据与视口不变时仍走完整帧:逐帧重建 K 线/Ichimoku 几何,ChartRenderer 每帧 clearRect 后全量重画,GPU 路径每帧 clear + submit。既有版本号只回答 "几何能否复用",没有任何一层回答"整帧要不要跳过",因此帧数没有下降。 - 新增 retainedGeometry(按 key 保留几何并计数重建)与 retainedProjection (合成 dataRevision、展示输入、轴映射与 kLineCenters 判定复用);K 线 Layer 与 Ichimoku Layer 接入,逐帧重放相同几何。 - ChartRenderer 在帧事务 derive 阶段比较主层/交互层内容版本,命中即跳过 prepare、清屏与绘制。主层含 dataRevision、比较序列快照、视口/DPR、布局与 轴参数、settings/主题、指标投影、markers、Scene 图层;交互层含十字线、 悬停、绘图、框选与倒计时秒值。仅交互层变化时复用缓存几何只重画 overlay, 主层像素保留,不再无条件清 overlay。 - 版本按值比较(Object.is),viewport 每次派生新对象不会造成无效重绘;异步 指标提交、比较数据加载、resize、DPR 变化与数据视图切换均显式使主层失效。 - 指针存在待推导 hover 时照常出帧,保证十字线命中与本帧几何同代完成。 - 主图指标 Layer 不再强制 role='primary',改由工厂声明;最新价虚线与标签保持 overlay,避免静态刷新清空表面后因 role 过滤跳过重绘。 - WebGL 上下文启用 preserveDrawingBuffer,使 MSAA resolve 后的可见画布内容 在跳帧期间继续显示。 - 新增 docs/design/frame-content-version.md,记录判据边界、失败方向与 WebGPU surface 持久性待实测项;bench 改用保留几何并输出 geometryBuildCount。 - 回归覆盖:chart.dpr 新增静态跳帧、仅重画 overlay,以及 data/zoom/settings/ resize/DPR 逐个失效主层的用例。 实测(同机 headless Chrome,两次 run 方向一致):几何准备 P50 0.3-6.9ms 降到 0.00ms,geometryBuildCount 600 降到 1(18/18 场景);WebGPU ichimoku 10000 116.4 升到 185.2/199.7 FPS、jank 63.7% 降到 8.0%/0.2%。Canvas2D/WebGL2 的 FPS 与提交耗时受机器状态影响波动 2-3 倍,未据此下结论;帧级跳帧的"静态帧绘制 调用为 0"仍需 chart 级场景实测。 同时删除 packages/react/preview/screenshot.png(与本次渲染改动无关的陈旧产物)。 Closes #271
Resolve period and comparison views through one decision so comparison commands preserve timeshare mode. Rewrite geometry, price-axis and pointer orchestration, remove shadow state, and replace prototype mocks with real Chart coverage. Validation: 2634 core tests, pnpm type-check, scoped Biome checks and git diff --check passed. BREAKING CHANGE: Chart.applyRenderState now accepts a zoomLevel or kWidth/slotWidth object. Chart.setSymbols rejects multiple primary symbols; use setComparisonSpecs for comparisons.
默认未来区屏数 DEFAULT_FUTURE_SCREENS 由 3 改为 1:未来区是给"下一根尚未到来的 K 线"预留的 落点,1 屏已够摆放光标与画线,3 屏只是把可滚动空白拉长,不增加信息量。该常量是内容宽度、 滚动上限与可见区间上限的唯一真源,只改这一处即可三处同步收窄;显式传 futureScreens 的调用方 行为不变。 顺带把本次改动辐射到的测试收敛为单一事实来源: - 抽出 scrollContainerStub,替换 stateKernel / viewportScrollBridge 中 5 份内联滚动容器替身; - 未来区 bar 夹具统一:kLineMode 复用 renderTestKit.createKLineData;interaction 复用新增的 interactionTestKit.createInteractionBars(其 yAxis 替身为恒等映射,价格须落在窄区间才能命中); - 删除仅复述默认值的恒等式 / 魔法数字用例,改用 DEFAULT_FUTURE_SCREENS 与行为断言替代; - 用例只声明差异,重复的 deps / 场景对象抽到 describe 作用域复用。 设计决策见 docs/design/future-space-default-screens.md。
The last-price countdown added a fixed period duration to the bar timestamp, so it ignored market opening hours: A-share daily bars counted down 24h to midnight instead of to 15:00, and intraday bars ran through the lunch break. Resolve the current symbol's registered market session and advance intraday bars across its trading intervals, skipping breaks and clipping an incomplete final bar at the close; daily bars end at the last session close. The countdown still does not consult a trading calendar, so weekly/monthly periods stay hidden. Convert wall clock via the shared sessionTimeLabels helpers and keep period keys bound to KLinePeriod instead of free strings.
Vue 的图例配置此前调用已移除的 updateRendererConfig,图表挂载即抛 TypeError 中断,图例开关与指标白名单全部失效。 改为经 updateOptionsFacade 写入 chart options,由 optionsState 统一持有; 两个主图图例 Layer 每帧读取配置快照,因此不必重建 Layer,也不再维护独立 配置缓存。visibleIndicatorIds 与当前数据视图投影出的主图指标集合取交集, visible 只控制 Canvas 绘制,图例上下文仍发布给外部模板。 顺带把本次改动辐射到的用例收敛为单一事实来源: - renderTestKit 新增 countFillTexts / createContextWithInstanceState / createMARenderState,替换图例、BOLL、EXPMA、ENE、MA 五个用例文件中重复的 fillText 断言与内联 state reader; - 图例用例改为文件级 ctx 与表驱动:BOLL / EXPMA / ENE / WMA 合并为用例表, 纯 not-throw 用例并入"无有效数据"分支,删除只复述默认值的断言; - Vue 测试替身改为记录 updateOptionsFacade 调用。 设计决策见 docs/design/canvas-legend-configuration.md。 Validation: pnpm test:packages (core 2644, vue 168), pnpm type-check, scoped Biome checks 通过。
取数链路把品种的 assetClass 当筛选条件用,unknown(数据源尚未归一化,例如 GoTDX 的恒指 HSI)不在源声明的 assetClasses 内,整源被判不支持,Router 在 发起请求前就抛 SourceRoutingError,用户看到的是流转层错误而非 K 线请求。 这违反 types.ts 既有约定:unknown 只表示未归一化,不允许作为筛选条件参与匹配。 - types.ts 新增唯一判定入口 isFilterableAssetClass,undefined 与 unknown 同义。 - registry 的 supportsCapability、router 的 resolveInstrument、protocol 的 catalog.search 三处筛选点统一复用该判定,不内联字符串判断。 - unknown 品种按无条件处理:已归一化为已知类别的同代码品种不再被误杀。 - 补设计决策文档 docs/design/asset-class-unknown-filter.md。 Validation: 22 相关 core 测试、pnpm type-check、改动文件的 Biome 检查通过。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
动机
渲染层存在两套并行契约:
RendererPlugin需要createLayerFromPlugin适配成Layer才能被Scene消化,适配层丢帧上下文、让每个渲染器自己重复实现 pane 过滤与异常隔离;同时ChartRenderer每帧无条件清屏 + 全量重画,数据与视口未变时也在重建几何。两者叠加的结果是:契约不统一 + 静态帧不跳帧。本 PR 范围(分支全量 9 个提交)
98f6f9352bd03ed8Layer<TFrame>/Scene<TFrame>泛型化;Layer.pane用具体paneId取代丢失 id 的paneRole;pane 匹配、可见性、role 过滤、z 排序、逐 Layer 异常隔离上收到createScene;~70 个渲染器工厂统一返回原生Layer<RenderContext>,业务 draw 体不变;新增指标接缝Indicator/shared/indicatorRendererLayer.ts,指标状态统一经context.indicatorStateReader读 kernel 信号;删除RendererPluginManager/updateRendererConfig/createLayerFromPlugin/wrapRendererAsLayerdf8841d0retainedGeometry(按 key 保留几何并计数重建)与retainedProjection(合成dataRevision、展示输入、轴映射、kLineCenters判定复用);帧事务 derive 阶段比较主层/交互层内容版本,命中即跳过 prepare、清屏与绘制;仅交互层变化时复用缓存几何、只重画 overlay;WebGL 开启preserveDrawingBuffer保证跳帧期间画布内容可见。实测 geometryBuildCount 600 → 1(18/18 场景),WebGPU ichimoku 10000 帧 jank 63.7% → 8.0%/0.2%6164b54e2e0ca56cee586a0dDEFAULT_FUTURE_SCREENS3 → 1(内容宽度、滚动上限、可见区间上限三处同步收窄);未来区缩放改为锚定指针所在 slot3246144cb871c699updateRendererConfig导致挂载即TypeError;改为经updateOptionsFacade写 chart options,图例 Layer 每帧读配置快照,无需重建 Layer230deee2assetClass: 'unknown'(数据源未归一化,如 GoTDX 恒指HSI)此前会在supportsCapability整源筛除,Router 发请求前抛SourceRoutingError;新增唯一判定入口isFilterableAssetClass,registry / router / protocol 三处筛选点统一复用,unknown按无条件处理顺带删除
packages/react/preview/screenshot.png(与渲染改动无关的陈旧产物),以及若干只复述默认值的恒等映射/魔法数字测试用例,替身统一收敛到renderTestKit/interactionTestKit/scrollContainerStub等共享 helper。破坏性变更
RendererPlugin/RendererPluginWithHost契约与RendererPluginManager;宿主与扩展改用Layer(Chart.useRenderer(layer)/getRenderer/setRendererEnabled)。随 major 版本发布。Chart.applyRenderState改为接受zoomLevel或{ kWidth, slotWidth }对象。Chart.setSymbols拒绝多主图 symbol;多标的对比改用setComparisonSpecs。Scene.paintPane删除,统一为Scene.paint(frame)。设计决策文档
docs/design/retire-renderer-plugin-contract.md— 后端分批迁移 106 个渲染器并最终删除RendererPlugin的计划docs/design/frame-content-version.md— 判据边界、失败方向、WebGPU surface 持久性待实测项docs/design/chart-view-transition.md、future-space-default-screens.md、last-price-market-session-countdown.md、canvas-legend-configuration.md、asset-class-unknown-filter.md验证
pnpm type-check绿;改动文件的 Biome 检查绿(data/provider/index.ts、protocol/index.ts、protocol/httpTransport.ts的organizeImports告警为分支前既有问题,未在本 PR 处理)chart.dpr.test.ts:静态跳帧、仅重画 overlay,以及 data / zoom / settings / resize / DPR 逐个失效主层assetClass: 'unknown'不排除已启用源,且仍能取到数据评审关注点
retainedProjection的失效清单是否完整(异步指标提交、比较数据加载、resize、DPR、数据视图切换已显式失效)Layer.pane由paneRole改为具体paneId后,sub pane 指标归属是否全覆盖