diff --git a/src/main/java/net/minecraftforge/fml/DeferredWorkQueue.java b/src/main/java/net/minecraftforge/fml/DeferredWorkQueue.java index 9d1a4d827f..20705ca432 100644 --- a/src/main/java/net/minecraftforge/fml/DeferredWorkQueue.java +++ b/src/main/java/net/minecraftforge/fml/DeferredWorkQueue.java @@ -19,26 +19,190 @@ package net.minecraftforge.fml; -import java.util.concurrent.Callable; -import java.util.concurrent.ConcurrentLinkedDeque; -import java.util.concurrent.Future; -import java.util.concurrent.FutureTask; +import static net.minecraftforge.fml.Logging.LOADING; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.apache.commons.lang3.time.StopWatch; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.google.common.base.Function; +import com.google.common.base.Stopwatch; + +import net.minecraft.util.IThreadListener; +import net.minecraftforge.forgespi.language.IModInfo; + +/** + * Utility for running code on the main launch thread at the next available + * opportunity. There is no guaranteed order that work from various mods will be + * run, but your own work will be run sequentially. + *
+ * Use of this class after startup is not possible. At that + * point, {@link IThreadListener} should be used instead. + *
+ * Exceptions from tasks will be handled gracefully, causing a mod loading
+ * error. Tasks that take egregiously long times to run will be logged.
+ */
public class DeferredWorkQueue
{
- public static ConcurrentLinkedDeque
+ * If the task must throw a checked exception, use
+ * {@link #runLaterChecked(CheckedRunnable)}.
+ *
+ * If the task has a result, use {@link #getLater(Supplier)} or
+ * {@link #getLaterChecked(Callable)}.
+ *
+ * @param workToEnqueue A {@link Runnable} to execute later, on the loading
+ * thread
+ * @return A {@link CompletableFuture} that completes at said time
+ */
+ public static CompletableFuture
+ * If the task does not throw a checked exception, use
+ * {@link #runLater(Runnable)}.
+ *
+ * If the task has a result, use {@link #getLater(Supplier)} or
+ * {@link #getLaterChecked(Callable)}.
+ *
+ * @param workToEnqueue A {@link CheckedRunnable} to execute later, on the
+ * loading thread
+ * @return A {@link CompletableFuture} that completes at said time
+ */
+ public static CompletableFuture
+ * If the task throws a checked exception, use
+ * {@link #getLaterChecked(Callable)}.
+ *
+ * If the task does not have a result, use {@link #runLater(Runnable)} or
+ * {@link #runLaterChecked(CheckedRunnable)}.
+ *
+ * @param
+ * If the task does not throw a checked exception, use
+ * {@link #getLater(Callable)}.
+ *
+ * If the task does not have a result, use {@link #runLater(Runnable)} or
+ * {@link #runLaterChecked(CheckedRunnable)}.
+ *
+ * @param > errorHandler) {
+ raisedExceptions.clear();
+ if (taskQueue.isEmpty()) return; // Don't log unnecessarily
+ LOGGER.info(LOADING, "Dispatching synchronous work after {}: {} jobs", fromStage, taskQueue.size());
+ StopWatch globalTimer = StopWatch.createStarted();
+ while (!taskQueue.isEmpty()) {
+ TaskInfo taskinfo = taskQueue.poll();
+ Stopwatch timer = Stopwatch.createStarted();
+ taskinfo.task.run();
+ timer.stop();
+ if (timer.elapsed(TimeUnit.SECONDS) >= 1) {
+ LOGGER.warn(LOADING, "Mod '{}' took {} to run a deferred task.", taskinfo.owner.getModId(), timer);
+ }
+ }
+ LOGGER.info(LOADING, "Synchronous work queue completed in {}", globalTimer);
+ errorHandler.accept(raisedExceptions);
+ }
}
diff --git a/src/main/java/net/minecraftforge/fml/ModList.java b/src/main/java/net/minecraftforge/fml/ModList.java
index e5aa1e851d..6af85bc904 100644
--- a/src/main/java/net/minecraftforge/fml/ModList.java
+++ b/src/main/java/net/minecraftforge/fml/ModList.java
@@ -91,7 +91,7 @@ public class ModList
public void dispatchLifeCycleEvent(LifecycleEventProvider.LifecycleEvent lifecycleEvent, final Consumer
> errorHandler) {
FMLLoader.getLanguageLoadingProvider().forEach(lp->lp.consumeLifecycleEvent(()->lifecycleEvent));
- DeferredWorkQueue.deferredWorkQueue.clear();
+ DeferredWorkQueue.clear();
try
{
modLoadingThreadPool.submit(()->this.mods.parallelStream().forEach(m->m.transitionState(lifecycleEvent, errorHandler))).get();
@@ -100,9 +100,7 @@ public class ModList
{
LOGGER.error(LOADING, "Encountered an exception during parallel processing", e);
}
- LOGGER.debug(LOADING, "Dispatching synchronous work, {} jobs", DeferredWorkQueue.deferredWorkQueue.size());
- DeferredWorkQueue.deferredWorkQueue.forEach(FutureTask::run);
- LOGGER.debug(LOADING, "Synchronous work queue complete");
+ DeferredWorkQueue.runTasks(lifecycleEvent.fromStage(), errorHandler);
FMLLoader.getLanguageLoadingProvider().forEach(lp->lp.consumeLifecycleEvent(()->lifecycleEvent));
}
diff --git a/src/main/resources/assets/forge/lang/en_us.json b/src/main/resources/assets/forge/lang/en_us.json
index 1bac7548d1..6cf899a7d0 100644
--- a/src/main/resources/assets/forge/lang/en_us.json
+++ b/src/main/resources/assets/forge/lang/en_us.json
@@ -25,6 +25,7 @@
"fml.modloading.errorduringevent":"{0,modinfo,name} ({0,modinfo,id}) encountered an error during the {1,lower} event phase\n\u00a77{2,exc,msg}",
"fml.modloading.failedtoloadforge": "Failed to load forge",
"fml.modloading.missingdependency": "Mod \u00a7e{4}\u00a7r requires \u00a76{3}\u00a7r \u00a7o{5,vr}\u00a7r\n\u00a77Currently, \u00a76{3}\u00a7r\u00a77 is \u00a7o{6,i18n,fml.messages.artifactversion.ornotinstalled}",
+ "fml.modloading.failedtoprocesswork":"{0,modinfo,name} ({0,modinfo,id}) encountered an error processing deferred work\n\u00a77{2,exc,msg}",
"fml.messages.artifactversion.ornotinstalled":"{0,ornull,fml.messages.artifactversion.notinstalled}",
"fml.messages.artifactversion":"{0,ornull,fml.messages.artifactversion.none}",