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 真相编译成运行态关键设计点:
engine_type是字符串(人类可读,装配期使用),slot_id和bus_address是 uint32(运行期 O(1) 索引)- 装配器是一次性纯函数,把字符串查找全部干掉
- 分离事件触发时,Assembler 被再次调用(局部增量装配),产生新的 AssembledPlant 快照
- 运行态数据结构只有 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 类型:
- SIL:
Bus<MemoryTransport>— 零拷贝零序列化,性能接近直接函数调用 - HIL(UDP):
Bus<UdpTransport>— 消息编解码 + 网络 I/O 与物理计算异步 - 上真总线:
Bus<CanTransport>— 同理
七、分离事件的完整生命线
- T=t₀:FCC Navigation 判定:高度/速度/时间达到分离条件
- T=t₀:FCC 发
SeparationCommand(target=bolt_icu_addr)到 bus - T=t₀+δ:bus 路由到
SeparationBoltICU - T=t₀+δ:ICU 收到命令,更新自己的 HardwareState (armed→fired),产出
LifecycleEvent::BoltFired(body=COMPOSITE, plane=stage_sep_plane) - T=t₀+δ:Phase 3 的
world_apply_topology_events消费LifecycleEvent- 查
stages.yaml:COMPOSITE + BoltFired-> 产生{STAGE_1_SPENT, CORE},各自初始 stage 已知 - 调用 Assembler 增量装配出两个新 body 的运行态
- 查
- 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)。