第七十四章 探究:execute的运行流程
(注:非常不推荐在手机上阅读此章节,请使用平板或电脑阅读此章)
(本章用到了大量的字符画,有可能会出现严重的错位情况,可手动调整字体和大小至最佳状态)
(此章节已于2022年7月17日重写)
在第六十九章,我为了提醒你注意各个子命令的顺序,专门举了个例子:
/execute as @e at @e run tbr / @s ~~1 ~
但是你是否有注意到游戏运行这条指令的过程:
①将玩家传送至玩家上方1米的位置(玩家此时抬高了1米)
②将玩家传送至村民上方1米的位置(玩家此时位于村民上方1米)
③将玩家传送至羊上方1米的位置(玩家此时位于羊上方1米)
④将村民传送至玩家上方1米的位置(村民此时位于玩家原本位置上方1米,玩家此时位于羊上方1米)
⑤将村民传送至村民上方1米的位置(村民此时位于村民原本位置上方1米,玩家此时位于羊上方1米)
⑥将村民传送至羊上方1米的位置(村民和玩家此时位于羊上方1米)
⑦将羊传送至玩家上方1米的位置(村民和玩家此时位于羊原本位置上方1米,羊位于玩家原本位置上方1米)
⑧将羊传送至村民上方1米的位置(村民和玩家此时位于羊原本位置上方1米,羊位于村民原本位置上方1米)
⑨将羊传送至羊上方1米的位置(村民、玩家和羊此时都位于羊原本位置上方1米)
这个过程有何特殊的呢?
你仔细看看第④、⑤、⑦、⑧和⑨条过程,你有没有什么发现?
当游戏将村民传送至玩家上方1米的位置时,虽然玩家已经被传送至了羊上方1米的位置,但游戏仍然将村民传送至玩家原本位置上方1米,而不是羊上方2米的位置。
这是怎么回事?
我们设玩家(2,2,2)为A、村民(3,2,3)为B、羊(4,2,4)为C,游戏在运行execute时,其实它的流程是这样的:
execute---A---------B---------C
游戏先解析as @e,得到了上面的三个目标。
execute---A---------B---------C
------------↓---------↓----------↓
---------2·2·2-----2·2·2-----2·2·2
------------↓---------↓----------↓
---------3·2·3-----3·2·3-----3·2·3
------------↓---------↓----------↓
---------4·2·4-----4·2·4-----4·2·4
然后游戏会解析at @e,预先将实体的位置记录下来。上面为了方便展示,用x·y·z来表示坐标。
execute---A------------------------B-----------------------C
------------↓-------------------|----↓------------------|-----↓
---------2·2·2—3·2·3—4·2·4-|-2·2·2—3·2·3—4·2·4-|-2·2·2—3·2·3—4·2·4
------------↓-------↓-------↓---|----↓------↓-------↓---|----↓-------↓-------↓
-----------①------②------③---|---④-----⑤------⑥---|---⑦------⑧------⑨
①:/tbr / 玩家名 2 3 2
②:/tbr / 玩家名 3 3 3
③:/tbr / 玩家名 4 3 4
④:/tbr / 村民UUID 2 3 2
⑤:/tbr / 村民UUID 3 3 3
⑥:/tbr / 村民UUID 4 3 4
⑦:/tbr / 羊UUID 2 3 2
⑧:/tbr / 羊UUID 3 3 3
⑨:/tbr / 羊UUID 4 3 4
接下来游戏会解析run tbr / @s ~~1 ~,根据三要素,将其中的目标选择器和相对坐标等参数具体化(但计分板分数之类的不会具体化,因为没必要),得到具体的指令(如上)。
最后,游戏运行具体的指令,也就是本章最开头的那九个过程。
其中,最重要的,也是最关键的一点,就在于execute指令解析at @e的过程。
execute并不是说运行一次解析一次,而是先全部解析了再运行,所以并不会使得『村民传到玩家传送过后上方1米的位置』之类的事情发生。
能理解吧?
那问题来了,如果execute再套一个execute会发生什么?比如我们将上述指令写成:
/execute as @e run execute at @e run tbr / @s ~~1 ~
其实效果还是一样的,具体原因就等你自己去推导吧,按照我上面的流程去推导。
这就是Java execute 1.13+版本的运行流程。如果你还不懂,我们再看一个简单并且效果比较明显的例子。
设有盔甲架A和B,分别位于主世界的(40,-60,29)和(42,-60,29)。盔甲架A的生成时间比盔甲架B更早,已加载区块中没有其他盔甲架。在盔甲架A、B旁运行如下指令:
/execute as @e[tybr /e=minecraft:armor_stand] at @s run tbr / @e[tybr /e=minecraft:armor_stand,distance=1..3]~~10 ~
让我们分析一下,运行上述指令会发生什么。
首先,如果我们按照正常的思维去分析这条指令,就会得到以下结果:
A会先将B传送到自己上方10米的位置,B由于处于那个位置无法选取到A来传送,最终仅仅B会被传送到A的上方10米处。
但其实,如果你真的去运行这条指令,就会发现A和B都会被传送到对方原位置的上方10米处。
为什么?我们按照游戏的思维分析一下就可以了:
execute---A---------B
游戏先解析as @e[tybr /e=minecraft:armor_stand],得到了上面的两个目标:盔甲架A和盔甲架B。
execute---A--------------B
------------↓--------------↓
-------40·-60·29-----42·-60·29
然后游戏会解析at @s,预先将实体的位置记录下来。
execute---A--------------B
------------↓--------------↓
-------40·-60·29-----42·-60·29
------------↓--------------↓
-----------①-------------②
①:/tbr / 盔甲架B的UUID 40 -50 29
②:/tbr / 盔甲架A的UUID 42 -50 29
接下来游戏会解析run tbr / @e[tybr /e=minecraft:armor_stand,distance=1..3]~~10 ~,根据三要素,具体化指令,得到具体的指令。由于此时还未传送,所以目标选择器会分别选择到『盔甲架A』和『盔甲架B』。
最后,游戏按照顺序执行指令,分别将盔甲架A和盔甲架B传送到对方上面10米高的位置。
这个例子比较简单,你应该能够理解吧?
所以你明白了吗?
上面讲的是Java1.13更新后的execute指令其运行的具体流程,那么Java1.13更新前的呢?以及基岩版的呢?
2016年6月22日,MCBBS大佬br /ca006132在『矿工茶馆』发布了一个猜猜乐(ID:594475),大致的问题如下:
execute @e ~~~... summon ArmorStand,这个指令在初始实体不同数目的时候出来的结果是什么
没想到竟然没人能够解答这个问题,于是这位大佬在次日讲解了这个问题(帖子ID:594698)。他举了一个简单的例子:
当初始实体数为2时,运行execute @e ~~~ execute @e ~~~ summon Armorstand
这个例子的结果竟然是8。
那如果在相同的初始情况下,运行execute @e ~~~ execute @e ~~~ execute @e ~~~ summon Armorstand,即套了三个execute的指令会发生什么?
答案是:2048。
很令人震惊啊!那为什么会这样呢?
如果我们在Java1.13及以上版本,运行类似的指令,将达不到一样的效果,因为在Java1.13之前,execute的运行逻辑是完全不一样的。
那么到底是个怎么个逻辑法呢?其实在Java1.13前,execute并不会在运行前先存好各种数据,而是运行一遍解析一遍。以上面那个嵌套了3层execute的指令为例子,我们来解析一下。
条件:初始两个实体A(1,2,1)和B(2,2,2),A比B离执行地点更近。
execute---A----------B
------------↓
----------1·2·1
游戏先解析第一个『execute @e ~~~』,得到了上面的结果。后面我们将会忽略执行地点,因为这边不需要考虑执行地点的影响。
execute---A----------B
------------↓
---------A——B
游戏按照顺序,先以A为执行者运行指令,并解析了第二个『execute @e ~~~』,得到了上面的结果。
execute---A----------B
------------↓
---------A——B
---------↓
------A——B
游戏按照顺序,再次以A为执行者运行指令,并解析了第三个『execute @e ~~~』,得到了上面的结果。
execute---A----------B
------------↓
---------A——B
---------↓
------A——B
------↓-----↓
------C-----D
第三个execute运行指令,产生了新的盔甲架C和D。
execute------A----------B
---------------↓
---------A————B
---------↓---------↓
--------+2---B—A—C—D
游戏回到第二层execute,以目标选择器顺序选取B为执行者,由于之前已经生成了C和D,所以B运行第三层execute指令时,会选取到4个实体来运行指令,最终实体数量+4(现在为8=2+2+4)。
execute---A-----------------B
------------↓-----------------↓
----------+6----B—A—C—D—E—F—G—H
游戏回到第一层execute,以目标选择器顺序选取B为执行者。由于已经有了八个实体,因此这一次第二层execute会选取到八个实体来运行第三层execute。
execute---A-------------------------B
------------↓-------------------------↓
----------+6----B——A——C——D———E———F———G———H
-----------------↓-----↓-----↓-----↓-------↓-------↓-------↓-------↓
--增加实体数---+8--+16--+32-+64--+128--+256---+512--+1024
--增加后数量----16---32---64---128----256---512----1024---2048
随后,游戏按照顺序依次以这八个实体运行指令,实体数量在此过程中快速增长,最终变为2048。
不难发现,每一次第三层的execute指令被运行,都会将当前实体数量×2,而上面一共运行了10次第三层的execute,相当于2被乘以了10次2,也就是2×2×2×2×2×2×2×2×2×2×2,即2的11次方,结果为2048,即2048个实体。
实在是太令人惊讶了是不是?在Java1.13以下的execute指令中,execute仅仅会在被选取的执行者开始执行指令时才会进行下一步的解析动作,而且不会一下子就将所有执行者运行指令的情况全部解析出来再运行指令。
所以,Java1.13对execute的改动不仅仅是格式上的,还有运行流程上的改动。
如果你并不能很好理解上面为什么会由2个实体产生出2048个实体,别担心,我们继续以刚才两个盔甲架互相传送为例子,看看类似的指令在Java1.13以下的版本有何不同的效果。
还是设有盔甲架A和B,分别位于主世界的(40,60,29)和(42,60,29)。盔甲架A的比盔甲架B更靠近执行地点,已加载区块中没有其他盔甲架。在盔甲架A、B旁运行如下指令:
/execute @e[tybr /e=armor_stand]~~~ telebr /ort @e[tybr /e=armor_stand,r=3,rm=1]~~10 ~
游戏先解析『execute @e[tybr /e=armor_stand]~~~』得到如下结果:
execute---A----------B
------------↓
-------40·60·29
然后以A为执行者,解析『telebr /ort @e[tybr /e=armor_stand,r=3,rm=1]~~10 ~』,得到了如下指令:
/telebr /ort 盔甲架B的UUID 40 70 29
运行上述指令,盔甲架B被传送至(40,70,29)处。随后游戏以B为执行者,先解析执行地点参数『~~~』,得到如下结果:
execute---A------------------B
------------↓------------------↓
-------40·60·29---------40·70·29
--将B传送至40·70·29
接下来,游戏以B为执行者,再次解析指令,得到如下内容:
选择器'@e[tybr /e=armor_stand,r=3,rm=1]'什么都没找到
没错,由于B被传送到了(40,70,29),因此目标选择器就选不到A,自然就无法执行指令。最终,正如我们在最开始以正常思维分析的那样,得到了如下结果:
A会先将B传送到自己上方10米的位置,B由于处于那个位置无法选取到A来传送,最终仅仅B会被传送到A的上方10米处。
虽然在Java1.13更新后,我们的『正常思维』没用了,但在Java1.13以下版本还是很准的。
那在基岩版呢?
作者在基岩版也测试过了(用的上面的两个盔甲架tbr /法),确认基岩版不管是旧版还是新版(1.19.10更新的)的execute,都是会得到和Java版1.13以下版本一模一样的数据。
其中,对于目前还在测试的新版execute,用的是如下指令:
execute as @e[tybr /e=armor_stand] at @s run tbr / @e[tybr /e=armor_stand,r=3,rm=1]~~10 ~
也就是说,如果你在基岩版运行上面套了3层execute的生成指令,在初始实体数为2的情况下,也会得到有2048个实体的纟
......
......
......
:(
你的电脑遇到问题,需要重新启动。
我们只收集某些错误信息,然后为你重新启动。
......
......
......
——附录:跟本章有关系的MCBBS帖子原链接
www.mcbbs.net/thread-594698-1-1.html
www.mcbbs.net/thread-594475-1-1.html
(上述帖子均已被MCBBS论坛系统自动关闭)