0%

深度剖析Minecraft #3.4 计划刻元件

3.4 计划刻元件

阅前必读:

深度剖析Minecraft #2 方块更新

深度剖析Minecraft #3 计划刻

3.4.1 红石二极管

名称 信息
net.minecraft.block.BlockRedstoneDiode
基类 net.minecraft.block.BlockHorizontal
延迟 非定值
优先级 非定值
位置需求 其下方依附着的方块上表面完整
受方块更新时 判断掉落,更新信号
受状态更新时 不响应

这是一个抽象类,是红石中继器以及红石比较器的基类,也就是说中继器跟比较器的本质是一个二极管,有许多的机制是完全相同的

3.4.1.1 方块状态

作为一个可水平旋转的方块,二极管拥有一个 facing 标签表示其水平指向。除此之外二极管还具有以下标签:

标签名 取值 用途
powered false, true 其当前的亮起状态。我们称该标签为假时二极管熄灭,该标签为真时二极管熄灭

3.4.1.2 响应方块更新

当二极管受到方块更新时,它会首先检测其所处位置是否满足位置需求,若不满足则掉落为物品,并先在所在位置发出方块更新,再在其毗邻 6 方块的位置发出方块更新(顺序:-y, +y, -z, +z, -x, +x 1);若合法,则开始检测信号变化 2。具体的检测信号变化的方法见 红石中继器 以及 红石比较器

3.4.1.3 发出更新

当二极管的状态发生变化的时候,它会先给其指向方块一个方块更新(下图黄色玻璃),再于其指向方块的位置处(下图黄色玻璃)发出一个除其指向方向反方向的方块更新。图中所有的染色玻璃也即是所有的受到方块更新的方块的位置

二极管更新范围示意

具体的方法为 net.minecraft.block.BlockRedstoneDiode#notifyNeighbors

这一部分的调用之处是在 onBlockAdded 以及 onReplaced 中,因此只要涉及到二极管的方块状态的变化,均可以让这个二极管发出上述更新

3.4.2 红石中继器

名称 信息
net.minecraft.block.BlockRedstoneRepeater
基类 net.minecraft.block.BlockRedstoneDiode
延迟 非定值
优先级 非定值
位置需求 红石二极管
受方块更新时 红石二极管
受状态更新时 更新自身被锁的状态

3.4.2.1 方块状态

红石中继器(简称中继器)在红石二极管的基础上,新增了两个方块标签:

标签名 取值 用途
delay 1, 2, 3, 4 储存中继器的延迟。计划刻事件的延迟将为 delay * 2
locked false, true 储存中继器是否被锁

3.4.2.2 检测信号变化

中继器接受来自其输入端方块的弱充能信号,与其当前的 powered 标签进行对比,若发现其状态需要改变,则添加一个计划刻事件用于延迟更新状态。计划刻事件的延迟如上所述,优先级如下:

  • 若其指向方块为红石二极管,且红石二极管的朝向非反向,则优先级为 -3
  • 若中继器的原 powered 标签为假,也即中继器准备熄灭,则优先级为 -2
  • 否则优先级为 -1

3.4.2.3 计划刻事件执行

如果中继器目前被锁住,则跳过该计划刻事件的执行。由于计划刻事件并不储存该事件的实际功能,中继器需要再次计算其输入端方块的弱充能信号,并与其当前的 powered 标签作比较:

  • 若当前状态为亮起且输入端无信号,则改为熄灭状态
  • 若的前状态为熄灭,则改为亮起状态,再判断输入端是否有信号,若无信号则添加一个延迟为其自身的延迟,优先级为 -1 3 的计划刻事件用于熄灭。也就是说,即便在执行计划刻事件的时候输入信号已经消失,中继器也依然会亮起,这也是中继器可响应并输出任意短的正脉冲的原理

上述过程中,改变其亮起状态时,在其所在位置发出的更新仅含状态更新。中继器在其前方发出的大范围方块更新见 红石二极管 的发出更新小节

3.4.2.4 受状态更新时

