
Structure Layout Optimizer
尝试优化拼图结构和NBT部件的生成
尽可能优化常规 Jigsaw 结构的生成。本质上,这个模组的目标是完全不改变结构的外观,只致力于让结构生成得更快、更高效。仅此而已。
以下是所做优化的更多技术细节。如果在你的模组包中运行此模组时发现任何冲突或问题,请报告!
用 BoxOctree 替换未优化的 VoxelShape 调用,使部件只检查附近的部件是否相交,而不是检查整个结构。
- 默认情况下,原版使用 VoxelShape 来存储正在组装的结构布局中各部件的边界。当要添加新部件时,需要检查它是否能放入布局中而不与其他任何部件相交。
问题在于 VoxelShape 不适合此用途,因为检查一个 VoxelShape 是否与另一个相交时,会比较所有顶点。在拥有大量部件的 Jigsaw 结构中,随着有效部件被添加到布局中,这种相交检查会严重减慢速度。在为递归 Jigsaw 结构生成大量部件时,可能会导致卡顿峰值。
此处的优化是用一个包含 BoxOctree 实现的虚拟 VoxelShape 替换普通的 VoxelShape,以便将 BoxOctree 传递到需要的地方。这个 BoxOctree 在检查相交时,只会检查与传入边界框附近的部件。从而防止相交检查时间的失控增长,因为它会忽略绝大多数距离太远、无关紧要的部件。
- 默认情况下,原版使用 VoxelShape 来存储正在组装的结构布局中各部件的边界。当要添加新部件时,需要检查它是否能放入布局中而不与其他任何部件相交。
检查 Jigsaw 方块是否被完全堵住,以判断何时跳过检查子刚体部件。
- 在原版中,父部件中的每个 Jigsaw 方块都会检查子部件中的每个 Jigsaw 方块,以确定何时尝试连接两个部件。问题是,如果父部件的 Jigsaw 方块朝向结构边界边缘,或者朝向另一个独立的部件,那么这个 Jigsaw 方块就永远没有空间生成刚体结构部件。因此,我们可以告诉游戏跳过检查这个刚体子部件,转而检查下一个部件。这避免了大量昂贵的 Jigsaw 方块匹配检查,尤其是在拥有大量 Jigsaw 方块的结构中。
用略微优化过的版本替换 Jigsaw 目标/朝向匹配。
- 将块属性获取次数减少一半。原版对每个 JigsawBlock 调用两次
getValue来获取同一个属性的 top 和 front 值,而实际上只需获取一次即可得到所有需要的值。同时略微提高了从 Jigsaw 方块的 NBT 中获取 joints、targets 和 name 字符串值的速度。
还简化了关节数据检查,无需再通过byName转换为枚举(性能不佳),并重新排序检查以减少常运行的逻辑量。
这对拥有极多 Jigsaw 方块的结构会有帮助,因为每个 Jigsaw 方块都会对其他结构部件中的所有 Jigsaw 方块运行此匹配代码。
- 将块属性获取次数减少一半。原版对每个 JigsawBlock 调用两次
使任何没有 finalizeProcessing StructureProcessor 的大型结构 NBT 现在加载得更快。
- 当 NBT 结构部件在区块中生成时,整个 NBT 部件会被加载到内存中,然后为了应用 StructureProcessor 而多次遍历所有位置,之后才忽略当前生成区块之外的所有位置。这效率不高(非常浪费),并导致大型 NBT 文件加载时间过长。
此模组的优化通过提前进行边界检查,在将数据传递给 StructureProcessor 之前剔除所有不需要的位置。然而,任何覆盖了finalizeProcessing方法的 StructureProcessor 可能都需要所有 NBT 位置才能正常工作,因此对于包含这类 StructureProcessor 的部件(这样的结构非常少),此优化会被禁用。在原版中,只有 Trail Ruins 不会获得此优化,因为它使用了覆盖finalizeProcessing方法的 Capped StructureProcessor。
- 当 NBT 结构部件在区块中生成时,整个 NBT 部件会被加载到内存中,然后为了应用 StructureProcessor 而多次遍历所有位置,之后才忽略当前生成区块之外的所有位置。这效率不高(非常浪费),并导致大型 NBT 文件加载时间过长。
(1.21.1+) 用略微更快的版本替换了 SinglePoolElement 中的 Jigsaw 方块列表洗牌和优先级排序逻辑。
- 主要优点是使用新方法一次获取 NBT 中的
selection_priority数据而不是两次,从而略微加快数据获取。原版会先检查数据类型再返回值,这样做了两次,有点奇怪且浪费。
还尝试了一种新的排序系统用于优先级排序,但这只对少数实际使用selection_priority的结构(如试炼密室)有帮助。总体而言,这可能是最弱的优化,但对于拥有数量极其庞大的 Jigsaw 方块的结构来说,它还是很有帮助的。所以值得保留此优化。
这可能会破坏与原版种子在 Jigsaw 方块运行顺序上的一致性,但我目前还没有找到证据。
- 主要优点是使用新方法一次获取 NBT 中的
跳过我们已检查过且无法在当前位置生成的 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,而不会让它们被完全从内存中移除。理论上,这应该能在长期游戏中实现更多缓存命中。
正在加载版本记录…
正在加载评论…
评论在新手盒子客户端中发表,这里同步展示。