CaptainZ

CaptainZ

Prompt Engineer. Focusing on AI, ZKP and Onchain Game. 每周一篇严肃/深度长文。专注于AI,零知识证明,全链游戏,还有心理学。
twitter

MUDv2是否棄用了ECS架構?

Lattice (@latticexyz) 的 MUD 是 Web3 領域最為久遠和出名的全鏈遊戲引擎,在之前的第一代版本時,曾清晰地表明,MUDv1 是基於 ECS 的一套框架,我也寫了一篇相關的介紹文章《深度解析全鏈遊戲引擎 MUD》。而幾週前,Lattice 公佈了 V2 版本,對 MUD 整體架構做了巨大改變,弱化了與 ECS 的聯繫,引入了一個新的鏈上數據庫 “Store”,該數據庫基於 “table” 的數據結構,那麼應該如何理解 “ECS” 和 “table” 的關係?新組件 “Store” 到底是如何工作的?“ECS” 到底適不適合製作全鏈遊戲?我們通過本文一探究竟。
 

MUDv2 概覽#

quick-start-mud-cli

MUDv2 是一個用於構建以太坊應用的框架。它通過一個緊密集成的軟件棧來壓縮構建 EVM 應用的複雜性。MUDv2 包括:Store(一個鏈上數據庫)、World(一個入口點框架,提供標準化的訪問控制、升級和模塊)、基於 Foundry 的快速開發工具、能夠反映鏈上狀態的客戶端數據存儲,以及 MODE(一個可以使用 SQL 查詢並反映你鏈上狀態的 Postgres 數據庫)。
 
MUDv2 不是一個 rollup 或者鏈,它是一套可以一起工作以構建鏈上應用的庫和工具。MUDv2 不限定於以太坊主網,它可以在任何 EVM 兼容的鏈上工作。MUDv2 不僅僅適用於自治世界和鏈上遊戲,也不會強制將數據模型強加給開發者,你可以用 MUD 做任何你可以用扁平的 Solidity 映射和數組做的事情。MUDv2 的數據可用性方案,與 ENS 和 Uniswap 這樣的常規部署在以太坊主網的應用一樣。
 
MUDv2 主要思想是,所有鏈上狀態都保存在 Store(MUD 鏈上數據庫)中。我們目前處理智能合約狀態的方式導致了一些主要問題,例如,狀態和邏輯的耦合使得升級邏輯非常困難。使用 MUD,你永遠不會使用 Solidity 編譯器驅動的數據存儲。所有狀態都使用 Store 保存和檢索,這是一個高效的鏈上數據庫。Store 就像 SQLite:它是一個在 Yul 中手動優化的嵌入式數據庫。它有表格,有列和行。
 
MUDv2 邏輯是無狀態的,且具有自定義權限,可以跨合約調用。MUD 推薦使用 World:一個入口點 kernel,負責從不同的合約中調解對 Store 的訪問。World 在部署時創建一個 Store。每個 Store 中的表格都在一個命名空間下註冊,用一個名字表示,就像一個扁平的文件系統路徑。
 
MUDv2 無需索引器或子圖,前端會自行同步:當使用 Store(以及擴展的 World)時,你的鏈上數據自我檢查,任何改變都通過標準事件進行廣播。這些事件和模式會被 MODE 利用:MODE 將你的鏈上狀態轉化為一個 SQL 數據庫,並保持毫秒級的延遲更新。

ECS 的本質是什麼#

Snip20230713_8

通過查閱文獻,我個人覺得(作者 @hicaptainz 不是開發人員如有錯誤請指正),ECS(實體 - 組件 - 系統)模式本質是一種數據結構的建模方式,它的核心在於如何存儲和組織數據。
 

  1. 實體(Entity):在 ECS 模式中,實體是一個抽象的概念,它並不直接持有數據,而是通過組件來關聯數據。實體可以被看作是一個或多個組件的容器,它的主要作用是為組件提供一個唯一的標識。

  2. 組件(Component):組件是數據的載體。在 ECS 模式中,所有的數據都被封裝在組件中。每個組件都代表了一種特定的屬性或者行為,例如位置、速度、顏色等。組件只包含數據,不包含任何邏輯或行為。

  3. 系統(System):系統是處理數據的地方。系統會根據實體的組件來決定如何處理這些實體。每個系統都有一個或多個特定的任務,例如渲染、物理模擬、AI 邏輯等。

 
