Skip to content

Pipeline Factory and Compilation: 四段式职责分离

> Aligned with PCR Master Blueprint v1.0 — see Blueprint §2.6.4(跨域判据:simulation/ 是唯一允许聚合所有子域的层)、§3.1(FCC 三层架构)、§3.3(FCC 隔离)、§7.3(双代数化)。 > 职责:本文是 FCC 预编译机制的唯一权威文档,规定 GNC pipeline 从工厂构造到运行期 tick 的完整四段式职责分离。 > 要解决的问题:FccEnv heavy(气动表 / 增益矩阵 / 卡尔曼参数 / 调度表)+ 10ms 热点循环 ⇒ 动态调度禁区。同时,必须保证 GNC 算法、pipeline 工厂、mission 映射、跨域 tick 四者互不知情,以满足 layer-agnostic 设计与 HIL 互换能力。 > C-Distillation note:四段在蒸馏后仍然存在,但 std::function 闭包退化为函数指针 + 静态绑定参数表;MissionProfile 是只读 PoD。


1. 问题陈述

如果不做四段式分离,最容易踩的两个反模式:

反模式 A — strategy 内部按相位查表

cpp
// ❌ 错:外壳每拍 cache thrashing
auto strategy = read_imu() >>= [](auto imu) {
    return get_state() >>= [imu](auto s) {
        auto tasks = env.schedule.tasks_for_stage(s.current_stage);  // map 查找
        for (auto& task : tasks) {
            switch (task.module) { /* 函数指针解引用 */ }
        }
    };
};

反模式 B — 把 FccStage→algorithm 映射放进 simulation/

cpp
// ❌ 错:违反 Blueprint §2.6.4 跨域判据
namespace sim::pipeline {
    InnerPipeline pick_pipeline(FccStage s, const FccEnv& env) {
        switch (s) {
            case FccStage::Boost1: return /* iterative guidance */;
            // ...
        }
    }
}

为什么 B 错FccStage 是 FCC 自己的概念,FccEnv 是 FCC 自己的环境,"哪个 stage 用哪个制导律"是 firmware mission 知识。simulation/ 是跨域绑定层,不该持有任何子域的内部决策。


2. 四段式职责分离

text
┌───────────────────────────────────────────────────────────────────┐
│ ① fcc/pipelines/        Pipeline 工厂(层级无知)                 │
│    make_iter_guidance_pipeline(FccEnv) → InnerPipeline            │
│    make_ballistic_pipeline(FccEnv)     → InnerPipeline            │
│    make_recovery_pipeline(FccEnv)      → InnerPipeline            │
│    make_standby_pipeline(FccEnv)       → InnerPipeline            │
│                                                                   │
│    工厂只知道:自己是哪套 GNC 算法、用什么速率                    │
│    工厂不知道:FccStage、Bus、simulation、runtime                 │
└───────────────────────────────────────────────────────────────────┘

                          │ 调用工厂、组装 per-stage 表

┌───────────────────────────────────────────────────────────────────┐
│ ② fcc/firmware/         MissionProfile + FccCompiler              │
│    MissionProfile { stage_to_factory[FccStage] = PipelineFactory }│
│    compile_fcc(profile, env) → CompiledFcc.per_stage[N]           │
│                                                                   │
│    这里知道:FccStage → 工厂的映射;这是 firmware mission 知识    │
│    这里不知道:Bus、simulation、runtime、Assembler                │
└───────────────────────────────────────────────────────────────────┘

                          │ 启动期一次调用

┌───────────────────────────────────────────────────────────────────┐
│ ③ runtime::Assembler    通用装配触发                              │
│    initialize():                                                  │
│      compiled_fcc_ = fcc::firmware::compile_fcc(mission, env);    │
│      fcc_tick_     = sim::pipeline::make_fcc_tick(compiled_fcc_); │
│                                                                   │
│    这里知道:"启动期调一次 fcc::firmware::compile_fcc"           │
│    这里不知道:FCC 内部结构、FccStage 语义、Bus 协议、GNC 算法   │
└───────────────────────────────────────────────────────────────────┘

                          │ const CompiledFcc& 传递

┌───────────────────────────────────────────────────────────────────┐
│ ④ simulation/pipeline/  FccTick(跨域 tick)                      │
│    run_one_tick(ctx, t):                                          │
│      1. Bus → FccInFrame                                          │
│      2. interpreter.run(flight_control_loop(compiled), ...)       │
│      3. FccOutFrame → Bus                                         │
│                                                                   │
│    这里知道:Bus 帧 ↔ FccFrame 转换;持有 const CompiledFcc&     │
│    这里不知道:FccStage→factory 映射、MissionProfile、GNC 算法   │
└───────────────────────────────────────────────────────────────────┘

