diff --git a/gradle.properties b/gradle.properties index 310e2375..c14aae82 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,6 @@ org.gradle.daemon=false mcmt_ver=0.22.86-pre -mappings_ver=20200723-1.16.1 mappings_chan=snapshot # TODO: update to (func_234923_W_) @@ -13,6 +12,7 @@ mappings_chan=snapshot # 1.16.5 # 1.16.4-35.0.5 +mappings_ver=20200723-1.16.1 mc_ver=1.16.5 fg_ver=36.1.2 diff --git a/src/main/java/org/jmt/mcmt/MCMT.java b/src/main/java/org/jmt/mcmt/MCMT.java index 51e08a6e..09b1359f 100644 --- a/src/main/java/org/jmt/mcmt/MCMT.java +++ b/src/main/java/org/jmt/mcmt/MCMT.java @@ -1,5 +1,6 @@ package org.jmt.mcmt; +import java.time.Instant; import java.util.stream.Collectors; import org.apache.logging.log4j.LogManager; @@ -115,4 +116,16 @@ public static void registerEntities(RegistryEvent.Register> e) { } } + + public static void time(Runnable r) { + Instant start = Instant.now(); + r.run(); + LOGGER.info("Task finished, took " + Instant.now().minusMillis(start.toEpochMilli()).toEpochMilli() + " ms."); + } + + public static void time(Runnable r, String taskName) { + Instant start = Instant.now(); + r.run(); + LOGGER.info(taskName + " finished in " + Instant.now().minusMillis(start.toEpochMilli()).toEpochMilli() + " ms."); + } } diff --git a/src/main/java/org/jmt/mcmt/asmdest/ASMHookTerminator.java b/src/main/java/org/jmt/mcmt/asmdest/ASMHookTerminator.java index db510b1c..c747d0e0 100644 --- a/src/main/java/org/jmt/mcmt/asmdest/ASMHookTerminator.java +++ b/src/main/java/org/jmt/mcmt/asmdest/ASMHookTerminator.java @@ -1,15 +1,23 @@ package org.jmt.mcmt.asmdest; +import java.util.ArrayList; import java.util.Deque; +import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; +import java.util.StringJoiner; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory; import java.util.concurrent.ForkJoinWorkerThread; -import java.util.concurrent.Phaser; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BooleanSupplier; @@ -19,10 +27,10 @@ import org.jmt.mcmt.MCMT; import org.jmt.mcmt.commands.StatsCommand; import org.jmt.mcmt.config.GeneralConfig; +import org.jmt.mcmt.paralelised.GatedLock; import org.jmt.mcmt.serdes.SerDesHookTypes; import org.jmt.mcmt.serdes.SerDesRegistry; import org.jmt.mcmt.serdes.filter.ISerDesFilter; -import org.jmt.mcmt.serdes.pools.PostExecutePool; import net.minecraft.block.BlockEventData; import net.minecraft.entity.Entity; @@ -45,7 +53,7 @@ * So DON'T rename this file (Or there will be a lot of other work todo) * * Fun point: So because this is hooking into a lot of the stuff, be careful what you reference here - * I attempted to reference a function on {@link GeneralConfig} and it got VERY angery at me with a "class refuses to load" error + * I attempted to reference a function on {@link GeneralConfig} and it got VERY angry at me with a "class refuses to load" error * So remember that if you start getting class loading errors * * @@ -57,14 +65,27 @@ public class ASMHookTerminator { private static final Logger LOGGER = LogManager.getLogger(); + + // static Phaser phaser; + public static ConcurrentHashMap worldExecutionStack = new ConcurrentHashMap<>(); + public static ConcurrentHashMap entityExecutionStack = new ConcurrentHashMap<>(); + + public static Set tracerStack = null; - static Phaser p; - static ExecutorService ex; - static MinecraftServer mcs; + private static GatedLock stackLock = new GatedLock(); + public static ExecutorService exec; + static MinecraftServer mcServer; static AtomicBoolean isTicking = new AtomicBoolean(); static AtomicInteger threadID = new AtomicInteger(); + + // Statistics + public static AtomicInteger currentWorlds = new AtomicInteger(); + public static AtomicInteger currentEnts = new AtomicInteger(); + public static AtomicInteger currentTEs = new AtomicInteger(); + public static AtomicInteger currentEnvs = new AtomicInteger(); + public static void setupThreadpool(int parallelism) { threadID = new AtomicInteger(); final ClassLoader cl = MCMT.class.getClassLoader(); @@ -75,12 +96,13 @@ public static void setupThreadpool(int parallelism) { fjwt.setContextClassLoader(cl); return fjwt; }; - ex = new ForkJoinPool( + + exec = new ForkJoinPool( parallelism, - fjpf, - null, false); + fjpf, + null, false); } - + /** * Creates and sets up the thread pool */ @@ -88,44 +110,117 @@ public static void setupThreadpool(int parallelism) { // Must be static here due to class loading shenanagins setupThreadpool(4); } - + static Map> mcThreadTracker = new ConcurrentHashMap>(); - - // Statistics - public static AtomicInteger currentWorlds = new AtomicInteger(); - public static AtomicInteger currentEnts = new AtomicInteger(); - public static AtomicInteger currentTEs = new AtomicInteger(); - public static AtomicInteger currentEnvs = new AtomicInteger(); - - //Operation logging - public static Set currentTasks = ConcurrentHashMap.newKeySet(); - - + public static void regThread(String poolName, Thread thread) { mcThreadTracker.computeIfAbsent(poolName, s -> ConcurrentHashMap.newKeySet()).add(thread); } - + public static boolean isThreadPooled(String poolName, Thread t) { return mcThreadTracker.containsKey(poolName) && mcThreadTracker.get(poolName).contains(t); } - + public static boolean serverExecutionThreadPatch(MinecraftServer ms) { return isThreadPooled("MCMT", Thread.currentThread()); } - public static void preTick(MinecraftServer server) { - if (p != null) { - LOGGER.warn("Multiple servers?"); + // Add a Runnable to the execution stack + private static void execute(String taskName, Runnable task, ConcurrentHashMap stack) { + // ensure there is no accidental key collision + while (stack.containsKey(taskName)) taskName = taskName + "+"; + String finalTaskName = taskName; + // add a CompletableFuture to the execution stack that runs the given task + stack.put(finalTaskName, () -> { + task.run(); + stack.remove(finalTaskName); + }); + } + + private static void awaitCompletion(ConcurrentHashMap waitOn) { + if (waitOn.size() == 0) return; // avoid hefty operations if we don't need them + // don't re-execute if code is already running + if (stackLock.isLocked(waitOn)) { + stackLock.waitForUnlock(waitOn); return; - } else { - isTicking.set(true); - p = new Phaser(); - p.register(); - mcs = server; - StatsCommand.setServer(mcs); } + stackLock.lockOn(waitOn); + + if (GeneralConfig.opsTracing) + tracerStack = new HashSet<>(waitOn.keySet()); + + // loop + while (!waitOn.isEmpty()) { + // execute every queued tick + List> allTasks = new ArrayList<>(waitOn.size()); + for (Entry x : waitOn.entrySet()) { + allTasks.add(CompletableFuture.runAsync(x.getValue(), exec)); + } + // convert all outstanding ticks to one CompletableFuture to wait on + CompletableFuture tickSum = CompletableFuture.allOf(allTasks.toArray(new CompletableFuture[allTasks.size()])); + try { + // wait on all executing ticks for up to 1 second (20 ticks) + tickSum.get(1, TimeUnit.SECONDS); + } catch (TimeoutException e) { + LOGGER.error("This tick has taken longer than 1 second, investigating..."); + LOGGER.error("Tick status: " + (tickSum.isDone() ? "done" : "not done")); + LOGGER.error("Initial queue size: " + allTasks.size()); + + if (GeneralConfig.opsTracing) { + // get all ticks still in queue that were also in the starting queue + tracerStack.retainAll(waitOn.keySet()); + + LOGGER.error("Current stuck ticks in queue:"); + StringJoiner sj = new StringJoiner(", ", "[ ", " ]"); + for (String taskName : tracerStack) sj.add(taskName); + LOGGER.error(sj.toString()); + LOGGER.error("====="); + } + + LOGGER.error("Current queue:"); + StringJoiner sj = new StringJoiner(", ", "[ ", " ]"); + for (String taskName : waitOn.keySet()) sj.add(taskName); + LOGGER.error(sj.toString()); + + if (GeneralConfig.continueAfterStuckTick) { + LOGGER.fatal("CONTINUING AFTER STUCK TICK! I REALLY hope you have backups..."); + tickSum.cancel(true); // cancel combined CompletableFuture + } else { + LOGGER.error("Continuing to wait for tick to complete... (don't hold your breath)"); + try { + tickSum.get(); // wait for this tick to complete (but if we're here, it probably won't) + for (CompletableFuture i : allTasks) i.get(); + } catch (InterruptedException | ExecutionException e1) { + LOGGER.fatal("Failed to wait for tick: ", e1); + } + } + } catch (ExecutionException | InterruptedException e) { + LOGGER.fatal("Tick execution failed: ", e); + tickSum.cancel(true); + } + + // debug data for how long the tick took + // if (GeneralConfig.opsTracing) + // LOGGER.info("This tick took " + Instant.now().minusMillis(before.toEpochMilli()).toEpochMilli() + " ms."); + } + + if (waitOn.size() > 0) { + LOGGER.fatal("Execution stack was not empty before continuing to next tick! " + waitOn.size()); + + StringJoiner sj = new StringJoiner(", ", "[ ", " ]"); + for (String taskName : waitOn.keySet()) sj.add(taskName); + LOGGER.fatal(sj.toString()); + } + + stackLock.unlock(waitOn); } - + + public static void preTick(MinecraftServer server) { + isTicking.set(true); + mcServer = server; + StatsCommand.setServer(mcServer); + } + public static void callTick(ServerWorld serverworld, BooleanSupplier hasTimeLeft, MinecraftServer server) { if (GeneralConfig.disabled || GeneralConfig.disableWorld) { try { @@ -137,109 +232,87 @@ public static void callTick(ServerWorld serverworld, BooleanSupplier hasTimeLeft } return; } - if (mcs != server) { + + if (mcServer != server) { LOGGER.warn("Multiple servers?"); GeneralConfig.disabled = true; serverworld.tick(hasTimeLeft); net.minecraftforge.fml.hooks.BasicEventHooks.onPostWorldTick(serverworld); return; } else { - String taskName = null; - if (GeneralConfig.opsTracing) { - taskName = "WorldTick: " + serverworld.toString() + "@" + serverworld.hashCode(); - currentTasks.add(taskName); - } - String finalTaskName = taskName; - p.register(); - ex.execute(() -> { + String taskName = "WorldTick: " + serverworld.toString() + "@" + + // append world's dimension name to world tick task + /* 1.16.1 code; AKA the only thing that changed */ + serverworld.func_234923_W_().func_240901_a_().toString(); + /* */ + /* 1.15.2 code; AKA the only thing that changed + serverworld.getDimension().getType().getRegistryName().toString(); + /* */ + + execute(taskName, () -> { try { currentWorlds.incrementAndGet(); serverworld.tick(hasTimeLeft); - if (GeneralConfig.disableWorldPostTick) { - p.register(); - ex.execute(() -> { - try { - //ForkJoinPool.managedBlock( - // new RunnableManagedBlocker(() -> { - synchronized (net.minecraftforge.fml.hooks.BasicEventHooks.class) { - net.minecraftforge.fml.hooks.BasicEventHooks.onPostWorldTick(serverworld); - } - // })); - //} catch (InterruptedException e) { - // e.printStackTrace(); - } finally { - p.arriveAndDeregister(); - } - }); + if (!GeneralConfig.disableWorldPostTick) { + // execute world post-tick asynchronously + execute(taskName + "|PostTick", () -> { + // synchronized (net.minecraftforge.fml.hooks.BasicEventHooks.class) { + net.minecraftforge.fml.hooks.BasicEventHooks.onPostWorldTick(serverworld); + // } + }, worldExecutionStack); } else { net.minecraftforge.fml.hooks.BasicEventHooks.onPostWorldTick(serverworld); } } finally { - p.arriveAndDeregister(); currentWorlds.decrementAndGet(); - if (GeneralConfig.opsTracing) currentTasks.remove(finalTaskName); } - }); + }, worldExecutionStack); } - } - + public static void callEntityTick(Entity entityIn, ServerWorld serverworld) { if (GeneralConfig.disabled || GeneralConfig.disableEntity) { entityIn.tick(); return; } - String taskName = null; - if (GeneralConfig.opsTracing) { - taskName = "EntityTick: " + entityIn.toString() + "@" + entityIn.hashCode(); - currentTasks.add(taskName); - } - String finalTaskName = taskName; - p.register(); - ex.execute(() -> { + String taskName = "EntityTick: " + entityIn.toString() + "@" + entityIn.hashCode(); + Runnable r = () -> { try { - //currentEnts.incrementAndGet(); - //entityIn.tick(); - final ISerDesFilter filter = SerDesRegistry.getFilter(SerDesHookTypes.EntityTick, entityIn.getClass()); - currentTEs.incrementAndGet(); - if (filter != null) { - filter.serialise(entityIn::tick, entityIn, entityIn.getPosition(), serverworld, SerDesHookTypes.EntityTick); - } else { - entityIn.tick(); - } + currentEnts.incrementAndGet(); + awaitCompletion(worldExecutionStack); // force world ticks to complete first + entityIn.tick(); } finally { currentEnts.decrementAndGet(); - p.arriveAndDeregister(); - if (GeneralConfig.opsTracing) currentTasks.remove(finalTaskName); } - }); + }; + + final ISerDesFilter filter = SerDesRegistry.getFilter(SerDesHookTypes.EntityTick, entityIn.getClass()); + if (filter != null) { + filter.serialise(entityIn::tick, entityIn, entityIn.getPosition(), serverworld, task -> { + execute(taskName, r, entityExecutionStack); + }, SerDesHookTypes.EntityTick); + } else { + execute(taskName, r, entityExecutionStack); + } } - + public static void callTickEnvironment(ServerWorld world, Chunk chunk, int k, ServerChunkProvider scp) { if (GeneralConfig.disabled || GeneralConfig.disableEnvironment) { world.tickEnvironment(chunk, k); return; } - String taskName = null; - if (GeneralConfig.opsTracing) { - taskName = "EnvTick: " + chunk.toString() + "@" + chunk.hashCode(); - currentTasks.add(taskName); - } - String finalTaskName = taskName; - p.register(); - ex.execute(() -> { + String taskName = "EnvTick: " + chunk.toString() + "@" + chunk.hashCode(); + execute(taskName, () -> { try { currentEnvs.incrementAndGet(); world.tickEnvironment(chunk, k); } finally { currentEnvs.decrementAndGet(); - p.arriveAndDeregister(); - if (GeneralConfig.opsTracing) currentTasks.remove(finalTaskName); } - }); + }, worldExecutionStack); } - - public static boolean filterTE(ITickableTileEntity tte) { + + public static boolean filterTickableEntity(ITickableTileEntity tte) { boolean isLocking = false; if (GeneralConfig.teBlackList.contains(tte.getClass())) { isLocking = true; @@ -256,40 +329,34 @@ public static boolean filterTE(ITickableTileEntity tte) { } return isLocking; } - + public static void callTileEntityTick(ITickableTileEntity tte, World world) { if (GeneralConfig.disabled || GeneralConfig.disableTileEntity || !(world instanceof ServerWorld)) { tte.tick(); return; } - String taskName = null; - if (GeneralConfig.opsTracing) { - taskName = "TETick: " + tte.toString() + "@" + tte.hashCode(); - currentTasks.add(taskName); + String taskName = "TETick: " + tte.toString() + "@" + tte.hashCode(); + final ISerDesFilter filter = SerDesRegistry.getFilter(SerDesHookTypes.TETick, tte.getClass()); + if (filter != null) { + filter.serialise(tte::tick, tte, ((TileEntity)tte).getPos(), world, (task) -> { + execute(taskName, () -> { + try { + currentTEs.incrementAndGet(); + awaitCompletion(worldExecutionStack); // force world ticks to complete first + task.run(); + } finally { + currentTEs.decrementAndGet(); + } + }, entityExecutionStack); + }, SerDesHookTypes.TETick); + } else { + execute(taskName, () -> { + awaitCompletion(worldExecutionStack); // force world ticks to complete first + tte.tick(); + }, entityExecutionStack); } - p.register(); - String finalTaskName = taskName; - ex.execute(() -> { - try { - //final boolean doLock = filterTE(tte); - final ISerDesFilter filter = SerDesRegistry.getFilter(SerDesHookTypes.TETick, tte.getClass()); - currentTEs.incrementAndGet(); - if (filter != null) { - filter.serialise(tte::tick, tte, ((TileEntity)tte).getPos(), world, SerDesHookTypes.TETick); - } else { - tte.tick(); - } - } catch (Exception e) { - System.err.println("Exception ticking TE at " + ((TileEntity) tte).getPos()); - e.printStackTrace(); - } finally { - currentTEs.decrementAndGet(); - p.arriveAndDeregister(); - if (GeneralConfig.opsTracing) currentTasks.remove(finalTaskName); - } - }); } - + public static void sendQueuedBlockEvents(Deque d, ServerWorld sw) { Iterator bed = d.iterator(); while (bed.hasNext()) { @@ -308,26 +375,17 @@ public static void sendQueuedBlockEvents(Deque d, ServerWorld sw bed.remove(); } } - + public static void postTick(MinecraftServer server) { - if (mcs != server) { + if (mcServer != server) { LOGGER.warn("Multiple servers?"); return; } else { - p.arriveAndAwaitAdvance(); - isTicking.set(false); - p = null; - //PostExecute logic - Deque queue = PostExecutePool.POOL.getQueue(); - Iterator qi = queue.iterator(); - while (qi.hasNext()) { - Runnable r = qi.next(); - r.run(); - qi.remove(); - } + awaitCompletion(worldExecutionStack); // this should be empty, but run it just in case + awaitCompletion(entityExecutionStack); } } - + public static String populateCrashReport() { StringBuilder confInfo = new StringBuilder(); confInfo.append("\n"); @@ -348,18 +406,18 @@ public static String populateCrashReport() { //TODO expand on TE settings if (GeneralConfig.opsTracing) { confInfo.append("\t\t"); confInfo.append("-- Running Operations Begin -- "); confInfo.append("\n"); - for (String s : currentTasks) { + for (String s : entityExecutionStack.keySet()) { confInfo.append("\t\t"); confInfo.append("\t"); confInfo.append(s); confInfo.append("\n"); } confInfo.append("\t\t"); confInfo.append("-- Running Operations End -- "); confInfo.append("\n"); } return confInfo.toString(); } - + static { CrashReportExtender.registerCrashCallable("MCMT", ASMHookTerminator::populateCrashReport); } - + public static void fixSTL(ServerTickList stl) { LOGGER.debug("FixSTL Called"); stl.pendingTickListEntriesTreeSet.addAll(stl.pendingTickListEntriesHashSet); @@ -382,6 +440,6 @@ public static boolean shouldThreadChunks() { } } //End Debug Section - */ + */ } diff --git a/src/main/java/org/jmt/mcmt/asmdest/DebugHookTerminator.java b/src/main/java/org/jmt/mcmt/asmdest/ChunkRepairHookTerminator.java similarity index 93% rename from src/main/java/org/jmt/mcmt/asmdest/DebugHookTerminator.java rename to src/main/java/org/jmt/mcmt/asmdest/ChunkRepairHookTerminator.java index ff27d650..a215c204 100644 --- a/src/main/java/org/jmt/mcmt/asmdest/DebugHookTerminator.java +++ b/src/main/java/org/jmt/mcmt/asmdest/ChunkRepairHookTerminator.java @@ -31,14 +31,13 @@ import net.minecraft.world.server.ChunkHolder.IChunkLoadingError; import net.minecraft.world.server.ServerChunkProvider; -// TODO Should be renamed ChunkRepairHookTerminator (Note requres coremod edit) /** - * Handles chunk forcing in scenarios where world corruption has occured + * Handles chunk forcing in scenarios where world corruption has occurred * * @author jediminer543 * */ -public class DebugHookTerminator { +public class ChunkRepairHookTerminator { private static final Logger LOGGER = LogManager.getLogger(); @@ -62,20 +61,28 @@ public static boolean isBypassLoadTarget() { return bypassLoadTarget; } - public static void chunkLoadDrive(ServerChunkProvider.ChunkExecutor executor, BooleanSupplier isDone, ServerChunkProvider scp, - CompletableFuture> completableFuture, long chunkpos) { + public static void chunkLoadDrive( + ServerChunkProvider.ChunkExecutor executor, + BooleanSupplier isDone, + ServerChunkProvider scp, + CompletableFuture> completableFuture, + long chunkpos) { + if (!GeneralConfig.enableChunkTimeout) { bypassLoadTarget = false; executor.driveUntil(isDone); return; } + int failcount = 0; while (!isDone.getAsBoolean()) { + if (!executor.driveOne()) { + if(isDone.getAsBoolean()) { - break; + break; // Nothing more to execute } - // Nothing more to execute + if (failcount++ < GeneralConfig.timeoutCount) { Thread.yield(); LockSupport.parkNanos("THE END IS ~~NEVER~~ LOADING", 100000L); diff --git a/src/main/java/org/jmt/mcmt/commands/ConfigCommand.java b/src/main/java/org/jmt/mcmt/commands/ConfigCommand.java index 1cfd298b..5c8d9f28 100644 --- a/src/main/java/org/jmt/mcmt/commands/ConfigCommand.java +++ b/src/main/java/org/jmt/mcmt/commands/ConfigCommand.java @@ -182,7 +182,7 @@ public static LiteralArgumentBuilder registerConfig(LiteralArgume BlockPos bp = ((BlockRayTraceResult)rtr).getPos(); TileEntity te = cmdCtx.getSource().getWorld().getTileEntity(bp); if (te != null && te instanceof ITickableTileEntity) { - boolean willSerial = ASMHookTerminator.filterTE((ITickableTileEntity)te); + boolean willSerial = ASMHookTerminator.filterTickableEntity((ITickableTileEntity)te); message = new StringTextComponent("That TE " + (!willSerial ? "will" : "will not") + " tick fully parallelised"); cmdCtx.getSource().sendFeedback(message, true); return 1; diff --git a/src/main/java/org/jmt/mcmt/commands/StatsCommand.java b/src/main/java/org/jmt/mcmt/commands/StatsCommand.java index 00d11c91..debeb61f 100644 --- a/src/main/java/org/jmt/mcmt/commands/StatsCommand.java +++ b/src/main/java/org/jmt/mcmt/commands/StatsCommand.java @@ -147,19 +147,17 @@ public static void runDataThread() { maxThreads[currentPos] = 0; } int total = 0; - int worlds = ASMHookTerminator.currentWorlds.get(); + int worldTicks = ASMHookTerminator.worldExecutionStack.size(); maxWorlds[currentPos] = Math.max(maxWorlds[currentPos], - worlds); - int tes = ASMHookTerminator.currentTEs.get(); - maxTEs[currentPos] = Math.max(maxTEs[currentPos], tes); - int entities = ASMHookTerminator.currentEnts.get(); + worldTicks); + int environmentTicks = ASMHookTerminator.currentEnvs.get(); + int tileEntityTicks = ASMHookTerminator.currentTEs.get(); + maxTEs[currentPos] = Math.max(maxTEs[currentPos], tileEntityTicks); + int entityTicks = ASMHookTerminator.entityExecutionStack.size(); maxEntities[currentPos] = Math.max(maxEntities[currentPos], - entities); - int envs = ASMHookTerminator.currentEnvs.get(); - maxEnvs[currentPos] = Math.max(maxEnvs[currentPos], envs); - total = worlds+tes+entities+envs; + entityTicks); + total = worldTicks+tileEntityTicks+entityTicks+environmentTicks; maxThreads[currentPos] = Math.max(maxThreads[currentPos], total); - } if (mcs != null && !mcs.isServerRunning()) { doLogging = false; diff --git a/src/main/java/org/jmt/mcmt/config/GeneralConfig.java b/src/main/java/org/jmt/mcmt/config/GeneralConfig.java index f9798434..2d242ff8 100644 --- a/src/main/java/org/jmt/mcmt/config/GeneralConfig.java +++ b/src/main/java/org/jmt/mcmt/config/GeneralConfig.java @@ -81,6 +81,8 @@ public class GeneralConfig { public static boolean opsTracing; public static int logcap; + public static boolean continueAfterStuckTick; + //Forge stuff public static final GeneralConfigTemplate GENERAL; public static final ForgeConfigSpec GENERAL_SPEC; @@ -157,6 +159,8 @@ public static void bakeConfig() { opsTracing = GENERAL.opsTracing.get(); logcap = GENERAL.logcap.get(); + continueAfterStuckTick = GENERAL.continueAfterStuckTick.get(); + teWhiteList = ConcurrentHashMap.newKeySet();//new HashSet>(); teUnfoundWhiteList = new ArrayList(); GENERAL.teWhiteList.get().forEach(str -> { @@ -207,6 +211,8 @@ public static void saveConfig() { GENERAL.opsTracing.set(opsTracing); GENERAL.logcap.set(logcap); + GENERAL.continueAfterStuckTick.set(continueAfterStuckTick); + GENERAL.teWhiteList.get().clear(); GENERAL.teWhiteList.get().addAll(teUnfoundWhiteList); GENERAL.teWhiteList.get().addAll(teWhiteList.stream().map(clz -> clz.getName()).collect(Collectors.toList())); @@ -248,6 +254,8 @@ public static class GeneralConfigTemplate { public final BooleanValue opsTracing; public final IntValue logcap; + public final BooleanValue continueAfterStuckTick; + public GeneralConfigTemplate(ForgeConfigSpec.Builder builder) { builder.push("general"); disabled = builder @@ -329,7 +337,7 @@ public GeneralConfigTemplate(ForgeConfigSpec.Builder builder) { .comment("Simply returns a new empty chunk instead of a re-generating fully") .define("enableBlankReturn", false); timeoutCount = builder - .comment("Ammount of workless iterations to wait before declaring a chunk load attempt as timed out\n" + .comment("Amount of workless iterations to wait before declaring a chunk load attempt as timed out\n" +"This is in ~100us itterations (plus minus yield time) so timeout >= timeoutCount*100us") .defineInRange("timeoutCount", 5000, 500, 500000); builder.pop(); @@ -344,6 +352,12 @@ public GeneralConfigTemplate(ForgeConfigSpec.Builder builder) { logcap = builder .comment("Maximum time between MCMT presence alerts in 10ms steps") .defineInRange("logcap", 720000, 15000, Integer.MAX_VALUE); + builder.pop(); + builder.push("continueAfterStuckTick"); + builder.comment("Allows continuation after a stuck tick. " + + "This is HIGHLY unstable, so don't enable it unless you know what you're doing and have backups."); + continueAfterStuckTick = builder.define("continueAfterStuckTick", false); + builder.pop(); } } diff --git a/src/main/java/org/jmt/mcmt/paralelised/ConcurrentArrayList.java b/src/main/java/org/jmt/mcmt/paralelised/ConcurrentArrayList.java new file mode 100644 index 00000000..0280e2d8 --- /dev/null +++ b/src/main/java/org/jmt/mcmt/paralelised/ConcurrentArrayList.java @@ -0,0 +1,458 @@ +package org.jmt.mcmt.paralelised; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.ListIterator; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Consumer; +import java.util.function.IntFunction; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; + +/** + * @author Hunter + * + * @param the type of elements in this list + */ +public class ConcurrentArrayList extends ArrayList { + private static final long serialVersionUID = -6104897338204207229L; + private ReadWriteLock lock = new ReentrantReadWriteLock(); + + public ConcurrentArrayList() { + super(); + } + + public ConcurrentArrayList(Collection arg0) { + super(arg0); + } + + public ConcurrentArrayList(int capacity) { + super(capacity); + } + + @Override + public boolean containsAll(Collection c) { + lock.readLock().lock(); + try { + return super.containsAll(c); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public boolean contains(Object o) { + lock.readLock().lock(); + try { + return super.contains(o); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public boolean equals(Object o) { + lock.readLock().lock(); + try { + return super.equals(o); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public void forEach(Consumer action) { + lock.readLock().lock(); + try { + super.forEach(action); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public E get(int index) { + lock.readLock().lock(); + try { + return super.get(index); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public int hashCode() { + lock.readLock().lock(); + try { + return super.hashCode(); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public int indexOf(Object o) { + lock.readLock().lock(); + try { + return super.indexOf(o); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public boolean isEmpty() { + return super.isEmpty(); // Immediate return doesn't require a read lock + } + + @Override + public int lastIndexOf(Object o) { + lock.readLock().lock(); + try { + return super.lastIndexOf(o); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public int size() { + lock.readLock().lock(); + try { + return super.size(); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public Object[] toArray() { + lock.readLock().lock(); + try { + return super.toArray(); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public T[] toArray(IntFunction generator) { + lock.readLock().lock(); + try { + return super.toArray(generator); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public T[] toArray(T[] a) { + lock.readLock().lock(); + try { + return super.toArray(a); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public String toString() { + lock.readLock().lock(); + try { + return super.toString(); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public boolean add(E e) { + lock.writeLock().lock(); + try { + return super.add(e); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public void add(int index, E element) { + lock.writeLock().lock(); + try { + super.add(index, element); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public boolean addAll(Collection c) { + lock.writeLock().lock(); + try { + return super.addAll(c); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public boolean addAll(int index, Collection c) { + lock.writeLock().lock(); + try { + return super.addAll(index, c); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public void clear() { + lock.writeLock().lock(); + try { + super.clear(); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public Object clone() { + lock.readLock().lock(); + try { + return super.clone(); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public void ensureCapacity(int minCapacity) { + lock.writeLock().lock(); + try { + super.ensureCapacity(minCapacity); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public E remove(int index) { + lock.writeLock().lock(); + try { + return super.remove(index); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public void trimToSize() { + lock.writeLock().lock(); + try { + super.trimToSize(); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public boolean remove(Object o) { + lock.writeLock().lock(); + try { + return super.remove(o); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public boolean removeAll(Collection c) { + lock.writeLock().lock(); + try { + return super.removeAll(c); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public boolean removeIf(Predicate filter) { + lock.writeLock().lock(); + try { + return super.removeIf(filter); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + protected void removeRange(int fromIndex, int toIndex) { + lock.writeLock().lock(); + try { + super.removeRange(fromIndex, toIndex); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public void replaceAll(UnaryOperator operator) { + lock.writeLock().lock(); + try { + super.replaceAll(operator); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public boolean retainAll(Collection c) { + lock.writeLock().lock(); + try { + return super.retainAll(c); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public E set(int index, E element) { + lock.writeLock().lock(); + try { + return super.set(index, element); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public void sort(Comparator c) { + lock.writeLock().lock(); + try { + super.sort(c); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public Iterator iterator() { + return new CopiedIterator(this); + } + + @Override + public ListIterator listIterator() { + return new CopiedListIterator(this); + } + + @Override + public ListIterator listIterator(int index) { + return new CopiedListIterator(this, index); + } + + /** + * Creates a snapshot of the Collection to iterate over. Useful for concurrent access to the entirety of a Collection. + *

