DEEP 10 · 第一辑 · 运动控制核心 · LEARN NOTES

收进学习手册 · 2026-09-11 · duck.whatled.com

解读 10 · robotd/intents.rs:意图层

解读对象:robotd/src/intents.rs(681 行)
需要的前置:消费方见解读 09;死手开关见解读 04

1. 槽、戳、和"多旧才算旧"

intents.rs 解决的问题是:多个客户端(语音、手柄、程序)同时跟一个 50 Hz 循环说话,循环又绝不能等客户端。方案是每个意图一个 ArcSwap 槽——原子指针槽,读者一次原子 load,写者一次原子 store,谁也堵不住谁(intents.rs:3-5)。真正的设计量在后面三句:

  1. twist 和 head 故意分槽(intents.rs:7-10)。合成一个槽要读-改-写才能更新一个字段,两个客户端——手柄开身体姿态、另一个程序转头——会静默互相覆盖。分开后每个槽实际单写者,"last-writer-wins"才字面成立。
  2. 每个槽都盖时间戳(intents.rs:12-13):Stamped<T> 就是值 + 微秒时间(intents.rs:44-48)。循环的真问题从来不是"值是多少"而是"它多旧了"——死手开关读的正是 twist_age。
  3. 冷启动即最旧:Intents::new 把 twist 戳在 0(intents.rs:264-267),任何客户端连上之前它已经"最大程度过期",死手开关从一开始就按住鸭子。测试 the_twist_starts_stale(intents.rs:610)钉住这一点。

2. 边沿、电平、事件:三种到达方式分三类

这个文件最值得学的是它拒绝用一种机制接所有请求:

哨兵值也有讲究:MODE_NONE = u8::MAX(intents.rs:22),注释一句话道破坑:0 是真实的模式码,和取值空间撞车的哨兵会让"切到行走"变成无声空操作。

3. 一个枚举救回来的第三种语义

PolicyChange(intents.rs:210-224)是"类型承载语义"的实例:线上是两个 Option,只能表达四种组合,而实际要说的有三件事——槽指到文件、全部回默认、重载但什么都不改。第三种是为"装了新策略集、配置本来就对、只是内存里的会话旧了"准备的;注释承认历史上把 Reload 和 ResetAll 混为一谈,"每次装新策略集都会把板子上的覆盖全部抹掉"。旁边 MAX_SKILLS = 30(intents.rs:111)说明上限为什么存在:不是防滥用,是让超限成为"带理由的拒绝"而不是被静默忽略的按钮。

4. "没按住"是两种状态

WheeeHold 三态是最小而完整的案例(intents.rs:128-135):Held(新鲜)、Released(活着的客户端说停)、Decayed(WHEEE_HOLD_FRESH = 300 ms 内没有再通知,intents.rs:119)。wheee 是一段按住就循环、松开才收尾的"骑乘"声音(源码称 ride),两种"没按住"对应两种结局:Released 在原地切断,Decayed 把骑乘放完——压成 bool 会直接丢掉 end 段,因为被切断的骑乘只看得见断掉的管道。测试 a_stale_hold_reads_as_decayed_not_released(intents.rs:577)按时间线走完三个状态。

你带走的收获

延伸

什么鸭 WHATDUCK · 复刻学习笔记 ← 全部手册 · 首页