串行化的机制和原理
今天終于把Seralization 的基本框架搭好了,簡單的測試了一下,存儲沒問題,讀取好像還有點問題.由于現在還沒有寫由Object派生出來的類,測試不出什么東西,等把場景管理的部分完成后再回來改.
?
由于整個Seralization的機制比較煩瑣,我今天把整個思路整理一下,一是作為交流,另外也作為CoagelEngine的開發文檔.
?
Seralization 又叫串行化,簡單的講,就是一種保存當前運行程序的狀態,下次運行程序時可以將被保存的狀態提取出來,這樣就可以從上次保存的狀態開始往后運行.在游戲設計中,這也就是所謂的存檔/讀檔功能.
?
那么Seralization如何才能實現呢. 在結構化的程序設計中,函數(方法)和數據是相互獨立的.
我們需要將當前內存塊中的每個變量數據都保存到文件(后者是一個內存塊,這點在下文提到)中,然后下次運行時在將這些數據都提取出來,付給每個變量. 比如說我的程序有兩個全局變量Ea,Eb.有兩個局部變量a,b. 其中a,b都是函數Fun 的局部變量.那么我需要保存這4個變量的值. 并且我要知道a,b是Fun的局部變量,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提供Save和Load兩個方法.
?
Save: 首先將order清空,然后對top中的每個實例都調用object::register.這個方法是將該實例的指針添加到order中,并遞歸的調用這個實例的子實例.這樣order就保存了所有實例的指針值了.
然后對每個實例都調用Object::Save 當然,這里Save 和 register都是虛函數.
Save的作用是將類的類型名,類的指針,類的名字,類的數據成員,類的成員指針都保存起來.如果這個實例是主物體,那就在類的類型名前加一個”Top”.
?
?
?
Load: 首先將order, mLink清空,依次讀出每一塊數據,根據數據中類的類型名,去factory中找到對應的方法,創建這個類,然后把類的名字,類的數據成員都從數據塊中讀出付給新的類.接下來是重點了,創建這個類(實例)以后,要把它跟一個Link類聯系起來.把這個實例現在的指針付個Link的Object*,并且將數據塊中的關于成員指針的數據都加入到Link的verctor中.注意這里vector中還是舊的指針數據哦.最后讀出數據塊中這個類的指針值,將它作為關于這個類的標示,和Link一起放入mLink中.于是mLink就成了用舊指針值作索引,用保存了這個實例的新指針值和這個實例的就指針成員的Link作值的hash表.
上面的步驟結束后.我們從頭查找m_link,對每一個Link中Object* ,我們都調用虛函數
Object::Link(),并把該Link作為參數傳給Link().Object::Link會根據Link的vector里面的每個值作索引,去m_link里面找到他對應的Link,并把Link的object* 傳回來,作為該Object新的成員指針.
?
大體的流程就是這樣,過幾天有空我會畫一張詳細的流程圖
?
轉載于:https://www.cnblogs.com/badkeeper/articles/123723.html
總結
- 上一篇: [《孔雀》观后]聪明的孩子提着易碎的灯笼
- 下一篇: 一个关于nvarchar字段排序,中英文