+ * Caveats:

+ * Creates a copy of the Collection as an array, so uses more memory + * (but not 100% more, just for object references) + *

+ * Any changes to the objects in the Iterator will be reflected in the originating Collection. + * + * @author hunterh + */ + private class CopiedIterator implements Iterator { + private Object[] internal; + private int pointer; + + public CopiedIterator(Collection c) { + this.pointer = 0; + this.internal = c.toArray(); + } + + @Override + public boolean hasNext() { + return internal.length > pointer; + } + + @SuppressWarnings("unchecked") + @Override + public E next() { + return (E) this.internal[this.pointer++]; + } + } + + /** + * Creates a snapshot of the Collection to iterate over. Useful for concurrent access to the entirety of a Collection. + *

+ * Caveats:

+ * Creates a copy of the Collection as a {@link ConcurrentArrayList}, so uses more memory + * (but not 100% more, just for object references) + *

+ * Any changes to the objects in the Iterator will be reflected in the originating Collection. + * + * @author hunterh + */ + private class CopiedListIterator implements ListIterator { + private ConcurrentArrayList backing; + private int pointer; + + public CopiedListIterator(Collection data) { + backing = new ConcurrentArrayList<>(data); + pointer = 0; + } + + public CopiedListIterator(Collection data, int start) { + backing = new ConcurrentArrayList<>(data); + pointer = start; + } + + @Override + public boolean hasNext() { + return backing.size() > pointer; + } + + @Override + public E next() { + return backing.get(pointer++); + } + + @Override + public boolean hasPrevious() { + return pointer > 0; + } + + @Override + public E previous() { + return backing.get(pointer--); + } + + @Override + public int nextIndex() { + return hasNext() ? pointer + 1 : backing.size(); + } + + @Override + public int previousIndex() { + return hasPrevious() ? pointer - 1: -1; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("CopiedListIterator does not support remove()"); + } + + @Override + public void set(E e) { + throw new UnsupportedOperationException("CopiedListIterator does not support set()"); + } + + @Override + public void add(E e) { + throw new UnsupportedOperationException("CopiedListIterator does not support add()"); + } + } +} diff --git a/src/main/java/org/jmt/mcmt/paralelised/GatedLock.java b/src/main/java/org/jmt/mcmt/paralelised/GatedLock.java new file mode 100644 index 00000000..e6d20c63 --- /dev/null +++ b/src/main/java/org/jmt/mcmt/paralelised/GatedLock.java @@ -0,0 +1,29 @@ +package org.jmt.mcmt.paralelised; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; + +public class GatedLock { + private ConcurrentHashMap locks = new ConcurrentHashMap<>(); + + public boolean isLocked(Object on) { + if (!locks.containsKey(on)) return false; + return locks.get(on).isLocked(); + } + + public void lockOn(Object on) { + locks.computeIfAbsent(on, (x) -> new ReentrantLock()).lock(); + return; + } + + public void waitForUnlock(Object on) { + if (!this.isLocked(on)) return; + locks.computeIfAbsent(on, (x) -> new ReentrantLock()).lock(); + locks.get(on).unlock(); + } + + public void unlock(Object on) { + if (!this.locks.contains(on)) return; + locks.get(on).unlock(); + } +} diff --git a/src/main/java/org/jmt/mcmt/paralelised/TickTask.java b/src/main/java/org/jmt/mcmt/paralelised/TickTask.java new file mode 100644 index 00000000..c735bbc5 --- /dev/null +++ b/src/main/java/org/jmt/mcmt/paralelised/TickTask.java @@ -0,0 +1,21 @@ +package org.jmt.mcmt.paralelised; + +import java.util.concurrent.CompletableFuture; + +public class TickTask { + private String name; + private CompletableFuture future; + + public TickTask(String name, CompletableFuture future) { + this.name = name; + this.future = future; + } + + public String getName() { + return name; + } + + public CompletableFuture getFuture() { + return future; + } +} diff --git a/src/main/java/org/jmt/mcmt/serdes/SerDesRegistry.java b/src/main/java/org/jmt/mcmt/serdes/SerDesRegistry.java index 1dbdb6c8..99fdaba6 100644 --- a/src/main/java/org/jmt/mcmt/serdes/SerDesRegistry.java +++ b/src/main/java/org/jmt/mcmt/serdes/SerDesRegistry.java @@ -3,12 +3,14 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; +import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; @@ -26,11 +28,9 @@ import org.jmt.mcmt.serdes.filter.VanillaFilter; import org.jmt.mcmt.serdes.pools.ChunkLockPool; import org.jmt.mcmt.serdes.pools.ISerDesPool; -import org.jmt.mcmt.serdes.pools.PostExecutePool; -import org.jmt.mcmt.serdes.pools.SingleExecutionPool; import org.jmt.mcmt.serdes.pools.ISerDesPool.ISerDesOptions; - -import com.google.common.collect.Lists; +import org.jmt.mcmt.serdes.pools.MainThreadExecutionPool; +import org.jmt.mcmt.serdes.pools.UnaryExecutionPool; import net.minecraft.tileentity.PistonTileEntity; import net.minecraft.util.math.BlockPos; @@ -46,15 +46,15 @@ public class SerDesRegistry { private static final Logger LOGGER = LogManager.getLogger(); private static final Map, ISerDesFilter> EMPTYMAP = new ConcurrentHashMap, ISerDesFilter>(); private static final Set> EMPTYSET = ConcurrentHashMap.newKeySet(); - + static Map, ISerDesFilter>> optimisedLookup; static Map>> whitelist; static Set> unknown; - + static ArrayList filters; - + static Set hookTypes; - + static { filters = new ArrayList(); optimisedLookup = new ConcurrentHashMap,ISerDesFilter>>(); @@ -66,16 +66,16 @@ public class SerDesRegistry { hookTypes.add(isdh); } } - + private static final ISerDesFilter DEFAULT_FILTER = new DefaultFilter(); - + public static void init() { SerDesConfig.loadConfigs(); initPools(); initFilters(); initLookup(); } - + public static void initFilters() { filters.clear(); // High Priority (I.e. non overridable) @@ -94,12 +94,12 @@ public static void initFilters() { sdf.init(); } } - + public static void initLookup() { optimisedLookup.clear(); for (ISerDesFilter f : filters) { - Set> rawTgt = f.getTargets(); - Set> rawWl = f.getWhitelist(); + Set> rawTgt = f.getFiltered(); + Set> rawWl = f.getAlwaysAsync(); if (rawTgt == null) rawTgt = ConcurrentHashMap.newKeySet(); if (rawWl == null) rawWl = ConcurrentHashMap.newKeySet(); Map>> whitelist = group(rawWl); @@ -117,7 +117,7 @@ public static void initLookup() { } } } - + public static Map>> group(Set> set) { Map>> out = new ConcurrentHashMap>>(); for (Class i : set) { @@ -129,24 +129,24 @@ public static Map>> group(Set> set) { } return out; } - + public static ISerDesFilter getFilter(ISerDesHookType isdh, Class clazz) { if (whitelist.getOrDefault(isdh, EMPTYSET).contains(clazz)) { return null; } return optimisedLookup.getOrDefault(isdh, EMPTYMAP).getOrDefault(clazz, DEFAULT_FILTER); } - + static Map registry = new ConcurrentHashMap(); - + public static ISerDesPool getPool(String name) { return registry.get(name); } - + public static ISerDesPool getOrCreatePool(String name, Function source) { return registry.computeIfAbsent(name, source); } - + public static ISerDesPool getOrCreatePool(String name, Supplier source) { return getOrCreatePool(name, i->{ ISerDesPool out = source.get(); @@ -154,17 +154,18 @@ public static ISerDesPool getOrCreatePool(String name, Supplier sou return out; }); } - + public static boolean removeFromWhitelist(ISerDesHookType isdh, Class c) { return whitelist.getOrDefault(isdh, EMPTYSET).remove(c); } - + public static void initPools() { registry.clear(); // HARDCODED DEFAULTS - getOrCreatePool("LEGACY", ChunkLockPool::new); - getOrCreatePool("SINGLE", SingleExecutionPool::new); - getOrCreatePool("POST", ()->PostExecutePool.POOL); + ISerDesPool chunkLockPool = getOrCreatePool("CHUNK_LOCK", ChunkLockPool::new); + registry.put("LEGACY", chunkLockPool); // copy the CHUNK_LOCK pool to the LEGACY entry so they share the same pool object + getOrCreatePool("MAIN", MainThreadExecutionPool::new); + getOrCreatePool("UNARY", UnaryExecutionPool::new); // LOADED FROM CONFIG List pcl = SerDesConfig.getPools(); if (pcl != null) for (PoolConfig pc : pcl) { @@ -195,7 +196,7 @@ public static void initPools() { } } } - + public static class DefaultFilter implements ISerDesFilter { //TODO make not shit @@ -216,10 +217,10 @@ public static boolean filterTE(Object tte) { } return isLocking; } - + ISerDesPool clp; ISerDesOptions config; - + @Override public void init() { clp = SerDesRegistry.getOrCreatePool("LEGACY", ChunkLockPool::new); @@ -227,9 +228,10 @@ public void init() { cfg.put("range", "1"); config = clp.compileOptions(cfg); } - + @Override - public void serialise(Runnable task, Object obj, BlockPos bp, World w, ISerDesHookType hookType) { + public void serialise(Runnable task, Object obj, BlockPos bp, World w, + Consumer multi, ISerDesHookType hookType) { if (!unknown.contains(obj.getClass())) { ClassMode mode = ClassMode.UNKNOWN; for (ISerDesFilter isdf : filters) { @@ -237,47 +239,45 @@ public void serialise(Runnable task, Object obj, BlockPos bp, World w, ISerDesHo if (cm.compareTo(mode) < 0) { mode = cm; } - if (mode == ClassMode.BLACKLIST) { + if (mode == ClassMode.FILTERED) { optimisedLookup.computeIfAbsent(hookType, i->new ConcurrentHashMap, ISerDesFilter>()) - .put(obj.getClass(), isdf); - isdf.serialise(task, obj, bp, w, hookType); + .put(obj.getClass(), isdf); + isdf.serialise(task, obj, bp, w, multi, hookType); return; } } - if (mode == ClassMode.WHITELIST) { + if (mode == ClassMode.ALWAYS_ASYNC) { whitelist.computeIfAbsent(hookType, k->ConcurrentHashMap.newKeySet()) - .add(obj.getClass()); - task.run(); // Whitelist = run on thread + .add(obj.getClass()); + multi.accept(task); // Whitelist = run on thread return; } unknown.add(obj.getClass()); } // TODO legacy behaviour please fix if (hookType.equals(SerDesHookTypes.TETick) && filterTE(obj)) { - clp.serialise(task, obj, bp, w, config); + clp.serialise(task, obj, bp, w, multi, config); } else { - try { - task.run(); - } catch (Exception e) { - LOGGER.error("Exception running " + obj.getClass().getName() + " asynchronusly", e); - LOGGER.error("Adding " + obj.getClass().getName() + " to blacklist."); - SerDesConfig.createFilterConfig( - "auto-" + obj.getClass().getName(), - 10, - Lists.newArrayList(), - Lists.newArrayList(obj.getClass().getName()), - null - ); - - AutoFilter.singleton().addClassToBlacklist(obj.getClass()); - // TODO: this could leave a tick in an incomplete state. should the full exception be thrown? - if (e instanceof RuntimeException) throw e; - } + multi.accept(() -> { + try { + task.run(); + } catch (Exception e) { + LOGGER.error("Exception running " + obj.getClass().getName() + " asynchronusly", e); + removeFromWhitelist(hookType, obj.getClass()); + // check to see if the UNARY pool is a better option for the generated filter + if (e instanceof ConcurrentModificationException) + AutoFilter.singleton().addClassToBlacklist(obj.getClass(), "UNARY"); + else + // default to CHUNK_LOCK pool (acceptable for most cases) + AutoFilter.singleton().addClassToBlacklist(obj.getClass()); + // TODO: this could leave a tick in an incomplete state. should the full exception be thrown? + if (e instanceof RuntimeException) throw e; + throw new RuntimeException(e); + } + }); } } - - } } diff --git a/src/main/java/org/jmt/mcmt/serdes/filter/AutoFilter.java b/src/main/java/org/jmt/mcmt/serdes/filter/AutoFilter.java index b655a1c7..443bf945 100644 --- a/src/main/java/org/jmt/mcmt/serdes/filter/AutoFilter.java +++ b/src/main/java/org/jmt/mcmt/serdes/filter/AutoFilter.java @@ -2,11 +2,16 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; +import org.apache.logging.log4j.LogManager; +import org.jmt.mcmt.config.SerDesConfig; import org.jmt.mcmt.serdes.ISerDesHookType; import org.jmt.mcmt.serdes.SerDesRegistry; -import org.jmt.mcmt.serdes.pools.ChunkLockPool; import org.jmt.mcmt.serdes.pools.ISerDesPool; +import org.jmt.mcmt.serdes.pools.MainThreadExecutionPool; + +import com.google.common.collect.Lists; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; @@ -18,10 +23,11 @@ * (https://opensource.org/licenses/BSD-2-Clause) */ public class AutoFilter implements ISerDesFilter { + private static org.apache.logging.log4j.Logger LOGGER = LogManager.getLogger(); private static AutoFilter SINGLETON; private ISerDesPool pool; - private Set> blacklist = ConcurrentHashMap.newKeySet(); + private Set> filtered = ConcurrentHashMap.newKeySet(); public static AutoFilter singleton() { if (SINGLETON == null) SINGLETON = new AutoFilter(); @@ -30,17 +36,18 @@ public static AutoFilter singleton() { @Override public void init() { - pool = SerDesRegistry.getOrCreatePool("AUTO", ChunkLockPool::new); + pool = SerDesRegistry.getOrCreatePool("MAIN", MainThreadExecutionPool::new); } @Override - public void serialise(Runnable task, Object obj, BlockPos bp, World w, ISerDesHookType hookType) { - pool.serialise(task, obj, bp, w, null); + public void serialise(Runnable task, Object obj, BlockPos bp, World w, + Consumer multi, ISerDesHookType hookType) { + pool.serialise(task, obj, bp, w, multi, null); } @Override - public Set> getTargets() { - return blacklist; + public Set> getFiltered() { + return filtered; } @Override @@ -49,6 +56,18 @@ public ClassMode getModeOnline(Class c) { } public void addClassToBlacklist(Class c) { - blacklist.add(c); + addClassToBlacklist(c, "CHUNK_LOCK"); + } + + public void addClassToBlacklist(Class c, String pool) { + LOGGER.error("Adding " + c.getName() + " to blacklist."); + SerDesConfig.createFilterConfig( + "auto-" + c.getName(), + 10, + Lists.newArrayList(), + Lists.newArrayList(c.getName()), + pool + ); + filtered.add(c); } } diff --git a/src/main/java/org/jmt/mcmt/serdes/filter/GenericConfigFilter.java b/src/main/java/org/jmt/mcmt/serdes/filter/GenericConfigFilter.java index f40aad00..94a68ca7 100644 --- a/src/main/java/org/jmt/mcmt/serdes/filter/GenericConfigFilter.java +++ b/src/main/java/org/jmt/mcmt/serdes/filter/GenericConfigFilter.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; import java.util.regex.Pattern; import org.jmt.mcmt.config.SerDesConfig; @@ -71,12 +72,12 @@ public void init() { } @Override - public Set> getWhitelist() { + public Set> getAlwaysAsync() { return whitelist; } @Override - public Set> getTargets() { + public Set> getFiltered() { return blacklist; } @@ -84,20 +85,21 @@ public Set> getTargets() { public ClassMode getModeOnline(Class c) { if (regexBlacklist != null) { if (regexBlacklist.matcher(c.getName()).find()) { - return ClassMode.BLACKLIST; + return ClassMode.FILTERED; } } if (regexWhitelist != null) { if (regexWhitelist.matcher(c.getName()).find()) { - return ClassMode.WHITELIST; + return ClassMode.ALWAYS_ASYNC; } } return ClassMode.UNKNOWN; } @Override - public void serialise(Runnable task, Object obj, BlockPos bp, World w, ISerDesHookType hookType) { - primePool.serialise(task, hookType, bp, w, primeOpts); + public void serialise(Runnable task, Object obj, BlockPos bp, World w, + Consumer executeMultithreaded, ISerDesHookType hookType) { + primePool.serialise(task, hookType, bp, w, executeMultithreaded, primeOpts); } } diff --git a/src/main/java/org/jmt/mcmt/serdes/filter/ISerDesFilter.java b/src/main/java/org/jmt/mcmt/serdes/filter/ISerDesFilter.java index 5a2011d8..cb459cd8 100644 --- a/src/main/java/org/jmt/mcmt/serdes/filter/ISerDesFilter.java +++ b/src/main/java/org/jmt/mcmt/serdes/filter/ISerDesFilter.java @@ -1,6 +1,7 @@ package org.jmt.mcmt.serdes.filter; import java.util.Set; +import java.util.function.Consumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -12,16 +13,17 @@ public interface ISerDesFilter { - public void serialise(Runnable task, Object obj, BlockPos bp, World w, ISerDesHookType hookType); + public void serialise(Runnable task, Object obj, BlockPos bp, World w, + Consumer executeMultithreaded, ISerDesHookType hookType); @Nullable - public default Set> getTargets() { + public default Set> getFiltered() { return null; } /** * Perform initialisation; this may include optimisation steps like looking up - * pools pre-emptively, generating pook configs, etc. + * pools pre-emptively, generating pool configs, etc. * * As such it is invoked after pools are initialised */ @@ -30,13 +32,13 @@ public default void init() { } @Nullable - public default Set> getWhitelist() { + public default Set> getAlwaysAsync() { return null; } public static enum ClassMode { - BLACKLIST, - WHITELIST, + FILTERED, + ALWAYS_ASYNC, UNKNOWN; } diff --git a/src/main/java/org/jmt/mcmt/serdes/filter/LegacyFilter.java b/src/main/java/org/jmt/mcmt/serdes/filter/LegacyFilter.java index 99beebde..f45b451f 100644 --- a/src/main/java/org/jmt/mcmt/serdes/filter/LegacyFilter.java +++ b/src/main/java/org/jmt/mcmt/serdes/filter/LegacyFilter.java @@ -3,6 +3,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; import org.jmt.mcmt.config.GeneralConfig; import org.jmt.mcmt.serdes.ISerDesHookType; @@ -28,17 +29,18 @@ public void init() { } @Override - public void serialise(Runnable task, Object obj, BlockPos bp, World w, ISerDesHookType hookType) { - clp.serialise(task, obj, bp, w, config); + public void serialise(Runnable task, Object obj, BlockPos bp, World w, + Consumer executeMultithreaded, ISerDesHookType hookType) { + clp.serialise(task, obj, bp, w, executeMultithreaded, config); } @Override - public Set> getTargets() { + public Set> getFiltered() { return GeneralConfig.teBlackList; } @Override - public Set> getWhitelist() { + public Set> getAlwaysAsync() { return GeneralConfig.teWhiteList; } diff --git a/src/main/java/org/jmt/mcmt/serdes/filter/PistonFilter.java b/src/main/java/org/jmt/mcmt/serdes/filter/PistonFilter.java index 0b493f46..86c96752 100644 --- a/src/main/java/org/jmt/mcmt/serdes/filter/PistonFilter.java +++ b/src/main/java/org/jmt/mcmt/serdes/filter/PistonFilter.java @@ -4,6 +4,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; import org.jmt.mcmt.serdes.ISerDesHookType; import org.jmt.mcmt.serdes.SerDesRegistry; @@ -30,12 +31,13 @@ public void init() { } @Override - public void serialise(Runnable task, Object obj, BlockPos bp, World w, ISerDesHookType hookType) { - clp.serialise(task, obj, bp, w, config); + public void serialise(Runnable task, Object obj, BlockPos bp, World w, + Consumer executeMultithreaded, ISerDesHookType hookType) { + clp.serialise(task, obj, bp, w, executeMultithreaded, config); } @Override - public Set> getTargets() { + public Set> getFiltered() { Set> out = new HashSet>(); out.add(PistonTileEntity.class); return out; diff --git a/src/main/java/org/jmt/mcmt/serdes/filter/VanillaFilter.java b/src/main/java/org/jmt/mcmt/serdes/filter/VanillaFilter.java index 50769ac0..e77c533e 100644 --- a/src/main/java/org/jmt/mcmt/serdes/filter/VanillaFilter.java +++ b/src/main/java/org/jmt/mcmt/serdes/filter/VanillaFilter.java @@ -1,5 +1,7 @@ package org.jmt.mcmt.serdes.filter; +import java.util.function.Consumer; + import org.jmt.mcmt.serdes.ISerDesHookType; import net.minecraft.tileentity.PistonTileEntity; @@ -9,14 +11,15 @@ public class VanillaFilter implements ISerDesFilter { @Override - public void serialise(Runnable task, Object obj, BlockPos bp, World w, ISerDesHookType hookType) { - task.run(); + public void serialise(Runnable task, Object obj, BlockPos bp, World w, + Consumer executeMultithreaded, ISerDesHookType hookType) { + executeMultithreaded.accept(task); } @Override public ClassMode getModeOnline(Class c) { if (c.getName().startsWith("net.minecraft") && !c.equals(PistonTileEntity.class)) { - return ClassMode.WHITELIST; + return ClassMode.ALWAYS_ASYNC; } return ClassMode.UNKNOWN; } diff --git a/src/main/java/org/jmt/mcmt/serdes/pools/ChunkLockPool.java b/src/main/java/org/jmt/mcmt/serdes/pools/ChunkLockPool.java index d233ad54..a79bd14b 100644 --- a/src/main/java/org/jmt/mcmt/serdes/pools/ChunkLockPool.java +++ b/src/main/java/org/jmt/mcmt/serdes/pools/ChunkLockPool.java @@ -1,9 +1,13 @@ package org.jmt.mcmt.serdes.pools; +import java.util.function.Consumer; + import javax.annotation.Nullable; import org.jmt.mcmt.paralelised.ChunkLock; +import org.jmt.mcmt.serdes.filter.AutoFilter; +import net.minecraft.entity.Entity; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; @@ -11,27 +15,32 @@ public class ChunkLockPool implements ISerDesPool { public class CLPOptions implements ISerDesOptions { int range; - + public int getRange() { return range; }; } - + ChunkLock cl = new ChunkLock(); - + public ChunkLockPool() { - + } - + @Override - public void serialise(Runnable task, Object o, BlockPos bp, World w, @Nullable ISerDesOptions options) { - int range = 1; - if (options instanceof CLPOptions) { - range = ((CLPOptions) options).getRange(); - } - long[] locks = cl.lock(bp, range); - try { - task.run(); - } finally { - cl.unlock(locks); - } + public void serialise(Runnable task, Object o, BlockPos bp, World w, + Consumer executeMultithreaded, @Nullable ISerDesOptions options) { + executeMultithreaded.accept(() -> { + int range = 1; + if (options instanceof CLPOptions) { + range = ((CLPOptions) options).getRange(); + } + long[] locks = cl.lock(bp, range); + try { + task.run(); + } catch (Exception e) { + if (o instanceof Entity) AutoFilter.singleton().addClassToBlacklist(o.getClass()); + } finally { + cl.unlock(locks); + } + }); } } diff --git a/src/main/java/org/jmt/mcmt/serdes/pools/ISerDesPool.java b/src/main/java/org/jmt/mcmt/serdes/pools/ISerDesPool.java index d6cbf35d..47d67bdf 100644 --- a/src/main/java/org/jmt/mcmt/serdes/pools/ISerDesPool.java +++ b/src/main/java/org/jmt/mcmt/serdes/pools/ISerDesPool.java @@ -1,6 +1,7 @@ package org.jmt.mcmt.serdes.pools; import java.util.Map; +import java.util.function.Consumer; import javax.annotation.Nullable; @@ -11,7 +12,8 @@ public interface ISerDesPool { public interface ISerDesOptions {} - public void serialise(Runnable task, Object o, BlockPos bp, World w, @Nullable ISerDesOptions options); + public void serialise(Runnable task, Object o, BlockPos bp, World w, + Consumer executeMultithreaded, @Nullable ISerDesOptions options); public default ISerDesOptions compileOptions(Map config) { return null; diff --git a/src/main/java/org/jmt/mcmt/serdes/pools/MainThreadExecutionPool.java b/src/main/java/org/jmt/mcmt/serdes/pools/MainThreadExecutionPool.java new file mode 100644 index 00000000..5206c055 --- /dev/null +++ b/src/main/java/org/jmt/mcmt/serdes/pools/MainThreadExecutionPool.java @@ -0,0 +1,15 @@ +package org.jmt.mcmt.serdes.pools; + +import java.util.function.Consumer; + +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public class MainThreadExecutionPool implements ISerDesPool { + + @Override + public void serialise(Runnable task, Object o, BlockPos bp, World w, Consumer executeMultithreaded, + ISerDesOptions options) { + task.run(); + } +} diff --git a/src/main/java/org/jmt/mcmt/serdes/pools/PostExecutePool.java b/src/main/java/org/jmt/mcmt/serdes/pools/PostExecutePool.java index 03bc9144..a7a7aea7 100644 --- a/src/main/java/org/jmt/mcmt/serdes/pools/PostExecutePool.java +++ b/src/main/java/org/jmt/mcmt/serdes/pools/PostExecutePool.java @@ -2,6 +2,7 @@ import java.util.Deque; import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.function.Consumer; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; @@ -15,13 +16,11 @@ private PostExecutePool() {} Deque runnables = new ConcurrentLinkedDeque(); @Override - public void serialise(Runnable task, Object o, BlockPos bp, World w, ISerDesOptions options) { + public void serialise(Runnable task, Object o, BlockPos bp, World w, Consumer executeMultithreaded, ISerDesOptions options) { runnables.add(task); } public Deque getQueue() { return runnables; } - - } diff --git a/src/main/java/org/jmt/mcmt/serdes/pools/SingleExecutionPool.java b/src/main/java/org/jmt/mcmt/serdes/pools/SingleExecutionPool.java deleted file mode 100644 index 08ea147c..00000000 --- a/src/main/java/org/jmt/mcmt/serdes/pools/SingleExecutionPool.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.jmt.mcmt.serdes.pools; - -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -import javax.annotation.Nullable; - -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; - -public class SingleExecutionPool implements ISerDesPool { - - private Lock l = new ReentrantLock(); - - @Override - public void serialise(Runnable task, Object o, BlockPos bp, World w, @Nullable ISerDesOptions options) { - try { - l.lock(); - task.run(); - } finally { - l.unlock(); - } - } - -} diff --git a/src/main/java/org/jmt/mcmt/serdes/pools/UnaryExecutionPool.java b/src/main/java/org/jmt/mcmt/serdes/pools/UnaryExecutionPool.java new file mode 100644 index 00000000..79e193cf --- /dev/null +++ b/src/main/java/org/jmt/mcmt/serdes/pools/UnaryExecutionPool.java @@ -0,0 +1,34 @@ +package org.jmt.mcmt.serdes.pools; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; + +import javax.annotation.Nullable; + +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +/** + * Run one-at-a-time. + */ +public class UnaryExecutionPool implements ISerDesPool { + + private Lock l = new ReentrantLock(); + + @Override + public void serialise(Runnable task, Object o, BlockPos bp, World w, + Consumer executeMultithreaded, @Nullable ISerDesOptions options) { + // Push to the executeMultithreaded as it's allowed to execute off main thread, just not more than one at a time + // The idea for this pool is global stuff like enderchests/tanks or equiv where you have a globally accessible single data element that will probably break + executeMultithreaded.accept(() -> { + try { + l.lock(); + task.run(); + } finally { + l.unlock(); + } + }); + } + +} diff --git a/src/main/resources/trans/debug/SCPDriveDebug.js b/src/main/resources/trans/debug/SCPDriveDebug.js index a64f2b9a..35a32a0c 100644 --- a/src/main/resources/trans/debug/SCPDriveDebug.js +++ b/src/main/resources/trans/debug/SCPDriveDebug.js @@ -73,7 +73,7 @@ function initializeCoreMod() { il.add(new VarInsnNode(opcodes.ALOAD, cfl)); il.add(new VarInsnNode(opcodes.LLOAD, 6)); il.add(new MethodInsnNode(opcodes.INVOKESTATIC, - "org/jmt/mcmt/asmdest/DebugHookTerminator", "chunkLoadDrive", + "org/jmt/mcmt/asmdest/ChunkRepairHookTerminator", "chunkLoadDrive", "(Lnet/minecraft/world/server/ServerChunkProvider$ChunkExecutor;Ljava/util/function/BooleanSupplier;Lnet/minecraft/world/server/ServerChunkProvider;Ljava/util/concurrent/CompletableFuture;J)V" ,false)); il.add(new JumpInsnNode(opcodes.GOTO, skipTarget)); @@ -134,7 +134,7 @@ function initializeCoreMod() { var il = new InsnList(); il.add(new MethodInsnNode(opcodes.INVOKESTATIC, - "org/jmt/mcmt/asmdest/DebugHookTerminator", "isBypassLoadTarget", + "org/jmt/mcmt/asmdest/ChunkRepairHookTerminator", "isBypassLoadTarget", "()Z" ,false)); il.add(new JumpInsnNode(opcodes.IFNE, labelTgt)); @@ -195,7 +195,7 @@ function initializeCoreMod() { var il = new InsnList(); //il.add(new InsnNode(opcodes.DUP)); //il.add(new MethodInsnNode(opcodes.INVOKESTATIC, - // "org/jmt/mcmt/asmdest/DebugHookTerminator", "checkNull", + // "org/jmt/mcmt/asmdest/ChunkRepairHookTerminator", "checkNull", // "(Ljava/lang/Object;)V", // false)); diff --git a/src/syncfu/java/org/jmt/mcmt/modlauncher/DevModeEnabler.java b/src/syncfu/java/org/jmt/mcmt/modlauncher/DevModeEnabler.java index 5c2aa720..d824a235 100644 --- a/src/syncfu/java/org/jmt/mcmt/modlauncher/DevModeEnabler.java +++ b/src/syncfu/java/org/jmt/mcmt/modlauncher/DevModeEnabler.java @@ -24,6 +24,7 @@ public class DevModeEnabler implements ITransformer { private static final Logger LOGGER = LogManager.getLogger(); + @SuppressWarnings("unused") private static final Marker M_LOCATOR = MarkerManager.getMarker("LOCATE"); private boolean isActive = true;