octopuscraft吧 关注:2,142贴子:44,166

[理论分析] 红石更新延迟理论

只看楼主收藏回复

大家好,我是一个来混脸熟的代码渣
之前用了将近一个月时间,将mc1.8红石部分的源码完全看了一遍,感觉有很多收获,所以来和大触们分享一下~
以下全部内容皆基于对Minecraft 1.8版本源码的解读,保证该理论的准确性。所有试验也均在1.8版本进行,除了一个自制的测试mod外没有使用任何插件,包括Optifine和tickspeed。理论适合所有的1.8.x版本,因1.9中活塞代码进行了修改,部分内容可能不再适用。


IP属地:陕西1楼2015-10-02 21:05回复
    1 什么是更新延迟理论?
    Mc中一切红石元件,都有其在1gt中更新的先后顺序,也称为微延时。是这种更新顺序造成了活塞等元件在不同电路中延迟的差异。


    IP属地:陕西2楼2015-10-02 21:05
    回复
      2 在游戏的1gt中,红石都进行了哪些更新呢?
      以下是1gt中,有关红石电路执行的部分,顺序便是从上到下
      2.1 Network Update
      这一步用来执行上一gt记录的玩家操作,并进行更新。
      2.2 World Time Update
      这一步用来更新世界的总时间,包括Day Time,即日出日落的时间,和Game Time,即世界生成到现在的gt总数。下一步中NTE更新就是依据Game Time进行(P.S. sbmojang,游戏延迟与时间更新不同步,在这里纠结半天)。
      2.3 Next Tick Entry
      很多有延时作用的电路元件,如中继器、比较器、火把等,在接收到更新时,会将自己加入游戏中一个Next Tick Entry(简称NTE)列表中,记录将要发生变化的时间,以实现在x gt之后的操作。
      此外,被加入NTE的元件,会按照不同元件的优先级进行排序,以实现优先级高的元件先被更新。
      2.4 Random Tick
      这是每gt执行的随机方块更新,包括作物的成长,树苗的长成等。
      2.5 Block Event
      所有被更新到的活塞,并不是被加入NTE,而是加入这一个名叫Block Event(简称BE)的列表中,以实现在每一gt中所有活塞一起更新。与NTE不同的是这个列表没有延迟,所以被BE更新的活塞如果更新到其他活塞,也会加入此列表,并在同一gt内进行更新(注:草稿中将列表名误认为是Piston Update,后来发现Note Block也在此处更新,修正为Block Event)。
      2.6 Entity Update
      此处执行的是生物的更新,看似和红石没什么关系,但是如小黑捡方块,末影龙生成终末之池等,会对电路造成改变,都在此处进行。
      2.7 Tile Entity
      活塞推动时,所有被推动的方块,都会生成一个个Tile Entity,移动完成后恢复为方块。移动的更新即在此处进行。
      2.8 Network
      所有玩家进行的操作,如拉下拉杆,放置方块等,都会在此处由客户端上传到服务器。注意在此处并不进行更新,只是记录所传的数据包(注:草稿中误认为更新也在此处进行)。
      注意,电路中所有的红石导线,在接收到更新时都是瞬间被更新的,也是在瞬间更新其他元件的。


      IP属地:陕西3楼2015-10-02 21:05
      回复
        3 多加一点,由于sbmojang的游戏延迟与时间更新不同步,两gt之间分界线的选择成了一个问题。鉴于以前红石理论多基于命令方块,而命令方块更新依赖Game Time,所以本理论中选择World Time Update作为相邻两gt的分界线,即Network Update算作前一gt的更新内容,从NTE开始算作本gt内的更新内容。所以1gt内的更新内容如下:
        Next Tick Entry
        Random Tick
        Block Event
        Entity Update
        Tile Entity
        Network & Update


        IP属地:陕西4楼2015-10-02 21:05
        回复
          4 这个理论要怎样使用?
          这里引用国外大神sancarn的视频Redstone Tick Bug,并引用其中一张统计表格
          视频地址(感谢b站hiback搬运):
          http://www.bilibili.com/video/av2537345
          http://www.bilibili.com/video/av2547012

          这张表格基本列举了所有情况下信号对活塞是同步或者不同步的。可以很容易地发现,所有不同步的情况,都是在1gt中更新顺序在活塞更新,即Block Event之前,或者就在Block Event处。而所有同步的情况,都是在活塞更新之后。这足以说明更新顺序对整个红石系统的重要性。具体内容参照原视频。
          注意其中几个容易误判断的更新顺序:
          4.1 活塞推出方块:
          当活塞普通推出,即用时3gt的推出,此时被推出的方块到达目标位置时,更新顺序为Tile Entity。而当活塞收到短脉冲,即0gt,1gt或者2gt,此时被推出的方块到达目标位置的更新顺序为Block Event。如果是推出方块使红石线连接,更新顺序也为Block Event。
          4.2 活塞破坏拌线:
          这是在活塞推出瞬间完成的,拌线被替换为36号方块。所以更新顺序为Block Event。
          4.3 树苗成熟:
          自然成熟的树苗是由random tick完成,更新顺序为random tick。发射器骨粉催熟树苗,更新顺序是在发射器更新的NTE。玩家骨粉催熟的树苗,更新顺序是在玩家更新的Network Update。
          4.4 命令输入:
          命令方块执行命令的更新顺序为NTE,而玩家执行命令的更新顺序为Network Update。
          4.5 红石灯
          红石灯是个很特殊的元件,它的点亮与熄灭更新顺序不同(P.S. sbmojang),点亮时瞬间点亮,与红石导线一样,没有任何延迟,而在熄灭时,有4gt的延迟,并且更新顺序为NTE。
          4.6 重力方块
          MC中的沙子、沙砾等,其掉落前1gt延迟也是通过NTE实现,但它们在接收到NTE更新时,并不是立即更新周围方块,而是先创建掉落的沙子实体,等到实体更新时才将方块所在位置设成空气。(这估计就是老版本简易刷沙机的原理吧)所以,沙子掉落形成的更新顺序是Entity Update。


          IP属地:陕西5楼2015-10-02 21:06
          收起回复
            5 对于活塞的详细解释
            活塞是电路中十分重要的元件之一,其本身行为也十分奇葩(P.S. sbmojang),众说纷纭,现在我就来详细解释一下活塞每一步的动作。
            5.1 活塞接收到更新
            判断自身是否在Block Event列表中,判断自己是否需要推出或收回(即判断自己推出/收回的状态与电路充能的状态是否一致),同时满足则将自身加入Block Event列表,重要:如果活塞在这一步需要收回,则立即将自身状态设为收回的状态,尽管活塞臂还在前面,也就是这时的活塞是可以被推动的,也是活塞瞬间收回的原理,sbmojang。
            5.2 Block Event更新
            此处判断自己是否仍然需要推出或收回,如果需要推出,则将所有被推动的方块变成36号方块,并给每个36号方块设立一个Tile Entity用来推动生物和渲染推动过程,然后将自身设为伸出状态。在这之前,活塞也是可以被推动的,同样是瞬间收回的原理。如果需要收回,先检查自己前方是否还有正在被推动的方块(即存在36号方块和其Tile Entity),如果有,便让其立即完成移动过程,并不在拉回方块,也就是所有短脉冲(包括0t)的原理。重要:只有收回才判断这些内容,也就是活塞只有瞬间推出,不存在瞬间收回。之后会将自身也设为36号方块,在收回完成后进行还原。
            5.3 推动中的方块的更新
            推动中的方块本质上是Tile Entity,因此在Tile Entity处进行更新。每个移动中的方块都有一个计数器,在第三次Tile Entity更新时即还原回方块,并更新周围方块。第三次更新之前,如果被活塞收回的Block Event强制阻断,就会立即还原方块,形成短脉冲效果。根据被强制阻断时已更新的次数,活塞短脉冲可以有3种,即0t,1gt和2gt,分别在第一次、第二次、第三次更新之前被强制阻断。
            5.4 关于活塞的瞬间收回
            瞬间收回有两种,活塞推出前瞬间的收回(例:陈老仙UBTF),和活塞收回前瞬间的收回(例:十分常见的论证中继器比较器优先级的电路)。两者有一个共同点,在活塞收到更新与Block Event更新之间,活塞都是处于收回的状态的,也就是可以被推动的状态。此时,如果有一个活塞在Block Event列表中的顺序处于该活塞之前,那么这个活塞就可以对该活塞进行操作(注:我看过部分1.9快照源码,活塞部分有少许改动,瞬间收回已经被修复)。


            IP属地:陕西6楼2015-10-02 21:06
            收起回复
              6 使用此理论计算电路耗时
              实际电路可能会十分复杂,这里只讨论单条电路的耗时。
              6.1 每一条电路均由电路元件和红石导线构成。计算电路耗时时,先列举这条电路上的所有元件,以下图中的电路为例:

              下路的元件依次为 拉杆、中继器、中继器、活塞。
              上路的元件依次为 拉杆、活塞、活塞。
              6.2 依次比较相邻的两个元件,如果后者被激活的更新顺序为NTE,则此部分延迟为前者的延迟。否则,判断前者更新后者的顺序和后者被更新的顺序,若前者在前或两者相同,则此部分延迟为前者延迟;若后者在前,则此部分延迟为前者延迟 + 1gt。这个说法有点绕,请参照实例来理解。
              先看下路,首先是拉杆和中继器,因为中继器是NTE元件,所以延迟为拉杆延迟= 0gt;然后是中继器和中继器,后者中继器是NTE元件,延迟为中继器延迟 = 2gt;最后是中继器和活塞,活塞不是NTE元件,且更新顺序在中继器之后,所以延迟为前者中继器延迟= 2gt。
              再看上路,首先拉杆和活塞,活塞不是NTE元件,且后者活塞更新顺序在前者拉杆之前,所以延迟为拉杆延迟 + 1gt = 0gt + 1gt = 1gt。随后是活塞推红石块和活塞,活塞不是NTE元件,且推红石块顺序在活塞更新之后,所以延迟为活塞延迟 + 1gt = 2gt + 1gt = 3gt。
              6.3 将上一步计算所得各部分延迟相加得到总耗时。
              下路耗时 = 2gt + 2gt = 4gt。
              上路耗时 = 1gt + 3gt = 4gt。
              计算所得上下两路耗时相等,实际测试也确实如此,活塞同时推出。
              6.4 为什么要这样计算?
              游戏每两次循环之间,也就是每两game tick之间会有一次系统延时,也就是电路延时的实现。而电路元件接收到更新时,并不是立即更新自身,这就造成相同元件的不同延迟,典型的就是活塞。每相邻两个元件,如果前者更新后者的代码在后者接收到更新的代码之前,那么从更新到被更新就是无延迟的,就是在1gt之内完成更新与被更新。反之,前者更新之后,后者需要等到下一gt才能被更新,于是更新与被更新之间就会有1gt的更新延迟。这1gt延迟在活塞延迟理论中被解释为活塞的启动延迟,实际上它的本质是元件的更新顺序。至于NTE元件为何是个例外,因为在Next Tick Entry列表中,储存了NTE元件将被更新的系统时间,无论顺序,NTE元件总会在预定的那一gt进行更新,不会受到更新延迟的影响。


              IP属地:陕西7楼2015-10-02 21:07
              收起回复
                7 关于包含有漏斗的电路
                在sancarn的视频中,漏斗被单独列出,被当作一个例外进行处理。实际上漏斗也遵循更新延迟理论,在Tile Entity处进行更新。我们看下面一个例子:

                漏斗内有一个64堆叠物品。这时如果拉动拉杆,两个活塞会同时收回。
                7.1 先来看下路。拉杆-中继器 = 0gt,中继器-中继器 = 2gt,中继器-活塞 = 2gt。总耗时 = 4gt。
                7.2 再看上路,拉杆-中继器 = 0gt,中继器-漏斗,漏斗不是NTE元件,更新顺序Tile Entity在NTE之后,所以延迟为中继器延迟 = 2gt。之后比较器为NTE元件,延迟为漏斗延迟 = 0gt。比较器-活塞,Block Event 在NTE之后,延迟为比较器延迟 = 2gt。总耗时 = 4gt。
                上下两路下边缘延迟计算结果相等,与实际测试相符。
                下图是sancarn统计的漏斗的例外情况,而他判断这是例外的参照物是活塞。

                很容易就能看出来,这些例外全部是由实体更新造成的,再看看我们的更新顺序表,实体更新的顺序Entity Update,恰好在活塞更新,即Block Event与漏斗更新,即Tile Entity之间。所以这实际上并不是例外,更新顺序可以完美解释这个问题。
                再看看这个电路:

                拉动拉杆后,上路延迟:拉杆-中继器 = 0gt,中继器-活塞 = 2gt,总和 = 2gt。下路延迟:拉杆-漏斗 = 1gt,漏斗-比较器 = 0gt,中继器-活塞 = 2gt,总和 = 3gt。计算结果下路比上路多耗时1gt,结果确实如此:

                这1gt延迟的来源就是拉杆到漏斗的1gt更新延迟。如果在拉杆前方加一个中继器,就可以避免更新延迟。此时上路延迟:拉杆-中继器 = 0gt,中继器-中继器 = 2gt,中继器-活塞 = 2gt,总耗时 = 4gt。下路延迟:拉杆-中继器 = 0gt,中继器-漏斗 = 2gt,漏斗-比较器 = 0gt,比较器-活塞 = 2gt,总耗时 = 4gt,两个活塞将同时收回:


                IP属地:陕西8楼2015-10-02 21:09
                回复
                  8 一些电路耗时计算的例子

                  举例sancarn视频中的一个实例:
                  8.1 右边拉杆拉下时
                  一路:拉杆-活塞 = 1gt,推红石块-活塞 = 2gt + 1gt = 3gt,总和 = 4gt。
                  二路:拉杆-中继器 = 0gt,中继器-活塞 = 2gt,推红石块-活塞 = 3gt,总和 = 5gt。
                  三路:拉杆-中继器 = 0gt,中继器-活塞 = 2gt,推红石块-中继器 = 2gt,中继器-活塞 = 2gt,总和 = 6gt。
                  四路:拉杆-中继器 = 0gt,中继器-活塞 = 4gt,推红石块-活塞 = 3gt,总和 = 7gt。
                  五路:拉杆-活塞 = 1gt,推红石块-活塞 = 3gt,推红石块-活塞 = 3gt,总和 = 7gt。
                  为什么都比视频中多了1gt?因为命令方块也是NTE元件,延迟为1gt。
                  8.2 左边拉杆拉下时
                  一路:拉杆-中继器 = 1gt,中继器-活塞 = 2gt,推红石块-活塞 = 3gt,总和 = 6gt。
                  二路:拉杆-中继器 = 1gt,中继器-中继器 = 2gt,中继器-活塞 = 2gt,推红石块-活塞 = 3gt,总和 = 8gt。
                  三路:拉杆-中继器 = 1gt,中继器-中继器 = 2gt,中继器-活塞 = 2gt,推红石块-中继器 = 2gt,中继器-活塞 = 2gt,总和 = 9gt。
                  四路:拉杆-中继器 = 1gt,中继器-中继器 = 2gt,中继器-活塞 = 4gt,推红石块-活塞 = 3gt,总和 = 10gt。
                  五路:拉杆-中继器 = 1gt,中继器-活塞 = 2gt,推红石块-活塞 = 3gt,推红石块-活塞 = 3gt,总和 = 9gt。
                  为什么比视频中多了3gt?2gt是第一个中继器延迟,1gt是命令方块延迟。
                  8.3 听取大触nenn的建议,使用命令方块测试XD


                  1到5路延迟分别为3、4、6、6、6。其中,1、2、4、5路后方命令方块的延迟抵消了推红石块到活塞的1gt更新延迟,而前方命令方块有1gt的延迟,所以测试延迟少了1gt。第3路由于最后一个元件是比较器,不存在更新延迟,所以前后两命令方块延迟抵消,测试延迟与计算值相等。


                  IP属地:陕西9楼2015-10-02 21:10
                  收起回复
                    9 一些常用元件的延迟:
                    发射器/投掷器:4gt。
                    (粘性)活塞:2gt。
                    四种压力版:0gt。弹起:20gt。
                    红石火把:2gt。
                    (木质/石质)按钮:0gt。弹起:木质30gt,石质20gt。
                    红石灯:0gt。熄灭:4gt。
                    漏斗:0gt。
                    中继器:档数x 2gt。
                    比较器:2gt。


                    IP属地:陕西10楼2015-10-02 21:11
                    回复
                      10 关于NTE元件的优先级
                      10.1 中继器的优先级:若对着另一个中继器且方向不同,优先级为 -3,否则若已点亮,优先级为 -2,否则优先级为 -1.
                      10.2 比较器的优先级:若对着一个中继器且方向不同,优先级为 -1,否则优先级为 0.
                      10.3 其他NTE元件的优先级:全部为0.
                      10.4 优先级的比较方式:优先级小的元件先被NTE更新。
                      WTF?这好像和已知的不太一样啊,中继器比比较器更先更新?事实确实如此,这张图就能说明问题:

                      无论什么方向,怎么旋转、翻转,结果都是中继器激活的活塞先推出。
                      至于中继器和比较器形成的瞬间收回,其实也恰恰说明了这个问题。在活塞收到更新直到Block Event更新期间,活塞实际上都处于已经收回的状态。这时那一个活塞先开始收回,就会将自己变成36号方块,其他活塞就推不动了。所以,一定是中继器激活的活塞先收回,此时比较器激活的活塞察觉到自己已经被推动了,于是也不进行收回前方方块操作了,直接将活塞臂删除。
                      附赠神图一张,可以直接看出活塞是在收到更新那一瞬间直接将自己变成已收回的状态的~。


                      IP属地:陕西11楼2015-10-02 21:11
                      回复
                        11 此外,我写了一个mod,mod中新加入了一种方块,当受到更新时,它会输出当前坐标、当前Game Time等信息,在游戏中输入/give @p order_test_block就能得到这个美丽的蓝色小方块了QwQ。因为这个方块是在收到更新时无延迟立即输出,所以它比命令方块输出/time query gametime更直观一点,颜色也更好看(大雾)。


                        嘛,因为命令方块是NTE元件,换成测试方块结果是一样的XD


                        IP属地:陕西12楼2015-10-02 21:12
                        收起回复
                          12 小花絮。。。

                          截到这张图的瞬间,WTF,简直打脸,后来才发现我用了optifine,我也只能将它解释成opt搞的鬼了,我没发现我的理论研究有什么错误。。为了实事求是我还是把它发上来了,以上所有图我都重新截了一遍。珍爱生命,远离optifine。
                          最后,我的理论肯定还有很多不足之处的,希望大家也能多多找茬,多多打脸哈~。
                          特别感谢:陈老仙帮忙修正草稿提出建议,感谢nenn触为实验方法提出建议,感谢所有TC服成员的鼓励,感谢所有能够认认真真看完这篇文章的人QwQ


                          IP属地:陕西13楼2015-10-02 21:13
                          回复
                            最后,所用时序测试方块mod下载地址:
                            http://pan.baidu.com/s/1gd10RvH
                            各位大触能赏脸提一点建议吗QwQ


                            IP属地:陕西14楼2015-10-02 21:14
                            收起回复
                              gp大触!_(:_」∠)_


                              IP属地:福建来自Android客户端15楼2015-10-02 21:22
                              收起回复