You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

yaml-cpp中YAML::LoadFile加载多文件致数据文件回滚问题咨询

问题描述

在游戏引擎中使用yaml-cpp进行场景数据序列化/反序列化时,遇到以下异常:

  • 初始有两个内容完全一致的文件A.yaml和B.yaml:
    entities:
      - entity1
      - entity2
      - entity3
    
  • 对B.yaml添加2个实体并保存后,文件内容正常更新为:
    entities:
      - entity1
      - entity2
      - entity3
      - entity4
      - entity5
    
  • 但在同一运行时中,先加载A.yaml,再重新加载B.yaml,B.yaml会恢复到初始状态;若保存B.yaml后重启引擎,多次加载文件则无此问题。

相关代码如下:

class scene_data {
public:
    void save(const std::filesystem::path &) const;
    void load(const std::filesystem::path &);
    std::vector<entity_data> entities() const;
    void add_entity();
    void delete_entity(const entity_data &);

private:
    std::vector<entity_data> _entities;
};

void scene_data::save(const std::filesystem::path &path) const {
    YAML::Emitter em;
    em << YAML::BeginMap << YAML::Key << "entities";
    em << YAML::BeginSeq;
    for (const auto &e: _entities) {
        e.serialize(em);//Each entity write themselves in emitter
    }
    em << YAML::EndSeq;
    em << YAML::EndMap;

    std::ofstream filepath(path);
    filepath << em.c_str();//I've heard that the ofstream doesn't need to be closed manually.
}

void scene_data::load(const std::filesystem::path &path) {
    auto root = YAML::LoadFile(path.string());

    if (auto ett_node = root["entities"]) {
        std::vector<entity_data> new_entities;
        for (const auto &ett: ett_node) {
            new_entities.push_back(entity_data(ett));//entity_data constructor takes yaml node
        }
        _entities = new_entities;
    }
}

原因分析与解决办法

核心原因:yaml-cpp的默认文件缓存机制

yaml-cpp默认会对已加载的文件进行全局缓存,缓存键为文件路径。当同一进程中重复加载同一路径的文件时,yaml-cpp会直接返回缓存中的旧内容,而不会重新读取磁盘上的最新文件。

你的场景中,初始加载B.yaml后缓存了它的初始内容;修改保存B.yaml后,若未重启进程,后续调用YAML::LoadFile("B.yaml")依然会读取缓存的初始内容。如果此时调用save()方法,就会把缓存的初始内容重新写入文件,导致B.yaml恢复到初始状态。

解决办法

1. 禁用yaml-cpp的文件缓存

在加载文件前,通过设置全局选项禁用缓存,确保每次加载都从磁盘读取最新内容:

YAML::SetGlobalConfig(YAML::Config::DisableCache);
auto root = YAML::LoadFile(path.string());

2. 绕开缓存,手动读取文件内容

不使用YAML::LoadFile,而是手动打开文件读取内容后再解析,彻底避开缓存机制:

void scene_data::load(const std::filesystem::path &path) {
    std::ifstream file(path);
    if (!file.is_open()) {
        // 处理文件打开失败的情况
        return;
    }
    std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
    auto root = YAML::Load(content);

    if (auto ett_node = root["entities"]) {
        std::vector<entity_data> new_entities;
        for (const auto &ett: ett_node) {
            new_entities.push_back(entity_data(ett));
        }
        _entities = new_entities;
    }
}

3. 确保文件写入完成

虽然std::ofstream会在销毁时自动关闭,但显式调用flush()可以确保所有数据都写入磁盘,避免因缓冲导致的文件内容不一致:

void scene_data::save(const std::filesystem::path &path) const {
    YAML::Emitter em;
    em << YAML::BeginMap << YAML::Key << "entities";
    em << YAML::BeginSeq;
    for (const auto &e: _entities) {
        e.serialize(em);
    }
    em << YAML::EndSeq;
    em << YAML::EndMap;

    std::ofstream filepath(path);
    if (filepath.is_open()) {
        filepath << em.c_str();
        filepath.flush(); // 确保数据写入磁盘
    }
}

额外注意:文件格式一致性

你初始的A.yaml和B.yaml是直接的序列格式(无entities键),但save()方法输出的是包含entities键的Map格式。这种格式不一致可能导致加载旧文件时_entities无法被正确赋值,建议统一文件格式,避免后续出现其他问题。

内容的提问来源于stack exchange,提问作者shklaurant

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 00:45:54