欢迎光临散文网 会员登陆 & 注册

浅析无延迟比较器链

2022-10-28 20:06 作者:Void0  | 我要投稿

0 引言

这是一条标准的比较器链,它可以以 1 b/gt 的速度向右保持信号强度地传递红石信号。如果你要使用模电,这个传递速度还是稍慢了,有的机器对时序比较敏感,为了防熊、稳定性等考量,也需要将模电信号瞬时地传递,这个时候,我们就需要无延迟的比较器链了。

本文所有的测试都在 Minecraft 版本 1.19.2 进行,所有的代码分析也都用的是 1.19.2 的[1]

1 无延迟比较器链的理论

无延迟比较器链,就是我们希望让一条比较器链在同一 gt 内全部亮起,并且保持红石信号地传输。为了分析这样的装置的理论可能,我们需要理解比较器为什么会亮起。为此我们需要翻代码。我们可以找到控制比较器信号变化的一段代码:

(如果你不想看代码分析,请直接翻到这一节的末尾看结论)

net.minecraft.block.ComparatorBlock#update(World, BlockPos, BlockState)

比较器是一个具有方块实体的方块,其信号强度存储在其方块实体中。我们看到上述方法一共做了这样几件事:

  • 计算当前应当输出的信号强度,这在 calculateOutputSignal() 中就执行了,包含了容器检测、比较大小、减法等一众逻辑。

  • 将比较器的信号强度改为上一步中计算的信号强度。

  • 如果发现信号强度改变了,就试着更新自身的亮灭状态。

  • 对比较器输出方向的方块及其除了这个比较器之外的所有毗邻方块发出 NC 更新。

这是比较器应当进行的所有逻辑,当这个方法被调用时,比较器会瞬时执行这些逻辑。那么这个方法在哪里被调用了呢?它总共就只有两处调用,有一处是在玩家更换比较器模式时,将比较器更新一次,这一处与我们的用途无关,我们不关心。重要的是下面的这一处:


net.minecraft.block.ComparatorBlock#scheduledTick(BlockState, ServerWorld, BlockPos, Random)

我们发现,比较器是计划刻元件。这指的是比较器的变化受计划刻控制,只有当比较器受到一个计划刻的时候,它才会更新自己的状态。我们看看,它在什么条件下,可以给自己一个计划刻。根据常识我们知道控制红石元件的更新是 NC 更新,我们应该到这相关的方法里面去找。



net.minecraft.block.AbstractRedstoneGateBlock#neighborUpdate(BlockState, World, BlockPos, Block, BlockPos, boolean)

在控制比较器的类 ComparatorBlock 里面没有找到 neighborUpdate() 方法,但是在它的超类——同时控制中继器和比较器的部分相似逻辑的 AbstractRedstoneGate 类中找到了。我们看到,当比较器受到 NC 更新时,如果自己的放置状态是好的,即不会掉落,那么就更新自己的能量状态,也就是调用 updatePowered() 方法。让我们看看这个方法写了什么。


net.minecraft.block.ComparatorBlock#updatePowered(World, BlockPos, BlockState)

我们看到,在这里比较器给自己规划计划刻了。这个计划刻具有 2 gt 的延迟,在 2 gt 后,比较器的 update() 方法将会被调用,瞬时更新自己的状态。我们来看一下这个产生计划刻的条件:这个比较器并没有已有一个计划刻,而且应该输出的能量不等于比较器存储的能量。

至此,我们可以总结出比较器发生一次变化的流程:

(不想看代码分析的读者请在这里停下,下面是结论)

  • 当比较器受到 NC 更新,将检测自己的状态。如果自己已经有了一个计划刻,无事发生。否则,检查自己现在应当输出的能量,如果不等于自己现在实际输出的能量,给自己规划一个计划刻。

  • 当比较器受到一个计划刻,将会瞬间更新自己的状态,并发出一些相应的 NC 更新。

那么我们为什么可以有无延迟比较器呢,我们还是考虑文首的那一条比较器链。现在如果在同一 gt 内,这条链上的比较器从输入端向输出端依次受到计划刻,并且假设此时输入端的比较器应该亮起,我们看看会发生什么:

  • 输入端的第一个比较器受到计划刻,发现自己应该亮起,于是瞬时亮起。

  • 输入端的第二个比较器受到计划刻,自检,发现自己应该亮起,因为在上一步中第一个比较器已经亮起,所以它也瞬时亮起。

  • 输入端的第三个比较器受到计划刻,自检,发现自己应该亮起,因为在上一步中第二个比较器已经亮起,所以它也瞬时亮起。

  • ……

于是结果就是,所有的比较器在同一 gt 内亮起!这就是无延迟比较器的基本工作原理,它强烈依赖于各个比较器计划刻执行的顺序。我们知道同一 gt 内,计划刻的执行顺序是:

  • 先比较产生时的 gametime 也就是 gt 数,产生的早的先执行。

  • 如果上一步相等,那么比较优先级,优先级高的先执行。

  • 如果上一步还是相等,比较计划刻的编号,计划刻的编号是一个自增的整数,永远不会出现相等的情况,在创建新的计划刻时自增。编号越小,说明在游戏的代码内部,它创建的越早。编号小的先执行。

这些计划刻具有同样的延迟而在同一 gt 执行,因此产生这些计划刻必须在同一 gt,而我们知道比较器的计划刻具有同样的优先级,因此,为了让比较器从输入端向输出端依次受到计划刻,必须让比较器从输入端向输出端依次创建计划刻!

于是我们得到了这一节的结论:无延迟比较器链是理论可行的,其生效的条件是:比较器链从输入端向输出端依次创建计划刻。

