diff --git a/src/main/java/org/quiltmc/loader/api/plugin/QuiltLoaderPlugin.java b/src/main/java/org/quiltmc/loader/api/plugin/QuiltLoaderPlugin.java
index 755d0fc7d..263030ad3 100644
--- a/src/main/java/org/quiltmc/loader/api/plugin/QuiltLoaderPlugin.java
+++ b/src/main/java/org/quiltmc/loader/api/plugin/QuiltLoaderPlugin.java
@@ -163,20 +163,11 @@ default void finish(ModSolveResult result) {}
// Solving
// #######
- /** Resolves a single {@link TentativeLoadOption} that was added via
- * {@link QuiltPluginContext#addTentativeOption(LoadOption)}. This is only called if the option was selected by the
- * solver - unselected options are not resolved.
- *
- * Long-running operations should use {@link QuiltPluginContext#submit(java.util.concurrent.Callable)} to perform
- * those tasks in the future, and possibly on different threads. Operations that require use of the gui should use
- * {@link QuiltPluginContext#addGuiRequest()} instead, and call submit after that has been accepted or denied.
- *
- * @return A {@link QuiltPluginTask} containing (or will contain) the {@link LoadOption} that will replace the
- * {@link TentativeLoadOption} next cycle. */
- default QuiltPluginTask extends LoadOption> resolve(TentativeLoadOption from) {
- throw new IllegalStateException(
- getClass() + " has added a TentativeLoadOption (" + from.getClass() + ") but can't resolve it!"
- );
+ /** Called once before {@link TentativeLoadOption}s are resolved. This is intended to allow plugins to queue
+ * downloads, or begin other long-running activities that would benefit from running simultaneously. This is only
+ * called with load options are selected by the solver - unselected options are excluded from this list. */
+ default void preResolve(List optionsToResolve) {
+ // Nothing to do by default
}
/** @return True if this plugin did something which will solve / change the error in future, and so loader won't ask
diff --git a/src/main/java/org/quiltmc/loader/api/plugin/QuiltPluginContext.java b/src/main/java/org/quiltmc/loader/api/plugin/QuiltPluginContext.java
index e6d7feb44..dac73bfef 100644
--- a/src/main/java/org/quiltmc/loader/api/plugin/QuiltPluginContext.java
+++ b/src/main/java/org/quiltmc/loader/api/plugin/QuiltPluginContext.java
@@ -98,43 +98,6 @@ public interface QuiltPluginContext {
* stacktrace to the crash report. */
void haltLoading();
- // ##############
- // # Scheduling #
- // ##############
-
- /** Submits a task to be completed after plugin resolution, but before the current cycle ends. The task may be
- * executed on a different thread, depending on loaders config options.
- *
- * This should only be called by {@link QuiltLoaderPlugin#resolve(TentativeLoadOption)},
- * {@link QuiltLoaderPlugin#finish(org.quiltmc.loader.api.plugin.solver.ModSolveResult)}, or by any tasks that are
- * passed to this function during their execution.
- *
- * @return A {@link QuiltPluginTask} which will contain the result of the task, or the failure state if something
- * went wrong. */
- QuiltPluginTask submit(Callable task);
-
- /** Submits a task to be completed after plugin resolution, and additionally after the given tasks have completed,
- * but before the current cycle ends. The task may be executed on a different thread, depending on loaders config
- * options. Note that the task will still be executed, even if the dependencies failed. This is to allow
- * the task to handle errors directly.
- *
- * @param deps The tasks that must complete before the given task can be executed.
- * @return A {@link QuiltPluginTask} which will contain the result of the task, or the failure state if something
- * went wrong. */
- QuiltPluginTask submitAfter(Callable task, QuiltPluginTask>... deps);
-
- // #######
- // # Gui #
- // #######
-
- /** Used to ask the real user of something. Normally this will append something to the existing gui rather than
- * opening a new gui each time this is called.
- *
- * TODO: Create all gui stuff! for now this just throws an {@link AbstractMethodError} */
- default QuiltPluginTask addGuiRequest() {
- throw new AbstractMethodError("// TODO: Add gui support!");
- }
-
// ###########
// # Solving #
// ###########
diff --git a/src/main/java/org/quiltmc/loader/api/plugin/QuiltPluginManager.java b/src/main/java/org/quiltmc/loader/api/plugin/QuiltPluginManager.java
index 18e30ad60..b2a131c8e 100644
--- a/src/main/java/org/quiltmc/loader/api/plugin/QuiltPluginManager.java
+++ b/src/main/java/org/quiltmc/loader/api/plugin/QuiltPluginManager.java
@@ -54,25 +54,6 @@ public interface QuiltPluginManager {
// Loading
// #######
- /** Returns a task which will load the specified zip file and returns a path to the root of it's contents.
- *
- * How the given zip is loaded depends on loaders config settings - in particular the zip could be extracted to a
- * temporary folder on the same filesystem as the original zip.
- *
- * WARNING: if this method allocates a new {@link FileSystem} then that will be closed, unless at least one
- * of the {@link QuiltLoaderPlugin}s {@link QuiltPluginContext#lockZip(Path) locks} it, or if a chosen mod is loaded
- * from it.
- *
- * The returned task may throw the following exceptions:
- *
- * - {@link IOException} if something went wrong while loading the file.
- * - {@link NonZipException} if {@link FileSystems#newFileSystem(Path, ClassLoader)} throws a
- * {@link ProviderNotFoundException}.
- *
- *
- * @see #loadZipNow(Path) */
- QuiltPluginTask loadZip(Path zip);
-
/** Loads the specified zip file and returns a path to the root of it's contents.
*
* How the given zip is loaded depends on loaders config settings - in particular the zip could be extracted to a
@@ -84,8 +65,7 @@ public interface QuiltPluginManager {
*
* @throws IOException if something went wrong while loading the file.
* @throws NonZipException if {@link FileSystems#newFileSystem(Path, ClassLoader)} throws a
- * {@link ProviderNotFoundException}.
- * @see #loadZip(Path) */
+ * {@link ProviderNotFoundException}. */
Path loadZipNow(Path zip) throws IOException, NonZipException;
/** Creates a new in-memory read-write file system. This can be used for mods that aren't loaded from zips.
diff --git a/src/main/java/org/quiltmc/loader/api/plugin/QuiltPluginTask.java b/src/main/java/org/quiltmc/loader/api/plugin/QuiltPluginTask.java
deleted file mode 100644
index abb1a805e..000000000
--- a/src/main/java/org/quiltmc/loader/api/plugin/QuiltPluginTask.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2022, 2023 QuiltMC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.quiltmc.loader.api.plugin;
-
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-
-import org.quiltmc.loader.impl.plugin.QuiltPluginTaskImpl;
-import org.quiltmc.loader.impl.util.QuiltLoaderInternal;
-import org.quiltmc.loader.impl.util.QuiltLoaderInternalType;
-
-/** Quilt plugin version of a {@link Future}.
- *
- * All implementations use {@link CompletableFuture} internally, since we only need to restrict the API surface of this,
- * rather than have a complete implementation of concurrent code. */
-@QuiltLoaderInternal(QuiltLoaderInternalType.PLUGIN_API)
-public interface QuiltPluginTask {
-
- /** @return True if this task has already finished - either successfully or exceptionally. */
- boolean isDone();
-
- /** @return The exception, if this task completed exceptionally, or null if it either hasn't completed or completed
- * successfully. */
- Throwable getException();
-
- /** @return The result, if the task completed successfully.
- * @throws ExecutionException if the task completed unsuccessfully.
- * @throws IllegalStateException if the task has not been completed yet. */
- V getResult() throws ExecutionException;
-
- /** @return A new {@link QuiltPluginTask} that has already been completed successfully, with the given result. */
- public static QuiltPluginTask createFinished(V result) {
- return QuiltPluginTaskImpl.createFinished(result);
- }
-
- /** @return A new {@link QuiltPluginTask} that has already been completed unsuccessfuly, with the given
- * exception. */
- public static QuiltPluginTask createFailed(Throwable cause) {
- return QuiltPluginTaskImpl.createFailed(cause);
- }
-}
diff --git a/src/main/java/org/quiltmc/loader/api/plugin/gui/package-info.java b/src/main/java/org/quiltmc/loader/api/plugin/gui/package-info.java
index eab20d795..9d5a77cc8 100644
--- a/src/main/java/org/quiltmc/loader/api/plugin/gui/package-info.java
+++ b/src/main/java/org/quiltmc/loader/api/plugin/gui/package-info.java
@@ -14,7 +14,5 @@
* limitations under the License.
*/
-/**
- * The package for
- */
+/** Deprecated package. Plugins should use {@link org.quiltmc.loader.api.gui.QuiltLoaderGui} instead. */
package org.quiltmc.loader.api.plugin.gui;
diff --git a/src/main/java/org/quiltmc/loader/api/plugin/solver/TentativeLoadOption.java b/src/main/java/org/quiltmc/loader/api/plugin/solver/TentativeLoadOption.java
index 4848458d1..55d15e519 100644
--- a/src/main/java/org/quiltmc/loader/api/plugin/solver/TentativeLoadOption.java
+++ b/src/main/java/org/quiltmc/loader/api/plugin/solver/TentativeLoadOption.java
@@ -16,14 +16,20 @@
package org.quiltmc.loader.api.plugin.solver;
+import org.quiltmc.loader.api.plugin.QuiltLoaderPlugin;
import org.quiltmc.loader.impl.util.QuiltLoaderInternal;
import org.quiltmc.loader.impl.util.QuiltLoaderInternalType;
/** {@link LoadOption}s can implement this if they must be processed before they can be used, if they are selected.
*
- * Unselected {@link TentativeLoadOption}s are left alone at the end of the cycle, and are not resolved.
- */
+ * Unselected {@link TentativeLoadOption}s are left alone at the end of the cycle, and are not resolved. */
@QuiltLoaderInternal(QuiltLoaderInternalType.PLUGIN_API)
public interface TentativeLoadOption {
+ /** Resolves this to a real {@link LoadOption}. This is only invoked after
+ * {@link QuiltLoaderPlugin#preResolve(java.util.List)} has already been called.
+ *
+ * @return a new {@link LoadOption} which can be loaded as-is. (It must not be another
+ * {@link TentativeLoadOption}) */
+ LoadOption resolve();
}
diff --git a/src/main/java/org/quiltmc/loader/impl/plugin/BasePluginContext.java b/src/main/java/org/quiltmc/loader/impl/plugin/BasePluginContext.java
index f7c6178b3..1d2c46c81 100644
--- a/src/main/java/org/quiltmc/loader/impl/plugin/BasePluginContext.java
+++ b/src/main/java/org/quiltmc/loader/impl/plugin/BasePluginContext.java
@@ -18,7 +18,6 @@
import java.nio.file.Path;
import java.util.Collection;
-import java.util.concurrent.Callable;
import org.quiltmc.loader.api.plugin.QuiltPluginContext;
import org.quiltmc.loader.api.gui.QuiltDisplayedError;
@@ -26,7 +25,6 @@
import org.quiltmc.loader.api.gui.QuiltTreeNode;
import org.quiltmc.loader.api.gui.QuiltTreeNode.SortOrder;
import org.quiltmc.loader.api.plugin.QuiltPluginManager;
-import org.quiltmc.loader.api.plugin.QuiltPluginTask;
import org.quiltmc.loader.api.plugin.gui.PluginGuiTreeNode;
import org.quiltmc.loader.api.plugin.solver.LoadOption;
import org.quiltmc.loader.api.plugin.solver.ModLoadOption;
@@ -102,16 +100,6 @@ public void haltLoading() {
manager.haltLoading(this);
}
- @Override
- public QuiltPluginTask submit(Callable task) {
- return manager.submit(this, task);
- }
-
- @Override
- public QuiltPluginTask submitAfter(Callable task, QuiltPluginTask>... deps) {
- return manager.submitAfter(this, task, deps);
- }
-
@Override
public RuleContext ruleContext() {
return ruleContext;
diff --git a/src/main/java/org/quiltmc/loader/impl/plugin/PerCycleStep.java b/src/main/java/org/quiltmc/loader/impl/plugin/PerCycleStep.java
index b646b6051..ae8030d5b 100644
--- a/src/main/java/org/quiltmc/loader/impl/plugin/PerCycleStep.java
+++ b/src/main/java/org/quiltmc/loader/impl/plugin/PerCycleStep.java
@@ -17,7 +17,6 @@
package org.quiltmc.loader.impl.plugin;
import org.quiltmc.loader.api.plugin.solver.ModSolveResult;
-import org.quiltmc.loader.api.plugin.solver.TentativeLoadOption;
import org.quiltmc.loader.impl.util.QuiltLoaderInternal;
import org.quiltmc.loader.impl.util.QuiltLoaderInternalType;
@@ -31,9 +30,6 @@ enum PerCycleStep {
/** Indicates that we've called {@link BuiltinQuiltPlugin#beforeSolve()}, and now we're solving. */
SOLVE,
- /** Indicates that solving has finished, and now we're resolving {@link TentativeLoadOption}s. */
- POST_SOLVE_TENTATIVE,
-
/** Indicates that solving was successful, and we have a resulting {@link ModSolveResult} to end loading with. */
SUCCESS;
}
diff --git a/src/main/java/org/quiltmc/loader/impl/plugin/QuiltPluginManagerImpl.java b/src/main/java/org/quiltmc/loader/impl/plugin/QuiltPluginManagerImpl.java
index ae8a8b893..134b45744 100644
--- a/src/main/java/org/quiltmc/loader/impl/plugin/QuiltPluginManagerImpl.java
+++ b/src/main/java/org/quiltmc/loader/impl/plugin/QuiltPluginManagerImpl.java
@@ -73,7 +73,6 @@
import org.quiltmc.loader.api.plugin.QuiltLoaderPlugin;
import org.quiltmc.loader.api.plugin.QuiltPluginContext;
import org.quiltmc.loader.api.plugin.QuiltPluginManager;
-import org.quiltmc.loader.api.plugin.QuiltPluginTask;
import org.quiltmc.loader.api.plugin.gui.PluginGuiManager;
import org.quiltmc.loader.api.plugin.gui.PluginGuiTreeNode;
import org.quiltmc.loader.api.plugin.gui.PluginGuiTreeNode.WarningLevel;
@@ -238,18 +237,6 @@ private BuiltinPluginContext addBuiltinPlugin(BuiltinQuiltPlugin plugin, String
// Loading
// #######
- @Override
- public QuiltPluginTask loadZip(Path zip) {
- if (config.singleThreadedLoading) {
- try {
- return QuiltPluginTask.createFinished(loadZipNow(zip));
- } catch (IOException | NonZipException e) {
- return QuiltPluginTask.createFailed(e);
- }
- }
- return submit(null, () -> loadZipNow(zip));
- }
-
/** Kept for backwards compatibility with the first versions of RGML-Quilt, as it invoked this using reflection. */
@Deprecated
private Path loadZip0(Path zip) throws IOException, NonZipException {
@@ -1356,7 +1343,7 @@ private ModSolveResultImpl runSingleCycle() throws ModResolutionException, Timeo
ModSolveResultImpl partialResult = getPartialSolution();
if (processTentatives(partialResult)) {
- this.perCycleStep = step = PerCycleStep.POST_SOLVE_TENTATIVE;
+ this.perCycleStep = step = PerCycleStep.START;
} else {
this.perCycleStep = step = PerCycleStep.SUCCESS;
result = partialResult;
@@ -1371,10 +1358,6 @@ private ModSolveResultImpl runSingleCycle() throws ModResolutionException, Timeo
return null;
}
}
- case POST_SOLVE_TENTATIVE: {
- // TODO: Deal with tentative load options!
- return null;
- }
case SUCCESS: {
cleanup();
@@ -1585,9 +1568,29 @@ private boolean processTentatives(ModSolveResult partialResult) {
return false;
} else {
+ Map> pluginsToOptions = new HashMap<>();
+
for (TentativeLoadOption option : tentatives) {
QuiltPluginContext pluginSrc = tentativeLoadOptions.get(option);
- QuiltPluginTask extends LoadOption> resolution = pluginSrc.plugin().resolve(option);
+ pluginsToOptions.computeIfAbsent(pluginSrc, s -> new ArrayList<>()).add(option);
+ }
+
+ for (Map.Entry> entry : pluginsToOptions.entrySet()) {
+ entry.getKey().plugin().preResolve(entry.getValue());
+ }
+
+ for (Map.Entry> entry : pluginsToOptions.entrySet()) {
+ for (TentativeLoadOption option : entry.getValue()) {
+ LoadOption replacement = option.resolve();
+ if (replacement instanceof TentativeLoadOption) {
+ throw new IllegalStateException(
+ "The TentativeLoadOption " + option.getClass()
+ + " resolved into another TentativeLoadOption " + replacement.getClass() + "!"
+ );
+ }
+ removeLoadOption((LoadOption) option);
+ addLoadOption(replacement, (BasePluginContext) entry.getKey());
+ }
}
return true;
@@ -1792,18 +1795,6 @@ private static void forceGcButBadly() {
System.gc();
}
- // #########
- // # Tasks #
- // #########
-
- QuiltPluginTask submit(BasePluginContext ctx, Callable task) {
- throw new AbstractMethodError("// TODO: Implement plugin tasks!");
- }
-
- QuiltPluginTask submitAfter(BasePluginContext ctx, Callable task, QuiltPluginTask>... deps) {
- throw new AbstractMethodError("// TODO: Implement plugin tasks!");
- }
-
// ########
// # Mods #
// ########
diff --git a/src/main/java/org/quiltmc/loader/impl/plugin/QuiltPluginTaskImpl.java b/src/main/java/org/quiltmc/loader/impl/plugin/QuiltPluginTaskImpl.java
deleted file mode 100644
index 192b9c1d1..000000000
--- a/src/main/java/org/quiltmc/loader/impl/plugin/QuiltPluginTaskImpl.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright 2022, 2023 QuiltMC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.quiltmc.loader.impl.plugin;
-
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import org.quiltmc.loader.api.plugin.QuiltPluginTask;
-import org.quiltmc.loader.impl.util.QuiltLoaderInternal;
-import org.quiltmc.loader.impl.util.QuiltLoaderInternalType;
-
-@QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL)
-public final class QuiltPluginTaskImpl implements QuiltPluginTask {
-
- final CompletableFuture future;
-
- QuiltPluginTaskImpl() {
- this.future = new CompletableFuture<>();
- }
-
- /** @return A new {@link QuiltPluginTask} that has already been completed successfully, with the given result. */
- public static QuiltPluginTask createFinished(V result) {
- QuiltPluginTaskImpl task = new QuiltPluginTaskImpl<>();
- task.future.complete(result);
- return task;
- }
-
- /** @return A new {@link QuiltPluginTask} that has already been completed unsuccessfully, with the given
- * exception. */
- public static QuiltPluginTask createFailed(Throwable cause) {
- QuiltPluginTaskImpl task = new QuiltPluginTaskImpl<>();
- task.future.completeExceptionally(cause);
- return task;
- }
-
- @Override
- public boolean isDone() {
- return future.isDone();
- }
-
- @Override
- public Throwable getException() {
- if (!future.isDone()) {
- return null;
- }
-
- try {
- future.get(0, TimeUnit.NANOSECONDS);
- return null;
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- return new ExecutionException(e);
- } catch (ExecutionException e) {
- return e;
- } catch (TimeoutException e) {
- throw new IllegalStateException(
- "Apparently the CompletableFuture was done, but it threw a TimeoutException?", e
- );
- }
- }
-
- @Override
- public V getResult() throws ExecutionException {
- if (!future.isDone()) {
- throw new IllegalStateException("Task not complete yet!");
- }
-
- try {
- return future.get(0, TimeUnit.NANOSECONDS);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new ExecutionException(e);
- } catch (ExecutionException e) {
- throw e;
- } catch (TimeoutException e) {
- throw new IllegalStateException(
- "Apparently the CompletableFuture was done, but it threw a TimeoutException?", e
- );
- }
- }
-}