跳到主要内容

第 10 课 · 记忆冷却

变量随运行而死。有些事实不该如此:"这个我已经收过了"必须活到下一次运行。

问题

一个按计划每几分钟跑一次的流程,收集的奖励却每个周期只刷新一次。一个周期收两次,第二次轻则白干,重则有害。

流程需要在每次运行开头问:最近的某次运行是不是已经干过这事了?——而答案必须活在任何单次运行之外

你将学到

  • Runtime Memory(运行时记忆):带名称、字段、作用域(scope)和 TTL 的条目。
  • Memory Match(记忆匹配):拿候选者对照每条已记住的条目。
  • 冷却守卫模式:先匹配,再记住,再动手
  • 为什么 TTL 胜过手动清理。

开始之前

  • 完成第 09 课。OCR 开关开着(我们要读周期计时)。

流程

编辑器中的第 10 课流程

徽标按阅读顺序给节点编号,与动手构建步骤对应。顶部的 Memory Match 才是重点——它决定下面的活儿到底跑不跑,于是冷却逻辑集中在一个节点里,而不是散落在整个流程中。

动手构建

  1. 新建流程;声明 cycle_minutesnumber?)。
  2. Read Text 1. Read the cycle timer 对着 Next Cycle 读数,类型 Time,分钟 → cycle_minutes
  3. Memory Match 2. Already collected this cycle?
    • Memorypa_collected_cycles
    • Expression{{item.cycle}} == {{cycle_minutes}}
  4. Runtime Memory 3. Remember this cycle (10 min TTL)
    • Operation remember,memory pa_collected_cycles,scope persist
    • 一个字段:cycle = {{cycle_minutes}}
    • TTL 600 秒
  5. Click 点 Collect,三个 Stop 如图(skippedsuccess——拒绝重复劳动是正确行为)。
  6. 连线:先匹配,再记住,再动手。 Save,Auto Layout。

两个记忆节点如何配合

Runtime Memory 写;Memory Match 读。 一条条目是个微型记录——这里是 {cycle: 20}——存在某个记忆名称之下、某个 scope 里、带一个寿命

部件此处缘由
名称 pa_collected_cycles两个节点共用的标签任一侧打错字 = 守卫永不命中
字段 cycle标识"这项工作"的东西匹配时写作 {{item.cycle}}
Scope persist活过运行结束应用重启正是重点——run 作用域立刻就忘
TTL 600 s条目自我删除冷却自动到期:没有清理流程,没有发霉的清单

Memory Match 对每条存活条目求值一次表达式,受测条目以 {{item.…}} 暴露。任一条目满足 → matched。过期条目干脆消失——TTL 就是把"永远记住"变成"恰好记住一个周期"的东西。

顺序就是一切:匹配 → 记住 → 动手。 若在匹配之前记住,每次运行都会命中自己刚写的新条目——流程永远跳过。若在记住之前动手,两步之间一次崩溃就收两次。安全顺序是先问、再占名、最后干活。

现实世界的容差

这里对分钟做等值比较就够了。真实的 OCR 坐标会漂移一两个像素——这正是 Memory Match 收整条表达式的原因:abs({{current_x}} - {{item.x}}) <= 1 匹配的是邻近,不是精确。毕业设计的守卫用的正是这一招的简化版。

运行

  1. 第一次:计时读到 04:20 → 无条目命中 → 记住 {cycle: 20} → Collect → success · Collected this cycle
  2. 立刻再跑:同样的读数 → matchedsuccess · Already collected this cycle。没有发生任何点击。
  3. 等十分钟(或改计时:dashboard.html?timer=07:15)再跑:旧条目已过期(或不匹配)→ 再次收集。

第 2 次运行就是本课的课义:一次不同的运行、几分钟之后,知道第 1 次运行做了什么。

最佳实践

  • 先匹配,再记住,最后动手。
  • TTL = 真实冷却时长,不是"图安全设长点"——太长会跳过正当的工作。
  • 单机守卫用 persist 只有所有实例必须尊重同一清单时才用 shared——那时它们互相覆盖对方的占名是设计使然。
  • 记忆名称要具体——pa_collected_cycles,不是 data
  • 跳过就是成功。 按成功来连线。

常见错误

错误后果修复
Remember 放在 Match 之前命中自己的条目;永远跳过先匹配
两个节点的记忆名不一致守卫永不命中两侧同一字符串
字段名 ≠ {{item.…}} 引用永不命中cycle{{item.cycle}}
不设 TTL条目堆积;冷却早过仍在跳过永远设 TTL
Scope 用 run运行一结束就忘——跨运行守卫失效persist

故障排查

从不跳过,哪怕背靠背运行。 先查两个节点的记忆名称,再查字段/item 名,再确认写入的 scope 不是 run

永远在跳过。 节点顺序——remember 排在了 match 上游。重连。

应用重启后意外地跳过。 那是 persist 在尽职:TTL 是绝对时间,跨重启存续。若你要的是"仅本次启动",session 作用域才是工具。

小结

  • Runtime Memory + Memory Match = 活得比运行更久、寿命自动到期的事实。
  • 守卫模式是一个顺序:匹配 → 记住 → 动手。
  • Scope 决定谁共享记忆;TTL 决定它约束多久。

下一课

单项技能你已集齐:操作、验证、读取、计数、重试、恢复、记忆。第 11 课 · 用子流程搭建是流程开始互相调用的地方——你的课程真正变成一座库。

另见:Runtime Memory · Memory Match · 变量与记忆