通信¶
同一个周期里"先算这个、再用它算那个",和两台设备各按自己的节拍交换状态,是两种不同的需求。前者写图边,后者显式创建旁路。System 不会把图边自动换成队列或缓冲,也不会替跨域的状态交换决定语义。
先选数据关系¶
| 需求 | 用什么 | 语义 |
|---|---|---|
| 同一次计算依赖上一步的结果 | 图边 sense >> plan >> act |
类型化的值,由 System 安排顺序与所有权 |
| 独立的域读取对方的最新状态或指令 | Latest<T> |
可以跳过中间版本;每个读者有自己的三槽缓冲 |
| 按顺序处理每个成功入队的事件 | SpscQueue<T> |
单生产者单消费者;队列满时把值交回,由应用处理背压 |
| 外部线程发来的指令驱动任务 | 事件通道 / 信号 | 见事件与信号 |
| 大块只读数据 | FrameBuffer → seal() → SharedFrame |
写入时独占,封存后共享,复制句柄不复制数据 |
| 不同进程或机器 | IPC | 独立的传输、发现与消息边界 |
旁路类型都在 roplat::comm 下。端点在头段创建,交给节点或节律的构造函数,在 process / drive 里读写;旁路读写不产生图边。
Latest¶
let channel = roplat::comm::Latest::<u64>::new(1); // 参数是订阅者数量
let mut publisher = channel.publisher();
let mut subscriber = channel.subscriber(0);
publisher.publish(7);
assert_eq!(subscriber.latest(), Some(&7));
- 每个端点只能从通道里取一次,取出后端点持有通道的生存期。发布端唯一、不可复制:不同节点要发布不同含义的信息时,各开一个通道。
- 读者在读取时刻拿到最新版本,可能跳过中间的发布;不同读者在不同时刻读取,没有全局快照。
latest()返回的借用不能跨过下一次读取;需要保留时复制或克隆。- 端点不需要独占操作系统线程,可以由同一个异步任务里的节点持有。
完整的双臂例子见多设备与旁路。
共享大块数据¶
FrameBuffer 在 Rust 端分配并独占写入,seal() 消耗写权限,得到只读的 SharedFrame。克隆 SharedFrame 共享同一份数据,最后一个持有者释放时回收内存。仍有分配、引用计数和语言桥接的成本,不能称整条链路零开销。
跨进程:本机消息格式¶
typed IPC 的消息类型要求 Copy + NativeMessage。#[roplat_msg] 会生成相应的契约;手写的 #[repr(C)] 类型可以 #[derive(roplat::NativeMessage)]。引用、指针、String、Vec 这类类型不能通过这个契约。普通图边和进程内的旁路不要求 NativeMessage。
- 有填充的结构体逐字段编码并把间隙置零;无填充时可以直接借用原始字节。
- 这是本机内存布局和端序,不是跨架构的线上格式。
NativeMessage不保证任意字节都是合法值。 0.5 不校验bool等受限字段收到的位模式,非法值可能导致 Rust 未定义行为;长度检查不能替代这一点。不要把它用于接收不可信的外部字节。
IPC 是另一条字节传输路径,不能用进程内 Latest 的测试结果推断 IPC 的性能或类型安全。
外语对象¶
透明的跨语言消息以 Rust 定义的布局为准。外语创建的不透明对象(OpaqueData、SendOpaque、SharedOpaque)跨线程移动、共享或在指定线程析构,需要显式的契约;套一层 Arc 不会自动证明外部对象线程安全。见 C++ 与 Python 节点。