当中继器受到状态更新时,若更新方向为水平方向,且方向为垂直于中继器的指向方向,则更新自身的被锁状态。中继器当且仅当被一个二极管指向侧面的时候会被锁住

中继器响应状态更新

实例

与 4gt 时钟交互

当将 4gt 时钟,如对脸侦测器,的输出接至中继器链时,常常会见到下图的现象:中继器链大部分都在 4gt 时钟下闪烁,可是最末端的中继器却处于常量状态

与 4gt 时钟交互

这是因为中继器添加事件的优先级所导致的。

4gtrepeater2

4gtrepeater3

3.4.3 红石比较器

名称 信息
net.minecraft.block.BlockRedstoneComparator
基类 net.minecraft.block.BlockRedstoneDiode
延迟 2
优先级 非定值
位置需求 红石二极管
受方块更新时 红石二极管
受状态更新时 不响应

3.4.3.1 方块状态

红石比较器(简称比较器)在红石二极管的基础上,新增了一个方块标签:

标签名 取值 用途
mode compare, subtract 储存着比较器当前的模式(比较模式、减法模式)

对于当前比较器的输出信号的储存,可能是因为一些历史遗留问题(1.13 以前的方块附加值储存能力不足),是储存于比较器方块实体之中的。也就是说,比较器其实是一个含有方块实体的方块。不过由于该方块实体并不是常运算的方块实体 4,因此并不会带来额外的卡顿

3.4.3.2 信号强度计算

net.minecraft.block.BlockRedstoneComparator#calculateInputStrength

首先,需要计算输入端的信号强度

令输入端,即比较器后方一格处的方块为 A,比较器后方二格处的方块为 B

比较器输入信号

  1. 维护一个取值为 0 ~ 15 的整数 $n$ 作为比较器待确定的输入的信号强度
  2. 计算方块 A 的弱充能信号强度(与中继器相同),将 $n$ 设为该信号强度
  3. 判断方块 A 是否可输出比较器信号
    • 如果是,则将 $n$ 设为 A 的比较器输出值。如比较器直接读取毗邻容器
    • 如果不是,若 $n < 15$ 且方块 A 为实体方块 5,则进行尝试使用 B 处可能的容器输出来覆盖当前的输入强度:
      1. 如果方块 B 可输出比较器信号,则将 $n$ 设方块 B 的比较器输出值
      2. 如果方块 B 为空气,则查找位于方块 B 处的可能依附于方块 A 的一个物品展示框实体,若找到,则将 $n$ 设置为物品展示架的角度
  4. $n$ 即为比较器的输入信号强度

随后,比较器会计算其来自侧面的输入的最大值。比较器会接受以下三种形式的输入:

  • 毗邻的红石块
  • 毗邻的红石粉
  • 毗邻的指向比较器的强充能元件

下图是一些常见的给予比较器侧面输入信号的方法

比较器侧面输入

不过,强充能元件并非只有中继器、比较器。当比较器前方有二极管时,比较器可响应侦测器的侧方输入。如果使用指令等方式得到了浮空方块,你甚至可以使用拉杆、按钮等元件进行输入:

comparetor_side_input_sp

现在,我们已经知道比较器的后方输入以及侧面输入的信号强度了,现在只需要根据其所处模式(比较模式/减法模式)再次计算,即可得出比较器的输出信号了:

  • 比较模式:输出信号 = 后方输入(大小比较的相关逻辑并不在此处)
  • 减法模式:输出信号 = max(0, 后方输入 - 侧面输入)

之后,比较器就可以判断是否应该输出信号了。这部分的实现很简单,直接结合代码说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected boolean shouldBePowered(World worldIn, BlockPos pos, IBlockState state)
{
int i = this.calculateInputStrength(worldIn, pos, state);
if (i >= 15) // 输入信号强度大于15
{
return true;
}
else if (i == 0) // 输入信号强度为0
{
return false;
}
else
{
return i >= this.getPowerOnSides(worldIn, pos, state);
}
}

这里也是比较模式大小相关逻辑的实现之处

3.4.3.3 检测信号变化

