文件启动
文件启动(File Launch)将系统描述从代码中解耦,用 YAML 文件描述系统架构和运行参数。
两文件模型
| 文件 | 时机 | 说明 |
|---|---|---|
架构文件 arch.yaml |
编译期 | 声明节点、资源、节律、拓扑,修改需重编译 |
参数文件 params.yaml |
运行时 | 配置节点 / 资源 / 节律参数,修改仅需重启 |
这样分离的原因:架构信息驱动 codegen(类型、连接关系),而参数只影响运行时行为。
架构文件
default_crate: my_lib # 可选,短名自动补全为 my_lib::ClassName
resources:
- id: shared_buf
type: "res::RingBuffer<SensorData>"
rhythms:
- id: timer
type: SysTimer
nodes:
- id: sensor
class: SensorNode
rhythm: timer
refs:
buffer: shared_buf
- id: filter
class: FilterNode
rhythm: timer
depends_on: [sensor]
hard: true
topology:
- from: sensor
to: filter
字段说明
顶层:
default_crate— 可选,不含::的短类名自动拼接此前缀(如SourceNode→my_lib::SourceNode)
nodes:
id— 实例名class— Rust 类型路径(必须已用#[node]标注)rhythm— 所属节律(引用rhythms中的 id)depends_on— 初始化依赖(影响拓扑排序,非数据流)refs— 资源引用映射:字段名 → 资源 id(与#[resource]字段对应)hard— 权重标记,标记后调度器会优先保持其与依赖项的串行
resources: id + type(类型路径字符串)
rhythms: id + type(节律驱动类型路径)
topology: from / to(支持 node.port.subport 格式)
验证
ArchConfig::from_yaml() 自动进行以下验证:
- 节点
rhythm引用的节律必须存在 - 节点
refs引用的资源必须存在 depends_on引用的节点必须存在- 依赖图不能存在环(Kahn 算法)
参数文件
resources:
shared_buf:
capacity: 64
rhythms:
timer:
period_ms: 10
nodes:
sensor:
sample_rate: 1000
filter:
alpha: 0.5
节点参数通过 #[param] 字段接收:
使用入口
方式一:#[system(file = "arch.yaml")]
在系统函数上标注文件路径,节点类型需在同 crate 或通过 use 引入:
use file_launch::{SourceNode, DoubleNode, PrinterNode};
use roplat::Node;
use roplat::rhythm::Rhythm;
#[roplat::system(file = "arch.yaml")]
async fn main() {
let __params = roplat::roplat_launch::ParamConfig::from_yaml(
&std::fs::read_to_string("params.yaml").unwrap()
).unwrap();
let __registry = roplat::roplat_launch::ResourceRegistry::new();
}
宏在编译期读取 YAML,生成 DSL 语句追加到函数体,再经 parser → graph → schedule → codegen 管线生成最终代码。
方式二:cargo roplat generate + cargo roplat run
纯 YAML 驱动,自动生成含 use 块的 .rs 入口文件:
# 生成到 roplat/ 目录(mtime 检测,arch 未变则跳过)
cargo roplat generate arch.yaml
# 生成 + 编译 + 运行(params 是运行时参数,换文件不触发重编译)
cargo roplat run arch.yaml params.yaml
# 强制重新生成
cargo roplat generate arch.yaml --force
生成的文件结构:
project/
├── Cargo.toml # 需添加 [[bin]] target
├── src/lib.rs # 节点定义(pub)
├── arch.yaml
├── params.yaml
└── roplat/
└── arch.rs # @generated — 含 use 块 + system fn
Cargo.toml 中需注册 [[bin]]:
变更检测:generate / run 通过 mtime 比较 arch.yaml 与 roplat/arch.rs,arch 未变则跳过生成,cargo 也会跳过编译。params.yaml 是运行时参数,更换不触发重编译。
方式三:cargo roplat codegen(预览代码)
其他子命令
内部管线
roplat_launch
共享库,被 roplat_macros(codegen feature)和 cargo-roplat 同时依赖:
| 模块 | 职责 |
|---|---|
arch |
ArchConfig 解析 + 验证 + 拓扑排序 + resolve_type_path / collect_use_paths |
param |
ParamConfig 运行时参数 |
registry |
ResourceRegistry 类型安全资源容器 |
codegen |
arch_to_tokens() + generate_full_file() — ArchConfig → 完整 .rs 文件 |
codegen 流程
arch_to_tokens() 生成的 TokenStream 与手写 DSL 等价:
- 资源声明 —
let mut buf = __registry.get::<_>("buf") - 节律声明 —
let mut timer = SysTimer::from_launch_params(...) - 节点声明 —
let mut sensor = SensorNode::__system_new_from_launch(...) - 拓扑连接 —
timer >> { sensor >> filter; }
生成后直接进入现有 system parser,复用全部调度和代码生成逻辑。
__system_new_from_launch
#[node] 宏自动生成此方法:
pub async fn __system_new_from_launch(
yaml_params: &roplat::serde_yaml::Value,
registry: &roplat::roplat_launch::ResourceRegistry,
refs: &[(&str, &str)],
) -> RoplatResult<Self>
字段分类:
#[param]→ 从 yaml_params 反序列化#[resource]→ 通过 refs 映射从 registry 取出#[state]→ 使用默认值
ResourceRegistry
类型安全的运行时资源容器,键为 (String, TypeId):
let mut reg = ResourceRegistry::new();
reg.insert("buffer", RingBuffer::<Data>::new(64));
// 获取 Arc 引用
let buf: Arc<RingBuffer<Data>> = reg.get("buffer").unwrap();
多个节点通过 refs 映射共享同一资源的 Arc。