Vyntek

.avtg 往返:serialize_roundtrip

标准示例文档:源码清单、学习路线、逐段讲解、常见误区、双平台构建、真实输出与退出码。

用途

把两条 passthrough 工具组成的组存成 .avtg 再读回,校验组名、工具数与顺序不变。演示的是组包往返,不是算法执行。

读完应能回答:为什么 Load 必须拿到注册表?ok 与 diagnostics 各管什么?为什么顺序值得单独断言?

源码

  • examples/serialize_roundtrip.cpp —— 示例本体:建组、保存、读回、比对、清理(片段 1–6) · 查看完整源文件
  • include/vyntek/toolgroup/serialize.hpp —— LoadAvtg / SaveAvtg 与 LoadResult / SaveResult 定义 · 查看完整源文件
  • examples/CMakeLists.txt —— serialize_roundtrip 目标:链接 Vyntek::toolgroup · 查看完整源文件

学习路线

  1. 先想清楚存了什么:.avtg 存的是组结构与 type_id,不存工具代码,所以读回必须靠注册表重建(片段 1、2)。
  2. 再看待保存对象:SaveAvtg 只读组,返回 SaveResult;失败细节在 diagnostics(片段 3)。
  3. 然后看读回:LoadAvtg 产生一个全新的 shared_ptr<ToolGroup>(片段 4)。
  4. 最后看契约断言:工具名序列与组名(片段 5),以及示例产物的清理(片段 6)。

片段 1 · 取工具名序列(serialize_roundtrip.cpp:19-26)

查看完整源文件
std::vector<std::string> ToolNames(const ToolGroup & group) {
    std::vector<std::string> names;
    names.reserve(group.tools().size());
    for (const auto & tool : group.tools()) {
        names.push_back(tool ? tool->name() : std::string{});
    }
    return names;
}

讲解:

  • group.tools() 返回 const std::vector<std::unique_ptr<Tool>>&,按下标即声明顺序;这就是往返要比对的契约。
  • 用 tool ? tool->name() : std::string{} 防御空指针:结构不应有空元素,但比较函数不该因此崩。
  • 只取名字:名字是 .avtg 里稳定可读的标识,ToolId 可能因加载重建而变化。

片段 2 · 建组并装载两个工具(serialize_roundtrip.cpp:33-47)

查看完整源文件
    auto group = ToolGroup::Create("serialize-demo");
    if (!group) {
        std::fprintf(stderr, "ToolGroup::Create failed\n");
        return 1;
    }

    ToolRegistry registry;
    registry.RegisterBuiltinTools();
    for (const char * name : { "t1", "t2" }) {
        auto tool = registry.Create("passthrough", name);
        if (!tool || group->AddTool(std::move(tool)) == nullptr) {
            std::fprintf(stderr, "AddTool(%s) failed\n", name);
            return 2;
        }
    }

讲解:

  • ToolRegistry registry; 必须在 Load 时仍然存活:加载要按 type_id 重新 Create 工具。
  • for (const char * name : { "t1", "t2" }) 用初始化列表固定顺序;工具顺序就是后面断言的期望值。
  • AddTool(std::move(tool)) == nullptr 判失败:所有权已转移,返回值只是观察指针。
  • 退出码 2 同时覆盖「创建失败」与「装载失败」——对调用方都是「组没建起来」。

片段 3 · 保存(serialize_roundtrip.cpp:49-55)

查看完整源文件
    const std::string path  = "serialize_roundtrip.avtg";
    const SaveResult  saved = SaveAvtg(*group, path);
    if (!saved.ok) {
        std::fprintf(stderr, "SaveAvtg failed (%zu diagnostics)\n", saved.diagnostics.size());
        return 3;
    }
    std::printf("save: ok -> %s\n", path.c_str());

讲解:

  • SaveAvtg(const ToolGroup&, const std::string& path) 返回 SaveResult:ok 与 diagnostics 分开。不要用异常或文件大小判断成功。
  • ok == false 时诊断数量是有用信息(示例直接打印),细节应从 diagnostics 逐条读。
  • 路径由调用方给:示例用当前工作目录的相对路径,建议在临时目录运行。

片段 4 · 读回(serialize_roundtrip.cpp:57-64)

