面板动画帧不再重绘绘图区(组名展开/收起卡顿根治)+ 均值面板两处修正 + 信号行集合自愈
1. 组名展开/收起卡顿的真因(用户报告:288 个信号同一网格、双光标/实验性功能开启时,
点组名展开收起的动画非常卡顿,而网格里没有任何需要重绘的变化)
- 滚动定时器给面板动画帧做的是整窗失效,WM_PAINT 里"更新区域不在绘图区内 ⇒
m_chromeDirty = true"这条启发式把整帧判成脏 ⇒ Render 走 fullChrome 分支 ⇒
标签栏/工具栏/绘图区全部重绘。绘图区走场景缓存很快,但**光标叠加层每帧都要重画**:
288 个信号的取值胶囊(圆角 + GDI 文本 + 重叠避让)实测 24~46ms/帧。
- 新增 Application::m_panelAnimationFrame:滚动定时器在只有面板动画时置位,
WM_PAINT 命中它且没有任何其它消息要求装饰区重绘时不再把整帧判脏 ⇒ 走 panelOnly。
- 新增 Application::m_plotPaintValid:panelOnly 帧且该标志为真时跳过 PlotView::Paint
(面板不覆盖绘图区,上一帧像素原样留在后备缓冲区,整窗回送也不会错)。
作废点:InvalidatePlotRect(签名多收一个 bool&,5 个调用点统一传入)、Relayout、
EnsureBackBuffer、WM_PAINT 中"更新区域就在绘图区内"、以及"面板动画帧里绘图区的
滚动/回到最上方动画同时也在跑"(否则会把绘图区动画冻住)。
- panelOnly 分支画分隔条时把右边界夹到 floor(m_plotRect.X):分隔条是浮点矩形
(457.5…462.5),GDI+ 会把最后一列按 50% 混进去;整帧重绘时绘图区随后会盖掉它,
但跳过绘图区的帧会留下**一整列 1px 接缝**(实测 847 像素)。
- 实测(283 信号同网格 + 双光标):动画帧 34~43ms → 1.6~2.1ms。
2. 均值滤波面板
- **未勾选的条目栏点击完全无响应**:未勾选时点名称区域不再自动勾选、也不展开/收起,
只能点右端复选框(点击仍被消费,不穿透)。
- **滚动时参数滑块不再消失**:原实现按条目栏矩形判断整行可见性,条目栏滚出内容区后
整行(含两行参数滑块)被跳过。现在按整行纵向范围(条目栏 ∪ 两行轨道/数值/复位)判定。
3. 信号列表:行集合与折叠状态自愈(用户报告:开自由标记并标记点后,点 MF4 组名,
三角显示已展开但下属 283 条信号行一条都不出现)
- 新增 SignalPanel::RowsSignature()(FNV 混合 分组集合/每组信号数/是否折起/是否有动画),
BuildRows 记录它,EnsureRows 每帧 O(分组数) 校验,不一致就地重建。
这样任何"改了折叠状态却忘记置 m_rowsDirty"的路径都不会再让行集合与折叠状态脱节。
4. 验证(全部离屏,不占鼠标/键盘/屏幕)
uicheck 69 / lbcheck 21 / lbresid 5 / lbzorder 4 / lbcovr 7 / lbpanel 10 /
derivecheck 16 / round15check 28 / themecheck 17 / meancheck 21 全绿;
新增 .preflight/panelbug 14/14(外部改折叠状态→行集合自愈、动画中途改状态→位移清零、
未勾选行点击无响应、条目栏滚出后滑块仍绘制);
新增 .preflight/panelperf 12/12(动画帧 <16ms、且跳过绘图区后该区域像素与紧接的
整幅重绘逐像素完全相同);
rendercheck A–H 8 个场景哈希与基线一致;make release / make debug 零 warning
(805,888 B)。
同步文档:Documents/agent_mem.md 追加「2026-10-30 第十八轮」,
并把第十七轮里"尚未启用 → 直接开始预览"改标注为已推翻。
This commit is contained in:
1 parent
2d83b494a3
commit
8271879ede
8 files changed
+268
-27
No files matched your search
@@ -749,6 +749,15 @@ namespace jdi
|
||||
*/
|
||||
void BuildRows();
|
||||
|
||||
/**
|
||||
* @brief 行集合的“定义状态”签名(分组集合 / 每组可见项数 / 折起状态 / 是否有动画)。
|
||||
*
|
||||
* `EnsureRows` 用它做**自愈校验**:只要签名与“上一次建行集合时的签名”不一致,
|
||||
* 就重建行集合。这样可以彻底堵住“改了折起状态却忘了置 `m_rowsDirty`”这类缺陷
|
||||
*(用户 2026-10-30 报告:组名三角已显示展开,但下属信号行完全不出现)。
|
||||
*/
|
||||
std::uint64_t RowsSignature() const;
|
||||
|
||||
/**
|
||||
* @brief 在需要时重建列表行集合。
|
||||
*/
|
||||
@@ -1337,6 +1346,7 @@ namespace jdi
|
||||
*/
|
||||
std::vector<float> m_rowTop;
|
||||
bool m_rowsDirty = true; /**< 列表行集合是否需要重建。 */
|
||||
std::uint64_t m_rowsSignature = 0; /**< 建行集合时的 `RowsSignature()`(用于自愈校验)。 */
|
||||
float m_contentHeight = 0.0f; /**< 列表内容总高度。 */
|
||||
float m_listFullWidth = 0.0f; /**< 列表区不含滚动条的完整宽度。 */
|
||||
bool m_scrollBarVisible = false; /**< 是否需要显示滚动条。 */
|
||||
|
||||
@@ -518,6 +518,8 @@ namespace jdi
|
||||
{
|
||||
m_rows.clear();
|
||||
m_rowsDirty = false;
|
||||
// 记住“本行集合对应的定义状态”(含空数据集的早退路径),供 `EnsureRows()` 做自愈校验。
|
||||
m_rowsSignature = RowsSignature();
|
||||
// 行集合重建后行下标的含义可能变化(增删/折起展开)⇒ 行位图缓存整体作废。
|
||||
// 这同时把"不再显示的行"的位图内存还回去(缓存条目按行数增长)。
|
||||
InvalidateRowCache();
|
||||
@@ -588,6 +590,37 @@ namespace jdi
|
||||
UpdateAnimGeometry();
|
||||
}
|
||||
|
||||
std::uint64_t SignalPanel::RowsSignature() const
|
||||
{
|
||||
// FNV-1a:混合“每个分组是否列出、列出多少项、是否折起、是否有折起动画”。
|
||||
// 这四项恰好完全决定行集合的内容,因此它们不变时无需重建。
|
||||
std::uint64_t hash = 1469598103934665603ull;
|
||||
const auto mix = [&hash](std::uint64_t value)
|
||||
{
|
||||
hash = (hash ^ value) * 1099511628211ull;
|
||||
};
|
||||
if (m_dataSet == nullptr || m_tab == nullptr)
|
||||
{
|
||||
return hash;
|
||||
}
|
||||
const std::vector<SignalGroup>& groups = m_dataSet->Groups();
|
||||
mix(static_cast<std::uint64_t>(groups.size() + 1));
|
||||
// 筛选态下“每组列多少项”要逐信号比对(成本 O(信号数)),因此只在非筛选时入签名;
|
||||
// 筛选文本变化本身会走 `ApplyFilterText()` 置脏,无需靠签名发现。
|
||||
const bool filtering = !m_filterText.empty();
|
||||
mix(filtering ? 1ull : 0ull);
|
||||
for (std::size_t index = 0; index < groups.size(); ++index)
|
||||
{
|
||||
const int groupIndex = static_cast<int>(index);
|
||||
const std::uint64_t count =
|
||||
filtering ? 0u : static_cast<std::uint64_t>(groups[index].signals.size());
|
||||
mix(count + 2u);
|
||||
mix(IsGroupCollapsed(groupIndex) ? 3u : 0u);
|
||||
mix((FindGroupAnimation(groupIndex) >= 0) ? 5u : 0u);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
void SignalPanel::EnsureRows()
|
||||
{
|
||||
if (m_rowsDirty)
|
||||
@@ -596,6 +629,17 @@ namespace jdi
|
||||
RefreshScrollBar();
|
||||
return;
|
||||
}
|
||||
// ⚠️ **自愈不变量**:行集合必须与“分组集合 / 每组可见项数 / 折起状态 / 是否有动画”
|
||||
// 严格一致。历史上确实出现过“折起状态变了但忘了置 `m_rowsDirty`”的路径 ——
|
||||
// 表现就是用户 2026-10-30 报告的:组名三角已经显示“展开”,
|
||||
// 但下属信号行一条都不出现(行集合里根本没有它们)。
|
||||
// 这里每帧花 O(分组数) 校验一次签名,不一致就地重建,不再依赖每个调用点自觉置脏。
|
||||
if (m_rowsSignature != RowsSignature())
|
||||
{
|
||||
BuildRows();
|
||||
RefreshScrollBar();
|
||||
return;
|
||||
}
|
||||
if (m_rowShift.size() != m_rows.size())
|
||||
{
|
||||
// 行集合没变但位移缓存与它不同步(例如首次构建后)—— 补齐,避免读到越界值。
|
||||
|
||||
Reference in new issue
Block a user