Skip to content

Releases: zeriehan/view-memory

1.2.7

Choose a tag to compare

@github-actions github-actions released this 29 Sep 08:27

这次日志(你开着 debug)直接指认了问题,没靠猜。

日志里的证据

记录位置:…数学物理方法….pdf 第 226 页 → 第 226 页(差异:scrollTop: 638→635)
记录位置:…第 226 页 → 第 226 页(差异:scrollTop: 639→573)      ← 一秒后又是它
PDF 阅读位置表已对平:6 条                                        ← 一分钟刷几十行

页码从头到尾没变,变的是 scrollTop —— 那是 pdf.js 在渲染过程中自己微调滚动位置。而我们把这个抖动判成「表变了」,于是每秒把整张 pdfjs.history 写一遍。

这一点值得单独说:localStorage 是同步 IO,占的是主线程;而 PDF 的 canvas 绘制也在主线程。 你截图里那个「页码显示 28/93、画面却全黑」,与这种持续阻塞是吻合的。

改了三处

① 我们不再写 pdfjs.history(除了打开前那一次)。
pdf.js 每次滚动本来就会把整张表写回去,而它的 database 就是我们交给它的共享表 —— 它写的时候天然带上全部条目。我们那次写是纯重复劳动。现在只保留「打开 PDF 之前写一次」,让 pdf.js 自己读到位置去恢复;其余时间只做一件事:让所有打开的视图都指向同一张表(这才是「多开 PDF 位置互相覆盖」的真正解法)。

② 补拍定时器加了硬上限与去重。
上限 10 个,超了直接丢弃并记账;id 存进 Set,执行时自己删掉。
之前那段是「数组超过 64 就截短到 32」—— 但被截掉的 id 没有 clearTimeout,那些定时器照样执行。所以那个上限是个假象。

③ layout-change 加 400ms 节流。
切视图、视图重建都会连着触发它,而它一次就排 6 个补拍。

顺便让限制自己会说话

被丢弃的补拍数、被节流的 layout 次数、以及「一秒内跑了太多次补拍」,各会写一行日志。下次你再反馈,我直接读这几行,不用再推理。

说清楚边界

这些都没有被证明是 4GB 的原因。 我能做的是把我们这一侧的浪费和失控都收干净 —— 每秒一次同步写盘、定时器无限增长,这两条都是实打实的(都有日志)。至于到底是 app 还是插件,仍然需要那次对照实验:

把「View Memory」禁用 → 重启 Obsidian → 快速打开那两个 PDF、来回切几次

  • 还崩 / 还涨 → 是 Obsidian + pdf.js 自己的事(两个大部头 PDF 双开本身就是重活)
  • 不崩了 → 是我们的锅,这轮加的日志会指出是哪一段

回归

合并套件 44 → 54 项,视窗侧 199 项,全绿。
其中最关键的一条:让一个 PDF 视图的 scrollTop 连抖十二拍,断言 localStorage 一次都没被写 —— 先验过红(把「每拍写一次」塞回去,它立刻报「写了 12 次」)。

1.2.6

Choose a tag to compare

@github-actions github-actions released this 29 Sep 05:43

你这次把调试日志开着,日志直接给出了两条我上一轮没修掉的问题。

日志里的两条硬证据

记录位置:…写作 第9版….pdf 第 356 页 → 第 1 页        ← 0.4 秒后又翻回来
记录位置:…写作 第9版….pdf 第 1 页 → 第 356 页
记录位置:…写作 第9版….pdf 第 356 页 → 第 356 页        ← 页码没变也照样写
视图就绪:…写作 第9版….pdf(pdf)…                    ← 同一个文件两分钟内"就绪"4 次
  • 页码在 356 ↔ 1 之间来回跳:说明记录的「位置」在被两个来源交替覆盖;
  • 同一路径反复"视图就绪":说明视图在被反复重建/重载 —— 这正是 4GB 的形态。

改了什么

① 同一份文件开在两个叶子里 → 只记录「活跃叶子」的位置,并且一个都不恢复。
两个视图各写一次就是那个 356 ↔ 1 的来回覆盖;而"恢复"在这里更糟 —— 两个窗格各有各的位置,恢复谁都是把另一边的人拽走。只剩一个叶子时一切照旧。