每段的"知道 / 不知道"是强约束,越界等于反模式。


3. 各段详解

3.1 段 ① fcc/pipelines/ —— 层级无知工厂

工厂的核心契约:签名里不能出现 FccStage。这与 plant/physics/Drag 不知道自己装在哪个 body 完全同构。

目录结构

src/fcc/pipelines/
    iter_guidance_pipeline.h
    iter_guidance_pipeline.cpp
    ballistic_pipeline.h
    ballistic_pipeline.cpp
    recovery_pipeline.h
    recovery_pipeline.cpp
    standby_pipeline.h
    standby_pipeline.cpp
    InnerPipeline.h          ← using 别名 + SchedulePolicy 编译期常量

Inner pipeline 别名(统一接口):

cpp
// fcc/pipelines/InnerPipeline.h
namespace fcc {

using InnerPipeline = std::function<
    FccState(const FccState& /*prev*/,
             const ImuMsg&   /*imu*/,
             Time            /*now*/,
             Time            /*dt*/)
>;

// 编译期常量速率(替代 runtime schedule 查表)
namespace SchedulePolicy {
    struct IterGuidance {
        static constexpr bool nav_due(Time t)      { return t.ticks() % 1  == 0; }   // 100Hz
        static constexpr bool guidance_due(Time t) { return t.ticks() % 10 == 0; }   // 10Hz
        static constexpr bool control_due(Time t)  { return t.ticks() % 2  == 0; }   // 50Hz
    };
    struct Ballistic {
        static constexpr bool nav_due(Time t)      { return true; }
        static constexpr bool guidance_due(Time)   { return false; }  // 不制导
        static constexpr bool control_due(Time t)  { return t.ticks() % 2 == 0; }    // passive hold
    };
    // ...
}

}

工厂实现示例

cpp
// fcc/pipelines/iter_guidance_pipeline.cpp
InnerPipeline fcc::pipelines::make_iter_guidance_pipeline(const FccEnv& env) {
    // 烘焙:Env 切片 const& 捕获,零拷贝
    const auto& nav_cfg      = env.nav_config;
    const auto& guidance_cfg = env.guidance_config_iter;
    const auto& control_cfg  = env.control_config_att_pid;
    using Rates              = SchedulePolicy::IterGuidance;

    return [&nav_cfg, &guidance_cfg, &control_cfg]
           (const FccState& prev, const ImuMsg& imu, Time t, Time dt) -> FccState {
        FccState s = prev;
        if (Rates::nav_due(t))      s.nav      = step_nav(s.nav, imu, nav_cfg, dt);
        if (Rates::guidance_due(t)) s.guidance = step_guidance(s.guidance, s.nav, guidance_cfg, dt);
        if (Rates::control_due(t)) {
            auto [next_c, out] = step_control(s.control, s.guidance, s.nav, control_cfg, dt);
            s.control = next_c;
            s.pending_output = out;
        }
        return s;
    };
}

契约

  • 工厂签名是 InnerPipeline (*)(const FccEnv&)——没有 FccStage 参数
  • 工厂不引用 FccStage::* 任何枚举值
  • 工厂内的速率是编译期常量SchedulePolicy::*),不查 schedule map
  • 工厂内调用的 step_* 函数也不知道 FccStage

复用收益

  • 同一个 make_iter_guidance_pipeline 可被多个 stage 复用(如 Boost1 / Boost2 都用迭代制导)
  • HIL 测试中,可以直接用工厂构造单一 pipeline 跑闭环,不需要拉起整套 FccStage 机器
  • 单元测试中,工厂构造的 InnerPipeline 是纯函数闭包,喂 (FccState, ImuMsg, Time, Time) 即可验证

3.2 段 ② fcc/firmware/ —— MissionProfile + FccCompiler

fcc/firmware/ 是 FCC 把"工厂集合"+"FccStage 状态机"装配成可运行 artifact 的层。它是 intra-FCC 的——不依赖任何 FCC 之外的代码。

目录结构

src/fcc/firmware/
    MissionProfile.h
    MissionProfile.cpp
    FccCompiler.h
    FccCompiler.cpp
    CompiledFcc.h
    mission_loader.h        ← YAML → MissionProfile 解析器(可选)
    mission_loader.cpp

