17 完整流程总览与跨章节索引
1 本章目的
前面 16 章深入讲解了区块管理系统的各个机制 —— 加载票、ChunkStatus、TACS、异步任务、ChunkHolder、计划刻、实体管理、保存卸载,以及边缘的加载降级路径。后续现象专题会把 Savestate、区块互换、光照抑制、PendingChunk 等特殊现象单独展开。
单一概念讲完之后,读者往往还有一个问题:"这些部件是怎样一起工作的?从玩家走进一片区域,到这片区域里的红石开始运转、怪物开始走动,中间到底发生了多少件事?"
本章不做新概念的讲解,只做一件事:把前面的知识点按照时间顺序串成完整的加载流程和卸载流程,并提供一个跨章节索引。
建议先读 01-03 章(基础概念),然后回到本章建立全局视图,再按需深入各专题。
2 分钟快速理解版
以 "玩家走进一片空白区域" 为例:
t = 0:玩家位置变化 → NearbyChunkTicketUpdater 添加 PLAYER 票(→ 06 章)
t = 1-2 gt:票传播完成 → TACS.setLevel() 创建 ChunkHolder(→ 04 章)
t = 2-20 gt:ChunkHolder 升温 → TACS 调度生成任务 → worldgen 线程执行(→ 02, 05 章)
t = 5-30 gt:INITIALIZE_LIGHT + LIGHT 阶段 → light 线程执行光照计算(→ 02, 05 章)
t = 10-40 gt:ChunkStatus 到达 FULL → ProtoChunk 转为 WorldChunk(→ 02 章)
t = 同时:ChunkHolder 继续升温,accessibleFuture/tickingFuture/entityTickingFuture 逐个 complete(→ 07 章)
t = 同时:ServerEntityManager 加载实体 FRESH → PENDING → LOADED(→ 10 章)
t = 同时:客户端同步 ChunkDataS2CPacket(→ 13 章)
t = 30-50 gt:玩家看到地形、红石运转、怪物走动 —— 区块进入稳定运行态
实际 gt 数取决于生成线程负载、区块距离、其他维度生成进度和服务器性能。
3 完整加载流程
3.1 阶段 1:Ticket 创建(→ 06 章)
玩家移动/传送门/forceload/World.getChunk() 触发 → ChunkTicketManager 持有 8 种 ticket 之一
3.2 阶段 2:Level 传播(→ 06 章)
ChunkPosDistanceLevelPropagator 按切比雪夫距离传播,level = 33 - radius
3.3 阶段 3:ChunkHolder 创建(→ 04 章)
TACS.setLevel() 的 4 条规则 → holder 进入 currentChunkHolders
3.4 阶段 4:升温判定(→ 07 章)
ChunkHolder.tick() 对比新旧 ChunkLevelType → accessible/ticking/entityTicking 三种升温路径
3.5 阶段 5:生成管线(→ 02, 05 章)
EMPTY → STRUCTURE_STARTS → BIOMES → NOISE → SURFACE → CARVERS → FEATURES,每步有 taskMargin
3.6 阶段 6:光照计算(→ 05 章)
INITIALIZE_LIGHT + LIGHT → ServerLightingProvider 双阶段执行 → light 线程
3.7 阶段 7:WorldChunk 转换(→ 02 章)
ProtoChunk → WorldChunk → levelTypeProvider 挂载
3.8 阶段 8:方块运算启动(→ 07, 08, 09 章)
makeChunkTickable () 等待 3×3 FULL → 计划刻 + 随机刻 + 方块实体刻开始
3.9 阶段 9:实体运算启动(→ 07, 10 章)
makeChunkEntitiesTickable () 等待 5×5 FULL → 实体 AI + 碰撞 + 刷怪开始
3.10 阶段 10:实体数据初始化(→ 10, 16 章)
FRESH → PENDING → LOADED,POI 也从方块状态派生(→ 12 章)
3.11 阶段 11:客户端同步(→ 13 章)
ChunkDataS2CPacket → 后续方块变化用增量更新包
完整流程的 ASCII 图:
Ticket 创建(06) → Level传播(06) → TACS.setLevel(04) → ChunkHolder.tick升温(07)
→ CF链(05) → ChunkStatus管线(02) → 光照(05) → Proto→World(02)
→ BLOCK_TICKING(07/08/09) → ENTITY_TICKING(07/10) → 实体LOADED(10) → 客户端同步(13) → 稳定
4 完整卸载流程
Ticket过期(06) → Level增加(06) → setLevel→unloadedChunks(04) → ChunkHolder.tick降温(07)
→ unloadChunks → tryUnloadChunk(11)
├─ 区块:save(chunk) → chunksToUnload.remove(11)
└─ 实体:updateTrackingStatus(HIDDEN) → pendingUnloads → unload重试(10/16)
5 五条并行管线
依赖关系:温度依赖生成,方块刻依赖温度,实体刻依赖温度,计划刻额外依赖实体管线(isChunkLoaded 检查 managedStatuses==LOADED),客户端独立。
6 异常路径速查
7 关键机制速查表
8 跨章节关键概念索引
8.1 加载相关
Ticket 系统:06 章定义、创建、过期;04 章 setLevel 规则;07 章升温触发
ChunkLevel:03 章分层定义;06 章传播算法;04 章 currentLevel 状态
ChunkHolder:04 章创建规则;07 章生命周期;08-10 章运行状态入口
NearbyChunkTicketUpdater:06 章玩家票管理;03 章视距计算;07 章升温触发时机
Propagator:06 章距离传播算法;04 章 setLevel 调用时机;03 章 level 值含义
8.2 生成相关
ChunkStatus:02 章完整管线;05 章异步调度;16 章降级处理
光照计算:02 章 LIGHT 阶段;05 章 ServerLightingProvider;20 章抑制器现象
worldgen 线程:05 章调度器;02 章任务类型;04 章 TACS 分发
taskMargin:02 章 ChunkStatus 定义;05 章异步任务半径;07 章 Future 依赖范围
ProtoChunk → WorldChunk:02 章转换时机;07 章 accessibleFuture complete;11 章保存格式差异
8.3 运算相关
计划刻:08 章调度器;21 章 isChunkLoaded 检查链;10 章实体状态依赖
随机刻:09 章频率计算;08 章 tickChunk () 执行点
实体刻:10 章实体管理器;07 章 entityTickingFuture;21 章 PENDING 卡死
方块实体刻:09 章 tickBlockEntities () 调用;07 章 BLOCK_TICKING 视图;08 章 tickingFuture 依赖
3×3 与 5×5 范围:07 章升温范围检查;08 章方块刻范围;10 章实体刻范围
8.4 保存卸载相关
保存流水线:11 章 save () 调用链;15 章保存冷却;12 章 POI 独立保存
卸载流水线:11 章 tryUnloadChunk;10 章实体卸载重试;21 章降温路径
客户端同步:13 章 ChunkDataS2CPacket;14 章 GameEvent 传播
unsavedChunks:11 章自动保存队列;15 章保存冷却判定;04 章 TACS 管理
unloadTaskQueue:11 章卸载重试;05 章任务调度器;10 章实体卸载依赖
8.5 异常路径相关
IOException 降级:16 章 recoverFromException;02 章空 ProtoChunk
PENDING 卡死:21 章状态机;08 章计划刻失效;11 章 flush 死锁
Savestate 分叉:18 章保存冷却;15 章刷盘机制;11 章 Watchdog 强退;10 章实体容器写时机
区块互换:19 章 region 文件头部指针;15 章异步写盘;16 章加载降级
光照抑制:20 章 LQ 净增长速率;06 章票传播;05 章光照任务调度
EmptyChunk:16 章 IOException 后降级;02 章 ChunkStatus.EMPTY;11 章卸载后保留
crash report:16 章生成器崩溃;05 章异步任务异常传播;04 章 TACS 异常处理
9 设计哲学与权衡
9.1 为什么要有 ChunkStatus 和 ChunkLevelType 两套系统
ChunkStatus 回答 "方块数据到什么程度"(生成管线),ChunkLevelType 回答 "运算到什么程度"(温度管线)。前者是生成器的进度条,后者是运行时的调度器。
它们解耦是因为:生成可以预先完成(预生成),运算必须跟随玩家(视距)。一个区块可以生成完毕(FULL)但不运算(INACCESSIBLE),也可以正在生成(NOISE)但已经可访问(BORDER)—— 后者用于跨区块的地形特性生成。
9.2 为什么实体数据和区块数据分开保存
区块数据(方块、光照、方块实体)变化频繁,保存冷却 10 秒。实体数据(生物、物品、矿车)变化更频繁,但数据量小,独立保存可以降低 region 文件写入压力(11/15 章)。
代价是两者可能不同步:区块保存了但实体没保存,或实体保存了但区块没保存。这会导致实体 "飘" 在空中(区块回档)或容器 "丢" 物品(实体回档)。Savestate 现象就是这种不同步的极端情况。
9.3 为什么 POI 要独立保存
POI(兴趣点)是村民 AI 和袭击系统的核心数据,变化频率低(只在放置/破坏工作站/床时更新),但读取频率高(村民每 gt 都要查询)。独立保存可以:
- 减少区块保存时的序列化开销(12 章)
- 支持跨区块的 POI 查询(村民寻找工作站)
- 在区块降级为空 ProtoChunk 时保留 POI 数据(16 章)
代价是 POI 和区块数据也可能不同步:方块已破坏但 POI 还在,或 POI 已删除但方块还在。这会导致村民 "幻视" 工作站或袭击系统错误定位。
9.4 为什么光照计算要独立线程
光照计算是 CPU 密集型任务,单个区块的光照更新可能影响周围 17×17 区块(光照传播半径)。如果在主线程执行,每次放置火把都会卡顿数百毫秒。
独立 light 线程可以并行处理多个光照任务,但代价是光照更新有延迟(可能数 gt)。这会导致玩家看到 "光照闪烁"(客户端插值)或 "黑影"(光照未传播完成)。光照抑制器利用的就是这个延迟(20 章)。
9.5 为什么要有保存冷却
保存冷却的目的是减少磁盘 I/O:一个区块如果 1 秒内被修改 100 次,没必要保存 100 次,只需要在 10 秒后保存最终状态。
代价是数据丢失风险:如果在冷却期间服务器崩溃/强退/断电,最近 10 秒的修改会丢失。Savestate 现象就是这种风险的具体体现(18 章)。
Mojang 认为这个权衡是值得的:正常关服时会等待冷却期结束,只有异常退出才会丢数据。服务器运维的任务是减少异常退出(Watchdog 调优、OOM 预防)。
10 本章小结
本章串联了前面 16 章的机制知识点,并为后续现象专题提供索引。它按照时间顺序展示完整的加载流程、卸载流程和五条并行管线的交互方式,同时提供关键机制速查表、阅读路线建议、常见问题快速定位、性能优化方向和典型场景追踪。
区块管理系统不是一个单一的状态机,而是多个独立又相互依赖的管线的集合:生成管线负责把空坐标变成方块数据,温度管线负责把方块数据激活成可运算的区块,实体管线负责加载和管理实体,POI 管线负责兴趣点增删,客户端管线负责同步可见性。它们通过 ChunkHolder、ChunkStatus、managedStatuses、watchDistance 等状态标志协调工作,通过 Future 链、队列、重试机制处理异常。
从 Ticket 创建到区块卸载,从正常流程到异常处理,从方块运算到实体管理,从客户端同步到保存流水线,前 17 章覆盖了区块管理系统的核心机制。18-21 章则把常见特殊现象独立成专题,便于按问题查阅。希望这套知识体系能帮助你理解 Minecraft 服务端最复杂的子系统之一,并在遇到区块相关问题时快速定位根源。
