序列化需要准备什么?
首先,我们需要一个被序列化类实现序列化函数,以及序列化库。准备我的序列化库是Yaml-cpp,序列话函数就命名为Serialize,另外我们不需要关心组件类型是具体是什么,所以我这边使用多态,接口类为
struct Serializer{
virtual void Serialize() = 0;
}
所有需要运行时序列化的类都实现接口;
另外当我们给定一个Carrier[Ecs系统中Enttity]时,我们需要知道这个实体的那些类实现了这个接口:
这边的话,我们可以在调用Carrier.AddComponent时用标准库type_traits中的std::is_base_of_v<base,T>方法来判断。那么这张表被谁持有呢?这边我是让Sence类型持有,原因如下:
1:每一个Sence的Carrier都应该被隔离,所以Serializable也应该被隔离,Sence的资源不应该交付给其他类托管;
2:方便管理
另外表的键值是组件名字的字符串;
看实现:

另外反序列话有点不同,序列话是类的数据流向文件,我们是知道组件类型的,但是反序列化是数据从文件流向组件,我们是不知道类类型,如果要调用成员函数的话,会比较复杂,所以我这边是用类静态成员,输入是Carrier和数据节点;
实现:

另外,怎么知道让哪个类调用哪个反序列化方法呢?或者说我们用什么数据将其与函数关联起来;
这边我选择的是用一张unordered_map<std::string,std::function<void(YAML::Node&,Carrier&)>>表,我们只需要在序列化的时候将类名字符串与数据对应起来,然后反序列化的时候取出节点中的对应数据即可;
接下来的问题是,我们怎么准备这张表?或者说,我们怎么在引擎loop运行前准备这张表,因为这张表是我们loop时随时可调用的;
有一个方法是硬编码,直接在邀请运行前增加unordered_map::emplace方法,但是...这太蠢了,硬编码一向不是我的风格;
我这边是利用了inline static变量会在运行main函数的第一段代码前都初始化好的特性,这意味这我们可以在引擎运行前就运行一些代码,而且这些代码是可以分布在不同类中的,这为我们软初始化表提供了可能;
我这边是定义了一个类,这种运行引擎前的做初始化的工具类我统称为Assistant类:
template<class T>
struct SerializeAssistant {
SerializeAssistant(::std::string name, std::function<void(YAML::Node& node, Carrier& carrier)> deSerializerFun) {
if constexpr (std::is_base_of_v<Serializable, T>)
SenceSerializer::GetDeserializerFunMap().emplace(name, deSerializerFun);
else
LC_CORE_ASSERT(false, "Serializable permission needs to be added");
}
};
这边为什么是个模板,这个是另外一个组件依赖的系统,我称之为权限系统,作用是规范代码,这里不做介绍;
那么这张表被谁持有呢?当然不能被Sence持有,因为Sence不是唯一的,但是表是唯一的,被因为不是Carrier的属性,所以不能被Sence持有,这边我是让Serializer类持有;
我这边定义了一个宏:
#define COMPONENTREGISTER(ClassName) inline static DynamicEmbedAssistant<ClassName> DynamicEmbedAss{#ClassName,&ClassName::OnEmbed};
在类完善这个宏可以将该类中的反序列花函数登记到Serializer中所持有的表中;
到此为止,我们已经有两张表了,一张是运行时可动态增删的表,里面存储着每一个Carrier持有的可序列化组件指针,另外一张表是运行时确定的表,里面存储着每一个组件的反序列化函数;
大概的序列化步骤是这样的:
另外还有一张小表需要维护,那就是每一个Carrier持有的组件的类名字符串,这张表也会被流向文件当中,在反序列化时会被作为索引,指定对应的Carrier需要调用那些反序列化函数
准备前工作:将每个组件的反序列化函数登记;
运行时工作:当一个Carrier增加一个组件,我们需要判断这个组件是否实现了Serializable接口,如果实现了,那么在Sence的序列化指针表中增加对应的指针
序列化:
1:实例化一个接受数据的emitter
2:便利Scene中的每一个Carrier,调用该Carrier中的所有的序列化指针,其会将组件进行序列化;
3:将序列化好的数据流向文件
反序类化:
1:实例化一个新的Scene,并提供对应的数据
2:提取小表节点,遍历每一个Carrier需要调用那些反序列话函数,并将数据数据节点和Carrier传入反序列化函数,向Carrier中流入数据