這篇文章的內容是介紹GH_Component中另外一對可以被 override 的函式:
- Read
- Write
當我們在自己的電池中直接 override 時,Visual Studio會幫我們添加基類實作:
public override bool Read(GH_IReader reader)
{
return base.Read(reader);
}
public override bool Write(GH_IWriter writer)
{
return base.Write(writer);
}
這一對函式是用來進行序列化電池資料的,它們的最主要的應用場景是將某些屬于電池的狀態資料 存盤 在電池所在的 .gh 檔案中,
舉個例子:Grasshopper中有許多電池是具備多個形態的,比如 Cross Reference 電池,它的不同作業模式可以使得兩個串列物件以不同的方式匹配到一起:

那么問題就來了,Grasshopper是如何實作打開一個 .gh 檔案時讓這個電池還保持它上次在檔案關閉時的電池形態呢?進一步的,GH在檔案保存時是怎么知道這個電池處于什么狀態下呢?
這里就需要使用到這一對函式了,在Grasshopper打開檔案時,會呼叫電池的Read函式,讀取 .gh 檔案中屬于該電池的資料內容;Write函式正好相反,是在每次 .gh 檔案保存時,將屬于電池的資料寫入到 .gh 檔案的資料內容存盤區,
電池的序列化
所有的電池其本質上都是一個自定義類,其繼承自GH_Component,但所有程式都是在記憶體中運行的,因此,我們在GH畫布上創作的電池其本質都是 在記憶體中 創造/銷毀資料,當我們的自定義電池被拖入GH畫布上時,一個自定義電池類的實體就會在記憶體中被創建;我們在GH畫布上洗掉一個電池時,一個自定義電池類的實體就會在記憶體中被銷毀,
當 .gh 檔案被保存時,這些記憶體中的電池實體就會被“序列化”至硬碟上, .gh 檔案被打開的時候,Grasshopper就會“反序列化”將硬碟上的內容讀取到記憶體中,

但是,我們都知道,硬碟上只能存盤0和1,那一堆0和1是怎么變成我們的自定義類的,我們自定義類又是如何變成0和1?這就需要我們定義“序列化規則”,
自定義電池類的基類GH_Component已經由GH的開發者實作了一套默認的序列化規則,放在這一對函式Read和Write中來幫助我們實作電池的序列化和反序列化,因此電池中的基礎資料就可以被正確地存盤和讀取,包括并不限于:
- 電池的名字(Name)
- 電池的描述(Description)
- 電池是否打開預覽(Preview)
- 電池是否處于激活狀態(Active)
- ……
因此,即便自定義電池中不重寫Read和Write,也能對自定義電池的基本狀態實作序列化和反序列化,但是如果自定義電池里出現了一個GH_Component中不包含的屬性,那么其默認的序列化規則就無法將它寫入 .gh 檔案中了,自然也沒辦法從 .gh 讀取該屬性了,
讓我們來看下面的例子:
這是一個可以把每次輸入的文字不斷拼接的電池,還附帶了一個重置按鈕,

