diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/world/chunk/MixinLevelChunk.java b/common/src/main/java/org/valkyrienskies/mod/mixin/world/chunk/MixinLevelChunk.java index ad158569f..c1b6d962c 100644 --- a/common/src/main/java/org/valkyrienskies/mod/mixin/world/chunk/MixinLevelChunk.java +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/world/chunk/MixinLevelChunk.java @@ -33,6 +33,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.valkyrienskies.core.api.ships.Ship; import org.valkyrienskies.mod.common.BlockStateInfo; +import org.valkyrienskies.mod.common.VSGameUtilsKt; import org.valkyrienskies.mod.common.util.VSLevelChunk; @Mixin(LevelChunk.class) @@ -61,7 +62,10 @@ public MixinLevelChunk(final Ship ship) { public void postSetBlockState(final BlockPos pos, final BlockState state, final boolean moved, final CallbackInfoReturnable cir) { final BlockState prevState = cir.getReturnValue(); - BlockStateInfo.INSTANCE.onSetBlock(level, pos, prevState, state); + // This function is getting invoked by non-game threads for some reason. So use executeOrSchedule() to schedule + // onSetBlock() to be run on the next tick when this function is invoked by a non-game thread. + // See https://github.com/ValkyrienSkies/Valkyrien-Skies-2/issues/913 for more info. + VSGameUtilsKt.executeOrSchedule(level, () -> BlockStateInfo.INSTANCE.onSetBlock(level, pos, prevState, state)); } @Shadow diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/VSGameUtils.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/VSGameUtils.kt index d917c4797..8c4160db7 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/VSGameUtils.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/VSGameUtils.kt @@ -10,6 +10,7 @@ import net.minecraft.resources.ResourceLocation import net.minecraft.server.MinecraftServer import net.minecraft.server.level.ServerChunkCache import net.minecraft.server.level.ServerLevel +import net.minecraft.util.thread.BlockableEventLoop import net.minecraft.world.entity.Entity import net.minecraft.world.entity.player.Player import net.minecraft.world.level.ChunkPos @@ -408,6 +409,25 @@ fun Level.transformAabbToWorld(aabb: AABBdc, dest: AABBd): AABBd { return dest.set(aabb) } +/** + * Execute [runnable] immediately iff the thread invoking this is the same as the game thread. + * Otherwise, schedule [runnable] to run on the next tick. + */ +fun Level.executeOrSchedule(runnable: Runnable) { + val blockableEventLoop: BlockableEventLoop = if (!this.isClientSide) { + this.server!! as BlockableEventLoop + } else { + Minecraft.getInstance() + } + if (blockableEventLoop.isSameThread) { + // For some reason MinecraftServer wants to schedule even when it's the same thread, so we need to add our own + // logic + runnable.run() + } else { + blockableEventLoop.execute(runnable) + } +} + fun getShipMountedToData(passenger: Entity, partialTicks: Float? = null): ShipMountedToData? { val vehicle = passenger.vehicle ?: return null if (vehicle is ShipMountedToDataProvider) {