介绍
尽我所能优化常规的拼图结构生成。本质上,这个模组的目标是完全不改变结构的外观。它只是努力让结构生成得更快、更高效。仅此而已。
以下是所做优化的更多技术细节。如果你在整合包中运行此模组时发现任何冲突或问题,请报告!
- 用 BoxOctree 替换 VertexShape 未优化的调用,使拼图块只检查附近的拼图块是否相交,而不是检查整个结构。
- 默认情况下,原版使用 VoxelShape 来存储它为结构布局所组装的拼图块的边界。当它要添加新拼图块时,需要查看它是否能放入布局中而不与其他任何拼图块相交。
问题在于 VoxelShape 并不适合这个用途,因为当检查一个 VoxelShape 是否与另一个 VoxelShape 相交时,会对比所有的顶点。在拥有大量拼图块的拼图结构中,随着布局中添加的有效拼图块越来越多,这种相交检查会大幅变慢。在为递归拼图结构生成大量拼图块时,可能会导致卡顿峰值。
这里的优化用一个内部持有 BoxOctree 实现的虚拟 VoxelShape 替换了普通的 VoxelShape,以便将 BoxOctree 传递到需要的地方。这个 BoxOctree 在检查相交时,只会检查与传入边界框附近的拼图块。因此,由于大多数拼图块距离太远而无关紧要,检查时会被忽略,从而防止相交检查时间失控增长。
- 检查拼图方块是否被完全封堵,以确定何时跳过检查子刚性拼图块。
- 在原版中,父拼图块中的每一个拼图方块都会检查子拼图块中的每一个拼图方块,以确定何时尝试连接这两个拼图块。问题在于,如果父拼图块的拼图方块朝向结构边界的边缘,或者朝向另一个独立的拼图块,那么这个拼图方块就没有空间生成刚性结构拼图块。因此,我们可以让游戏跳过检查这个刚性子拼图块,转而检查下一个拼图块。这使我们免于进行大量昂贵的拼图方块匹配检查。尤其是在拥有大量拼图方块的结构中。
- (26.2 及以下)用略微优化的版本替换拼图目标/朝向匹配。
- 将方块属性的获取次数减少了一半。原版对每个 JigsawBlock 调用两次 getValue,从同一个属性获取 top 和 front 值,而实际上只需获取一次就能得到它需要的所有值。还略微提高了从拼图方块的 NBT 中获取 joints、targets 和 name 字符串值的速度。
此外,简化了 joint 数据检查,不再需要通过 byName 转换为 Enum(性能不佳),并重新排序检查以减少需要频繁运行的逻辑量。
这将有助于拥有大量拼图方块的拼图结构,因为每个拼图方块都会对其他结构拼图块中的所有拼图方块运行这段匹配代码。
- 使任何没有 finalizeProcessing StructureProcessor 的巨型结构 NBT 现在加载得快得多。
- 当一个 NBT 结构片段准备在区块中生成时,整个 NBT 片段都会被加载到内存中,然后多次遍历其中的所有位置,以便应用 StructureProcessors,最后再忽略所有位于当前生成区块之外的位置。这种做法效率不高(实际上非常浪费),并且会导致大型 NBT 文件的加载时间大幅增加。
本模组的优化方式是提前进行边界检查,在传递给 StructureProcessors 之前,先移除所有不需要的位置。不过,任何重写了 finalizeProcessing 方法的 StructureProcessor 都可能需要全部 NBT 位置才能正常工作,因此对于包含此类 StructureProcessor 的结构片段,会禁用这项优化;这类结构应该非常少。在原版中,只有 Trail Ruins 由于使用了会重写 finalizeProcessing 方法的 Capped StructureProcessor,因此无法享受这项优化。
- (1.21.1+) 使用稍快的版本替换了 SinglePoolElement 中对拼图方块列表进行打乱和排序的逻辑。
- 主要收益是通过一种只读取一次条目的新方法,让从 NBT 中获取
selection_priority数据的速度略有提升。原版会先检查数据类型,再返回数值,因此会读取两次。这个设计有点奇怪,也比较浪费。我还尝试了新的排序系统来处理优先级,但它只对少数实际使用
selection_priority的结构有帮助,例如 Trials Chamber。总体来说,这可能是效果最弱的一项优化,但对于那些包含多得离谱的拼图方块的拼图结构,它确实能带来相当明显的帮助。因此,这项优化仍然值得保留。这可能会导致拼图方块的执行顺序与原版种子不完全一致,但目前我还没有找到相关证据。
- 跳过已经检查过、且无法在当前位置生成的 SinglePoolElement 的逻辑处理。
- 这项优化源于这样一个事实:原版的 StructureTemplatePool 会根据权重值,将所有 SinglePoolElement 以重复形式存储在列表中。例如,如果你在模板池中为一座房屋指定权重 100,那么这座房屋就会在列表中出现 100 次!在生成布局时,游戏会复制这份列表,将其打乱,然后遍历列表,尝试生成找到的第一个符合条件的结构片段。这意味着,即使某个重复条目之前已经被检查过并被发现无法在当前位置生成,游戏仍然会再次对它执行检查逻辑。我所做的优化是跳过之前已经检查过的结构片段,继续处理列表中的下一个片段。对于模板池中使用了许多高权重值的结构,这能带来不错的性能提升。
- 此外,你还可以开启
deduplicateShuffledTemplatePoolElementList配置选项,以进一步提升模板池中高权重元素的性能。不过,这项配置会付出改变结构布局的代价。布局仍然有效且合理,只是会与关闭该配置选项时生成的布局有所不同。简单来说,它会破坏结构布局的种子一致性,以换取额外的性能提升。
- *需要说明的是,种子一致性是指每次你使用种子 777 时,某个位置有一个带有 2 个铁匠铺房屋的村庄,每次你使用同一个种子,那个村庄都会保持在那个位置并带有两个铁匠铺房屋。开启这个优化配置并使用相同种子时,那个村庄仍然会在相同位置。但这次它可能只有 1 个铁匠铺房屋。而且每次你在开启该配置的情况下使用同一个种子,村庄都会继续以 1 个铁匠铺房屋生成。布局是种子稳定的。只是不再与关闭配置时相同。
- (1.21.4 搭配 v1.1.0+):将 StructureTemplate$Palette 的 StructureBlockInfo 对象列表替换为 StructureBlockInfo 对象的调色板,以便在不生成该缓存的 StructureTemplate 时减少内存占用。
- 此优化是修改自 contaria 为 1.16.1 制作的 Glacier 模组。特别感谢他们允许我使用他们的代码作为此优化的基础!
- 本质上,在原版中,每次加载结构 nbt 文件时,它都会被转换为一个 StructureTemplate 对象并缓存。埋在该对象中的是一个列表,包含该 nbt 文件中的所有位置,以及每个位置上的方块和 nbt 标签。这是 StructureTemplate 对象最大的内存占用者。这个模组会在底层将这个列表替换为一个特殊的调色板数据结构对象,该对象假装是一个列表。调色板的内存大小显著小于列表!然后当世界生成查询时,它会再次构造该列表作为 WeakReference,以便世界生成保持快速并正常工作。当世界生成完成后,这个列表会被垃圾回收以再次释放内存,允许每个 StructureTemplate 在世界生成时仅使用其所需的全部内存,但长期以较小的形式保持缓存。缺点是此列表不能也不应通过 add、remove、clear 调用进行修改,如果这样做会抛出异常。幸运的是,我认为没有任何模组直接在 StructureTemplate 上修改此列表,但如果有,请告诉我。
- ModernFix 确实有一个 mixin,会使 StructureTemplate 对象本身成为 SoftReference,并在游戏内存满时完全从内存中移除。这是一个有效的解决方案,旨在阻止将结构 nbt 加载到缓存中的内存泄漏,该缓存在长期游戏中永远不会清除。缺点是如果该部分从内存中清除,则必须再次直接从文件加载。这个 mixin 与我的模组的优化可以很好地协同工作。可能发生的情况是,我的模组使 StructureTemplate 在内存中更小,这允许 ModernFix 在内存中保留更多 StructureTemplate 而不会完全从内存中移除。理论上,这应该允许长期更多的缓存命中。
TelepathicGrunt 的其他作品
查看全部 →Repurposed Structures (Neoforge/Forge)
添加更多原版结构和特性的变种,比如丛林要塞!
Repurposed Structures – Fabric
添加更多原版结构和特性的变体,例如丛林堡垒!
The Bumblezone – NeoForge/Forge
一个充满蜜蜂的不可思议维度,如果你拿走它们的蜂蜜,它们会变得非常愤怒!
WITS (What Is This Structure?)
使用 \wits 查看你所在位置的建筑名称!
评论
登录后评论和评分