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 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: - *

- * - * @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 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 - ); - } - } -}