淺析無延遲比較器鏈
0 引言

這是一條標準的比較器鏈,它可以以 1 b/gt 的速度向右保持信號強度地傳遞紅石信號。如果你要使用模電,這個傳遞速度還是稍慢了,有的機器對時序比較敏感,為了防熊、穩(wěn)定性等考量,也需要將模電信號瞬時地傳遞,這個時候,我們就需要無延遲的比較器鏈了。
本文所有的測試都在 Minecraft 版本 1.19.2 進行,所有的代碼分析也都用的是 1.19.2 的[1]。
1 無延遲比較器鏈的理論
無延遲比較器鏈,就是我們希望讓一條比較器鏈在同一 gt 內(nèi)全部亮起,并且保持紅石信號地傳輸。為了分析這樣的裝置的理論可能,我們需要理解比較器為什么會亮起。為此我們需要翻代碼。我們可以找到控制比較器信號變化的一段代碼:
(如果你不想看代碼分析,請直接翻到這一節(jié)的末尾看結(jié)論)

比較器是一個具有方塊實體的方塊,其信號強度存儲在其方塊實體中。我們看到上述方法一共做了這樣幾件事:
計算當前應(yīng)當輸出的信號強度,這在 calculateOutputSignal() 中就執(zhí)行了,包含了容器檢測、比較大小、減法等一眾邏輯。
將比較器的信號強度改為上一步中計算的信號強度。
如果發(fā)現(xiàn)信號強度改變了,就試著更新自身的亮滅狀態(tài)。
對比較器輸出方向的方塊及其除了這個比較器之外的所有毗鄰方塊發(fā)出 NC 更新。
這是比較器應(yīng)當進行的所有邏輯,當這個方法被調(diào)用時,比較器會瞬時執(zhí)行這些邏輯。那么這個方法在哪里被調(diào)用了呢?它總共就只有兩處調(diào)用,有一處是在玩家更換比較器模式時,將比較器更新一次,這一處與我們的用途無關(guān),我們不關(guān)心。重要的是下面的這一處:

net.minecraft.block.ComparatorBlock#scheduledTick(BlockState, ServerWorld, BlockPos, Random)
我們發(fā)現(xiàn),比較器是計劃刻元件。這指的是比較器的變化受計劃刻控制,只有當比較器受到一個計劃刻的時候,它才會更新自己的狀態(tài)。我們看看,它在什么條件下,可以給自己一個計劃刻。根據(jù)常識我們知道控制紅石元件的更新是 NC 更新,我們應(yīng)該到這相關(guān)的方法里面去找。

net.minecraft.block.AbstractRedstoneGateBlock#neighborUpdate(BlockState, World, BlockPos, Block, BlockPos, boolean)
在控制比較器的類 ComparatorBlock 里面沒有找到 neighborUpdate() 方法,但是在它的超類——同時控制中繼器和比較器的部分相似邏輯的?AbstractRedstoneGate 類中找到了。我們看到,當比較器受到 NC 更新時,如果自己的放置狀態(tài)是好的,即不會掉落,那么就更新自己的能量狀態(tài),也就是調(diào)用 updatePowered() 方法。讓我們看看這個方法寫了什么。

