解读 10 · robotd/intents.rs:意图层
解读对象:
robotd/src/intents.rs(681 行)
需要的前置:消费方见解读 09;死手开关见解读 04
1. 槽、戳、和"多旧才算旧"
intents.rs 解决的问题是:多个客户端(语音、手柄、程序)同时跟一个 50 Hz 循环说话,循环又绝不能等客户端。方案是每个意图一个 ArcSwap 槽——原子指针槽,读者一次原子 load,写者一次原子 store,谁也堵不住谁(intents.rs:3-5)。真正的设计量在后面三句:
- twist 和 head 故意分槽(intents.rs:7-10)。合成一个槽要读-改-写才能更新一个字段,两个客户端——手柄开身体姿态、另一个程序转头——会静默互相覆盖。分开后每个槽实际单写者,"last-writer-wins"才字面成立。
- 每个槽都盖时间戳(intents.rs:12-13):
Stamped<T>就是值 + 微秒时间(intents.rs:44-48)。循环的真问题从来不是"值是多少"而是"它多旧了"——死手开关读的正是twist_age。 - 冷启动即最旧:
Intents::new把 twist 戳在 0(intents.rs:264-267),任何客户端连上之前它已经"最大程度过期",死手开关从一开始就按住鸭子。测试the_twist_starts_stale(intents.rs:610)钉住这一点。
2. 边沿、电平、事件:三种到达方式分三类
这个文件最值得学的是它拒绝用一种机制接所有请求:
- 边沿(取走即无):
power是AtomicU8编码的请求,take_power_request用swap取走(intents.rs:539-545)。注释算过账(intents.rs:149-156):一次set_torque是每关节一笔总线事务,电平槽会让每拍都写十六次。mode_switch、shutdown、技能掩码同理,take_skills一次swap(0)(intents.rs:348-355)。 - 电平(新值顶旧值):twist、head、pose、mouth。嘴用
AtomicU64存f64的位(intents.rs:146),注释坦白:客户端死了嘴张着,"原型就是这样"。 - 事件队列:只有
chorale_heard用Mutex<Vec>(intents.rs:171)。理由在字段注释里:其他意图是电平,错过的值本来就该被顶掉;而信标是带到达时刻的事件,被 last-writer-wins 吃掉的一个节拍是相位锁永远平均不到的一拍。队列封顶 64,满了丢最旧的——积压说明循环没在跑,那只有最新的值得要(intents.rs:508-514)。
哨兵值也有讲究: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)按时间线走完三个状态。
你带走的收获
- 跨线程递送先分清三种语义:边沿取走、电平覆盖、事件排队——一种机制包打天下必然丢信息。
- 共享槽盖时间戳,"值多旧"和"值是多少"一样是一等公民;冷启动按"最旧"初始化。
- 哨兵值不要和真实取值空间重叠,
MAX当"无"比 0 安全;枚举而非多个 Option 承载互斥语义。 - 每个槽的单写者属性是设计出来的(分槽),不是碰运气。