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

浅析mc中漏斗卡顿的部分来源

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

mc中漏斗的卡顿是比较大的,很多人谈漏斗色变,当然这是完全没必要的,因为生物的AI更卡一些(笑)然而漏斗的卡顿确实很大,其中比较大的一个部分归结为寻找上方的物品实体以求吸取这一过程,但是其实还有一部分的卡顿来源于屎山代码,这一部分长时间不被记载,比较不为人知。现在我们来看一下:

首先从一个方法讲起:net.minecraft.world#World.updateComparators

这是一个毫无槽点的方法,用于更新一个方块附近的比较器。这用于让比较器能够检测容器。那么我们看看它在什么时候可以被调用。下面是net.minecraft.block.entity#BlockEntity中的两段代码:

我们看到,markDirty() 方法用于标注某个位置方块实体的信息已被更改,保存世界的时候需要注意。它还会调用 world.updateComparators() 以更新附近的比较器,毕竟方块实体的一大类是容器,当容器内容被更改时,更新一下比较器是必要的,至此,一切正常。

然后很快你就会发现事情变得非常的不正常……

我们现在看一下漏斗逻辑的控制,这部分代码全都在net.minecraft.block.entity#HopperBlockEntity里面。以下是漏斗执行一次吸取操作的代码:

注释为编者所加

我们看到漏斗会优先检测上方有没有容器,有的话就对容器的每一格执行一次遍历,如果能够吸取这一格就吸取掉,然后结束遍历,没有的话就试着吸取物品实体。

问题就出在从容器吸取这一过程,我们来看代码。

注释为编者所加,下面注释掉的代码,是编者给出的更为优化的解法

从这里,我们就可以看到麻将的迷惑操作了。我们可以从中看到主要的逻辑(以下并非是严格的源代码逻辑,是经过编者简化的等价的逻辑):

  • 把要吸取的容器的那一格取出来,如果非空进行接下来的操作

  • 将容器的这一格内容复制一份,然后在原来的容器中,将这一格的内容物数量 -1

  • 试着将减掉的 1 个内容物放到漏斗里面

  • 如果放进去了,那么很好,进程结束

  • 如果没放进去,就把刚刚复制的内容替代容器那一格的内容,相当于撤销刚刚 -1 的操作

这个逻辑虽然很奇怪,但是好像也能实现目的,貌似没问题,但是结合刚才说的比较器更新,问题就大了

这个过程中,假设吸取失败,那么会先减掉容器的 1 个内容物,然后再复原,这两个过程都会更改容器的内容,理应调用 markDirty() 方法,从而引发比较器更新。事实上它确实会调用,比如我们以箱子为例,它的物品栏在 net.minecraft.block.entity#LootableContainerBlockEntity 中实现,我们看看漏斗吸取过程中调用的两个方法,一个是 removeStack(),一个是 setStack(),它们确实都调用了 markDirty():

将要被反复调用而面露难色的两方法

于是,漏斗每次对一格失败的吸取,会产生 2 次比较器更新,这问题貌似也不太大,但是别忘了每次吸取,漏斗会对上面的容器的每一格进行遍历

现在考虑这样一个情形,上面有一个塞满东西了的大箱子,下面接了一个漏斗,漏斗里面的物品类型和箱子的内容不匹配,因此吸不进任何东西,并且漏斗不是满的(所以会尝试吸东西)。那么漏斗每次尝试吸取一格的操作,都会是失败的。于是每一格都会被遍历一遍,尝试吸取一遍——

这个过程中,上面产生 2 次没必要的比较器更新的 extract 方法,将被调用 54 遍。

这会产生 108 次比较器更新!!!!!!

也无怪乎漏斗的卡顿较大了,这样的卡顿当然是显著的,都能拿来做卡服机了(逃)

不愧是你啊麻将

啊↑啊↓我要把麻将撕成两半!

浅析mc中漏斗卡顿的部分来源的评论 (共 条)

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