MissionProfile

cpp
// fcc/firmware/MissionProfile.h
namespace fcc::firmware {

using PipelineFactory = InnerPipeline(*)(const FccEnv&);

constexpr size_t kFccStageCount = static_cast<size_t>(FccStage::_Count);

struct MissionProfile {
    std::array<PipelineFactory, kFccStageCount> stage_to_factory;
    // 可扩展:mission-level overrides (e.g., abort behavior, transition tables)
};

MissionProfile default_mission();
std::optional<MissionProfile> load_mission_from_yaml(std::string_view path);

}
cpp
// fcc/firmware/MissionProfile.cpp
MissionProfile fcc::firmware::default_mission() {
    using namespace fcc::pipelines;
    MissionProfile p{};
    p.stage_to_factory[(size_t)FccStage::PreLaunch]       = make_standby_pipeline;
    p.stage_to_factory[(size_t)FccStage::Boost1]          = make_iter_guidance_pipeline;
    p.stage_to_factory[(size_t)FccStage::Coast1]          = make_ballistic_pipeline;
    p.stage_to_factory[(size_t)FccStage::Boost2]          = make_iter_guidance_pipeline;
    p.stage_to_factory[(size_t)FccStage::Coast2]          = make_ballistic_pipeline;
    p.stage_to_factory[(size_t)FccStage::Reentry]         = make_ballistic_pipeline;
    p.stage_to_factory[(size_t)FccStage::TerminalDescent] = make_recovery_pipeline;
    p.stage_to_factory[(size_t)FccStage::Landed]          = make_standby_pipeline;
    p.stage_to_factory[(size_t)FccStage::Aborted]         = make_standby_pipeline;
    return p;
}

CompiledFcc

cpp
// fcc/firmware/CompiledFcc.h
namespace fcc::firmware {

struct CompiledFcc {
    std::array<InnerPipeline, kFccStageCount> per_stage;
};

}

FccCompiler

cpp
// fcc/firmware/FccCompiler.cpp
CompiledFcc fcc::firmware::compile_fcc(const MissionProfile& profile, const FccEnv& env) {
    CompiledFcc out;
    for (size_t i = 0; i < kFccStageCount; ++i) {
        PipelineFactory f = profile.stage_to_factory[i];
        // 缺失工厂回退到 standby,避免 nullptr
        out.per_stage[i] = f ? f(env) : fcc::pipelines::make_standby_pipeline(env);
    }
    return out;
}

契约

  • 依赖:fcc/pipelines/ + fcc/state/(FccStage、FccEnv、InnerPipeline);依赖 simulation/runtime/bus/
  • MissionProfile 是数据;可硬编码、可 YAML 加载、可外部脚本生成
  • FccCompiler 是无业务的单一职责:调表 + 调工厂

HIL / Mission 互换收益

  • 换 mission:换 MissionProfile 即可;不动工厂、不动外壳、不动 Assembler
  • 切实飞 FCC:MissionProfile 嵌入芯片 firmware;simulation 完全不知道映射细节

3.3 段 ③ runtime/Assembler —— 通用装配触发

runtime::Assembler 是 Blueprint §3 已经规定的薄装配层。它只负责"启动期调一次 compile",不持有业务定义。

cpp
// runtime/Assembler.cpp(概念片段)
void Assembler::initialize() {
    // ... 加载 environment / plant assets / 构造 FccEnv / 构造 RocketBody ...

    // 拿 mission profile(来源不重要:硬编码、YAML、CLI 参数)
    auto mission = fcc::firmware::default_mission();
    //   或者 auto mission = fcc::firmware::load_mission_from_yaml(yaml_path).value();

    // 启动期调一次——Assembler 不知道编译内部干啥
    compiled_fcc_ = fcc::firmware::compile_fcc(mission, fcc_env_);

    // 装入 simulation 层的 tick 结构
    fcc_tick_ = sim::pipeline::make_fcc_tick(compiled_fcc_,
                                              fcc_interpreter_,
                                              bus_,
                                              fcc_state_,
                                              fcc_env_);
}

契约

  • Assembler 不知道 FccStage / FccOp / InnerPipeline 的内部结构
  • Assembler 不知道 MissionProfile 的字段——它只是把对象往下传
  • Assembler 是唯一调用 fcc::firmware::compile_fcc 的地方(除测试外)

层级薄性收益runtime/ 永远只做"启动 / 装配 / runtime loop / 退出"四件事;新增 FCC 功能不会污染 runtime/。