可以看到這個電池在這個 gh 檔案保存關閉再打開之后,原來存盤在 myMessage 屬性中的字串值沒能夠成功存盤,(電池輸出端的 “Hello, World” 在關閉檔案再次打開時消失了,)
這種情況下,在與其它人分享交流 .gh 檔案的時候會變得十分不方便,因為別人再打開 .gh 檔案時,電池的“狀態”與它在被保存的時候的狀態是不一致的,
gif中電池關鍵代碼如下
public class ReadAndWrite : GH_Component
{
// 其他電池的函式省略,比如Guid, 建構式等
string myMessage = ""; // 自定義電池的額外屬性,用于存盤字串,
protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
{
pManager.AddTextParameter("Message", "M", "", GH_ParamAccess.item);
Params.Input[0].Optional = true;
pManager.AddBooleanParameter("Reset", "R", "", GH_ParamAccess.item, false);
}
protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
{
pManager.AddTextParameter("Result", "S", "", GH_ParamAccess.item);
}
protected override void SolveInstance(IGH_DataAccess DA)
{
bool resetBtn = false;
if (!DA.GetData(1, ref resetBtn)) return;
if (resetBtn)
{
myMessage = string.Empty;
return;
}
string msg = string.Empty;
if (!DA.GetData(0, ref msg)) return;
if (!string.IsNullOrWhiteSpace(msg)) myMessage += msg;
DA.SetData(0, myMessage);
}
}
重寫Read()和Write()
要把自定義電池的屬性(狀態)存盤在 .gh 檔案中,就需要重寫其默認的 Read 和 Write 方法 —— Write用來實作序列化,Read用來實作反序列化,
在重寫的函式體內,使用傳入的引數 writer 和 reader 分別來進行資料的存盤和讀取,這里可以存盤常用的簡單資料型別,包括 Boolean、Byte、Int、Double、String 等,也可以儲存一些特定的Grasshopper特有的資料型別,比如Interval、Plane等,對應的序列化和反序列化操作需要呼叫的函式分別是以Set開頭的和Get開頭的函式,比如對于整形資料,對應的序列化和反序列化操作函式分別是 writer.SetInt32 和 reader.GetInt32,
要實作對于上一節介紹的 myMessage 屬性的序列化/反序列化操作,就需要使用到字串的序列化,下面是其代碼的例子,
public override bool Write(GH_IWriter writer)
{
// 將變數myMessage的內容存入至gh檔案的資料表中,并且給這個內容起名叫 "MyMessage"
// 名字會在反序列化時用到,從資料表中取出時需要通過名字來獲取
writer.SetString("MyMessage", myMessage);
return base.Write(writer);
}
public override bool Read(GH_IReader reader)
{
// 一定要先確定資料表中有沒有存在這個名字的項,否則會報I/O錯誤
if (reader.ItemExists("MyMessage"))
// 從gh檔案資料表中獲取名為MyMessage的資料,并轉為string,存入變數中
myMessage = reader.GetString("MyMessage");
return base.Read(reader);
}

可以看到,在重寫了這兩個函式后,自定義電池已經可以“記住”自己在 .gh 檔案被保存時的狀態,并且在其被打開時再次恢復上次被保存的狀態了,
Read()和Write()在什么時候被GH呼叫
Read僅僅會在 .gh 檔案被打開時呼叫,當一個 .gh 檔案被打開時,Grasshopper會使用反序列化規則在記憶體中挨個創建電池:先會按照 默認建構式 創建電池的實體,然后呼叫 Read 函式,其內部需要包含給其他并不存在于默認建構式中的屬性 賦值,上例中的
myMessage = reader.GetString("MyMessage");
就是給 myMessage 這個不存在于默認建構式中的屬性進行賦值,這樣以來,當序列化完成之后,電池的各個自定義屬性的值就會回到 .gh 檔案被保存時的狀態,Read函式是自定義電池類實體創建完成之后呼叫的,
對應的,Write函式會在每次 .gh 檔案保存時被呼叫,用于存盤自定義電池的各個屬性(狀態),一個屬性只有在被保存了之后,才能被成功讀取,否則就會在Grasshopper打開檔案時報I/O錯誤,大家可以嘗試將上例中的 if (reader.ItemExists(...)) 判斷陳述句拿掉,并且試圖讀取一個壓根不存在的名字對應的值,看看在檔案打開時會出現什么樣的錯誤吧,(沒錯就是下面這個框)

總之,.gh 檔案就好似一個資料庫,而 Read 和 Write 這兩個函式就是電池與這個資料庫之間溝通的橋梁,通過它們倆,記憶體中的電池實體中的資料才能被永久化地存盤于 .gh 檔案中,
本次關于電池的序列化與反序列化內容就介紹到這里,其中有未詳盡之處,歡迎大家留言交流,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/262166.html
標籤:其他
下一篇:Hive、MySQL安裝
