Skip to content

PlantScope — 部署切片

> Status: NEW · 已对齐 PCR Master Blueprint v1.0 §2.8 > 范畴: runtime/PlantScope.hdata/input/sim/deployments/*.yaml > 依赖: contracts(BusAddr) > 被依赖: runtime/Assembler, runtime/Runner


1. 问题陈述

PlantPhysics 资产是全局唯一的真理(Blueprint §7.15)。但在不同部署中,本进程实际实例化什么会变:

部署本进程要不要 physics body?本进程要不要传感器真值采样?本进程要不要作动器效应?本进程要不要 FCC?
avionics_dry❌(冻结)✓(FCC 看到 dummy 传感数据)✓(FCC 命令进 mech FSM)
sil_monolithic
hil_dyn(plant 节点)
hil_dyn(fcc 节点)
hil_fcc(plant 节点)

反例:每种部署写一个独立的 WorldEnv 构造分支,把"实例化什么"硬编码。 正解:用 PlantScope 显式声明本进程切片,Runner 与 BodyTick 据此跳过不需要的步骤。


2. 结构定义

cpp
// runtime/PlantScope.h
namespace runtime {

struct PlantScope {
    // ─── 物理仿真切片 ───
    bool physics_bodies;     // 是否演化 RocketBody.spatial/inertial(ODE 积分)
    bool sensor_truth;       // 是否采样 IMU/GPS 真值(plant 真值→ noise → bus)
    bool actuator_effect;    // 是否计算 EngineEffect/FinDefl(mech FSM)
    bool fcc;                // 是否实例化 RocketBody.fcc 并运行 FccTick

    // ─── 信道选择 ───
    enum class TransportKind { InMemory, UdpServer, UdpClient };
    TransportKind dyn_transport;
    UdpConfig     udp_config;       // dyn_transport == Udp* 时有效

    enum class BusKind { InMemory, Bridge1553B, RealtimeBus };
    BusKind   bus_kind;
    BusBridgeConfig bus_bridge_config;   // bus_kind == Bridge1553B 时有效
    BusRtConfig     bus_rt_config;       // bus_kind == RealtimeBus 时有效

    // ─── 总线地址路由(本进程能访问哪些设备地址) ───
    std::vector<contracts::BusAddr> local_bus_addrs;
    std::vector<contracts::BusAddr> remote_bus_addrs;
};

} // namespace runtime

关键性质

  • 四个 bool 描述本进程承担哪些 plant 职责
  • dyn_transport / bus_kind 决定信道实例化
  • local_bus_addrs / remote_bus_addrs 决定 bus 路由:本地地址直接 InMemory 处理,远端地址通过 BusBridge 转发

3. 四种部署的 Scope 配置

3.1 sil_monolithic(默认 SIL)

yaml
# data/input/sim/deployments/sil_monolithic.yaml
plant_scope:
  physics_bodies:  true
  sensor_truth:    true
  actuator_effect: true
  fcc:             true
  dyn_transport:   InMemory
  bus_kind:        InMemory
  local_bus_addrs:  [ALL]
  remote_bus_addrs: []

含义:所有 plant 职责本地化,DynChannel + Bus 都内存零拷贝。最快路径用于调试 + CI 回归。

3.2 avionics_dry(FCC 模飞测试)

yaml
plant_scope:
  physics_bodies:  false   # ← 物理冻结
  sensor_truth:    true    # ← 但传感器真值仍采样(plant 静态)
  actuator_effect: true    # ← 设备 FSM 接 FCC 命令推进
  fcc:             true
  dyn_transport:   InMemory
  bus_kind:        InMemory

含义:火箭不动,但 FCC 跑全闭环。验证调度/状态机/总线时序。

BodyTick 行为(详见 06_Simulation/Body_World_Tick.md §10):

  • ①Avionics step:跑(包括 sensor 采样和 actuator FSM)
  • ②FCC tick:跑
  • ③Plant Physics:跑(计算 Forces)
  • ④Dynamics integrate:跳过(spatial/inertial 不更新)
  • ⑤DynOutFrame 封装:跑(取冻结值)

3.3 hil_dyn(plant 在本机,FCC 在远端)

plant 节点

yaml
plant_scope:
  physics_bodies:  true
  sensor_truth:    true
  actuator_effect: true
  fcc:             false       # ← FCC 在远端
  dyn_transport:   UdpServer   # ← 等待 FCC 连接
  udp_config:
    local_port: 31000
  bus_kind:        InMemory    # ← 远端 FCC 通过 BusBridge 看本机 Bus

fcc 节点(在另一台机器,可能跑同一 binary 但不同 YAML):

yaml
plant_scope:
  physics_bodies:  false
  sensor_truth:    false
  actuator_effect: false
  fcc:             true
  dyn_transport:   UdpClient
  udp_config:
    remote_host: "192.168.1.100"
    remote_port: 31000
  bus_kind:        Bridge1553B   # ← 真实 1553B 转换器

3.4 hil_fcc(FCC 在 RTOS,plant 在 SIL 仿真器)

plant 节点(SIL 仿真器):

yaml
plant_scope:
  physics_bodies:  true
  sensor_truth:    true
  actuator_effect: true
  fcc:             false
  dyn_transport:   InMemory      # ← FCC 与 plant 通过物理总线,不走 DynChannel
  bus_kind:        RealtimeBus   # ← 真实 1553B / TTE 直接驱动 FCC 板卡

fcc 节点(RTOS):跑独立 binary,由 plant 节点的 RealtimeBus 驱动。


4. PlantScope 在代码各层的解释

4.1 Runner 构造

cpp
Runner::Runner(SimulationInstance inst) : instance_(std::move(inst)) {
    const auto& scope = instance_.scope;
    bus_         = make_bus(scope);              // 据 bus_kind 决定
    dyn_channel_ = make_dyn_channel(scope);      // 据 dyn_transport 决定

    if (!scope.fcc) {
        // 各 body 的 RocketBody.fcc 应该已是空(mission YAML 控制实例化)
        // 这里只是 sanity check
    }
}

4.2 WorldTick(部署透明)

world_tick 本身不读 scope;它只调用 BodyTick。BodyTick 根据 RocketBody 内的 fcc.has_value() + scope.physics_bodies 来决定跳哪步。

cpp
// simulation/pipeline/BodyTick.cpp(伪代码)
BodyRWS<BodyOut> body_tick(Time dt) {
    return body_ask() >>= [dt](const BodyEnv& env) {
        // ... ① avionics step(条件:scope.sensor_truth || scope.actuator_effect)...
        // ... ② fcc tick(条件:body.fcc.has_value() && env.fcc_should_tick)...
        // ... ③ plant physics(总是跑,但 force computer 可能为 no-op) ...
        // ... ④ dynamics integrate(条件:env.scope.physics_bodies)...
        //     注:env.scope 是 BodyEnv 中的 PlantScope const& 引用
    };
}

实施细节:BodyEnv 需要持有 const PlantScope& scope 引用(来自 WorldEnv)。详见 06_Simulation/WorldEnv_Assembly.md §4 与下文 §5。

4.3 Bus 路由

cpp
// runtime/Runner main loop C 阶段(cf Body_World_Tick.md §3.2)
void route_bus_global_to_local(IBus& global, BusBuffer& local, const PlantScope& scope) {
    for (auto& msg : global.poll_all()) {
        if (in_set(scope.local_bus_addrs, msg.dst)) {
            local.publish(msg);
        }
        // remote_bus_addrs 的消息通过 BusBridge 转发(由 IBus 实现负责)
    }
}

5. WorldEnv 中的 scope 注入

cpp
// simulation/env/WorldEnv.h
namespace sim {

struct WorldEnv {
    // ... environment + plant_assets + scheduler_config + mission_config ...

    // 部署切片:装配期由 Assembler 写入,运行期只读
    runtime::PlantScope scope;
};

} // namespace sim

> 为什么 scope 进 WorldEnv 而不是 BodyEnv? > scope 全局唯一,所有 body 共享。BodyEnv 通过 world.scope 间接访问。

> 为什么 scope 进 WorldEnv 而不是 SimulationInstance? > 双层: > - SimulationInstance.scope:runtime 的副本(用于工厂选信道) > - WorldEnv.scope:simulation 的副本(用于 BodyTick 内部跳步) > 两者由 Assembler 同源装配,保证一致。

> 反例:每次 BodyTick 都从 SimulationInstance 中查 scope(破坏 BodyEnv 的纯输入语义)。 > 正解:scope 在 WorldEnv 内冻结,BodyEnv 引用之,BodyTick 是纯函数。


6. 不变量与契约

契约强度
PlantScope 在 Runner 构造后不可修改必须
WorldEnv.scopeSimulationInstance.scope 严格相等(Assembler 同源装配)必须
local_bus_addrs ∩ remote_bus_addrs == ∅必须
local_bus_addrs ∪ remote_bus_addrs == 所有 mission 配置的设备地址强烈建议
dyn_transport == InMemory ↔ 双向消息全部本地(仅 SIL / avionics_dry)推导关系
fcc=false 时各 RocketBody.fcc 必须为空必须
physics_bodies=false 时 ④Dynamics integrate 必须 skip必须

7. 反模式

反模式后果正确做法
PlantScope 中混入业务参数("用 PID 还是 LQR")部署配置膨胀;与 fcc_v1/control.yaml 重复业务策略归 fcc_v1/,scope 只描述实例化什么
if (scope.fcc) { compile_fcc_pipeline(...) } 在 BodyTick 内热路径分支 + 反复编译FCC 编译在 Assembler;BodyTick 仅查 body.fcc.has_value()
三个 main_*.cpp(sil / hil_dyn / hil_fcc)漂移一个 main + 三个 YAML
Runner 直接读取 scope.bus_kind 之外的字段决定逻辑分支部署切换逻辑分散部署逻辑集中在 ChannelFactory + Assembler ⑤ load_scope_
HIL 节点上 PlantScope 还配 physics_bodies=true 但实际不演化与 actual 行为不符;调试痛苦部署 YAML 严格反映 actual 切片

8. C-Distillation 路径

C++ 抽象C 蜕化
PlantScope 结构C struct(POD),bool → uint8_t
TransportKind / BusKind enumenum
std::vector<BusAddr> local_bus_addrs固定大小数组 + count
UdpConfig / BusBridgeConfig 嵌套结构嵌套 C struct
YAML 解析编译期 codegen:YAML → header static const PlantScope scope_xxx = {...};
工厂分发(switch case)#ifdef DEPLOYMENT_*

9. 测试策略

9.1 单元层

  • 四种 deployment YAML 各加载一次,验证字段正确

9.2 组件层

  • make_dyn_channel(scope) × 4 scope:返回正确具体类型
  • route_bus_global_to_local × scope:本地地址消息进 local,远端消息走 bridge

9.3 集成层

  • avionics_dry × 1s:state.bodies[0].spatial 保持不变(physics 冻结)
  • sil_monolithic × 1s:与 avionics_dry 对比,spatial 演化非平凡

10. 引用

  • Blueprint §2.8(PlantScope 定义)、§1.3(三种部署)、§7.18(runtime 退化)
  • 07_Runtime/Assembler_and_Runner.md(Assembler 装配 scope;Runner 构造时调工厂)
  • 07_Runtime/IDynChannel_SIL_HIL.md(信道工厂据 scope 分发)
  • 07_Runtime/PCR_Configuration.md(YAML 目录结构)
  • 06_Simulation/WorldEnv_Assembly.md(scope 字段在 WorldEnv 内)
  • 06_Simulation/Body_World_Tick.md §10(BodyTick 根据 scope 跳步)