世界是如何運行的--MCBE方塊更新機制不完整分析

先打廣告:

摸完村莊機制,這次填一下方塊更新的坑
前排提示:
所有內容來自1.14.21的BDS反編譯后的偽源碼,但是全文不會直接出現(xiàn)代碼,大可放心閱讀
部分內容參考了國外玩家earthcomputer和Floan的一篇文檔:https://docs.google.com/document/d/17B0ecrI8BSU1VWpfUNQOoco9l6rhkx1U6S4J1ihFRNg/edit
(免責聲明)所有內容不保證完全正確,僅供參考
下面是正文:

世界(存檔)每隔特定的時間就會更新一次數(shù)據,這更時間叫做游戲刻(gametick),也就是常說的gt。反映到游戲上就是你能看到游戲內植物的生長,紅石的運作,生物的移動等等。這篇文章會聚焦在方塊相關的數(shù)據更新上(所以不會包括spawn和despawn行為)。
區(qū)塊之間的更新
世界的更新是以區(qū)塊為單位的,當模擬距離為4 的時候每gt 有57 個區(qū)塊發(fā)生了更新(57個是絕大多數(shù)情況下),更新的區(qū)塊圍繞在玩家周圍,下圖是某個gt內的區(qū)塊更新:

玩家位于中心區(qū)塊中,數(shù)字大小展示了這一gt 區(qū)塊更新的順序.很不幸的是這個區(qū)塊間更新的順序沒有什么很好的規(guī)律,mojang的預期行為是每20gt就把這個順序隨機打亂一次,也就是說每過20gt這個區(qū)塊間的更新順序會變化一次。
但是根據我的實測,這個變化周期不是恒定的20gt,而是在17-20gt之間反復橫跳,但是這還不是最要命的,最要命的是每次更新順序變化的時候都會有一次不均勻的區(qū)塊更新,也就是在那個游戲刻某些區(qū)塊會獲得兩次更新,而某些區(qū)塊不會得到更新.
下面是一個這樣的例子:

區(qū)塊內的更新
區(qū)塊內的更新的產生往大了講主要有兩個類別,一種觸發(fā)性的(比如玩家在某一時刻破壞一個方塊而更新它周圍的六個方塊),一種周期性的(比如在特定條件下每gt 都會嘗試讓水結冰)。
觸發(fā)性的更新
觸發(fā)性更新目前唯一已知的來源是周圍方塊的更新導致當前方塊進行的更新。這時候會(檢測玩家操作和區(qū)塊更新行為的次序未知,有興趣的可以合理猜測)調用該方塊的neighborChanged 函數(shù)(所以簡稱NC更新?)。比如你挖掉了一個插了火把的木板的時候,木板周圍的六個方塊會立即調用它們的neighborChanged函數(shù),而附著于它的火把方塊也會因為調用了neighborChanged 函數(shù)而立即做掉落檢查(在其NC函數(shù)內部調用了_checkDoPop() ).
根據測試,以下是已知的能產生NC更新的事件
方塊的放置和破壞
拉桿的拉動
鐵軌的激活
等等
而調用周圍6個方塊的更新的順序為:
-x 西
+x 東
+z?南
-z 北
+y 上
-y 下
知道了更新源頭就要知道接收到更新后會做什么。下面是常見的響應NC更新的方塊以及可能發(fā)生的事件:

可以注意到有些方塊在受到neighborChanged更新的時候有可能把實際更新行為加入到隊列,這里的隊列指的是一個緩沖區(qū),上面的方塊受到NC更新后并不會馬上更新自己的狀態(tài),而是把相關信息放到隊列中,稍后再更新。
更新隊列
更新隊列(BlockTickingQueue)是一個優(yōu)先隊列,也就是一個更新項( BlockTick )的列表,會暫存還沒有實際更新的更新項目,這樣的隊列每個區(qū)塊都有一個。
更新項
每個更新項有下面幾個字段:
該更新的坐標(x,y,z)
(游戲內)時間戳
方塊狀態(tài)
方塊名字
其它狀態(tài)信息(依據不同方塊的不同而不同)
更新項的數(shù)據為后面的實際更新行為提供了數(shù)據
更新隊列的消耗
隨著游戲的進行,隊列里面的更新項目會越來越多(因為更新一直在產生),但是這個隊列里面的更新項也在不斷地被消耗,
下面是消耗的過程:
獲取更新隊列的長度
從隊列中獲取前100 個方塊更新項(不足100就全部獲取)然后放置到另外一個緩沖隊列中(目的未知)
按照特定順序依次取出更新隊列中的更新項目然后執(zhí)行其對應的tick() 函數(shù)。順序是這樣的:每次優(yōu)先更新時間戳小(也就是更新項里面的游戲內時間)的更新項,如果兩個更新的時間相同,先后順序未知。(當然不排除針對某些更新的特意的隨機行為)
這個過程每gt每個區(qū)塊會執(zhí)行兩次
當退出游戲的時候每個區(qū)塊的更新隊列會被完整地寫入到存檔文件(你可以用mcctool[http://mcctoolchest.com/]在存檔的區(qū)塊數(shù)據文件內看到這個更新隊列的信息,包括所有更新項的具體數(shù)據),加載世界的時候這些數(shù)據也會被完整讀取出來.
不難發(fā)現(xiàn),當更新的速度大于消耗的速度的時候,更新隊列越來越長越來越長越來越長的情況,輕則出現(xiàn)更新延遲,當然這個特性已經被云龍大叔發(fā)現(xiàn)了:

重則導致崩檔。
更新順序
bb這么久終于到達更新順序問題了,以下描述的是每個區(qū)塊一個gt 內依次發(fā)生的事情(忽略Spawn 和despawn ):
隨機刻更新
更新隊列更新
更新隊列更新(對應上面說的兩次)
方塊實體更新
紅石更新
上面說了更新隊列,下面介紹隨機刻更新(tickBlocks) ,方塊實體更新(tickBlockEntities)和紅石
更新( tickRedstoneBlocks )
隨機刻更新
這個事件里面的更新都是基于概率的,因此才有"隨機"二字,其內部會依次發(fā)生如下事情:
嘗試改變天氣
如果在下雨或者打雷就嘗試生成閃電來劈實體,然后嘗試生成骷髏馬陷阱
進行一次結冰嘗試
進行一次給地表鋪上雪片的嘗試
根據當前隨機刻計算更新次數(shù),選擇一些方塊進行隨機刻更新(調用特定方塊的tick() 函數(shù)),當然選擇的方塊要合適,更新的次數(shù)和如何選擇暫時未知,這里一般都是方塊的自然更新行為.
方塊實體更新
每個區(qū)塊都維護了一個方塊實體( BlockActor )列表,每gt 這個列表都會被有意地完全隨機地打亂一次,打亂后遍歷這個列表,對列表內的每個方塊實體執(zhí)行tick() 函數(shù),括號內是猜測的行為(不能保證100%準確,僅供參考)

其中玩家最為熟悉的大概就是活塞的隨機推出順序了吧,這也是一些活塞互推隨機數(shù)生成器的原理。
紅石更新
紅石組件以及繼承樹
下面這個繼承樹主要來自于國外玩家earthcomputer和Floan的一篇文檔,鏈接見開頭:
各種紅石組件有一個相同的父類(理解成都是同一個爸生出來的,有不同點也有相同點),下面是繼承樹(理解成父子關系),括號內是翻譯,中文僅供參考
Component(組件)
Consumers(消費者)
Piston Consumers(活塞消費者)
Producers(生產者)
Capacitors (= producer with a direction)(特定方向的生產者)
Repeater Capacitors(中繼器電容器)
Comparator Capacitors(比較器電容器)
Pulse Capacitors (observers?)(脈沖電容器(觀察者?))
Redstone Torch Capacitors(紅石火把電容器?)
Transporters(運輸者)
Rail Transporters(鐵軌運輸者)
Powered Blocks(充能方塊)
這里“生產和消費”的猜測是紅石信號,生產者產生紅石信號,消費者會感應紅石信號(僅限猜測),紅石火把閃一段時間后會熄滅就是這個Redstone Torch Capacitors的鍋。
更新行為
每個維度有個電路系統(tǒng)( Circuit System ),而這個系統(tǒng)會維護一個紅石組件(Component)的哈希表:每當一個紅石組件被放置的時候這個列表就會記錄下這個原件的信息以及位置,當然原件也會在合適的時候被移除(比如被玩家破壞)。
每個區(qū)塊都會獲取這個存著所有原件的哈希表,然后對位于該區(qū)塊內的紅石原件執(zhí)行一次它們對應的onRedstoneUpdate 函數(shù)。幾乎所有方塊都有onRedstoneUpdate 函數(shù),只不過絕大部分的方塊這個函數(shù)什么都不做的,下面這些特定方塊才會有具體的事件(更新行為不保證100%準確):

總結與展望
在一定的粒度上更新的次序是隨機的或者未知的,在一定的粒度上更新次序又是固定的
機制應該回歸到游戲,空談是萬萬不行的,上面的內容部分受到了實踐的檢驗,但是部分沒有被玩家檢驗過,因此如果你發(fā)現(xiàn)這些理論的某些部分和你的實際游戲行為不一致歡迎抓蟲
畢竟不是每一行代碼都能看懂,因此我無法得到完整詳細的邏輯,也就不可能做到完全客觀,我當然可能會潛意識地把代碼行為往我希望的方向描述,也可能遺漏了某些關鍵部分
希望有更多的玩家能在自己擅長的領域對MCBE社區(qū)發(fā)展做貢獻