与中继器类似,比较器会计算其当前应该输出的信号强度,并与当前自身储存着的信号强度作对比。如果输出的信号强度有改变,或者其亮起状态有变化,则添加一个计划刻事件,优先级如下:

  • 若其指向方块为红石二极管,且红石二极管的朝向非反向,则优先级为 -1
  • 其他情况,优先级为 0

3.4.3.4 计划刻事件执行

与中继器类似,由于计划刻事件并不储存该事件的实际功能,比较器需要再次计算其当前应该输出的信号强度,并与当前自身储存着的信号强度作对比。若两者不同,或者其亮起状态有变化,则更新当前的亮起状态,并发出更新。可以注意到,比较器并非同中继器一样,当原状态是熄灭的时候,无论如何都会点亮。如果比较器的计划刻执行时输入信号已消失,比较器是不会输出的

注意到对于任意一个红石二极管,当其方块状态发生变化时也会发出一次更新,因此若比较器在上述步骤中改变了亮起状态时,它会发出更新两次 <3 mojang

实例

响应计划刻元件的 2gt 脉冲

下面以典型的侦测器为例。正常情况下,侦测器脉冲是无法激活比较器的,如下面这个例子

侦测器尝试激活比较器

时序分析:

游戏刻 游戏阶段 事件
0 TT.0.侦测器 侦测器点亮。侦测器添加位于 gt 2 的计划刻事件用于熄灭,优先级为 0(关于侦测器的执行计划刻事件时的逻辑,见 侦测器
0 TT.0.侦测器 侦测器发出方块更新。比较器受到方块更新,发现可输出信号,添加位于 gt 2 的计划刻事件,优先级为 0
2 TT.0.侦测器 侦测器熄灭
2 TT.0.比较器 比较器判断目前输出信号强度(0)与应当输出的信号强度(0),无变化,不执行任何操作

可见,比较器的计划刻事件执行的时候输入端信号已经消失,这导致了比较器不响应侦测器的脉冲


若想要使用侦测器来激活比较器,有两种方法

1. 提升比较器的优先级

观察时序分析表,容易发现如果让 gt 2 中比较器的事件先于侦测器执行,则可以让侦测器执行计划刻事件时侦测器仍处于激活状态。一种可行的方法就是提升比较器的优先级,更准确地说,提升比较器添加用于亮起的计划刻事件的优先级。我们可以通过在比较器前方添加中继器来实现

侦测器尝试激活比较器

时序分析:

游戏刻 游戏阶段 事件
0 TT.0.侦测器 侦测器 A 点亮,添加位于 gt 2 的计划刻事件。侦测器更新比较器,比较器发现自身可亮起,添加位于 gt 2 的计划事件
2 TT.-1.比较器 比较器判断目前输出信号强度(0)与应当输出的信号强度(15),不相等。比较器亮起
2 TT.0.侦测器 侦测器熄灭

2. 延长等优先级的 2gt 脉冲

为了让比较器在执行其计划刻事件的时候输入端仍有信号,除了提升比较器的计划刻事件的优先级外,是否还有方法?更新比较器,让比较器添加计划刻事件的那个侦测器肯定不能起到作用了,那么是否可以额外添加一个侦测器,让第二个侦测器在比较器事件执行完毕之后再熄灭?答案是可以,方案如下:

双侦测器激活比较器

时序分析如下:

游戏刻 游戏阶段 事件
0 TT.0.侦测器 A 侦测器 A 点亮,添加位于 gt 2 的计划刻事件。侦测器更新比较器,比较器发现自身可亮起,添加位于 gt 2 的计划事件
0 TT.0.侦测器 B 侦测器 A 点亮,添加位于 gt 2 的计划刻事件。侦测器更新比较器,比较器尝试添加位于 gt 2 的计划事件,不过由于计划刻队列中已有该事件,忽略操作
2 TT.0.侦测器 A 侦测器 A 熄灭
2 TT.0.比较器 虽然侦测器 A 熄灭了,但是侦测器 B 仍处于亮起状态。比较器判断目前输出信号强度(0)与应当输出的信号强度(15),不相等。比较器亮起
2 TT.0.侦测器 B 侦测器 B 熄灭

值得注意的是,该方案的铁轨不可用 2gt 的计划刻元件脉冲去激活,具体原因与侦测器的实现有关

使用 0gt 信号激活比较器

通过上述例子我们可发现,若想要激活一个比较器,仅需要在比较器检查输入端是否有信号的时候,也就是以下两个时刻:

  • 添加计划刻事件时
  • 执行计划刻事件时

让输入端存在信号就行了。这意味着,激活比较器的信号并不需要是一个连续的信号,我们只需要用两次脉冲分别覆盖上述两个时刻,就能让比较器点亮。这两个脉冲的时长可以任意短,可以短到 0gt 时长,只要覆盖到了上述两个时刻就行了

对于上述的第一个时刻“添加计划刻事件时”,可在任意时刻实现;对于第二个时刻“执行计划刻事件时”,需理清楚比较器的计划刻事件的执行时间点

下图是一个使用两次 0gt 信号激活比较器的例子

0gt脉冲激活比较器

时序分析:

该装置中含有两个 0gt 脉冲发生器,脉冲的持续时间为 TT.-1 ~ BE,可覆盖正常优先级的比较器的计划刻事件时刻 TT.0

游戏刻 游戏阶段 事件
0 TT.-1.上中继器 A 中继器 A 亮起,充能铁块激活红石粉,更新比较器。比较器添加位于 gt 2 的计划刻事件
0 BE 活塞 A 推出,红石粉熄灭
2 TT.-1.上中继器 B 中继器 B 亮起,充能铁块激活红石粉
2 TT.0.比较器 比较器亮起,信号强度为 14(信号强度源自中继器 B)
2 BE 活塞 B 推出,红石粉熄灭。红石粉更新比较器,比较器添加位于 gt 4 的计划刻事件
4 TT 比较器熄灭

如果将比较器输入信号是否存在视作 0 1,画出波形图的话,是这样子的

波形图

类似的,如果我们每 2gt 都发出一次上述 0gt 脉冲的话,比较器就能一直保持常亮状态。下方是上述例子示意装置的一个简单扩展,如果有必要的话甚至可以让比较器的输出信号强度也保持不变

0gt脉冲常激活比较器

无延迟比较器

在传统的使用比较器传输模拟信号的线路中,比较器逐个亮起,依次往前传递信号,传输速度较慢,如下图

有延迟比较器

让我们分析一下上图,为什么这个比较器链传输信号如此之慢。上图中,比较器 B 需要等待比较器 A 亮起后才可亮起;比较器 C 需要等待比较器 B 亮起后才可亮起;比较器 D 需要等待比较器 C 亮起后才可亮起……每次等待,都耗费了 2gt 的时间进行延迟

这些一个个的 2gt 延迟真的有必要存在吗?答案是不。我们可以仅在一个 gt 的计划刻阶段中就完成所有比较器的点亮,这就是无延迟比较器

下图是一个简单的无延迟比较器的例子

无延迟比较器

无延迟比较器的基本思路是,通过使用额外的计划刻元件生成的短脉冲,主动使所有比较器在同一个 gt 依次执行自己用于亮起的计划刻事件,使得比较器链在瞬间同时点亮。这种诱导比较器在指定时刻添加计划刻事件的脉冲,我们称之为诱导脉冲

上述操作将长距离传输信号的时间复杂度从 $\Theta(n)$ 降至了 $\Theta(1)$,从线性降为常数级,仿佛用于传输信号的比较器都是 0 延迟,因此我们称之为无延迟比较器

下面是上图中的无延迟比较器的亮起操作的时序分析。熄灭/信号改变的时序分析同理:

游戏刻 游戏阶段 事件
0 NU 拉杆拉下
2 TT 比较器 1 亮起。侦测器亮起,中继器从近到远依次添加位于 gt 4 的计划刻事件
4 TT.-1.中继器 A 亮起,更新比较器 A,比较器 A 添加位于 gt 6 的计划刻事件
4 TT.-1.中继器 B 亮起,更新比较器 B,比较器 B 添加位于 gt 6 的计划刻事件
…… …… ……
4 TT.-1.中继器 F 亮起,更新比较器 F,比较器 F 添加位于 gt 6 的计划刻事件
4 TT.0 比较器 2 亮起。侦测器熄灭
6 TT.-1 中继器 A ~ F 依次熄灭
6 TT.0.比较器 A 比较器 A 发现其输入端有来自比较器 2 的信号,亮起
6 TT.0.比较器 B 比较器 B 发现其输入端有来自比较器 A 的信号,亮起
…… …… ……
6 TT.0.比较器 F 比较器 F 发现其输入端有来自比较器 E 的信号,亮起

不过,上述无延迟比较器有一个缺点,就是在输入信号强度为 15 的情况下改变信号强度时不能维持无延迟。这是因为当输入的信号强度是 15 时,比较器链中都储存着强度为 15 的信号,而侧方位中继器发出的脉冲信号强度也是 15,两者大小相等,导致了诱导脉冲无法诱使比较器添加计划刻事件。如下图所示:

于输入15信号的下降沿失效

对此的解决方法也并不困难。我们需要的是诱使比较器添加计划刻事件,只要按顺序添加了就可以,至于比较器是因为什么原因而添加计划刻事件,我们并不关心

当比较器输出信号是 15 的时候,还有什么方法能诱使比较器添加计划刻事件呢?在减法模式下向比较器侧面输入脉冲!这样子,比较器会发现其输出信号需要减去其侧面输入,从而添加计划刻事件

在向其侧面输入脉冲的条件下,向其背面输入端的脉冲也不可去掉,如下图所示

侧面输入脉冲

设比较器的原信号强度为 $n$

  • 若 $n > 0$,则来自比较器侧面的脉冲可让比较器输出的信号强度减小,从而诱使比较器添加计划刻事件
  • 若 $n < 15$,则来自比较器后方的脉冲可让比较器输出的信号强度变为 15,从而诱使比较器添加计划刻事件

【疑似有虫】当侧面输入与后方输入的信号强度相等时,比如使用中继器或侦测器这类输出 15 强度信号的元件时,这两个脉冲的顺序也是有讲究的。对于无延迟链上的每一个比较器,我们需要首先向其后端输入脉冲,再向其侧面输入脉冲,才可在所有情况下无延迟地传递信号。如果顺序相反,则当后端输入脉冲时,由于比较器侧面已存在信号强度 15 的输入,比较器将会输出 0 强度信号。若比较器储存着的信号已经是 0 强度了,那么这次脉冲输入就无法诱使比较器添加计划刻事件,从而使无延迟比较器失效

脉冲顺序【此图有虫】

下面图这类兼容所有情况的信号变化的无延迟比较器的一个实现

全情况可用的无延迟比较器

以上图为例,在使用中继器或中继器时,所有需要以以下的顺序向比较器输入诱导脉冲:

  1. 比较器 A 的后方输入端
  2. 比较器 A 的侧面输入端
  3. 比较器 B 的后方输入端
  4. 比较器 B 的侧面输入端
  5. ……
  6. 比较器 F 的后方输入端
  7. 比较器 F 的侧面输入端

当然,在实际使用中,由于上述无延迟比较器设计都不能无限地延长。不过,如果配合无延迟信号线使用,即可实现可无限延伸的无延迟比较器链。下面是一个可行的例子

无限延伸的无延迟比较器链

比较器更新检测器

3.4.4 红石火把

计划刻事件执行

blabla

3.4.5 侦测器

计划刻事件执行

blabla

投掷器/发射器

计划刻事件执行

blabla

红石灯

计划刻事件执行

blabla

探测类元件

计划刻事件执行

blabla

其他红石元件

计划刻事件执行

blabla

非红石元件

计划刻事件执行

blabla

流体

计划刻事件执行

blabla


  1. 1.net.minecraft.util.EnumFacing#values
  2. 2.net.minecraft.block.BlockRedstoneDiode#updateState
  3. 3.在 1.15 及以后,该事件的优先级改为 -2
  4. 4.也就是比较器方块实体并未实现 net.minecraft.util.ITickable 这个接口
  5. 5.net.minecraft.block.Block#isNormalCube