TFMG电力性能补丁

TFMG电力性能补丁

修复了“机械动力:工厂必须发展”中每 tick 电力网络重建循环及二次方网络更新的问题。

Bug修复

这是什么

一个非官方的性能和崩溃修复补丁,适用于 NeoForge 21.1.x / Minecraft 1.21.1 上的 机械动力:工厂必须成长 (TFMG) 1.2.0。

仅包含 Mixin。TFMG 本身未被修改或重新分发——不影响任何内容、配方、注册表或世界生成。将其与 TFMG 一同安装,随时可移除;不会向你的世界写入任何数据。

性能问题

如果你的电网中有电动机,TFMG 会以每秒20次的频率重新计算整个电网——即使没有任何变化。而且单次重新计算的实现方式导致其成本与电网规模成 O(n²) 关系,因此电线长度翻倍会使每刻(tick)的计算成本翻两番。

这种组合正是长距离电线和电动机导致 TPS(每秒刻数)和 FPS(每秒帧数)下降的原因。

修复内容

1. 电动机上无限的网络更新循环。 ElectricalNetwork.updateNetwork() 通过 onNetworkChanged(..) 通知每个成员。ElectricMotorBlockEntity 通过 updateNextTick() 响应通知,而该方法会使 tickElectricity() 在下一刻再次调用 updateNetwork(),进而再次通知电动机——如此无限循环。

其他所有消耗设备都通过变更检查来防护其响应:

if (voltage != getData().getVoltage() || powerUsage != getPowerUsage())

电动机是唯一无条件响应的设备。现在它也被施以相同的防护,因此静态电网不再触发任何操作,而实际的变化仍会正常传播。delayedUpdate、notifyUpdate() 和 updateGeneratedRotation() 的行为与之前完全相同。

1.0.0 和 1.1.0 版本是直接移除此调用而非加以防护,这可能导致在电动机是唯一反复变化来源的电网中,变压器无输出。 已在 1.2.0 中修复——如果你使用较早版本,请更新。

2. 单次网络更新的二次方开销。 updateNetwork() 的最后一个循环对每根电缆调用一次 getCableCurrent(member),而该方法会对所有成员的总电流求和——由于它忽略自身参数,每次计算的值都相同。现在该值每轮更新只计算一次并复用。该循环中不涉及电压或电阻变化,因此复用的值与原始代码产生的结果完全相同。O(n²) → O(n)。

当电网中存在变压器和开关时,这一点更为重要,因为 getCurrent() 会调用 resistance(),而 VoltageAlteringBlockEntity.resistance() 本身是 O(n) 复杂度——原始更新会退化至 O(n³)。

3. 空蓄能器:第二个每秒重建循环。 AccumulatorBlockEntity.tick() 在电池组为空(完全正常的状态)时,每刻都调用 updateNextTick()。现在限制为每20刻执行一次——电网仍会得知电池耗尽,最多延迟一秒。

4. 过载电网上的大型开关重建整个图。 LargeSwitchBlockEntity.lazyTick() 在 notEnoughPower 被设置时调用 onPlaced(),这会重新运行 checkForLoops() 和 onConnected()——通过 getBlockEntity() 查找对整个电网进行递归泛洪。现在限制为每10个惰性刻(约5秒)尝试一次。

修复的崩溃

通过工程师护目镜观察钢制储罐并破坏其一个方块时,会导致客户端崩溃:

NullPointerException: Cannot invoke "SteelTankBlockEntity.getBlockPos()" because
the return value of "SteelTankBlockEntity.getControllerBE()" is null
  at SteelTankBlockEntity.addToGoggleTooltip(SteelTankBlockEntity.java:423)
  at GoggleOverlayRenderer.renderOverlay(GoggleOverlayRenderer.java:111)

在多方块结构的一个方块被破坏到连通性重新计算完成之间的间隙,getControllerBE() 返回 null,而提示文本在未进行空检查的情况下就对其解引用。该操作在渲染线程上运行,因此此空指针异常是致命的。TFMG 在 FireboxBlockEntity、VatBlockEntity 和 BlastStoveBlockEntity 中已对此进行了防护,它们的提示文本都以 if (getControllerBE() == null) return false 开头;而钢制储罐缺少这一行。

5. 已在钢制储罐以及另外两个存在相同缺陷的提示文本上恢复了此防护:蒸馏控制器(tank.getControllerBE().activeHeat,会回退到储罐自身的值)和普通引擎(在非控制器方块上调用 getControllerBE().addToGoggleTooltip(..))。

安装

将 jar 文件放入 mods/ 目录,与 TFMG 一同使用。

服务器和客户端都需要安装。 客户端也会构建电网并运行相同的循环,因此不安装此补丁,客户端的 FPS 下降仍会存在。已设置 displayTest = "IGNORE_ALL_VERSION",因此可以仅在一端安装而不会出现模组列表不匹配的问题。

如果更新 TFMG

Mixin 配置使用 "defaultRequire": 1,因此如果未来版本中目标方法发生变化,游戏将以明确的 Mixin 错误启动失败,而不是静默无效运行并让卡顿重现。如果发生这种情况,请移除本补丁。

备注

与 TFMG 作者无关联。发现基于反编译 tfmg-1.2.0.jar;目标是这些修复最终能进入上游,届时本补丁将不再需要。