② 不再每拍改写 pdf.js 的内部对象。
pdf.js 每个视图都握着 store.database.files === 那张表。我以前每拍都换一个新数组,于是每拍都要把所有 store 重新绑一次 —— 等于每秒一次改写别人的内部状态。现在表对象保持身份(原地同步),绑定只在视图新出现时做一次;applyPdf 也不再自己手写 store.file,统一交给对平逻辑做。

③ 顺手修的:preparePdfFor 现在合并全部记录(以前只带当前那一条,写回时可能把别的 PDF 的条目从表里抹掉)。

这次日志会自己说明问题

  • 视图就绪:… key=<叶子|路径> —— 同一个文件反复出现不同的 key = 视图在被反复重建(我怀疑的和 4GB 相关的就是它);
  • 视图关闭:key=… —— 与上一行对着看;
  • 记录位置:…(差异:scrollTop: 11→604) —— 页码一样却判定变了时直接告诉你是哪个字段在动;
  • 套用 PDF:… 想第 356 页,套用前第 1 页,读回第 356 页(共 653 页) —— 分辨跳页是我们干的还是 pdf.js 自己重载后落的位。

仍然要请你配合的一件事

4GB 那个我还没找到源头。这次改的是"我们能控的读写与内部改写",量级应该明显下来;但如果还崩,请做一次对照实验(一分钟):禁用「View Memory」→ 重启 → 快速打开那两个 PDF。

  • 还崩 → 是 Obsidian + pdf.js 自己的事;
  • 不崩 → 是我们的锅,我接着挖。

回归

视窗侧 187 → 199、合并 44,全绿。新增的"双叶子"用例在旧行为下会红(记录在两个窗格之间来回翻)。

1.2.5

Choose a tag to compare

@github-actions github-actions released this 29 Sep 03:43

两个问题:一个是我能定位的 bug,一个是"先把我们自己能控的都收干净 + 把下次取证做足"。

一、切到第二个 PDF 后滚一下,零点几秒被拽回原页

这是我上一版的判据留下的漏洞。判断「用户在动」要求 输入时间 > firstReadyAt,而 firstReadyAt 是我们第一次看见这个视图就绪的那一拍 —— tick 一秒一拍,而你切过去往往立刻就滚了。那一下输入发生在我们的观测之前,于是没算数,接着照常恢复 → 把你拽回切过去时的那一页。

现在按视图记账(叶子 + 路径),并且给 1.5 秒的观察余量:只要输入落在这个视图里,哪怕发生在我们第一次看见它之前,也算数。附带两个改善:在别的面板点一下不会再取消这篇笔记的恢复;几分钟前的输入也不会再影响这一次。

验过红:把判据退回旧写法,新用例精确复现 restored=1 applied=1(就是"拽回")。

二、内存涨到 4GB / 崩溃

日志里能看到一个确实该修的问题:

记录位置:…数学物理方法….pdf 第 53 页 → 第 53 页
记录位置:…数学物理方法….pdf 第 53 页 → 第 53 页   ← 一秒一行

页码根本没变,却每秒判定"变了" —— 因为 scrollTop 这种浮点每秒都会在末位抖,而比对用的是严格相等。结果:每秒重写记录、每秒往 localStorage 写一次表。已改成 2px 容差 + 取整指纹,静止时一次都不写。

同时把能收的都收干净:

  • 没有 PDF 视图开着时,连 localStorage 都不解析;
  • 记录页码超出文档总页数就不跳(硬跳只会落在最后一页,然后每拍都"不符"再跳一次);
  • 同一文件连续套用 6 次仍不成功就停手 —— 防的是"视图被反复重建 → 计数被重置 → 我们跟着反复套用"那种失控(成功到位即清零,正常反复开同一份文件不受影响);
  • 调试日志文件封顶 2MB;
  • onunload 清掉所有补拍定时器。

但要说实话:上面这些都不足以单独解释 4GB。我能做的是把属于我们这一侧的浪费和隐患都堵住、把量级压下来;"到底是 app 还是插件"还需要一次对照实验(见下)。

三、下次取证会更直接

applyPdf 现在会记一行:想要第几页 / 套用前第几页 / 读回第几页 / 共几页。所以下次再出现"跳回",日志能立刻分辨是我们跳的还是 pdf.js 自己重载后落的位。

1.2.4

Choose a tag to compare

@github-actions github-actions released this 21 Sep 09:54

这次找到真正的原因了 —— 而且不是一个,是两个叠在一起,任一都足以让恢复一次都不发生。

