Skip to content

文件启动

文件启动(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 — 可选,不含 :: 的短类名自动拼接此前缀(如 SourceNodemy_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() 自动进行以下验证:

  1. 节点 rhythm 引用的节律必须存在
  2. 节点 refs 引用的资源必须存在
  3. depends_on 引用的节点必须存在
  4. 依赖图不能存在环(Kahn 算法)

参数文件

resources:
  shared_buf:
    capacity: 64

rhythms:
  timer:
    period_ms: 10

nodes:
  sensor:
    sample_rate: 1000
  filter:
    alpha: 0.5

节点参数通过 #[param] 字段接收:

#[roplat::node]
struct FilterNode {
    #[param]
    alpha: f64,
    // ...
}

使用入口

方式一:#[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]]

[[bin]]
name = "arch"
path = "roplat/arch.rs"

变更检测generate / run 通过 mtime 比较 arch.yamlroplat/arch.rs,arch 未变则跳过生成,cargo 也会跳过编译。params.yaml 是运行时参数,更换不触发重编译。

方式三:cargo roplat codegen(预览代码)

cargo roplat codegen arch.yaml            # 输出到 stdout
cargo roplat codegen arch.yaml -o out.rs  # 输出到文件

其他子命令

cargo roplat validate arch.yaml params.yaml  # 验证文件
cargo roplat list-nodes                      # 列出可用节点

内部管线

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 等价:

  1. 资源声明 — let mut buf = __registry.get::<_>("buf")
  2. 节律声明 — let mut timer = SysTimer::from_launch_params(...)
  3. 节点声明 — let mut sensor = SensorNode::__system_new_from_launch(...)
  4. 拓扑连接 — 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