net.minecraft.block.ComparatorBlock#updatePowered(World, BlockPos, BlockState)
我們看到,在這里比較器給自己規(guī)劃計劃刻了。這個計劃刻具有 2 gt 的延遲,在 2 gt 后,比較器的 update() 方法將會被調(diào)用,瞬時更新自己的狀態(tài)。我們來看一下這個產(chǎn)生計劃刻的條件:這個比較器并沒有已有一個計劃刻,而且應(yīng)該輸出的能量不等于比較器存儲的能量。
至此,我們可以總結(jié)出比較器發(fā)生一次變化的流程:
(不想看代碼分析的讀者請在這里停下,下面是結(jié)論)
當比較器受到 NC 更新,將檢測自己的狀態(tài)。如果自己已經(jīng)有了一個計劃刻,無事發(fā)生。否則,檢查自己現(xiàn)在應(yīng)當輸出的能量,如果不等于自己現(xiàn)在實際輸出的能量,給自己規(guī)劃一個計劃刻。
當比較器受到一個計劃刻,將會瞬間更新自己的狀態(tài),并發(fā)出一些相應(yīng)的 NC 更新。
那么我們?yōu)槭裁纯梢杂袩o延遲比較器呢,我們還是考慮文首的那一條比較器鏈?,F(xiàn)在如果在同一 gt 內(nèi),這條鏈上的比較器從輸入端向輸出端依次受到計劃刻,并且假設(shè)此時輸入端的比較器應(yīng)該亮起,我們看看會發(fā)生什么:
輸入端的第一個比較器受到計劃刻,發(fā)現(xiàn)自己應(yīng)該亮起,于是瞬時亮起。
輸入端的第二個比較器受到計劃刻,自檢,發(fā)現(xiàn)自己應(yīng)該亮起,因為在上一步中第一個比較器已經(jīng)亮起,所以它也瞬時亮起。
輸入端的第三個比較器受到計劃刻,自檢,發(fā)現(xiàn)自己應(yīng)該亮起,因為在上一步中第二個比較器已經(jīng)亮起,所以它也瞬時亮起。
……
于是結(jié)果就是,所有的比較器在同一 gt 內(nèi)亮起!這就是無延遲比較器的基本工作原理,它強烈依賴于各個比較器計劃刻執(zhí)行的順序。我們知道同一 gt 內(nèi),計劃刻的執(zhí)行順序是:
先比較產(chǎn)生時的 gametime 也就是 gt 數(shù),產(chǎn)生的早的先執(zhí)行。
如果上一步相等,那么比較優(yōu)先級,優(yōu)先級高的先執(zhí)行。
如果上一步還是相等,比較計劃刻的編號,計劃刻的編號是一個自增的整數(shù),永遠不會出現(xiàn)相等的情況,在創(chuàng)建新的計劃刻時自增。編號越小,說明在游戲的代碼內(nèi)部,它創(chuàng)建的越早。編號小的先執(zhí)行。
這些計劃刻具有同樣的延遲而在同一 gt 執(zhí)行,因此產(chǎn)生這些計劃刻必須在同一 gt,而我們知道比較器的計劃刻具有同樣的優(yōu)先級,因此,為了讓比較器從輸入端向輸出端依次受到計劃刻,必須讓比較器從輸入端向輸出端依次創(chuàng)建計劃刻!
于是我們得到了這一節(jié)的結(jié)論:無延遲比較器鏈是理論可行的,其生效的條件是:比較器鏈從輸入端向輸出端依次創(chuàng)建計劃刻。
(注意我反復(fù)用紅字強調(diào)這個順序,這是因為無延遲比較器鏈完全依賴于這個順序,它怎么強調(diào)都不為過)
2 無延遲比較器鏈的建造
現(xiàn)在我們可以關(guān)掉代碼,打開游戲,真正地開始建造這條目前還在理論中的比較器鏈了。根據(jù)我們剛才的思路,我們要從輸入端向輸出端依次創(chuàng)建計劃刻,我們還知道了比較器創(chuàng)建計劃刻的條件——應(yīng)輸出能量和實際輸出能量不相等。為了滿足這個條件,我們可以在每個比較器后面的完整方塊上放一個紅石粉,之后我們會將它們點亮。

現(xiàn)在假設(shè)我們點亮了其中的一個紅石粉,那么這一瞬間,這個紅石粉將會產(chǎn)生對二階毗鄰的 NC 更新,更新其前后的兩個比較器,這兩個比較器會自檢,只有前面那個比較器會發(fā)覺自己的實際輸出能量(0)和應(yīng)輸出能量(15)不同,于是產(chǎn)生計劃刻。如果后面那個比較器它后面的紅石粉也被點亮了,那么它也會察覺到這個不同,但是點亮那個紅石粉的時候,它已經(jīng)產(chǎn)生了一個計劃刻了,不會再產(chǎn)生第二個。因此,將上面的結(jié)構(gòu)中的一個紅石粉點亮 1 gt 將給它前面的那個比較器一個計劃刻。
于是我們就把問題轉(zhuǎn)化了,問題轉(zhuǎn)化為:將上面結(jié)構(gòu)的紅石粉在 1 gt 內(nèi)從左到右依次點亮。為此,我們需要一些微時序相關(guān)的知識。我們知道,在粘性活塞收回時,大致上靠近活塞的方塊將先于遠離活塞的方塊轉(zhuǎn)化為 B36,我們還可以利用佛冷的 pistorder 模組顯示活塞將方塊轉(zhuǎn)化為 B36 的順序,這也就是這些 B36 固化的順序。

觀察上圖,粘液塊下面粘的紅石塊便是用來點亮下面的紅石粉的?,F(xiàn)在假設(shè)上圖中,左邊的粘性活塞收回,我們分析移動結(jié)構(gòu):