上一版加的调试日志直接把真相摊开了:一条「套用」记录都没有,说明不是「套了没用」,是压根没套。

一、配置里少了一个字段(它让恢复窗口永远不成立)

配置是浅合并的:Object.assign(默认值, 存档) 会用存档里的 view 整个替换默认的 view 对象 —— 于是「上次保存之后才新增的字段」就是 undefined。

restoreWindowMs 正是后来才加的,所以你本机配置里没有它。而 已过时间 <= undefined 恒为 false ——「恢复窗口」判定永远不成立,wantsRestore 恒为 false。不报错、不写日志,任何类型都不恢复。

现在 loadSettings 会逐字段重建,每个数值项都有兜底(0 仍然算「填了 0」,不会被当成没填)。

二、把「实况变了」当成了「用户在动」(这条更隐蔽)

宿主自己也会异步移动视图:Markdown 渲染完成后会自己把滚动位置摆一下、pdf.js 会自己去读那张表。这两件事都被当成了「用户已经接手」,于是视图在打开后约一秒就被永久交还 —— 日志里看得清清楚楚:

视图就绪后实况自己动了: xxx.md
用户在动,交还视图: xxx.md

改成只认真实输入事件(滚轮 / 触摸 / 按下 / 按键),而且必须发生在这个视图自己的容器里。click 与 scroll 故意不收:宿主的程序化滚动同样会触发它们。折叠侧保持原来的宽口径,不受影响。

顺带的改进

  • 「已经到位」的记账不再等 2.5 秒静默期过了才认。
  • 恢复失败时不再拿「还没恢复的默认值」覆盖记录(只在我们确实试过、确实不符、而用户又没碰过这个视图时保护)。
  • 找不到视图时 applyRecord 会写日志,不再安静返回(1.2.1 那次就是被这份安静藏住的)。
  • 启动日志与设置页「当前状态」都会列出 就绪等待 / 恢复窗口 / 最多套用次数,并逐个视图显示「记录 vs 实况、套用几次、用户是否操作过」。

回归

视窗侧 172 → 178、合并 41 → 44,全绿。两条新回归都先验过红:
把「实况变了就交还」塞回去 → 精确复现 restored=0 applied=0;把 restoreWindowMs 去掉 → 套用列表为空。

1.2.3

Choose a tag to compare

@github-actions github-actions released this 21 Sep 09:34

这次是真凶:记录被「还没恢复的默认值」覆盖了。

调试日志里看得很清楚:md 记录被改写成 0,而且一个「套用」都没发生过 —— 两件事合起来只有一个解释。

原因(我 1.2.0 埋的雷)

那一拍判定「该恢复了,但用户正在操作」,于是标记为「交还给你」,然后没有 return,顺手按实况记了一笔。可那一刻的实况通常不是你的位置,而是「这个视图还没恢复」的默认值 —— 于是它把正要恢复的那个记录覆盖成了 0,而「已交还」标记又让插件此后再也不尝试恢复。静默、永久地丢数据。

改法

  • 「用户正在操作」那一拍改成直接跳过:不套用,也不记录。
  • 判断「用户在动」改用真正的证据:本视图就绪以来实况自己动过没有(按视图记账,粘性)。原来用的是「800ms 内有没有输入」—— 既会漏(tick 间隔 1 秒比它还长),又会误伤(点一下设置页也算操作)。
  • 再加一道保险:记录与实况不同、又还没成功套用过、实况也没动过时,绝不用实况覆盖记录;等恢复不再可能(总开关关掉 / 已交还 / 恢复窗口过了)才如实跟随实况。
  • 顺带修了两条测试场景本身不忠实的用例(用静止的实况来模拟「用户在滚」,引擎根本看不到这种「动」)。

回归

165 → 172 项。验红:把结构退回旧版「else-if 链 + 无条件捕获」,会精确复现症状 —— 记录被写成 0、窗口过后也不再恢复;换回修复后全绿。

上一版加的调试日志与「当前状态」面板这次直接立了功:日志里「记录被改成 0」和「没有任何套用记录」这两条,是定位真凶的关键。

1.2.2

Choose a tag to compare

@github-actions github-actions released this 21 Sep 09:22

接着修 Markdown 滚动位置:「存下来了、但完全没动」。

