创建火车碰撞修复

修复了机械动力模组的列车因无限位置可能导致服务器崩溃的问题。

技术

Create Train Crash Fix

修复当列车具有无限坐标时可能发生的崩溃(即使列车数据文件中并未显示为无限)

一个非常简单的模组

给极客的信息(如何修复的) 首先我们得看一下问题所在:列车在列车数据文件中并没有无效的坐标。这意味着问题一定出在序列化过程中的某个地方。

问题 #6795 的创建
2024年8月7日

我添加了两个 mixin 注入。一个在 createEntity(来自 create)上,另一个在 readNbt(来自 minecraft)上。

@Mixin(Carriage.DimensionalCarriageEntity.class)
public abstract class DimensionalCarriageMixin {
  @Shadow public Vec3d positionAnchor;

  @Inject(at = @At("HEAD"), method = "createEntity")
  private void sendEntityInfo(World level, boolean loadPassengers, CallbackInfo ci) {
    CreateTrainFix.LOGGER.info(positionAnchor.toString());

    if (!Double.isFinite(positionAnchor.getX()) || !Double.isFinite(positionAnchor.getY()) || !Double.isFinite(positionAnchor.getZ())) {
        CreateTrainFix.LOGGER.info("Train failed to be created, because of infinity checks.");
    }
  }
}

@Mixin(Entity.class)
public abstract class EntityMixin {
  @Shadow public abstract double getX();

  @Shadow public abstract double getY();

  @Shadow public abstract double getZ();

  @Shadow public abstract Vec3d getPos();

  @Inject(method = "readNbt", at = @At(value = "INVOKE", target = "Ljava/lang/Double;isFinite(D)Z"))
  private void checkFiniteDebug(NbtCompound nbt, CallbackInfo ci) {
    if (!Double.isFinite(getX()) || !Double.isFinite(getY()) || !Double.isFinite(getZ())) {
      CreateTrainFix.LOGGER.info("INFINITE location " + getPos());
    }
  }
}

这是记录下来的日志,之后它不可避免地崩溃了。坐标不知为何是无效的,而在 createEntity 方法中却仍然是正常的。

[18:18:15] [Server thread/INFO]: (225.5, 58.0, -165.8600004762411)
[18:18:15] [Server thread/INFO]: INFINITE location (NaN, NaN, NaN)

经过更彻底的检查后发现,NBT 传入时已经是 NaN,而 create_tracks.dat 文件中却不包含任何类似的内容。create_tracks.dat 检查 见第 123 行(嘿,有趣的数字)


~2.5 个月后 IThundxr 提出了在代码中添加类似以下内容的方案:

serialisedEntity.remove("Pos");
serialisedEntity.put("Pos", newDoubleList(positionAnchor.x(), positionAnchor.y(), positionAnchor.z()));

这就是最终的概念,并且它生效了!


这是所添加内容的最终版本

@Inject(at = @At("HEAD"), method = "createEntity")
private void createTrainFix$fixEntity(World level, boolean loadPassengers, CallbackInfo ci) {
  try {
    // RefUtil is a class with a few methods to access private fields from the superclass.
    NbtCompound serialisedEntity = (NbtCompound) RefUtil.getPrivateFieldValue(this$0, "serialisedEntity");
    serialisedEntity.remove("Pos");
    serialisedEntity.put("Pos", newDoubleList(positionAnchor.x, positionAnchor.y, positionAnchor.z));
    // Set the value again
    RefUtil.setFieldValue(this$0, "serialisedEntity", serialisedEntity);
  } catch (NoSuchFieldException | IllegalAccessException e) {
    // If this all didn't work, throw an error
    CreateTrainFix.LOGGER.error("(CreateTrainFix) Failed to fix train position");
    throw new RuntimeException(e);
  }

  // Final check
  if (!Double.isFinite(positionAnchor.getX()) || !Double.isFinite(positionAnchor.getY()) || !Double.isFinite(positionAnchor.getZ())) {
    CreateTrainFix.LOGGER.info("Train failed to be created, because of infinity checks.");
  }
}