Skip to content

PCR 重构蓝图:从脚手架到生产级 C 的演进路径


一、配置层:以物理真相为唯一出处的六层 YAML

按照您的洞察(engine_id 其实是 engine_type,bus 上只有 ICU 地址,分离事件决定 body 拓扑),配置层需要重新分层:

text
data/input/
├── world/                  # 环境真相(大气、引力、地球)
├── rocket_v1/
│   ├── rocket.yaml         # 装配清单(引用下层)
│   ├── electrical/
│   │   ├── bus.yaml        # 总线拓扑:列出所有 bus node 地址与类型
│   │   ├── icus.yaml       # 每台 ICU 的 bus 地址 + 它所控制的 engine_slot 列表
│   │   └── sensors.yaml    # IMU/GPS 的 bus 地址 + 噪声模型
│   ├── propulsion/
│   │   ├── engine_types/   # 型号库:YF-100.yaml, 小推力姿控.yaml...
│   │   │                   #   含: rated_thrust, Sa, P_ref, startup_dur, 所有曲线
│   │   └── engine_groups/  # 安装拓扑:composite.yaml、stage_1.yaml...
│   │                       #   每个 slot: { slot_id, engine_type, install: {...} }
│   ├── mass/               # 质量/惯量表,按 body 组织
│   ├── aero/               # 气动表,按 body 组织
│   └── separation.yaml     # 分离硬件清单(爆炸螺栓、推冲器的硬件属性)

├── dynamics_v1/            # ★ 新增层:动力学自己的"飞行阶段"真相
│   └── stages.yaml         # per-body 动力学阶段机,分离事件→新 body 集合+初始阶段

├── fcc_v1/                 # 飞控软件(已有)
└── sim/                    # Runner(已有)

关键新概念 dynamics_v1/stages.yaml

yaml
# 动力学阶段与 body 拓扑演化的真相
initial_bodies:
  - body_id: COMPOSITE
    stage: ASCENT_FIRST_STAGE
    composed_of: [stage_1_group, core_group, payload_group]  # 装配引用

events:
  - name: FIRST_STAGE_SEPARATION
    trigger: SeparationCommand(src=FCC, target=separation_bolt_0)
    transform:
      source_body: COMPOSITE
      produces:
        - body_id: STAGE_1_SPENT
          stage: DESCENT_BALLISTIC
          composed_of: [stage_1_group]
          inherit_state_from: COMPOSITE   # 空间状态继承
        - body_id: CORE
          stage: ASCENT_SECOND_STAGE
          composed_of: [core_group, payload_group]
          inherit_state_from: COMPOSITE

  - name: STAGE_1_CHUTE_DEPLOY
    scope: body=STAGE_1_SPENT    # ★ per-body 局部事件
    trigger: ...
    transform:
      source_body: STAGE_1_SPENT
      produces:
        - body_id: STAGE_1_SPENT
          stage: DESCENT_AERO_BRAKING   # 只换阶段,不拆分

这个文件同时解决了:分离事件定义、body 拓扑演化、per-body 独立阶段推进、阶段控制下的物理参数切换。


二、代码层:以真相流向为骨架的八个分层库

重构方案:按"真相类别 × 部署约束"来重构 src/,形成八个彼此独立可编译的静态库:

text
src/
├── truth/            # 【纯数据/数学】Vec3, Matrix, Quat, Time, Frame, 插值表
│                     # 无依赖。SIL/HIL 都链接。C-Distillation 的种子。

├── plant_model/      # 【Plant 真相】body/engine/icu/imu/separation 的纯数据结构
│                     # + YAML 装载。无状态机、无物理运算。仅依赖 truth。

├── plant_physics/    # 【Plant 物理法则】compute_thrust、compute_aero、compute_gravity
│                     # 纯函数,接受 PlantModel + 当前状态 -> Forces。
│                     # 仅依赖 truth + plant_model。

├── plant_hardware/   # 【Plant 硬件算子】engine state machine、ICU、IMU、GPS
│                     # 接受 BusMessage -> (HardwareOutput, BusMessage')
│                     # 仅依赖 truth + plant_model。

├── bus/              # 【总线抽象】BusMessage 定义 + Transport 接口
│                     # transports: MemoryTransport, UdpTransport, CanTransport
│                     # 通过编译期策略(template)或运行期 vtable 选择。