上一版把「找不到叶子」修了,但仍然不动 —— 这次从运行中的包里读到了真正的原因:预览模式的 renderer.applyScroll() 在「文本还没渲染完 / 段落还没测量完」时会安静地 return false,什么也不做、也不报错。而刚打开文件时恰好就是这个状态。我原来直接调它,所以经常等于没调。

Obsidian 自己走的是 MarkdownView.setEphemeralState({ scroll }) —— 内部会转成 applyScrollDelayed:先试一次,没成就在 onRendered 之后再来一次。源码模式与预览模式都认这个字段(都在包里核实过),而且它会把值存进 leaf 状态,宿主重启后自己也能恢复。

改动:md 回填改走这条路,同时保留一次直接调用作兜底,并且立刻读回一次,日志里能看出到底有没有生效。

另外把「猜」变成「看得见」

  • log() 现在除了控制台还会写插件目录下的 view-memory-debug.log
  • 启动时写一行版本号与当前设置(一眼确认跑的是哪一版)
  • 设置页「当前状态」现在逐个视图列出:记录 vs 实况、是否已交还给用户、套用过几次 —— 出问题一眼能分清是压根没套还是套了但宿主没照做

回归 37 → 41 项(断言优先走 setEphemeralState、兜底调用、实况到位、状态面板同时显示记录与实况)。

1.2.1

Choose a tag to compare

@github-actions github-actions released this 21 Sep 09:12

修:Markdown 滚动位置存下来了、但摆不回去(等于没生效)。

原因是个很低级的匹配错误:插件内部把这类视图叫 md,但 Obsidian 的 markdown 叶子类型叫 markdown。回填时它拿 md 去查叶子,查不到 —— 而查不到只会安静地返回空,不报错、不写日志,于是全程没有任何动静。

改动:加一张「视图类型 → leaf 类型」的显式映射表,凡是按类型找叶子都走它。同时补了一条端到端回归:用真实构建产物 + 假 markdown 叶子,断言存的位置真的被喂给了 applyScroll(先验证过这条断言在旧代码下会红)。

如果你之前在 1.2.0 上试过:记录其实已经存下来了(data.json 里能看到),装上这版就会开始生效。

1.2.0

Choose a tag to compare

@github-actions github-actions released this 21 Sep 09:04

两件事:修掉一个我自己引入的 bug,外加一个新能力。

修:PDF 来回闪

之前的引擎会在「实况和记录不一致」时反复把视图摆回记录的位置 —— 于是你从第 15 页翻到 16 页,过一会儿就被判成「跑偏了」拽回 15 页,再跳回 16,来回闪。

现在一个视图只摆一次:只要命中下面任一条就永久交还给它(此后只记录、不再套用)——

  • 已经摆到位了;
  • 摆过之后实况自己又变了(那是你在翻,不是它跑偏);
  • 该摆回去的那一拍你正在翻;
  • 试满次数还不成。

另外恢复阶段本身也有时间上限(6 秒,从视图就绪算起),过了就只记录。

新:Markdown 滚动位置

记住每个笔记滚到哪,下次打开摆回原处。用的是 Obsidian 自己的滚动口径(小数行号,不是像素),所以源码模式和阅读模式通用 —— 在阅读模式读到的地方,切到源码模式也落在同一处。

设置页里可以单独关掉(「接管 Markdown 滚动位置」,默认开)。

回归

视窗侧 139 → 165 项,合并 35 项,全绿。其中新增一节专门复现上面那个「翻页来回闪」:连续多拍断言一次都不许再套用。

1.1.1

Choose a tag to compare

@github-actions github-actions released this 21 Sep 00:43

按社区目录审核反馈修正 manifest 描述。

  • 描述改为以 ASCII 句点结尾(全角句号不被识别为标点)

功能无变化。

1.1.0

Choose a tag to compare

@github-actions github-actions released this 21 Sep 00:11

设置页改用 Obsidian 1.13 起的声明式设置接口。

  • 这些设置项现在会出现在 Obsidian 自己的设置搜索里
  • 「当前状态」信息改用描述片段渲染;原来的三个按钮改成动作项(点整行执行)
  • 官方 eslint 检查已完全清零(0 error / 0 warning)
  • 代码层面:内部结构改用类型声明(新增 src/internals.ts),解析不可信 JSON 全走 unknown + 收窄

注意:因为声明式设置接口只在 1.13 起存在,本版本起要求 Obsidian 1.13.0+(1.11 / 1.12 无法安装)。