查看完整源文件
    const LoadResult loaded = LoadAvtg(path, registry);
    if (!loaded.group) {
        std::fprintf(stderr, "LoadAvtg failed (%zu diagnostics)\n", loaded.diagnostics.size());
        return 4;
    }
    const std::vector<std::string> before = ToolNames(*group);
    const std::vector<std::string> after  = ToolNames(*loaded.group);
    std::printf("load: ok, tools=%zu\n", after.size());

讲解:

  • LoadAvtg(path, registry) 返回 LoadResult:group 为空即失败,诊断在 diagnostics 里,不要只看空指针。
  • 失败原因可能是 IoFailure、SchemaTooNew、未知 type_id 等;不同原因对应不同修复动作,诊断必须保留。
  • 成功时得到一个新的 shared_ptr<ToolGroup>,与保存前的对象无关。

片段 5 · 顺序与组名断言(serialize_roundtrip.cpp:66-73)

查看完整源文件
    if (before != after) {
        std::fprintf(stderr, "tool list/order changed across round-trip\n");
        return 5;
    }
    if (loaded.group->name() != group->name()) {
        std::fprintf(stderr, "group name changed across round-trip\n");
        return 5;
    }

讲解:

  • 先比工具名序列(数量 + 顺序),再比组名:两者是 .avtg 往返最容易被悄悄破坏的部分。
  • 失败统一用退出码 5,但错误信息区分「列表/顺序」与「组名」——排障时先看 stderr。
  • 只比「工具数」会漏掉顺序错乱,而顺序决定执行序(Run 按 tools[] 自上而下)。

片段 6 · 收尾(serialize_roundtrip.cpp:81-83)

查看完整源文件
    std::remove(path.c_str());  // demo artifact only
    std::puts("serialize_roundtrip ok");
    return 0;

讲解:std::remove 删除示例自己写的临时包,避免污染工作目录;真实宿主应把 .avtg 存到自己的配方目录并纳入版本管理。

常见误区

  • 「Load 不需要 registry」 —— .avtg 只存 type_id;没有注册表就无法重建工具,未知类型会失败
  • 「保存成功 = 内容正确」 —— ok 只表示写入成功;字段保真由 TC-SER-* 与往返断言覆盖
  • 「工具数对就行」 —— 顺序是执行契约,必须逐项比对
  • 「能往返 = 算法可用」 —— 解析已实现;passthrough 的 Run 仍是基类桩

构建与运行

VYNTEK_BUILD_EXAMPLES=ON 才会构建示例;ninja-debug-all 预设已同时打开 tests 与 examples。二进制统一落在 build/ninja-debug-all/bin/。

Linux(本机 VPS,AGENTS.md「本机 VPS」小节)

cmake --preset ninja-debug-all
cmake --build --preset ninja-debug-all --target serialize_roundtrip
cd /tmp && /path/to/vyntek/build/ninja-debug-all/bin/serialize_roundtrip

Windows(PowerShell,AGENTS.md「构建」小节,仓库在 D:\Works\Vyntek)

cmake --preset ninja-debug-all
cmake --build --preset ninja-debug-all --target serialize_roundtrip
cd $env:TEMP; D:\Works\Vyntek\build\ninja-debug-all\bin\serialize_roundtrip.exe

示例在当前工作目录写 serialize_roundtrip.avtg,成功读完即删除,所以建议在临时目录运行。

输出

本机实跑(Linux,2026-10-22,在空临时目录):

$ ./build/ninja-debug-all/bin/serialize_roundtrip
vyntek 0.1.0 (commit <build-sha>)
save: ok -> serialize_roundtrip.avtg
load: ok, tools=2
name: serialize-demo, tools: t1 t2
serialize_roundtrip ok
$ echo $?
0

失败路径也实跑过:把当前目录设为不可写后运行,SaveAvtg 报 1 条诊断并以 3 退出:

$ cd /tmp/read-only-dir && ./serialize_roundtrip
SaveAvtg failed (1 diagnostics)
vyntek 0.1.0 (commit <build-sha>)
$ echo $?
3

(stderr 与 stdout 混排顺序由缓冲决定,这里保留实测顺序。)

退出码

  • 0 —— 往返成功且工具列表、组名一致,打印 serialize_roundtrip ok
  • 1 —— ToolGroup::Create 失败
  • 2 —— registry.Create / AddTool 失败
  • 3 —— SaveAvtg 失败(SaveResult.ok == false;诊断见 diagnostics)
  • 4 —— LoadAvtg 失败(返回空组;诊断见 diagnostics)
  • 5 —— 工具数、顺序或组名不一致

On this page