运行服务器

GameTick概念

众所周知,在mc中存在时间,一切事件会随着时间流逝相继发生。在现实世界,时间往往是一个连续的概念,也就是任何一个瞬间都可以成为一个时间点。而在计算机中,我们很难描述一个绝对连续的时间去按序执行大量的事件。mc的解决办法是,将时间像手表的秒针一样分片,每过一段时间就会走1刻(1tick)。而每一tick就是游戏的最小时间单位,每1tick都会去做重复的工作去处理游戏的变化,这就是通常讲的game tick,也简称为gt。

由于在当前讨论环境下,不会存在其他时间单位,所以后文中的gt简称为tick或t

此处引出gt的概念是因为在mc中,普遍认为存在游戏刻(gametick)以及红石刻(redstone tick)这两种不同的时间单位,而gt则是更加贴近游戏底层的单位

runServer

入口: net.minecraft.server.MinecraftServer#runServer

此方法为运行服务器的主逻辑,控制整个服务器运行的生命周期。整体来看有如下几部分

image-20240825232040213

最重要的方法就是this.tickServer(),玩家能感知到的所有游戏内的逻辑都在此方法中。我们后续的分析基本上都是在分析该方法。

在这之前,还有一些点可以注意一下

过载判断

image-20240825233749070

这是服务器判断是否过载以及相关的处理代码。接下来逐行分析

  1. long l; 此处的l即为服务器设置的每tick的ms数。第一个if条件其实默认是不会触发的,因为正常运行的情况下,都是1tick保证50ms。除非使用/tick指令调整速度。(才发现1.20.3开始tick指令从carpet合并到了原版,还以为我看错了)

  2. 在else逻辑中,存在如下几个常量:

    1. OVERLOADED_THRESHOLE_NANOS: 1s
    2. OVERLOADED_WARNING_INTERVAL_NANOS:10s
    3. NANOSECONDS_PER_MILLISECOND:1ms

    代码逻辑并不复杂,简单点可以归纳为如下几点:

    1. l 为预设的每tick的时间
    2. m为当前的时间 - nextTickTime。注意,此处nextTick还没更新,所以此处的nextTickTime其实是这一次循环对应tick的时间。换而言之,m就是循环中的这1tick延迟了多少时间
    3. 如果延迟超出了1s + 20tick = 2s,并且本tick具体上次过载告警的tick时间已经超过了 10s + 100tick = 15s,那就需要处理过载。
      1. 计算延迟了多少tick
      2. 在控制台中打warning日志这行日志如果你是服主相信你经常会看到
      3. 更新nextTickTime为实际时间,重设告警时间(这是为了让控制台不要产生告警风暴)

至此,nextTickTime已经被修正,服务器将进行实际的tick操作了。


tickServer

入口:net.minecraft.server.MinecraftServer#tickServer

参数

image-20240826000739521

bl:其实就是一个表达式的值,表示游戏是否在加速。正常状态为false

haveTime: 这是MinecraftServer的方法,直接看源码:image-20240826001048569

返回当前是否有任务在执行,或者当前时间是否还没有到达下一tick或者任务延迟的最大时间

逻辑

  1. tick count + 1
  2. tickManager执行冻结相关的判断
  3. 执行整个tick中的各种流程(tick children)
  4. 设置player的一些状态,在Server status
  5. 每6000tick(30s) 触发一次自动保存
  6. 统计平均的tps

MinecraftServer.tickChildren

  1. 执行commandFuncions
  2. 遍历server中的各个维度,每过1s将维度的时间同步给玩家
  3. 对于每个维度,执行对应维度的tick
  4. 对当前服务器的连接执行tick
  5. 对玩家列表执行tick
  6. 其余流程

比较重要的是各个维度的tick,接下来详细讲

ServerLevel.tick

世界(以下称level)的tick执行流程较为复杂,涉及点也很多,还是从概览到细节逐个分析。

  1. 设置level的handlingTick为true PS.这个属性除了标识状态以外,还会影响活塞行为
  2. 如果服务器处于运行中,就更新世界边界以及天气状态。(如果服务器被/tick freeze了就跳过)
  3. 判断是否跳过夜晚,检查配置以及玩家是否充足睡眠,如果跳过夜晚,清除下雨和打雷的天气image-20240826003416932
  4. 更新时间相关事件image-20240826003725904
    1. level的游戏时间 + 1
    2. level中的定时事件执行tick,传入新的游戏时间
    3. 如果游戏没有关掉daylight的游戏规则,游戏中的白天时间 + 1(影响日夜循环)PS.终于看到实际游戏里能用上的东西了
  5. 如果没有冻结,依次执行 blockTicks.tick(方块刻), fluidTicks.tick(流体刻),raids.tick(袭击刻),chunkSource.tick(区块刻),runBlockEvents(方块事件)image-20240826004149563
    在这些阶段结束后,设置handlingTick为false,表示不再处理tick。
  6. 判断世界中是否存在玩家或者强加载区块。如果存在,那世界的emptyTime设为0。
  7. 在末地维度,上述条件满足,或者自不满足开始300tick内会执行末影龙战斗tick
  8. 对entityTickList中的实体进行检查,如果有需要清除的实体将会将其清除,第一阶段清除和gamerule或者配置文件不符的实体
  9. 对entityTickList中的实体检查,对在tick运算距离内的实体判断骑乘状态,如果被骑乘的实体被移除或未处于被骑乘状态,那么当前实体应当停止骑乘状态
  10. 执行tickNonPassenger,对于处于非骑乘状态的实体进行运算
  11. 执行方块实体运算 this.tickBlockEntites()image-20240826005646015