從這個角度來看,ECS 模式的確是一種數據結構建模的方式。它將數據(組件)和行為(系統)分離,使得數據的存儲和處理更加靈活和高效。這種模式的優點在於:

 

  • 可組合性:通過組合不同的組件,可以創建出具有各種屬性和行為的實體,而不需要創建大量的類或結構。

  • 數據局部性:由於組件只包含數據,因此可以將相關的數據緊密地存儲在一起,提高緩存利用率,從而提高性能。

  • 可重用性:系統只關心數據,而不關心數據來自哪個實體,因此可以在多個實體之間重用同一個系統。

  • 並行性:由於數據和行為的分離,使得在多線程環境下對數據的並行處理變得更加容易。

ECS 數據結構建模的幾種類型#

那麼可以實現 ECS 的數據結構方式到底有哪幾種呢?一般來說,比較流行的兩種是 Archetype 和 Sparse set ,其他不太常見的還有 Bitset 和 Reactive ECS。它們各自有其優點和適用場景。
 

  1. Archetype(也叫 Table based ECS):Archetype (原型)是一種優化了數據局部性的存儲方法,它將具有相同組件集的實體存儲在一個表格中,其中組件是列,實體是行。Unity 的 ECS 系統就採用了這種方法。Archetype 的優點在於,由於相同的組件集在內存中是連續的,因此可以極大地提高緩存效率和數據訪問速度。此外,由於每個 Archetype 都知道它包含的組件,因此可以在運行時動態地創建和銷毀實體,而無需進行大量的內存操作。

  2. Sparse Set (也叫 Sparse ECS):Sparse Set(稀疏集)是一種高效的數據存儲和訪問方法,它結合了數組和哈希表的優點。基於稀疏集的 ECS 將每個組件存儲在自己的稀疏集中,該集合以實體 ID 為 key。這種方法的優點是可以有效地利用緩存,同時也可以節省空間。然而,這種方法的缺點是實現起來比較複雜。

 
至於 Bitset 和 Reactive ECS,它們也有其獨特的優點,但可能並不適合所有的應用場景。
 

  • Bitset:Bitset 是一種簡單且高效的方法,用於檢查實體是否具有特定的組件。然而,由於 Bitset 本身無法存儲組件數據,因此通常需要與其他存儲方法結合使用。

  • Reactive ECS:Reactive ECS 是一種更高級的模式,它允許系統對組件的添加、刪除和修改進行響應。這種模式可以使代碼更加清晰和易於理解,但可能會增加實現的複雜性。
     
    如果有讀者看過我的文章《Curio 是如何把 ECS 遊戲引擎內置到 OPStack 中的?》,立馬可以發現,Curio 採用的正是 Sparse Set(稀疏集)的方式來儲存和操作 ECS 數據,那麼 MUDv2 呢?顯然就是 “Archetype” 了,也就是 “table based ECS”。

Store 組件的工作原理#

MUDv2 的 Store 組件提供的數據模型可以很好地支持 ECS 模式。在 Store 中,你可以創建一個表格來表示實體,每個實體都有一個唯一的鍵。你可以為每個實體添加各種組件,這些組件可以是表格中的字段。然後,你可以編寫系統來處理這些組件,這些系統可以是智能合約中的函數。
 
例如,你可以創建一個 "Player" 的表格,它有 "position" 和 "health" 兩個字段。然後,你可以編寫一個 "move" 的函數來改變玩家的位置,一個 "damage" 的函數來減少玩家的生命值。所以,雖然 MUD 的 Store 組件並不直接實現 ECS 模式,但它提供的數據模型可以很好地支持 ECS 模式的實現。
 
也許有人會問,這種基於 table 的鏈上數據庫也支持 OOP(面向對象編程)模式嗎?
 
Store 組件提供了一種基於表格的數據模型,這種模型更接近於關係型數據庫或者說是數據驅動的設計,而不是面向對象編程(OOP)的模型。在面向對象編程中,數據和操作這些數據的方法被封裝在對象中,而在 Store 的模型中,數據被存儲在表格中,操作數據的邏輯則由智能合約中的函數來處理。
 
然而,這並不意味著你不能在智能合約中使用面向對象的設計模式。你可以創建一個智能合約來表示一個對象,這個智能合約可以有一些狀態變量來表示對象的屬性,也可以有一些函數來表示對象的方法。然後,你可以使用 Store 來持久化這些對象的狀態。
 
