串行化的机制和原理
今天終于把Seralization 的基本框架搭好了,簡(jiǎn)單的測(cè)試了一下,存儲(chǔ)沒(méi)問(wèn)題,讀取好像還有點(diǎn)問(wèn)題.由于現(xiàn)在還沒(méi)有寫由Object派生出來(lái)的類,測(cè)試不出什么東西,等把場(chǎng)景管理的部分完成后再回來(lái)改.
?
由于整個(gè)Seralization的機(jī)制比較煩瑣,我今天把整個(gè)思路整理一下,一是作為交流,另外也作為CoagelEngine的開(kāi)發(fā)文檔.
?
Seralization 又叫串行化,簡(jiǎn)單的講,就是一種保存當(dāng)前運(yùn)行程序的狀態(tài),下次運(yùn)行程序時(shí)可以將被保存的狀態(tài)提取出來(lái),這樣就可以從上次保存的狀態(tài)開(kāi)始往后運(yùn)行.在游戲設(shè)計(jì)中,這也就是所謂的存檔/讀檔功能.
?
那么Seralization如何才能實(shí)現(xiàn)呢. 在結(jié)構(gòu)化的程序設(shè)計(jì)中,函數(shù)(方法)和數(shù)據(jù)是相互獨(dú)立的.
我們需要將當(dāng)前內(nèi)存塊中的每個(gè)變量數(shù)據(jù)都保存到文件(后者是一個(gè)內(nèi)存塊,這點(diǎn)在下文提到)中,然后下次運(yùn)行時(shí)在將這些數(shù)據(jù)都提取出來(lái),付給每個(gè)變量. 比如說(shuō)我的程序有兩個(gè)全局變量Ea,Eb.有兩個(gè)局部變量a,b. 其中a,b都是函數(shù)Fun 的局部變量.那么我需要保存這4個(gè)變量的值. 并且我要知道a,b是Fun的局部變量,Ea,Eb是全局變量. 并且我需要知道程序執(zhí)行到哪一步了. 這是個(gè)復(fù)雜且難以完成的工作. 因此Seralization 都是在OOP 面向?qū)ο蟮脑瓌t下進(jìn)行的. 甚至有的人直接稱其為類的串行化.
?
基于OOP原則的程序, 類是數(shù)據(jù)和方法的集合. 我們只需要保存當(dāng)前生存著的每個(gè)類中的數(shù)據(jù), 類與類之間的關(guān)系, 當(dāng)然如果有全局變量也需要保存. 下次還原時(shí)我們還原被保存的這些信息, 就還原了程序的狀態(tài).
?
這里就引入了幾個(gè)問(wèn)題.
1. 我們知道類繼承機(jī)制中的虛函數(shù)機(jī)制可以動(dòng)態(tài)的決定調(diào)用哪個(gè)函數(shù). 比如:
Class A
{
Public:
??? Visual void fun() { return 1;};
}
?
Class B : public A
{
? Public:
??? Virtual void fun(){ return 2;};
}
?
我定義一個(gè)指針 A* p = new B;
注意這里雖然p是一個(gè)A* 類的指針,但他實(shí)際上指向的是一個(gè)class B.
因此調(diào)用p->func 其實(shí)調(diào)用的是 B::func();
?
回到剛才的話題.類的成員函數(shù)如果被聲明是虛函數(shù)的話,可以動(dòng)態(tài)的決定調(diào)用哪個(gè)類的函數(shù).但是類的創(chuàng)建必須要寫成 A p = new A, 這里我們不能利用虛函數(shù)來(lái)實(shí)現(xiàn)動(dòng)態(tài)的決定創(chuàng)建的是什么類.因?yàn)閯?chuàng)建時(shí)程序還不知道p到底值向什么類.創(chuàng)建A類就必須寫成new A,創(chuàng)建B類就必須寫成new B. 這就是問(wèn)題所在了.這個(gè)問(wèn)題又被分為兩個(gè)方面.首先,我們必須在保存類的信息時(shí)同時(shí)保存該類的類型
(是class A,還是 class B).這點(diǎn)可以利用RTTI的機(jī)制來(lái)實(shí)現(xiàn).RTTI的原理很簡(jiǎn)單,我們?cè)诿總€(gè)類中都添加一個(gè)字符串,這個(gè)字符串記錄的這個(gè)類的類型名字.比如class A的字符串就是”A”,class B的字符串就是”B”.當(dāng)我們保存的時(shí)候,我們把這個(gè)字符串也保存起來(lái).這樣當(dāng)我們讀取的時(shí)候就知道現(xiàn)在讀取的數(shù)據(jù)是屬于什么類的了.
?
然后我們需要解決的問(wèn)題就是如果創(chuàng)建這個(gè)類了.前面說(shuō)了.我們不能利用new 來(lái)創(chuàng)建,因?yàn)檫@里的創(chuàng)建是程序執(zhí)行時(shí)動(dòng)態(tài)決定的.我們事先不知道哪些類需要?jiǎng)?chuàng)建.這個(gè)問(wèn)題我們利用object factory的思想來(lái)解決.我們?yōu)槊糠N類都定義一個(gè)static factoryFunc();由于是static
的,所以該類的所以實(shí)例都共享這個(gè)方法,同時(shí)這個(gè)factoryFunc()是類相關(guān)的,不是實(shí)例相關(guān)的,因此在類還沒(méi)被實(shí)例化之前就存在了(可以想像成全局的,從程序運(yùn)行開(kāi)始一直存在到程序結(jié)束). 我們將每個(gè)類的factoryFunc和該類的類型名(A,B)一起添加到一個(gè)map
容器中,一個(gè)類型名對(duì)應(yīng)一個(gè)factoryFunc. factoryFunc封裝了類的new 操作.這樣我讀取存檔的時(shí)候.我只需要先讀取類的類型名,然后到map里去查找這個(gè)類型名對(duì)應(yīng)的factoryFunc,然后調(diào)用它來(lái)創(chuàng)建這個(gè)類就行了.偽代碼如下
?
Read(stream, classname);
factoryFunc = Map->find(classname)
object* p = factoryFunc();
?
這樣就實(shí)現(xiàn)了類的動(dòng)態(tài)創(chuàng)建.
?
2.如何保存類之間的關(guān)系
這里我說(shuō)類之間的關(guān)系就是指類的指針成員指向的其它類.由于我設(shè)計(jì)的引擎是以OOP為基礎(chǔ)的.我定義了一個(gè)Object 類,并且認(rèn)為其它一切的類都是這個(gè)類的派生類.注意最開(kāi)始我們提到的保存機(jī)制保存了類的指針.但是如果我們新創(chuàng)建了一個(gè)類,那這個(gè)類的指針是系統(tǒng)自動(dòng)分配的,和上次保存的地址是不一樣的.因此不能簡(jiǎn)單的將當(dāng)前類的指針成員的值保存下來(lái),然后在讀取出來(lái)重新付給它(因?yàn)楝F(xiàn)在這個(gè)值代表的內(nèi)存塊里的數(shù)據(jù)和上次不一樣了).我們需要一個(gè)映射機(jī)制,能將舊的指針值和新的指針值對(duì)應(yīng)起來(lái).我這里也是利用的map來(lái)實(shí)現(xiàn).
?
?
整個(gè)Seralization機(jī)制流程如下:
?
主要是2個(gè)類.Object , Stream
其中Stream里面有一個(gè)vector<object*> top,用來(lái)保存場(chǎng)景中的主要物體.這里需要解釋一下.我的設(shè)計(jì)思路是場(chǎng)景中的物體是有關(guān)聯(lián)關(guān)系的.比如一輛汽車,它有4個(gè)子物體――輪子.如果這輛汽車沒(méi)有父節(jié)點(diǎn),那汽車就是一個(gè)主要物體.而輪子不是主要物體.這個(gè)容器的作用是用來(lái)遞歸的調(diào)用Object的方法.
Stream的第2個(gè)容器是static map<string,factortFunc*> factory, 就是上面提到的,將factoryFunc和類的類型名對(duì)應(yīng)起來(lái).
Stream的第3個(gè)容器是map<Object*,Link*> ?mLink ,Link是一個(gè)自定義的類,它有一個(gè)object*
,用來(lái)表示它是哪個(gè)實(shí)例的Link,還有一個(gè)vector<object*>,里面保存這個(gè)實(shí)例的指針成員數(shù)據(jù).
?
Stream的第4個(gè)容器是vector<Object*> order,用來(lái)保存場(chǎng)景中所有的實(shí)例指針.
?
Stream提供Save和Load兩個(gè)方法.
?
Save: 首先將order清空,然后對(duì)top中的每個(gè)實(shí)例都調(diào)用object::register.這個(gè)方法是將該實(shí)例的指針添加到order中,并遞歸的調(diào)用這個(gè)實(shí)例的子實(shí)例.這樣order就保存了所有實(shí)例的指針值了.
然后對(duì)每個(gè)實(shí)例都調(diào)用Object::Save 當(dāng)然,這里Save 和 register都是虛函數(shù).
Save的作用是將類的類型名,類的指針,類的名字,類的數(shù)據(jù)成員,類的成員指針都保存起來(lái).如果這個(gè)實(shí)例是主物體,那就在類的類型名前加一個(gè)”Top”.
?
?
?
Load: 首先將order, mLink清空,依次讀出每一塊數(shù)據(jù),根據(jù)數(shù)據(jù)中類的類型名,去factory中找到對(duì)應(yīng)的方法,創(chuàng)建這個(gè)類,然后把類的名字,類的數(shù)據(jù)成員都從數(shù)據(jù)塊中讀出付給新的類.接下來(lái)是重點(diǎn)了,創(chuàng)建這個(gè)類(實(shí)例)以后,要把它跟一個(gè)Link類聯(lián)系起來(lái).把這個(gè)實(shí)例現(xiàn)在的指針付個(gè)Link的Object*,并且將數(shù)據(jù)塊中的關(guān)于成員指針的數(shù)據(jù)都加入到Link的verctor中.注意這里vector中還是舊的指針數(shù)據(jù)哦.最后讀出數(shù)據(jù)塊中這個(gè)類的指針值,將它作為關(guān)于這個(gè)類的標(biāo)示,和Link一起放入mLink中.于是mLink就成了用舊指針值作索引,用保存了這個(gè)實(shí)例的新指針值和這個(gè)實(shí)例的就指針成員的Link作值的hash表.
上面的步驟結(jié)束后.我們從頭查找m_link,對(duì)每一個(gè)Link中Object* ,我們都調(diào)用虛函數(shù)
Object::Link(),并把該Link作為參數(shù)傳給Link().Object::Link會(huì)根據(jù)Link的vector里面的每個(gè)值作索引,去m_link里面找到他對(duì)應(yīng)的Link,并把Link的object* 傳回來(lái),作為該Object新的成員指針.
?
大體的流程就是這樣,過(guò)幾天有空我會(huì)畫一張?jiān)敿?xì)的流程圖
?
轉(zhuǎn)載于:https://www.cnblogs.com/badkeeper/articles/123723.html
總結(jié)
- 上一篇: [《孔雀》观后]聪明的孩子提着易碎的灯笼
- 下一篇: 一个关于nvarchar字段排序,中英文