3.4 段 ④ simulation/pipeline/FccTick —— 每拍跨域 tick

这是唯一允许同时引用 bus/fcc/runtime/ 的代码位置(Blueprint §2.6.4 跨域判据)。

cpp
// simulation/pipeline/FccTick.h
namespace sim::pipeline {

struct FccTick {
    const fcc::firmware::CompiledFcc&  compiled;
    fcc::FccInterpreter&               interpreter;
    bus::IBus&                         bus;
    fcc::FccState&                     state_ref;
    fcc::FccEnv&                       env_ref;
};

FccTick make_fcc_tick(const fcc::firmware::CompiledFcc& c,
                       fcc::FccInterpreter& i,
                       bus::IBus& b,
                       fcc::FccState& s,
                       fcc::FccEnv& e);

void run_one_tick(FccTick& ctx, Time t);

}
cpp
// simulation/pipeline/FccTick.cpp
void sim::pipeline::run_one_tick(FccTick& ctx, Time t) {
    // 1. 跨域:Bus → FccInFrame
    auto in_frame = bus::decode_to_fcc_in(ctx.bus, t);

    // 2. 顶层策略:跨所有 stage 共用同一段 monadic 代码
    auto strategy = fcc::flight_control_loop(ctx.compiled);

    // 3. 解释执行
    ctx.interpreter.run(strategy, ctx.env_ref, ctx.state_ref, in_frame, t);

    // 4. 跨域:FccOutFrame → Bus
    bus::encode_from_fcc_out(ctx.state_ref.pending_output, ctx.bus, t);
}

契约

  • FccTick 持有 const CompiledFcc&——不构造、不修改
  • FccTick 内部禁止 switch (state.current_stage)——派发完全由 compiled.per_stage[] 自动完成
  • FccTick 不知道 mission profile、不知道 FccStage→factory 映射

跨域职责

  • Bus 上的字节流 ↔ FccInFrame 结构化字段
  • FccOutFrame.events ↔ Bus 上的 DiscreteEvent

4. 依赖图与禁忌

4.1 依赖方向(只允许的箭头)

fcc/algorithms/  ◄── fcc/pipelines/   ◄── fcc/firmware/   ◄── runtime/Assembler


                                                       simulation/pipeline/FccTick

                                                              ├──► bus/
                                                              ├──► fcc/free_monad/  (解释器)
                                                              └──► fcc/firmware/    (只读 CompiledFcc)

4.2 禁忌矩阵

越界类型例子违反修正
工厂引用 FccStagemake_iter_guidance_pipeline(env, FccStage::Boost1)段 ① 层级无知移除 stage 参数;让 fcc/firmware/MissionProfile 决定
firmware 引用 Bus#include "bus/IBus.h" in fcc/firmware/段 ② intra-FCCBus 在段 ④
Assembler 写映射compiled.per_stage[Boost1] = ... in runtime/段 ③ 薄性把映射移到 fcc/firmware/MissionProfile
simulation 决策 stageif (stage == Boost1) call X in sim/段 ④ 跨域判据派发用 compiled.per_stage[] 自动选择
GNC 算法读 FccStageif (stage == Boost) gain *= 2 in step_control算法 ↔ 相位解耦把 stage 相关增益写进不同的 control_cfg 切片,由不同工厂选用
外壳每拍读 Envstrategy 内部 get_env() >>= ... 做查表段 ④ 性能契约Env 切片已烘焙进闭包;GetEnv 算子在预编译模式下退化

5. 与 dynamics 侧的对比

dynamics 侧采用同构但不完全对称的四段式(合理的非对称,源于 Blueprint §2.6.4 跨域判据):

FCCDynamics
① 工厂fcc/pipelines/make_*_pipelineintra-FCC,层级无知simulation/pipeline/factories/make_*_body_pipeline跨域工厂,已在 simulation/)
② 编译fcc/firmware/compile_fccsimulation/pipeline/compile_dynamics
③ 触发runtime::Assembler 启动期runtime::Assembler 启动期
④ ticksimulation::pipeline::FccTicksimulation::pipeline::BodyTick + WorldTick
编译产物CompiledFcc.per_stage[FccStage]CompiledDynamics.per_stage[WorldStage]
派发compiled.per_stage[s.current_stage]compiled.per_stage[body.world_stage]

