跟蹤數據結構的變更

py9600 7年前發布 | 28K 次閱讀 數據結構 游戲開發

這兩個月,我的主要工作是跟進公司內一個 MMORPG 項目,做一些代碼審查提出改進意見的工作。

在數月前,項目經理反應程序不太穩定,經常出一些錯誤,雖然馬上就可以改好,但是隨著開發工作推進,不斷有新的 bug 產生。我在瀏覽了客戶端的代碼后,希望修改一下客戶端的 UI 框架以及消息分發機制等,期望可以減少以后的 bug 出生概率。由于開發工作不可能停下來重構,所以這相當于給飛行中的飛機換引擎,做起來需要非常小心,逐步迭代。

工作做了不少,其中一個小東西我覺得值得拿出來寫寫。

我希望 UI 部分可以嚴格遵守 MVC 模式來實現。其實道理都明白,但實際操作的時候,大部分人又會把這塊東西實現得不倫不類。撇開各種條條框框,紙上談兵的各種模式,例如 MVC MVP MVVM 這些玩意,我認為核心問題不在于 M 和 V 大家分不清楚,而是 M 和 V 產生聯系的時候,到底應該怎么辦。聯系它們的是 C 還是 P 或是 VM 都只為解決一個問題:把 M 和 V 解耦。

我們的底層使用的是 Unity3D 及它的 UGUI ,UGUI 提供了 UI 需要的顯示控件對象,我們開發的業務邏輯則是圍繞這些對象開發的。

由于在移動設備上內存有限,項目又做了一系列的對象管理工作,UI 控件并不一定常駐內存,會根據需要加載或刪除。又由于這是一個網絡游戲,UI 操作和反饋經常需要和服務器打交道,又許多異步操作。故而有一段時間頻發的 bug 來自于異步操作訪問了被刪除的控件對象。

我認為其根本原因在于 M 和 V 沒有很好的解耦。

對于 UGUI 引擎提供的控件,應該完全封裝在 View 中;而業務數據則應該全部放在和這些控件無關的數據結構即 Model 中。而 Model 改變引起的 View 更新邏輯,如果即不存在于 Model 的方法中,也獨立于 View 之外的話,那么這類 Bug 應當是不會產生的。

在迭代的代碼中,我們要求 Model 必須是一個純粹的數據結構,也不一定和顯示(View)結構一一對應。比如玩家的 HP 在 Model 中只是一個字段,但可能反應在 View 里的多處地方;而 View 里某個呈現的狀態也可以是 Model 中多個字段的復合結果。

業務代碼應該可以任意修改 Model ,不必關心 View 是否有效,理論上及時界面控件全部不存在,甚至是一個文本界面,代碼也應該可以正常工作。修改 Model 的行為不需要立刻去更新 View ,即修改 M 和更新 V 不需要也不應該是一個同步行為。它們在框架中是兩個明確獨立的執行階段,執行流程會清晰的多。

這就類似網頁前端,你的業務部分可以提交完整的網頁 DOM ,然后瀏覽器更新 DOM 呈現成圖像。當然,如果 UI 結構很復雜的話,每幀次都提交全新的完整 DOM 效率很低,如果可以篩選出每次的差異,根據差異來更新控件,效率就高的多了。

去年底,我寫了這么一個 lua 模塊: https://github.com/cloudwu/tracedoc

它可以構造一張 lua 表對象,并跟蹤對這張表的所有修改。調用 commit 方法可以比較和上次 commit 的版本間的差異,并生成差異集。

這個差異是指的最終葉節點的值差異,比如如果原來的表里有一個字段是 a.x = { 1, 2} ;如果你重新寫 a.x = {1, 2} 對 a.x 重新賦值,因為新的值還是 { 1, 2} ,模塊會認為沒有變化。

在表里只可以存放 lua 原生數據類型:數字、字符串、布爾量。表內可以用 table 創建子結構,但子表的 key 只能是數字或字符串。表里也可以存放對象的引用,但必須用 table + metatable 的形式。所有帶 metatable 的 table 都被識別為對象,不會遞歸比較內部細節。

為了方便 UI 框架的使用,還允許使用者定義一組映射函數,當變更集變更的時候,調用變更數據節點在表內的路徑串對應的預定義函數。

這樣,方便使用者實現對應的數據綁定特性。

除了在 UI 框架中的應用,這個模塊還可以用于 skynet 服務器服務間的數據同步。

比如,我們可以為每個玩家創建一個 agent 服務管理玩家的數據,但多個玩家在一個場景中戰斗時,場景服務也需要讀寫每個玩家的部分數據。

即使對數據做精心的劃分:分為玩家私有數據(例如背包)和玩家交互數據(例如玩家的戰斗 buf ,屬性等),把它們分開存放在 agent 和場景服務中。依然也有一定的需求兩類數據間發生交互。

如果我們在設計上能保證一組數據是存在一個寫入者,其它服務都是讀取者,我們可以把數據放在擁有寫入權的地方,而其它地方都是這份數據的副本。

數據體和數據副本間的同步就可以利用上面這個模塊。

在分布式系統中,不存在完全的狀態同步,但共享狀態需要保證版本的原子性,也就是說如果你的一次數據修改若涉及多個字段,那么需要保證讀取方每次都讀到這組數據不同字段的同一個版本,而不能是 a 是上個版本, b 是下個版本。

如果采用這個模塊,我們可以定期對數據表做 commit ,生成差異集發送到副本所在的服務,讓它每次都原子性的 patch 差異集。這樣持有副本方就一定能保證讀到的是一個完整的版本了。至于版本落后數據源一小段時間,則是在設計范疇內的了。

 

來自:http://blog.codingnow.com/2017/02/tracedoc.html

 

 本文由用戶 py9600 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!