可以看到這和常識相符——粘性活塞收回時,近端的比遠端的先變?yōu)?B36,從而先到位;另外,由于活塞的運動是方塊事件,這有一個特性,就是在前一個活塞將所有的 B36 創(chuàng)建以及原有方塊刪除這些動作完畢之前,后一個活塞不會有響應(yīng)。我們看到在上方的移動結(jié)構(gòu)中,被標了1、2、4的紅石塊代表變?yōu)?B36 的順序,它們按這個順序變?yōu)?B36 之后,前一個活塞的收回動作完成,后一個活塞開始收回。這樣的話,紅石塊變?yōu)?B36 的順序確實是從輸入端到輸出端,從而在 2 gt 后的方塊實體階段,B36 固化的順序也是這樣。我們知道紅石粉亮起是瞬間完成的動作,即從 B36 固化到紅石粉亮起兩者之間無法插入其他操作[2],因此,紅石粉從輸入端到輸出端依次亮起,這正是我們的需求。
根據(jù)理論分析,這個結(jié)構(gòu)應(yīng)該是可以完成無延遲比較器鏈的工作的。接下來,我們做一個測試:

我們在裝置的尾端加一個箱子,箱子里面放一個物品,上面蓋一個固體方塊,稍后我們將把這個固體方塊打掉,這時輸入端的第一個比較器將變成 CUD,我們可以很方便地檢驗效果。

這個 CUD 式的輸入只是測試用的,我們可以加一個偵測器的結(jié)構(gòu),讓它變成真正的無延遲比較器——無論長度如何,在傳入一個信號之后,它可以保持信號強度地在固定時間內(nèi)將信號傳任意遠,并且不需要玩家手動拆除紅石塊。比如說在輸入端加裝這樣的一個結(jié)構(gòu):

至此,我們將無延遲比較器的構(gòu)想轉(zhuǎn)化為了現(xiàn)實。
3 各種不同的無延遲比較器鏈的架構(gòu)
看到了上面的步驟后,我們發(fā)現(xiàn)建造無延遲比較器鏈的理論要求是很寬松的,很容易實現(xiàn),MC 的各種機制都在這方面對我們有利——比如方塊事件的串行性,粘性活塞收回時,近端比遠端先到位等等。我們看看幾種最容易想到的可用的設(shè)計。

如果想要節(jié)省粘液塊,可以采用這種設(shè)計。它的時序是對的,這基于方塊事件的串行性,所以你看,這對我們確實十分有利,只要你不亂設(shè)計,幾乎都是對的。
我們也沒有說過,一定要通過比較器后面完整方塊上的紅石粉來更新比較器。只要你不會用到強模,直接在比較器后面接紅石粉為什么不行呢?

(此處啟動的地方因為紅石粉高了一格,結(jié)構(gòu)略有不同)
也沒說過只能從后面更新,從側(cè)面更新有何不可?

這樣的無延遲比較器鏈,也可以使用減法模式等等,也可以進行自然的衰減,你甚至可以將其做到 1 寬:

上面的結(jié)構(gòu)如果你把比較器前面的方塊換成帶釉陶瓦或者黑曜石,那么這就是無衰減的 1 寬的無延遲比較器鏈。
理論上也可以利用偵測器去更新,但是筆者還沒有掌握可控的控制偵測器更新順序的方法,這方面的發(fā)掘就留給讀者了。偵測器的好處是可以從下面更新,這樣如果上面沒有空間擺放結(jié)構(gòu)的話,那么也可以使用無延遲的比較器鏈。筆者做出了一個基于偵測器的從下更新的版本,但是沒有掌握通用的設(shè)計規(guī)律:

期待讀者進一步地發(fā)掘更多的無延遲比較器鏈相關(guān)的信息。
4 結(jié)言
本文中,我們淺析了無延遲比較器鏈的理論工作原理,并給出了一些可以實現(xiàn)這個理論的設(shè)計實例。無延遲比較器鏈并不是筆者的原創(chuàng),根據(jù)筆者看到的信息,它在 2018 年附近就有了,筆者在?BV13R4y1Q7BM 了解到了這個東西的存在。筆者設(shè)計的編碼全物品需要利用比較器鏈傳遞模擬信號,以傳遞信息,因此探究了這一部分的內(nèi)容。
借物表:
使用材質(zhì):XeKr 方紋,XeKr 姹紫嫣紅附加包
(XK 的材質(zhì)包是真的好看,大家都可以嘗試著用用)
參考文獻:
[1] Minecraft 1.19.2 源碼,請自行使用諸如 MCP 之類的工具生成
[2] Fallen_Breath 的專欄:CV4122124、CV4565671、CV10002253