如何实现包含entt::sparse_set的EnTT注册表组件的序列化与反序列化?
我完全懂你现在的头疼点——用Cereal序列化EnTT注册表时,其他组件都好好的,唯独带entt::sparse_set的组件要么编译报错,要么序列化出来不对。其实核心原因很简单:Cereal默认不知道怎么处理EnTT的sparse_set容器,咱们只需要给它加个“翻译器”,或者在组件里手动处理序列化逻辑就行。下面给你两种靠谱的解决方案:
方案一:给entt::sparse_set全局添加Cereal序列化特化
这个方案一劳永逸,所有用到entt::sparse_set的组件都能直接用,不用修改组件内部的序列化代码。
你只需要在项目里添加这段模板特化代码(放在Cereal和EnTT的头文件之后):
#include <cereal/cereal.hpp> #include <entt/entt.hpp> #include <vector> // 为entt::sparse_set实现Cereal序列化/反序列化逻辑 template <class Archive, typename Entity, typename Allocator> void serialize(Archive& archive, entt::sparse_set<Entity, Allocator>& sparse_set) { // 序列化(存文件):把sparse_set转成Cereal能识别的vector if constexpr (cereal::is_output_serialization<Archive>::value) { std::vector<Entity> elements{sparse_set.begin(), sparse_set.end()}; archive(elements); } // 反序列化(读文件):把vector的元素插回sparse_set else { std::vector<Entity> elements; archive(elements); sparse_set.clear(); // 先清空避免脏数据 for (const auto entity : elements) { sparse_set.insert(entity); } } }
加了这段之后,你原来的StarSystem组件的serialize函数完全不用改,直接保留CEREAL_NVP(entities)就行——Cereal会自动调用咱们写的特化来处理sparse_set。
方案二:在组件内部手动处理sparse_set序列化
如果不想全局修改,只在当前组件生效,那可以在StarSystem的serialize模板里手动处理sparse_set:
template<class Archive> void serialize(Archive& archive) { // 先序列化普通成员 archive(CEREAL_NVP(location), CEREAL_NVP(bodies)); // 单独处理entt::sparse_set if constexpr (cereal::is_output_serialization<Archive>::value) { // 存的时候转成vector std::vector<entt::entity> entities_list{entities.begin(), entities.end()}; archive(CEREAL_NVP(entities_list)); } else { // 读的时候把vector转回sparse_set std::vector<entt::entity> entities_list; archive(CEREAL_NVP(entities_list)); entities.clear(); for (const auto ent : entities_list) { entities.insert(ent); } } }
这个方案的好处是针对性强,不会影响其他地方的sparse_set,适合只在个别组件用到的场景。
额外要注意的小问题
实体遍历的坑:你原来的
serialize_registry里用了registry.view<entt::entity>(),这是错误的——entt::entity不是组件类型,这个view不会返回任何实体!正确的获取所有存活实体的方式是:// 方式1:用空view获取所有实体 auto regView = registry.view<>(); // 方式2:用each遍历 registry.each([&archive, ®istry](auto entity) { // 序列化单个实体的逻辑 });entt::entity的序列化:不用额外处理,因为
entt::entity本质是整数类型,Cereal能直接序列化它。反序列化的时候要注意实体ID的一致性——如果你的存档里存储的是实体的原始ID,反序列化时可以用registry.create(entity)来创建对应ID的实体(如果ID未被占用的话)。反序列化的实体引用:比如
StarSystem里的bodies和entities存储的是其他实体的ID,反序列化时要确保这些实体已经被创建,否则会出现无效实体引用。可以先序列化所有实体的元数据,再序列化组件,或者在反序列化组件时延迟处理引用。
按照上面的方法修改后,你的StarSystem组件里的entt::sparse_set就能正常序列化和反序列化了,整个注册表的保存加载流程也能跑通。
内容来源于stack exchange

