Skip to content

Assembler & Runner — 生命周期管理

> Status: NEW · 已对齐 PCR Master Blueprint v1.0 §2.7 > 范畴: runtime/{Assembler, Runner, SimulationInstance}.{h,cpp} > 依赖: simulation(唯一) > 被依赖: 各 main/*.cpp


1. 问题陈述

runtime/ 在 v0 中持有了业务定义——WorldEnv 直接定义在 runtime/WorldEnv.h、装载大气模型 lambda、持有 BusMonitor 实例。这违背了"runtime 是纯生命周期管理者"的原则,导致:

  • runtime 被强制依赖 plant/bus/environment 的实现细节
  • HIL 部署时无法切片(runtime 装的"全部"必须 enter 进程)
  • main 函数互相重复装配代码

v1.0 裁定(§7.18)runtime/ 退化为非常薄的一层,只负责:

  1. 装配(Assembler):读 YAML → 构造冻结的 sim::WorldEnv + 初始 sim::WorldState
  2. 运行(Runner):拥有 main loop,反复调用 sim::world_tick
  3. 退出:析构 RAII 资源、Flush 日志

业务定义全部归 simulation/(WorldEnv/WorldState/RocketBody)或下游业务库(plant/env/bus/...)。


2. SimulationInstance:装配产物

cpp
// runtime/SimulationInstance.h
namespace runtime {

struct SimulationInstance {
    sim::WorldEnv    env;          // 装配后冻结(指针稳定)
    sim::WorldState  state;        // 初始 t=0 状态(可被 Runner 演化)
    PlantScope       scope;        // 部署切片
    LogConfig        log_config;   // 日志策略(路径、采样率)
};

} // namespace runtime

关键属性

  • env 是 Reader,构造后 Runner 不再写入。
  • state 是 Runner 的循环变量(实际由 world_tick 演化)。
  • scope 在 Runner 构造时决定信道选择(详见 IDynChannel_SIL_HIL.mdPlantScope.md)。
  • log_config 不属于 WorldEnv(业务无关),保留在 SimulationInstance 内由 Runner 解释。

> 反例:把 LogConfig 塞进 sim::WorldEnv 中。 > 正解LogConfig 是 runtime 的事,不污染业务装配产物。


3. Assembler:装配流水线

3.1 接口

cpp
// runtime/Assembler.h
namespace runtime {

class Assembler {
public:
    // 唯一入口:从 run_xxx.yaml 装配整个 SimulationInstance
    SimulationInstance assemble(const std::string& run_config_path);

private:
    // 装配期局部状态(RAII,装配结束析构)
    std::unordered_map<std::string, uint32_t> path_index_;

    // 阶段函数(按顺序调用)
    void load_environment_(const YAMLNode& root, sim::WorldEnv& env);
    void load_plant_assets_(const YAMLNode& root, sim::WorldEnv& env);
    void instantiate_world_state_(const YAMLNode& root,
                                  const sim::WorldEnv& env,
                                  sim::WorldState& state);
    void load_scope_(const YAMLNode& root, PlantScope& scope);
    void load_log_config_(const YAMLNode& root, LogConfig& log);
};

} // namespace runtime

3.2 装配阶段

#阶段输入输出关键规则
load_environment_world/earth.yamlworld/atmosphere.csvenv::Atmosphere/GravityField/WindField 写入 WorldEnv大气模型用强类型对象(不是 std::function
load_plant_assets_rocket_v1/*.yamlfcc_v1/aero/*.yamlplant::model::*Spec vector 写入 WorldEnv.plant_assets必须 reserve(N),禁止后续 push_back(指针稳定性)
instantiate_world_state_mission.yaml(初始位置/姿态/拓扑)sim::RocketBody × N,spec 指针指向 ② 的 vectorRocketBody 持有 spec 的非拥有指针(参考 06_Simulation/RocketBody_Composite.md §3)
load_scope_sim/run_xxx.yaml: plant_scopePlantScope 结构选择 dyn/bus 信道类型
load_log_config_sim/run_xxx.yaml: loggingLogConfig 结构日志路径、是否按 stage 分流

3.3 装配流程图

assemble(run_xxx.yaml):
  ┌────────────────────────────────────────────────┐
  │ A. YAML 解析(io::YAMLReader 全图加载)       │
  │    ├─ world.yaml (env)                         │
  │    ├─ rocket_v1.yaml (plant assets)            │
  │    ├─ fcc_v1.yaml (controller assets)          │
  │    ├─ mission.yaml (initial conditions)        │
  │    └─ run_xxx.yaml (deployment + logging)      │
  ├────────────────────────────────────────────────┤
  │ B. ① load_environment_                         │
  │    env::Atmosphere atm = parse_atm(...);       │
  │    env.atmosphere = std::move(atm);            │
  ├────────────────────────────────────────────────┤
  │ C. ② load_plant_assets_                        │
  │    env.plant_assets.engines.reserve(N);        │
  │    for each engine_yaml: emplace_back(...)     │
  │    (path_index_ 在此填充,装配结束析构)         │
  ├────────────────────────────────────────────────┤
  │ D. ③ instantiate_world_state_                  │
  │    state.bodies.reserve(M);                    │
  │    for each body in mission:                   │
  │      body.engines[i].spec = &env.assets[k];    │
  ├────────────────────────────────────────────────┤
  │ E. ④ ⑤ load_scope_ + load_log_config_          │
  └────────────────────────────────────────────────┘
  return SimulationInstance{env, state, scope, log_config}

3.4 关键不变量

不变量强度理由
Assembler 返回后 env 不再被 Assembler 修改必须Reader 语义;Runner 也必须遵守
Assembler 内的 path_index_ 在返回时析构必须Blueprint §7.7:path_index 装配期 RAII,禁止持久化
env.plant_assets.* vector 容量在 ② 之后冻结必须RocketBody 持有的 spec 指针稳定性
Assembler 不创建任何 Bus / DynChannel 实例必须信道实例属于 Runner(生命周期与 main loop 一致)
Assembler 不创建任何 RocketBody.fcc 内部状态必须FCC 实例在 Runner 选定部署后实例化
Assembler 不知道 bus::IBus 的具体实现必须scope 决定信道,不属于 env

> 反例:Assembler 内 env.bus = std::make_unique<TransparentBus>(...)。 > 正解:Runner 根据 scope.bus_kind 决定实例化哪种 IBus。

3.5 v0 → v1 迁移摘要

v0 行为v1 行为
runtime::assemble_run 返回 WorldEnv(含大气 lambda + bus)Assembler::assemble 返回 SimulationInstance(env + state + scope + log)
大气 = std::function<double(double)>env::Atmosphere 强类型对象(详见 02_Physical_World/Environment_Fields.md
BusMonitor 由 Assembler 装配并塞入 env.monitorBusMonitor 不存在(Writer Monad 内化进 BusLog
env.body_assetsstd::map<string, BodyAsset>env.plant_assetsstd::vector<*Spec>(路径稳定 + 索引派发)
LogConfig 在 WorldEnvLogConfig 在 SimulationInstance

4. Runner:main loop

4.1 接口

cpp
// runtime/Runner.h
namespace runtime {

class Runner {
public:
    explicit Runner(SimulationInstance instance);
    ~Runner();   // RAII 关闭信道、Flush 日志

    int run();   // 阻塞至仿真完成,返回 exit code

private:
    SimulationInstance        instance_;
    std::unique_ptr<bus::IBus>            bus_;          // scope.bus_kind 决定
    std::unique_ptr<IDynChannel>          dyn_channel_;  // scope.dyn_transport 决定
    sim::MultiRateScheduler   scheduler_;
    LogWriter                 log_writer_;
};

} // namespace runtime

4.2 构造期

cpp
Runner::Runner(SimulationInstance inst)
    : instance_(std::move(inst))
{
    // 1. 根据 scope 实例化信道
    bus_         = make_bus(instance_.scope);          // InMemory / 1553B / TTE
    dyn_channel_ = make_dyn_channel(instance_.scope);  // InMemory / Udp

    // 2. 调度器
    scheduler_.phys_period = instance_.env.scheduler_config.phys_period;
    scheduler_.fcc_period  = instance_.env.scheduler_config.fcc_period;

    // 3. 日志
    log_writer_.open(instance_.log_config);
}

关键:Runner 是第一个也是唯一一个实例化 bus/dyn_channel 的地方。一旦构造完成,BodyTick 内部对 IBus 的写入会被全局路由(见 06_Simulation/Body_World_Tick.md §3.2 C 阶段)。

4.3 main loop

cpp
int Runner::run() {
    auto& env   = instance_.env;
    auto& state = instance_.state;
    Time end_time = instance_.env.mission_config.end_time;

    while (state.current_time < end_time) {
        Time dt = scheduler_.phys_period;

        // 调用 simulation 层的 world_tick
        auto [wout, new_state, wlog] =
            sim::world_tick(dt).run(env, std::move(state));
        state = std::move(new_state);

        // 日志落地(Writer Monad 输出 → 文件)
        log_writer_.write(wlog);

        // 信道交互(HIL 模式下 send/recv DynFrame)
        if (dyn_channel_) {
            dyn_channel_->send(make_dyn_in_frames(wout));
            auto recv = dyn_channel_->recv();
            apply_dyn_out_frames(state, recv);
        }
    }

    log_writer_.flush();
    return 0;
}

4.4 关键不变量

不变量强度
Runner 只调用 sim::world_tick,不直接调用 dynamics::ode::rk4必须
Runner 只在 main loop 之外(构造/析构)实例化信道必须
Runner 不修改 env(Reader 语义)必须
state 是 Runner 的唯一可变载体必须
world_tick 的 Writer 输出在 main loop 内立即写盘(不积压)强烈建议

4.5 v0 → v1 迁移摘要

v0 行为v1 行为
closed_loop_10s.cpp 直接调用 PhysicalRegistry::tickRunner 调 sim::world_tick;PhysicalRegistry 被裁撤
main 函数自己拼装 dyn channelRunner 构造期通过 scope 装配
BusMonitor 通过 stdout 打印bus::BusLog(Writer)累加 → log_writer_.write() 写盘

5. 装配 → 运行的完整时序

main(argc, argv):
    runtime::Assembler asm;
    SimulationInstance inst = asm.assemble("data/input/sim/run_sil_monolithic.yaml");
    //   ├─ env: 冻结 (Reader)
    //   ├─ state: 初始拓扑 (RocketBody × N)
    //   ├─ scope: {dyn=InMemory, bus=InMemory, fcc=true, physics=true}
    //   └─ log_config: paths + filters

    runtime::Runner runner(std::move(inst));
    //   ├─ 实例化 InMemoryDynChannel
    //   ├─ 实例化 InMemoryBus
    //   ├─ scheduler 配置(phys=1kHz, fcc=50Hz)
    //   └─ 打开日志文件

    return runner.run();
    //   while (t < end): world_tick(dt); log; channel io
    //   析构: 信道关闭,日志 flush

详细每个阶段产物见 IDynChannel_SIL_HIL.mdPlantScope.md


6. 反模式

反模式后果正确做法
Assembler 内创建 bus::TransparentBus 并塞 env业务定义污染 runtime;HIL 切换困难信道由 Runner 构造期根据 scope 创建
WorldEnv 内包含 LogConfig业务装配与日志策略耦合;HIL 节点之间需共享 env 但日志策略不同LogConfig 在 SimulationInstance 内
path_index_ 升格为 WorldEnv::path_index 字段装配期局部状态泄漏到 Reader;冻结失败装配期 RAII(unordered_map 成员),结束析构
Runner 直接调 dynamics_core::rk4绕过 simulation 的 tick 编排;事件路由失效必须走 sim::world_tick
多个 main_*.cpp 各自重复装配代码漂移;测试不一致统一走 Assembler;变化的只是 run_xxx.yaml
装配后 vector push_back 新 spec指针失效;segfaultreserve(N) 在装配开头;之后只 emplace_back 不超容量
Runner 在 main loop 内 reopen 日志多次开关文件 IO;性能崩溃open 在构造,flush 在析构
Assembler 返回多个对象(std::pair<env, state>接口僵硬;增加 scope 字段需要改全部 caller统一 SimulationInstance 结构

7. C-Distillation 路径

C++ 抽象C 蜕化
SimulationInstance 结构体C struct(plain data)
Assembler::assemble 阶段函数静态 init array:init_env[], init_assets[], init_state[]
std::unique_ptr<bus::IBus>bus_kind enum + 静态 dispatch table(函数指针表)
std::unique_ptr<IDynChannel>dyn_transport enum + 静态 dispatch table
path_index_ unordered_map编译期 string→uint 表(codegen by Python)
YAML parse(runtime)offline codegen:YAML → header constexpr 数组
Runner::run while loop裸 RTOS task:while (t < end) world_tick_c(...)

参见 09_Cross_Cutting/C_Distillation.md(待写)与 Blueprint §7.12。


8. 测试策略

8.1 单元层

  • Assembler::assemble 给定一组小 YAML,验证 env.atmosphere.density_at(0) 与期望一致
  • path_index_assemble 返回后无法访问(编译期作用域)

8.2 组件层

  • Assembler × run_sil_monolithic.yaml:验证 state.bodies.size() == mission 配置 body 数、每个 body 的 spec 指针非空且指向 env 内
  • Runner::run(1 步):验证 state 在 dt 后推进 dt 时间

8.3 集成层

  • 三种部署 YAML(sil_monolithic / hil_dyn / hil_fcc)都能成功 Assembler::assemble + Runner ctor(不实际 run)
  • 1s 平凡仿真:Assembler + Runner.run() 退出码 0,日志文件非空

详见 08_Cross_Cutting/Testing_Framework.md


9. 引用

  • Blueprint §2.7(Runtime:生命周期管理)、§7.18(runtime 退化裁定)、§7.7(WorldEnv 严格只读 + path_index 装配期 RAII)、§7.15(Environment 与 Plant 严格分离)、§2.8(PlantScope)
  • 06_Simulation/WorldEnv_Assembly.md(被 Assembler ② ③ 调用的装配函数)
  • 06_Simulation/Body_World_Tick.md(Runner.run 主循环调用的 world_tick)
  • 07_Runtime/IDynChannel_SIL_HIL.md(信道实例由 Runner 构造)
  • 07_Runtime/PlantScope.md(scope 决定信道选择)
  • 07_Runtime/PCR_Configuration.md(YAML 文件结构)