├── fcc/              # 【Controller】纯飞控软件。无物理表。
│                     # 只依赖 truth (向量数学) + bus (消息协议)。
│                     # ★ 可独立编译为 RTOS 目标!

├── dynamics_core/    # 【ODE + 拓扑演化】Integrator、StageMachine、TopologyEvolver
│                     # 消费 plant_physics + plant_hardware 的输出。
│                     # 不知道 FCC 存在,只认识 bus。

└── runtime/          # 【Runner】按部署模式装配所有库
    ├── sil_main.cpp       # = plant_* + dynamics_core + fcc + bus(Memory)
    ├── dyn_node_main.cpp  # = plant_* + dynamics_core      + bus(Udp)
    └── fcc_node_main.cpp  # =                         fcc  + bus(Udp)

这样三种部署场景的编译目标就是天然隔离的:

  • SIL 单机:sil_main 链接所有库
  • 动力学节点 HIL:dyn_node_main 根本不链接 fcc,因此永远不可能误用 FCC 代码
  • FCC 节点 HIL/实时机:fcc_node_main 根本不链接 plant_ /dynamics_core,实时机上不会有气动表占内存

三、Assembler 模式:解决 engine_type vs engine_slot 的核心

Assembler 是连接"配置真相"到"运行态"的关键一环:

cpp
// plant_model 层
struct EngineTypeLibrary {
    std::unordered_map<std::string, EngineTypeSpec> types;  // "YF-100" -> spec
};

struct EngineInstallation {
    uint32_t slot_id;              // 本 body 内的槽位(装配时分配)
    std::string engine_type;       // 指向 library
    InstallParams install;         // 安装角/偏移
};

struct IcuNode {
    uint32_t bus_address;          // ★ 真正的总线地址
    std::vector<uint32_t> controlled_slots;  // 它管的槽位
};

// 装配器(Plant 启动期一次性运行)
struct AssembledPlant {
    // 运行期热路径数据结构,全部 O(1) 直取
    std::vector<Body> bodies;
    std::unordered_map<uint32_t, const EngineTypeSpec*> slot_to_spec;
    std::unordered_map<uint32_t, uint32_t> slot_to_icu_bus_addr;
    std::unordered_map<uint32_t, std::vector<uint32_t>> icu_to_slots;
};

AssembledPlant assemble(const PlantConfig& cfg);  // 把所有 YAML 真相编译成运行态

关键设计点:

  1. engine_type 是字符串(人类可读,装配期使用),slot_idbus_address 是 uint32(运行期 O(1) 索引)
  2. 装配器是一次性纯函数,把字符串查找全部干掉
  3. 分离事件触发时,Assembler 被再次调用(局部增量装配),产生新的 AssembledPlant 快照
  4. 运行态数据结构只有 uint32 索引,不再有任何字符串查找——这正是 C-Distillation 降级到 C 的基础

四、Stage 机制:per-body 的代数效应

cpp
// dynamics_core 层
enum class BodyStageTag { /* 从 stages.yaml 载入的阶段编号 */ };

struct BodyStageMachine {
    BodyStageTag current;
    // 从 YAML 编译而来的转移表
    std::vector<StageTransition> transitions;
};

// 纯函数签名:阶段只在收到事件时推进
BodyStageMachine step_stage(
    BodyStageMachine prev,
    const std::vector<PhysicsEvent>& events
);

推力管道因此感知阶段,但感知的是动力学域的阶段:

cpp
BodyRWS<ThrustAndMassFlux> compute_thrust_pipeline(
    std::vector<EngineEffect> hw_effects
) {
    return body_ask() >>= [=](const BodyEnv& env) {
        // BodyEnv 携带了该 body 当前的 stage
        // stage 决定使用哪套偏差表、哪套气压补偿规则
        auto stage = env.body_stage;   // 干净地通过 Reader 传入
        auto perturb_table = env.asset.stage_perturb[stage];
        // ... 计算
    };
}

body 间阶段相互独立就自然成立了——每个 body 独立跑 BodyRWS,各自读自己 BodyEnv.body_stage


五、HardwareState 优雅地进入 Thrust pipeline

硬件状态不是通过污染积分状态进入的,而是三相管线:

cpp
// 一个 world tick = 三相监子链
WorldRWS<std::monostate> world_tick(Time dt) {
    return  // Phase 1: Hardware evaluation (Plant 算硬件)
            world_for_each_body([dt](Body& b) {
                return rocket_step(b, dt);   // 产生 HardwareOutput
            })
        >>= // Phase 2: Physics evaluation (Plant 算力)
            world_for_each_body([](Body& b, HardwareOutput hw) {
                // hw 作为 BodyEnv 的一部分注入,由 Reader 单子携带
                return body_local([hw](auto& env) { env.hw = hw; }, 
                    compute_thrust_pipeline(hw.engine_effects)
                    >> compute_aero_forces()
                    >> compute_gravity());
            })
        >>= // Phase 3: Integration + topology (ODE + 拓扑演化)
            world_integrate(dt)
        >>= world_apply_topology_events()   // 分离事件在这里改变 body 集合
        >>= world_route_bus_messages();     // 总线路由到 FCC (or 发出 UDP)
}

HardwareOutput 不是 State,而是 Phase 1 产出、Phase 2 通过 Reader 注入的瞬时数据。


六、总线抽象:跨部署场景的性能无损方案

cpp
// bus 层
template <typename Transport>
class Bus {
public:
    void publish(BusMessage m);
    std::vector<BusMessage> drain(uint32_t subscriber_addr);
private:
    Transport transport_;
};

// 三种 Transport,编译期选择(零虚函数开销)
struct MemoryTransport { /* SPSC ring buffer */ };
struct UdpTransport    { /* asio, 背景线程 */ };
struct CanTransport    { /* socketcan */ };

三种部署场景对应三个 main.cpp,编译期决定 Transport 类型:

  • SILBus<MemoryTransport> — 零拷贝零序列化,性能接近直接函数调用
  • HIL(UDP)Bus<UdpTransport> — 消息编解码 + 网络 I/O 与物理计算异步
  • 上真总线Bus<CanTransport> — 同理

七、分离事件的完整生命线

  1. T=t₀:FCC Navigation 判定:高度/速度/时间达到分离条件
  2. T=t₀:FCC 发 SeparationCommand(target=bolt_icu_addr) 到 bus
  3. T=t₀+δ:bus 路由到 SeparationBoltICU
  4. T=t₀+δ:ICU 收到命令,更新自己的 HardwareState (armed→fired),产出 LifecycleEvent::BoltFired(body=COMPOSITE, plane=stage_sep_plane)
  5. T=t₀+δ:Phase 3 的 world_apply_topology_events 消费 LifecycleEvent
    • stages.yamlCOMPOSITE + BoltFired -> 产生 {STAGE_1_SPENT, CORE},各自初始 stage 已知
    • 调用 Assembler 增量装配出两个新 body 的运行态
  6. T=t₀+δ+dt:下一 tick,world_for_each_body 自然地并行遍历 2 个 body,每个 body 的 BodyEnv.body_stage 已经是新的,推力管道使用新的偏差表、新的气压补偿公式。

八、对您三项硬约束的逐一交付

约束本方案如何满足
物理真相 + 阶段化六层 YAML 用物理硬件分层;新增 dynamics_v1/stages.yaml 将 per-body 阶段机与分离事件统一为声明式真相;Assembler 把真相编译成 O(1) 运行态
多部署场景 + 零性能代价八层 src/ 各自静态库;三个 main.cpp 按链接决定部署;Bus 用模板 Transport 编译期绑定,消除虚函数;FCC 库独立可编译至 RTOS
函数式优雅WorldRWS 三相管线(Hardware→Physics→Integration+Topology)全部纯函数;HardwareOutput 通过 Reader 注入,不污染积分 State;Stage 作为代数数据随事件演化;Free Monad 为未来 HIL 的异步化打底

九、我认为的最深一层设计哲学

这个方案最内核的原则是:让 YAML 真相、C++ 类型、运行态数据结构、C-Distillation 输出,四者共享同一个同态

这意味着:当您未来做 C-Distillation 时,八个库里有七个要么天然是 C(truth, plant_model, bus::can_transport, fcc),要么可以机械提取(plant_physics, plant_hardware, dynamics_core)。只有 runtime 和模板化的 bus 部分需要手工降级——而这部分本就应该是各目标平台的 BSP(Board Support Package)。