(注意我反复用红字强调这个顺序,这是因为无延迟比较器链完全依赖于这个顺序,它怎么强调都不为过)

2 无延迟比较器链的建造

现在我们可以关掉代码,打开游戏,真正地开始建造这条目前还在理论中的比较器链了。根据我们刚才的思路,我们要从输入端向输出端依次创建计划刻,我们还知道了比较器创建计划刻的条件——应输出能量和实际输出能量不相等。为了满足这个条件,我们可以在每个比较器后面的完整方块上放一个红石粉,之后我们会将它们点亮。

现在假设我们点亮了其中的一个红石粉,那么这一瞬间,这个红石粉将会产生对二阶毗邻的 NC 更新,更新其前后的两个比较器,这两个比较器会自检,只有前面那个比较器会发觉自己的实际输出能量(0)和应输出能量(15)不同,于是产生计划刻。如果后面那个比较器它后面的红石粉也被点亮了,那么它也会察觉到这个不同,但是点亮那个红石粉的时候,它已经产生了一个计划刻了,不会再产生第二个。因此,将上面的结构中的一个红石粉点亮 1 gt 将给它前面的那个比较器一个计划刻。

于是我们就把问题转化了,问题转化为:将上面结构的红石粉在 1 gt 内从左到右依次点亮。为此,我们需要一些微时序相关的知识。我们知道,在粘性活塞收回时,大致上靠近活塞的方块将先于远离活塞的方块转化为 B36,我们还可以利用佛冷的 pistorder 模组显示活塞将方块转化为 B36 的顺序,这也就是这些 B36 固化的顺序。

观察上图,粘液块下面粘的红石块便是用来点亮下面的红石粉的。现在假设上图中,左边的粘性活塞收回,我们分析移动结构:

pistorder 对移动结构的分析

可以看到这和常识相符——粘性活塞收回时,近端的比远端的先变为 B36,从而先到位;另外,由于活塞的运动是方块事件,这有一个特性,就是在前一个活塞将所有的 B36 创建以及原有方块删除这些动作完毕之前,后一个活塞不会有响应。我们看到在上方的移动结构中,被标了1、2、4的红石块代表变为 B36 的顺序,它们按这个顺序变为 B36 之后,前一个活塞的收回动作完成,后一个活塞开始收回。这样的话,红石块变为 B36 的顺序确实是从输入端到输出端,从而在 2 gt 后的方块实体阶段,B36 固化的顺序也是这样。我们知道红石粉亮起是瞬间完成的动作,即从 B36 固化到红石粉亮起两者之间无法插入其他操作[2],因此,红石粉从输入端到输出端依次亮起,这正是我们的需求。

根据理论分析,这个结构应该是可以完成无延迟比较器链的工作的。接下来,我们做一个测试:

我们在装置的尾端加一个箱子,箱子里面放一个物品,上面盖一个固体方块,稍后我们将把这个固体方块打掉,这时输入端的第一个比较器将变成 CUD,我们可以很方便地检验效果。

测试效果

这个 CUD 式的输入只是测试用的,我们可以加一个侦测器的结构,让它变成真正的无延迟比较器——无论长度如何,在传入一个信号之后,它可以保持信号强度地在固定时间内将信号传任意远,并且不需要玩家手动拆除红石块。比如说在输入端加装这样的一个结构:

至此,我们将无延迟比较器的构想转化为了现实。

3 各种不同的无延迟比较器链的架构

看到了上面的步骤后,我们发现建造无延迟比较器链的理论要求是很宽松的,很容易实现,MC 的各种机制都在这方面对我们有利——比如方块事件的串行性,粘性活塞收回时,近端比远端先到位等等。我们看看几种最容易想到的可用的设计。

如果想要节省粘液块,可以采用这种设计。它的时序是对的,这基于方块事件的串行性,所以你看,这对我们确实十分有利,只要你不乱设计,几乎都是对的。

我们也没有说过,一定要通过比较器后面完整方块上的红石粉来更新比较器。只要你不会用到强模,直接在比较器后面接红石粉为什么不行呢?

(此处启动的地方因为红石粉高了一格,结构略有不同)

也没说过只能从后面更新,从侧面更新有何不可?

这样的无延迟比较器链,也可以使用减法模式等等,也可以进行自然的衰减,你甚至可以将其做到 1 宽:

上面的结构如果你把比较器前面的方块换成带釉陶瓦或者黑曜石,那么这就是无衰减的 1 宽的无延迟比较器链。

理论上也可以利用侦测器去更新,但是笔者还没有掌握可控的控制侦测器更新顺序的方法,这方面的发掘就留给读者了。侦测器的好处是可以从下面更新,这样如果上面没有空间摆放结构的话,那么也可以使用无延迟的比较器链。笔者做出了一个基于侦测器的从下更新的版本,但是没有掌握通用的设计规律:

期待读者进一步地发掘更多的无延迟比较器链相关的信息。

4 结言

本文中,我们浅析了无延迟比较器链的理论工作原理,并给出了一些可以实现这个理论的设计实例。无延迟比较器链并不是笔者的原创,根据笔者看到的信息,它在 2018 年附近就有了,笔者在 BV13R4y1Q7BM 了解到了这个东西的存在。笔者设计的编码全物品需要利用比较器链传递模拟信号,以传递信息,因此探究了这一部分的内容。

借物表:

使用材质:XeKr 方纹,XeKr 姹紫嫣红附加包

(XK 的材质包是真的好看,大家都可以尝试着用用)

参考文献:

[1] Minecraft 1.19.2 源码,请自行使用诸如 MCP 之类的工具生成

[2] Fallen_Breath 的专栏:CV4122124CV4565671CV10002253

浅析无延迟比较器链的评论 (共 条)

分享到微博请遵守国家法律