diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index d40341d8..f50e3070 100755 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -36,6 +36,7 @@ import net.minecraft.client.options.Option; import net.minecraft.resource.DirectoryResourcePack; import net.minecraft.resource.ResourcePackCreator; import net.minecraft.resource.ResourcePackContainer; +import net.minecraft.util.NonBlockingThreadExecutor; //#else //$$ import com.google.common.util.concurrent.ListenableFutureTask; //$$ import net.minecraft.resources.FolderPack; @@ -409,6 +410,46 @@ public class ReplayMod implements private boolean inRunLater = false; //#if MC>=11400 private boolean inRenderTaskQueue = false; + // Starting 1.14 MC clears the queue of scheduled tasks on disconnect. + // This works fine for MC since it uses the queue only for packet handling but breaks our assumption that + // stuff submitted via runLater is actually always run (e.g. recording might not be fully stopped because parts + // of that are run via runLater and stopping the recording happens right around the time MC clears the queue). + // Luckily, that's also the version where MC pulled out the executor implementation, so we can just spin up our own. + public static class ReplayModExecutor extends NonBlockingThreadExecutor { + private final Thread mcThread = Thread.currentThread(); + // Fail-fast in case we ever switch to async loading and forget to change this + { if (!MinecraftClient.getInstance().isOnThread()) throw new RuntimeException(); } + + private ReplayModExecutor(String string_1) { + super(string_1); + } + + @Override + protected Runnable prepareRunnable(Runnable runnable) { + return runnable; + } + + @Override + protected boolean canRun(Runnable runnable) { + return true; + } + + @Override + protected Thread getThread() { + return mcThread; + } + + @Override + public void send(Runnable runnable) { + method_18858(runnable); + } + + @Override + public void executeTaskQueue() { + super.executeTaskQueue(); + } + } + public final ReplayModExecutor executor = new ReplayModExecutor("Client/ReplayMod"); //#endif public void runLater(Runnable runnable) { @@ -423,7 +464,7 @@ public class ReplayMod implements } }); } else { - mc.method_18858(() -> { + executor.method_18858(() -> { inRunLater = true; try { runnable.run(); diff --git a/src/main/java/com/replaymod/replay/InputReplayTimer.java b/src/main/java/com/replaymod/replay/InputReplayTimer.java index 862c449f..93820f59 100644 --- a/src/main/java/com/replaymod/replay/InputReplayTimer.java +++ b/src/main/java/com/replaymod/replay/InputReplayTimer.java @@ -7,8 +7,11 @@ import com.replaymod.replay.camera.CameraEntity; import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.RenderTickCounter; +//#if MC>=11400 +import com.replaymod.core.ReplayMod; +//#endif + //#if MC>=11300 -import com.replaymod.core.versions.MCVer; import org.lwjgl.glfw.GLFW; //#else //$$ import net.minecraft.client.settings.KeyBinding; @@ -57,6 +60,10 @@ public class InputReplayTimer extends WrappedTimer { //$$ FML_BUS.post(new RunScheduledTasks()); //#endif + //#if MC>=11400 + ReplayMod.instance.executor.executeTaskQueue(); + //#endif + // If we are in a replay, we have to manually process key and mouse events as the // tick speed may vary or there may not be any ticks at all (when the replay is paused) if (mod.getReplayHandler() != null) {