- 游戏开发
【免费下载链接】Pumpkin
Empowering everyone to host fast and efficient Minecraft servers
本文档是 Pumpkin(用 Rust 编写的高性能 Minecraft 服务端)中pumpkin-world模块的开发与贡献指南,聚焦于区块加载(Chunk Loading)的 ticket/level 机制、与原版一致的世界生成(World Generation)、以及区块生成热路径的性能基准验证。读完本文,你将掌握 Pumpkin 区块系统的核心设计、如何通过proto_chunk_test.rs对照原版 chunk dump 校验生成结果,以及如何用 Criterion 基准测试为区块生成改动把关。
本文内容以 crates/pumpkin-world/AGENTS.md 为骨架展开,并引用 crates/pumpkin-world/src/chunk_system/ 与 crates/pumpkin-world/src/generation/ 下的源码作为依据。
一、模块总览:pumpkin-world 的三大核心关注点
仓库根目录的 AGENTS.md 对全项目生效,而pumpkin-world的这份 AGENTS.md 则专门界定本 crate 的开发纪律,共三大主题:
- 区块加载(Chunk loading):采用仿原版(vanilla)的 ticket + level 体系,位于
src/chunk_system/; - 世界生成(World generation):必须与原版输出逐字节对比,而不是只与上一个 Pumpkin 版本对比;
- 性能(Performance):区块生成是服务器最热路径,任何改动都要跑 Criterion 基准并附上
master与分支的对比数据。
这三个主题相互咬合:ticket/level 决定“哪些区块该加载到哪一级”,世界生成决定“加载出来的区块长什么样”,性能基准则保证上述一切改动不会让服务端变慢。
二、区块加载:ticket + level 体系
2.1 设计原则:仿原版,但不抄原版数字
区块加载的核心实现在 src/chunk_system/chunk_loading.rs,其结构体ChunkLoading维护了三个关键数据结构:
pub struct ChunkLoading { pub is_priority_dirty: bool, pub pos_level: ChunkLevel, // 每个区块当前的加载等级 change: HashMapType<ChunkPos, (StagedChunkEnum, StagedChunkEnum)>, // 等级变化记录 pub ticket: HashMapType<ChunkPos, Vec<i8>>, // 每个区块上的 ticket 集合 pub high_priority: Vec<ChunkPos>, // 强制加载(force)的高优先级区块 pub sender: Arc<LevelChannel>, // 与调度线程通信的通道 }AGENTS.md 中有一条非常明确的告诫:Pumpkin 的 level 数值与原版 Java 不对齐,必须查阅 Pumpkin 自己的常量,而不是复用 Java 代码里的数字。ChunkLoading中直接定义了这些常量:
// pub const FULL_CHUNK_LEVEL: i8 = 33; // 原版 Java 的数值(注释保留作对照) pub const FULL_CHUNK_LEVEL: i8 = 43; // Pumpkin 实际使用的完整区块等级 pub const MAX_LEVEL: i8 = 49; // 等级 49 表示将被卸载同时提供了两个从配置推导等级的辅助函数:
pub const fn get_level_from_view_distance(view_distance: u8) -> i8 { Self::FULL_CHUNK_LEVEL - (view_distance as i8) } pub const fn get_level_from_simulation_distance(simulation_distance: u8) -> i8 { Self::FULL_CHUNK_LEVEL - (simulation_distance as i8) }即:level = 43 - 视距/模拟距离。区块离玩家越远,level 数值越大,代表加载状态越浅(直到 49 被卸载)。
为什么 level 比原版大:这是 Pumpkin 自己的约定,从 src/chunk_system/chunk_state.rs 的
level_to_stage映射可以看出,Pumpkin 用 43~48 这一档来对应从Full到Surface的各生成阶段,与原版数字无关。
2.2 ticket 的加减:add_ticket / remove_ticket 的扩散逻辑
区块能否加载、加载到哪一级,完全由 ticket 驱动。add_ticket(pos, level)会在目标位置挂上一张“要求达到 level 的票据”,并沿**切比雪夫环(Chebyshev ring)**向外扩散,每一环的等级要求逐级放宽:
let max_range = (Self::MAX_LEVEL - level - 1).max(0) as u8; for r in 0..=max_range { let ring_level = level + r as i8; for &(dx, dy) in pumpkin_data::chunk_view_lut::get_chebyshev_ring(r) { let p = pos.add_raw(dx as i32, dy as i32); // 用 ring_level 更新 p 的 pos_level,并 record_change 记录变化 } }切比雪夫环的坐标表来自pumpkin_data::chunk_view_lut::get_chebyshev_ring,这是一个 codegen 生成的查表模块(见下文第三节)。由于等级会向四周扩散,一张等级设错的 ticket 可能让远超预期的区块被加载甚至生成——这正是 AGENTS.md 强调“不能只看区块有没有到达,而要检查新 ticket 的完整影响面”的原因。
remove_ticket则相反:删掉一张 ticket 后,需要重新计算受影响范围内所有区块的等级(取剩余 ticket 的最小等级),等级升到MAX_LEVEL的区块会从pos_level中移除,进入卸载流程。
2.3 force ticket:高优先级加载
add_force_ticket/remove_force_ticket是强制加载接口,常用于传送点、出生点等必须立即可用的位置:
pub fn add_force_ticket(&mut self, pos: ChunkPos) { self.high_priority.push(pos); self.is_priority_dirty = true; self.add_ticket(pos, Self::FULL_CHUNK_LEVEL); }被 force 的区块会进入high_priority列表,并以FULL_CHUNK_LEVEL(43)要求完整生成;调度线程据此用更高优先级抢占生成任务(见第四节calc_priority)。
2.4 正确性校验与单元测试
ChunkLoading内部提供debug_check_error()与dump_level_debug()两个调试设施:前者在每次 add/remove 后用断言重算全图等级并核对pos_level一致性,后者把pos_level渲染成一张以X/Y为表头的 ASCII 等级网格,便于肉眼排查扩散错误。
文件底部自带的#[test]单元测试覆盖了典型场景:相邻 ticket 的加删、同位置重复 ticket、负数坐标((-72, 457)一带)、以及“移除一张大范围 ticket 后邻近 ticket 能否正确接管”的回归场景,并打印loading level:网格供人工核对。
三、世界生成:一切以原版为准
3.1 校验基准:proto_chunk_test.rs 与原版 chunk dump
AGENTS.md 反复强调:对比输出要以原版(vanilla)为准,而不是以 Pumpkin 上一个版本为准。执行这一纪律的是 src/generation/proto_chunk_test.rs,它通过pumpkin_util::read_data_from_file!读取仓库根目录 assets/tests/ 下的.chunkdump 文件,逐块块状态(u16 的 block state ID)比对 Pumpkin 的生成结果与原版:
#[test] fn no_blend_no_beard_0_0() { let expected: Vec<u16> = pumpkin_util::read_data_from_file!( "../../../../assets/tests/noise_no_blend_no_beard_0_0.chunk" ); verify_chunk_noise(0, Dimension::OVERWORLD, 0, 0, &expected, "no_blend_no_beard_0_0"); }测试矩阵覆盖了:
- 主世界 / 下界 / 末地三种维度(如
noise_nether_no_blend_no_beard_0_0、noise_end_no_blend_no_beard_7_4); - 噪声地形与地表构建两个阶段(
verify_chunk_noise与verify_chunk_surface); - 多种种子与坐标:种子 0 的
(0,0)、(7,4),种子 13579 的(-6,11)、(-2,15)、(-7,9),以及地形差异巨大的badlands(-595,544)、frozen_ocean(-119,183); - cell cache 的插值变体(
noise_no_blend_no_beard_only_cell_cache_interpolated_0_0,对应生成缓存不同策略)。
两个校验函数都设定了允许偏差上限:let allowed_mismatches = 6000;,当偏差超过上限时测试失败;同时assert_air_above_dumped_window保证噪声窗口之上的区域必须是空气,防止“多余方块”混入。超过 6000 个不匹配意味着生成管线发生了系统性偏差(比如密度函数或噪声参数实现错误),而非个别块的随机误差。
3.2 生成数值来源:vanilla datapack + codegen,禁止抄反编译数值
AGENTS.md 明确规定:噪声设置(noise settings)、密度函数(density functions)、地物(features)、结构(structures)等世界生成数值,全部来自assets/下的 vanilla datapack,并经 codegen 生成。仓库根目录 assets/datapack/ 存放原版数据包,而 codegen 工具位于 tools/pumpkin-codegen/src/,其中与生成相关的生成器包括:
- noise_parameter.rs —— 噪声参数
- noise_router.rs —— 噪声路由(原版的 NoiseRouter)
- noise_settings.rs —— 维度噪声设置
- configured_feature.rs、placed_feature.rs —— 地物
- structures.rs、template_pool.rs —— 结构与模板池
这些 codegen 输出到 crates/pumpkin-data/src/generated/(如noise_parameter.rs、noise_router.rs、noise_settings.rs等)。当 datapack 已定义某个数值时,绝不能从反编译的 Java 代码里抄数——否则一旦原版更新数据包,Pumpkin 将无法通过重新 codegen 同步。
proto_chunk_test.rs中新增阶段应沿用同一套方法:把原版对应阶段的输出 dump 成.chunk文件放到 assets/tests/,再写一个verify_chunk_xxx风格的测试。
3.3 读取高度与限制:永远来自 context 或 dimension
AGENTS.md 最后一条铁律:高度与生成范围必须从 generation context 或 dimension 读取,禁止写死 256、384 之类的猜测值,因为数据包可能改变维度高度。在proto_chunk_test.rs中能看到这一惯例的落实:chunk.bottom_y()、chunk.height()均来自 dimension 配置,而不是硬编码:
let min_y = chunk.bottom_y() as i32; for local_y in 0..dumped_height { let y = local_y as i32 + min_y; ... }从源码结构看,src/generation/ 的ProtoChunk通过step_to_biomes→set_structure_starts→set_structure_references→step_to_noise→step_to_surface的分步推进(对应StagedChunkEnum的Biomes → StructureStart → StructureReferences → Noise → Surface,见 chunk_state.rs),每个阶段都从生成上下文取值,这就是“不做任何高度假设”的代码级保证。
四、区块生成的调度与状态机
4.1 十一阶段状态机:StagedChunkEnum
src/chunk_system/chunk_state.rs 定义了区块从空白到完整的分级状态,与原版ChunkStatus一一对应:
| StagedChunkEnum | 阶段含义 | 对应的 ChunkStatus |
|---|---|---|
Empty | 初始空区块,等待填充生物群系 | Empty |
Biomes | 生物群系已填充 | Biomes |
StructureStart | 结构起点 | StructureStarts |
StructureReferences | 结构引用 | StructureReferences |
Noise | 地形噪声已生成 | Terrain |
Surface | 地表已构建 | Terrain |
Carvers | 雕刻器已应用 | Terrain |
Features | 地物与结构已放置 | Features |
Lighting | 光照已计算 | Light |
Spawn | 怪物已生成 | Spawn |
Full | 完整区块 | Full |
阶段之间存在依赖关系与读写半径(get_direct_dependencies、get_direct_radius、get_write_radius),例如Surface阶段需要读取相邻区块的生物群系(read_radius = 1),但不需要写入邻居(write_radius = 0)——chunk_state.rs底部的单元测试专门断言了这一行为。第 2.1 节提到的 level→stage 映射也在这里:
pub const fn level_to_stage(level: i8) -> Self { if level <= 43 { Self::Full } else if level <= 44 { Self::Spawn } else if level <= 45 { Self::Lighting } else if level <= 46 { Self::Features } else if level <= 47 { Self::Carvers } else if level <= 48 { Self::Surface } else { Self::None } }这正好印证了“Pumpkin 的 level 数字与原版不同”的警告:level 43~48 在这里被映射成完整到地表的生成阶段,而 49 以上则代表无需加载。
4.2 调度器:DAG + 优先级队列 + 生成线程池
src/chunk_system/schedule.rs 中的GenerationSchedule是整个区块系统的调度中枢:
- DAG 任务图:任务节点(
Node,含pos与stage)与依赖边存储在 src/chunk_system/dag.rs 的SlotMap中,节点的in_degree归零即代表依赖全部满足,可入队执行; - 优先级队列:
BinaryHeap<TaskHeapNode>按calc_priority计算出的优先级排序。calc_priority综合了加载等级、生成阶段与距高优先级区块(force ticket)的距离:距离FULL_RADIUS(5)以内且阶段满足FULL_DEPENDENCIES时直接给予-100级的大幅加成,确保玩家周围的区块被抢先完成; - 生成线程池:以
rayon构建,线程数为available_parallelism / 2,钳制在 2~16 之间,max_in_flight = gen_threads * 2控制并发在途任务数; - 等待机制:
waiting_for_chunks集合暂存“图就绪但邻居区块数据未到”的任务,由check_waiting_tasks在每个区块到达后重新放行; - IO 分流:
io_read_work/io_write_work(见 src/chunk_system/worker_logic.rs)分别负责从磁盘读取(fetch_chunks)与落盘保存(save_chunks),通过IOLock(Mutex + Notify)避免同一区块并发读写。
4.3 从磁盘恢复与重光照
worker_logic.rs的process_loaded_chunk展示了区块从磁盘加载后的两条路径:
- 状态为
Full的区块:若needs_relighting检测到“配置需要光照但区块只有均匀光照”(例如从 full/dark 模式切回 default 模式),会降级回Features阶段重新走光照流程; - 非
Full的区块:通过ProtoChunk::from_chunk_data恢复为ProtoChunk,继续推进剩余阶段。
而 proto_chunk_test.rs 中的structure_references_are_rebuilt_when_resuming_generation、block_entities_survive_chunk_data_resume、heightmap_roundtrip_through_chunk_data_resume等测试,正是为了验证“部分生成→落盘→恢复→继续生成”这一过程不会丢结构引用、方块实体或高度图。
五、性能:区块生成是服务器最热路径
5.1 基准测试清单
AGENTS.md 要求:改动区块生成相关代码后,必须在 crates/pumpkin-world/benches/ 的 Criterion 基准上,分别运行master与你的分支,把两组数字一起写进 PR。现有基准文件与关注点如下:
| 基准文件 | 测量内容 |
|---|---|
| chunk.rs | 区块数据结构的常规操作 |
| chunk_gen.rs | 单区块生成全流程 |
| chunk_gen_concurrent.rs | 并发区块生成的吞吐 |
| chunk_io.rs | 区块磁盘 IO |
| jigsaw.rs | 拼图结构(jigsaw)拼接 |
| noise_router.rs | 噪声路由各分位点的求值 |
| template_pool.rs | 结构模板池取样 |
5.2 性能排查的纪律
AGENTS.md 给出一条极具实战价值的调试忠告:
如果生成变慢,先对比两个 commit 之间的生成数据与 codegen 输出,再考虑线程问题。原因往往不在你以为的地方。
这句话背后的含义是:区块生成速度的波动,常见根因并非调度或并发,而是数据层面的改变——datapack 数值更新后 codegen 重新生成的噪声参数、密度函数、结构布局表(chunk_view_lut)发生了变化,导致同一坐标生成工作量不同。因此排查顺序应该是:
- 确认两个 commit 的
.chunkdump 对比测试(proto_chunk_test.rs)是否仍通过; - 对比 codegen 输出(crates/pumpkin-data/src/generated/)是否有内容变化;
- 确认数据一致后,再审视调度器(schedule.rs)的优先级、线程池大小、IO 并发等结构性因素。
六、给 Pumpkin 区块系统贡献者的行动清单
综合 AGENTS.md 与源码,向pumpkin-world提交改动时应遵循以下流程:
- 加载逻辑改动:先在 chunk_loading.rs 中用 Pumpkin 自己的 level 常量推演新 ticket 的扩散半径,再跑文件内置单元测试,必要时用
dump_level_debug打印等级网格人工核对;不要直接套用原版 Java 的等级数字; - 生成逻辑改动:新增阶段时,沿用
proto_chunk_test.rs的既有模式——从原版导出.chunkdump 到 assets/tests/,编写verify_chunk_xxx测试并保持全部既有测试通过;生成参数一律取自 assets/datapack/ 并经 tools/pumpkin-codegen/ 重新生成,禁止硬编码维度高度或抄反编译数值; - 性能敏感改动:在 crates/pumpkin-world/benches/ 上跑
master与分支两组 Criterion 基准,同时附上数据对比;若出现性能回退,先比对生成数据与 codegen 输出,再排查线程与调度; - PR 提交:把基准两组数字、测试结果与数据对比结论一并写入 PR 描述,确保新 ticket 的加载影响面被明确记录。
pumpkin-world的这三条纪律——等级体系自查、原版 dump 对照、热路径基准验证——共同保证了 Pumpkin 在区块系统上的每一次改动都既有原版一致性,又有可量化的性能保障。
- 游戏开发
【免费下载链接】Pumpkin
Empowering everyone to host fast and efficient Minecraft servers
相关推荐
Relay Fullstack部署教程:本地环境到Heroku云平台的完整流程
Relay Fullstack部署教程:本地环境到Heroku云平台的完整流程 Relay Fullstack是一个集成了Relay、GraphQL、Expre
5个实用案例:用googlesearch-python批量获取搜索结果
5个实用案例:用googlesearch python批量获取搜索结果 googlesearch python是一个功能强大的Python库,专为批量获取Goo
从零开始:Unitree机器人强化学习完整实战指南
从零开始:Unitree机器人强化学习完整实战指南 想让你自己的四足机器人像真正的动物一样行走、奔跑甚至跳跃吗?Unitree RL Gym正是这样一个强大的开
人工智能强化学习机器人具身智能
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考