例如,你可以創建一個 "Player" 的智能合約,這個智能合約有 "position" 和 "health" 兩個狀態變量,以及 "move" 和 "damage" 兩個函數。然後,你可以使用 Store 來存儲每個 "Player" 實例的 "position" 和 "health"。所以,雖然 Store 的數據模型並不直接支持面向對象編程,但你可以在智能合約中使用面向對象的設計模式,並使用 Store 來持久化對象的狀態。
 
但正如我們一直強調的,對於全鏈遊戲來說,ECS 模式還是更加合適一些,因為在這裡的一個大優勢是,新的系統(System)可以在開發的任何階段引入,並且會自動與具有正確組件的任何現有和新的實體匹配。這促進了一種設計,其中系統被開發為單一責任,小的功能單位,可以輕鬆地部署到不同的項目中,從而實現全鏈遊戲的 “可組合性”。

鏈上數據的儲存#

既然 Store 組件是建立一個鏈上數據庫,那麼這些鏈上數據到底是存在什麼地方呢?我們先回憶一下 EVM 的儲存方式。
 
在以太坊虛擬機(EVM)中,鏈上數據主要存儲在兩種數據結構中:存儲(Storage)和內存(Memory)。

  1. 存儲(Storage):這是每個智能合約的持久化存儲,它的數據在交易之間是持久的。存儲是由鍵值對組成的,其中鍵和值都是 256 位的。在 Solidity 中,合約的狀態變量就存儲在這裡。例如,如果你在合約中定義了一個uint256 public counter;,那麼這個counter變量就存儲在存儲中。每次你調用改變counter的函數,它就會在存儲中更新。

  2. 內存(Memory):這是每個函數調用的臨時存儲,它的數據在函數調用結束後就會被清除。內存是線性的,可以想象成一個字節數組。在 Solidity 中,函數的局部變量就存儲在這裡。

此外,還有一個叫做棧(Stack)的數據結構,它用於存儲函數調用的參數和返回值,以及一些臨時的計算結果。棧的大小是有限的,最多可以包含 1024 個元素。
 
在 EVM 中,讀取和寫入存儲的成本都是非常高的,因為它需要消耗 gas。因此,智能合約的開發者通常會儘量優化他們的代碼,以減少對存儲的操作。相比之下,讀取和寫入內存的成本要低得多,但是由於內存的數據在函數調用結束後就會被清除,所以它只適合存儲臨時的數據。
 
MUDv2 的 Store 組件正是將數據存儲在 EVM 的存儲(Storage)中。每個智能合約在 EVM 中都有自己的存儲空間,這個存儲空間是持久的,也就是說,即使在交易之間,存儲中的數據也會保持不變。
 
Store 組件提供了一種更高級的抽象,使得開發者可以更方便地在智能合約中存儲和讀取數據。開發者可以定義自己的表格,每個表格都有一個特定的模式,定義了表格中的字段和它們的類型。然後,開發者可以使用 Store 提供的 API,如 set、get 等函數,來操作這些表格中的數據。
 
這些操作最終都會被轉換為對 EVM 存儲的讀寫操作。例如,當你調用 MyTable.set 函數來設置一條記錄時,這個函數會將數據編碼為字節,然後寫入到 EVM 的存儲中。當你調用 MyTable.get 函數來獲取一條記錄時,這個函數會從 EVM 的存儲中讀取數據,然後將數據解碼為你定義的類型。

被忽略的狀態機問題#

我們在上文的分析中,似乎看到的都是 ECS 模式的優點,那麼真的就沒有缺點嗎?這裡就要從 EVM 自身和狀態機講起了。
 
狀態機,又稱狀態自動機,是描述對象狀態變化以及如何響應輸入(比如事件或者條件)的模型。在狀態機中,系統在任何給定時間都處於某種狀態,並且會在輸入的影響下從一個狀態轉移到另一個狀態。在傳統遊戲引擎中,狀態機通常用於管理遊戲對象(如角色、敵人、AI 等)的行為狀態。這些狀態可能包括行走、跑動、跳躍、攻擊、防禦、死亡等。狀態機可以幫助我們清晰地定義和控制遊戲對象在不同狀態之間的轉換。
 
