串行化的机制和原理

今天终于把Seralization 的基本框架搭好了,简单的测试了一下,存储没问题,读取好像还有点问题.由于现在还没有写由Object派生出来的类,测试不出什么东西,等把场景管理的部分完成后再回来改.

 

由于整个Seralization的机制比较烦琐,我今天把整个思路整理一下,一是作为交流,另外也作为CoagelEngine的开发文档.

 

Seralization 又叫串行化,简单的讲,就是一种保存当前运行程序的状态,下次运行程序时可以将被保存的状态提取出来,这样就可以从上次保存的状态开始往后运行.在游戏设计中,这也就是所谓的存档/读档功能.

 

那么Seralization如何才能实现呢. 在结构化的程序设计中,函数(方法)和数据是相互独立的.

我们需要将当前内存块中的每个变量数据都保存到文件(后者是一个内存块,这点在下文提到),然后下次运行时在将这些数据都提取出来,付给每个变量. 比如说我的程序有两个全局变量Ea,Eb.有两个局部变量a,b. 其中a,b都是函数Fun 的局部变量.那么我需要保存这4个变量的值. 并且我要知道a,bFun的局部变量,Ea,Eb是全局变量. 并且我需要知道程序执行到哪一步了. 这是个复杂且难以完成的工作. 因此Seralization 都是在OOP 面向对象的原则下进行的. 甚至有的人直接称其为类的串行化.

 

基于OOP原则的程序, 类是数据和方法的集合. 我们只需要保存当前生存着的每个类中的数据, 类与类之间的关系, 当然如果有全局变量也需要保存. 下次还原时我们还原被保存的这些信息, 就还原了程序的状态.

 

这里就引入了几个问题.

1. 我们知道类继承机制中的虚函数机制可以动态的决定调用哪个函数. 比如:

Class A

{

Public:

    Visual void fun() { return 1;};

}

 

Class B : public A

{

  Public:

    Virtual void fun(){ return 2;};

}

 

我定义一个指针 A* p = new B;

注意这里虽然p是一个A* 类的指针,但他实际上指向的是一个class B.

因此调用p->func 其实调用的是 B::func();

 

回到刚才的话题.类的成员函数如果被声明是虚函数的话,可以动态的决定调用哪个类的函数.但是类的创建必须要写成 A p = new A, 这里我们不能利用虚函数来实现动态的决定创建的是什么类.因为创建时程序还不知道p到底值向什么类.创建A类就必须写成new A,创建B类就必须写成new B. 这就是问题所在了.这个问题又被分为两个方面.首先,我们必须在保存类的信息时同时保存该类的类型

(是class A,还是 class B.这点可以利用RTTI的机制来实现.RTTI的原理很简单,我们在每个类中都添加一个字符串,这个字符串记录的这个类的类型名字.比如class A的字符串就是”A”,class B的字符串就是”B”.当我们保存的时候,我们把这个字符串也保存起来.这样当我们读取的时候就知道现在读取的数据是属于什么类的了.

 

然后我们需要解决的问题就是如果创建这个类了.前面说了.我们不能利用new 来创建,因为这里的创建是程序执行时动态决定的.我们事先不知道哪些类需要创建.这个问题我们利用object factory的思想来解决.我们为每种类都定义一个static factoryFunc();由于是static

的,所以该类的所以实例都共享这个方法,同时这个factoryFunc()是类相关的,不是实例相关的,因此在类还没被实例化之前就存在了(可以想像成全局的,从程序运行开始一直存在到程序结束). 我们将每个类的factoryFunc和该类的类型名(A,B)一起添加到一个map

容器中,一个类型名对应一个factoryFunc. factoryFunc封装了类的new 操作.这样我读取存档的时候.我只需要先读取类的类型名,然后到map里去查找这个类型名对应的factoryFunc,然后调用它来创建这个类就行了.伪代码如下

 

Read(stream, classname);

factoryFunc = Map->find(classname)

object* p = factoryFunc();

 

这样就实现了类的动态创建.

 

2.如何保存类之间的关系

这里我说类之间的关系就是指类的指针成员指向的其它类.由于我设计的引擎是以OOP为基础的.我定义了一个Object 类,并且认为其它一切的类都是这个类的派生类.注意最开始我们提到的保存机制保存了类的指针.但是如果我们新创建了一个类,那这个类的指针是系统自动分配的,和上次保存的地址是不一样的.因此不能简单的将当前类的指针成员的值保存下来,然后在读取出来重新付给它(因为现在这个值代表的内存块里的数据和上次不一样了).我们需要一个映射机制,能将旧的指针值和新的指针值对应起来.我这里也是利用的map来实现.

 

 

整个Seralization机制流程如下:

 

主要是2个类.Object , Stream

其中Stream里面有一个vector<object*> top,用来保存场景中的主要物体.这里需要解释一下.我的设计思路是场景中的物体是有关联关系的.比如一辆汽车,它有4个子物体――轮子.如果这辆汽车没有父节点,那汽车就是一个主要物体.而轮子不是主要物体.这个容器的作用是用来递归的调用Object的方法.

Stream的第2个容器是static map<string,factortFunc*> factory, 就是上面提到的,将factoryFunc和类的类型名对应起来.

Stream的第3个容器是map<Object*,Link*>  mLink ,Link是一个自定义的类,它有一个object*

,用来表示它是哪个实例的Link,还有一个vector<object*>,里面保存这个实例的指针成员数据.

 

Stream的第4个容器是vector<Object*> order,用来保存场景中所有的实例指针.

 

Stream提供SaveLoad两个方法.

 

Save: 首先将order清空,然后对top中的每个实例都调用object::register.这个方法是将该实例的指针添加到order中,并递归的调用这个实例的子实例.这样order就保存了所有实例的指针值了.

   然后对每个实例都调用Object::Save 当然,这里Save 和 register都是虚函数.

Save的作用是将类的类型名,类的指针,类的名字,类的数据成员,类的成员指针都保存起来.如果这个实例是主物体,那就在类的类型名前加一个”Top”.

 

 

 

Load: 首先将order, mLink清空,依次读出每一块数据,根据数据中类的类型名,去factory中找到对应的方法,创建这个类,然后把类的名字,类的数据成员都从数据块中读出付给新的类.接下来是重点了,创建这个类(实例)以后,要把它跟一个Link类联系起来.把这个实例现在的指针付个LinkObject*,并且将数据块中的关于成员指针的数据都加入到Linkverctor中.注意这里vector中还是旧的指针数据哦.最后读出数据块中这个类的指针值,将它作为关于这个类的标示,和Link一起放入mLink中.于是mLink就成了用旧指针值作索引,用保存了这个实例的新指针值和这个实例的就指针成员的Link作值的hash表.

上面的步骤结束后.我们从头查找m_link,对每一个LinkObject* ,我们都调用虚函数

Object::Link(),并把该Link作为参数传给Link()Object::Link会根据Linkvector里面的每个值作索引,去m_link里面找到他对应的Link,并把Linkobject* 传回来,作为该Object新的成员指针.

 

大体的流程就是这样,过几天有空我会画一张详细的流程图

 

posted @ 2005-03-22 20:14  BadKeeper  阅读(2337)  评论(2编辑  收藏  举报