> 注 1:为什么 dynamics 的"段 ①"已经在 simulation/,而 FCC 的段 ① 在 fcc/pipelines/? > > FCC 的 inner pipeline 是intra-FCC(只调 fcc/algorithms/ 的 step_nav / step_guidance / step_control),因此整个段①可以保持层级无知地放在 fcc/pipelines/。 > > dynamics 的 body pipeline 工厂内部要调用: > - plant::physics::compute_thrust_contribution (plant/) > - plant::physics::compute_aero_contribution (plant/) > - plant::physics::compute_gravity_contribution(plant/ + environment/) > - dynamics_core::ode::integrate_rk4 (dynamics_core/) > > 这是跨多个子域的组合。Blueprint §2.6.4 判据:跨域 → simulation/。因此 dynamics 没有"intra-domain 工厂"层——它的整个段①从一开始就在 simulation/pipeline/factories/。 > > 这个不对称是正确应用判据的结果,不是架构裂痕。两侧的派发结构(compiled.per_stage[stage])保持对称,仅工厂归属不同。

详见 05_Dynamics_Core/Topology_Algebra.md §6。


6. 测试策略

测试目标级别描述
工厂层级无知unitgrep fcc/pipelines/ 不得包含 FccStage::
工厂闭包纯净unit喂同样 (prev, imu, t, dt),多次调用结果完全一致
MissionProfile 完整性unit每个 FccStage 必须有非空 factory(或 make_standby_pipeline 兜底)
编译产物可重入unitcompile_fcc 输入相同 (profile, env) 输出等价闭包行为
Assembler 薄性structuralgrep runtime/Assembler.cpp 不得包含 FccStage:: / step_nav
FccTick 跨域纯净structuralgrep simulation/pipeline/FccTick.cpp 不得包含 FccStage::Boost1 字样
Per-stage 派发正确integration注入 FccState.current_stage = Coast1,验证调用的是 make_ballistic_pipeline 构造的闭包
Mission 互换integration替换 MissionProfile,相同 FccState 跑出不同 controls

7. C-Distillation 视角

C++ 阶段:

  • 工厂返回 std::function<FccState(...)>
  • InnerPipelinestd::function
  • CompiledFcc.per_stagestd::array<std::function<...>, N>

C 蒸馏阶段:

  • 工厂返回 struct InnerPipeline { fp; bound_env_ptrs[]; }——函数指针 + 静态绑定参数
  • InnerPipeline 是 PoD
  • CompiledFcc.per_stagestatic const InnerPipeline per_stage[N],整张表在 .rodata
  • 派发是 per_stage[stage].fp(per_stage[stage].bound, prev, imu, t, dt),零分配

详见 Static_Compilation_FSM.md08_Cross_Cutting/Hardware_Decoupling.md


8. Wave 落地计划

当前代码状态:

  • src/fcc/FccCore.h 是 7 行 stub
  • src/fcc/free_monad/ 实现了 8 个 op + 解释器 + std::any visitor
  • src/fcc/pipelines/src/fcc/firmware/src/simulation/pipeline/FccTick.* 尚不存在

Wave 1 落地顺序:

  1. 创建 src/fcc/pipelines/InnerPipeline.h(别名 + SchedulePolicy)
  2. 创建 src/fcc/pipelines/standby_pipeline.{h,cpp}(最小可工作工厂)
  3. 创建 src/fcc/firmware/{MissionProfile,FccCompiler,CompiledFcc}.{h,cpp}
  4. 修改 src/runtime/Assembler.cpp 调用 fcc::firmware::compile_fcc
  5. 创建 src/simulation/pipeline/FccTick.{h,cpp}
  6. 逐步把 iter_guidance / ballistic / recovery pipeline 落地

每步可独立验证:上一步落地后,可用 make_standby_pipeline 跑通整条数据流,再逐步替换。


9. Cross References

  • 跨域判据 → Blueprint §2.6.4
  • Strategy 写法 + O(1) 派发 → Free_Monad_DSL.md §5 / §7
  • GNC 三模块 Mealy → FCC_State_Machine.md §2
  • FccStage 代数 → FCC_State_Machine.md §3
  • 静态编译 FSM 总览 → Static_Compilation_FSM.md
  • WorldStage 双代数化 + per-WorldStage 预编译 → 05_Dynamics_Core/Topology_Algebra.md §5 / §6
  • BodyRWS pipeline 跨域实例化 → 06_Simulation/Body_Tick.md(待写)/ Blueprint §2.6.4
  • 解释器 → Interpreter_and_RWS.md
  • Bus 装包 → 03_Avionics_and_Bus/Semantic_Bus_Pattern.md §9