對於 EVM(Ethereum Virtual Machine),我們可以把它看作一個全球分佈式的狀態機。在 Ethereum 網絡中,每個區塊都會包含一系列的交易,而這些交易就是輸入。EVM 會執行這些交易,根據交易的內容來更新 Ethereum 的全局狀態。Ethereum 的全局狀態包含了所有賬戶的餘額信息,智能合約的代碼,以及智能合約存儲的數據等等。當一個交易被執行時,它可能會轉移資金,調用合約,或者改變合約的存儲狀態,這些都會導致 Ethereum 的全局狀態發生改變。
 
所以對於全鏈遊戲來說,在遊戲引擎中儲存狀態機是一件必須要做的事情。但是在 ECS 模式中儲存狀態機(特別是一個分佈式狀態機),會有一系列的問題。
 
狀態機在 ECS 中的問題:使用 ECS 開發遊戲,可能會遇到是否要在 ECS 中存儲狀態機的問題。這個問題比預期的要複雜得多,原因有很多。例如,更改實體的狀態、讓實體參與多個狀態機、獲取當前狀態的實體、更改狀態機的狀態列表等操作在 ECS 中都有挑戰。
 
標籤狀態的問題:一種直觀的實現狀態機的方式是為每個狀態創建一個標籤。這種方法在查詢給定狀態的所有實體時表現良好,因為 ECS 實現通常擅長找到給定組件 / 標籤的所有實體。然而,其他操作,如更改狀態,需要添加一個標籤並刪除另一個標籤,這可能會引入複雜性和性能問題。
 
我們具體到 Archetype 這種數據結構來分析。
 
實施 Archetype 的方法是將所有具有相同組件集合的實體聚集在一張表中。避開具體細節,這種方式為組件的高效迭代緩存提供了便利,因為可以連續迭代多個組件。但是,這也帶來了一些代價。
 
為了保持具有相同組件集的實體的聚合,每次添加或刪除組件時,都需要在不同的表間移動實體。這同樣適用於標籤。因此,頻繁地添加或刪除和狀態有關的標籤可能會變得非常昂貴,因為每次狀態變化時都需要複製實體的所有組件(實際上,由於存儲方式的原因,每個組件需要被複製兩次)。
 
為了解決上面的问题,Bevy ECS 的作者提出了一種新的方法來實現存儲變體:將 Archetype 與表格分開。在這種方法中,表格只存儲實體的(數據)組件,而 Archetype 存儲組件 + 標籤。多個 Archetype 可以指向同一個表格。有了這個,應用程序可以在常數時間內添加和刪除標籤(以及狀態),這比必須複製所有組件有了巨大的改進。
 
也許,MUDv3 會朝著這個方向改進,我們看看它在未來是否會在 table 之外新加一個 Archetype 存儲組件吧。

小結#

現在我們來試著回答開始的問題。table 是實現 “Archetype ECS” 的一種建模方式,MUDv2 並沒有棄用 ECS,只不過剛好 table 這個組件也可以實現 OOP 的架構。這個 table 是通過 Store 組件來生成的,並且儲存在 EVM 的 Storage 裡面,因此所有對 table 的讀寫操作都會轉化為對 Storage 的讀寫操作。雖然 ECS 架構用來儲存 EVM 狀態機有不少的困難,但是仍舊有不少辦法來幫助解決。如果您對這些優化方法感興趣,可以關注作者 twitter(@hiCaptainZ)來獲取最新信息。作者目前在 Gametaverse(@GametaverseDAO)任職 Head of Research,曾獲北卡大學光學工程碩士和香港中文大學金融碩士,目前研究領域為 Web3 Gaming,AI 和 ZKP,預計未來幾週的研究課題如下:
 

  • 傳統遊戲引擎與 Web3 遊戲引擎的對比分析
  • 如何確定遊戲的可玩性
  • Web3 中的博彩遊戲與 GambleFi 的崛起
  • 全鏈遊戲的可組合性到底有哪些
  • Web3 遊戲的經濟系統設計
  • 不同遊戲題材如何影響 Tokenomics 的設計
  • ECS 架構是不是一種範式(優勢和劣勢)
  • 如何從零到一設計一種 ECS
  • AI 如何輔助鏈遊製作
  • ZK 零知識證明全賽道一覽
  • ZK 技術與機器學習 ZK-ML
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。