feat(launcher): rewrite mds-flow runOnce to wait on future of internal scan if scan thread is running

This commit is contained in:
Johannes Frohnmeyer 2024-06-29 18:00:07 +02:00
parent 9b55a3f08d
commit a67a0b4b4f
Signed by: Johannes
GPG Key ID: E76429612C2929F4
6 changed files with 98 additions and 38 deletions

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.inceptum.launcher.system.mds;
import io.gitlab.jfronny.commons.throwable.ThrowingBiConsumer;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.launcher.system.mds.flow.FlowMds;
import io.gitlab.jfronny.inceptum.launcher.system.mds.noop.NoopMds;
@ -10,7 +11,6 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;
import java.util.function.BiConsumer;
public interface ModsDirScanner extends Closeable {
static ModsDirScanner get(Path modsDir, InstanceMeta meta) throws IOException {
@ -46,5 +46,5 @@ public interface ModsDirScanner extends Closeable {
return hasScanned(mod.getMetadataPath());
}
void runOnce(ScanStage targetStage, BiConsumer<Path, Mod> discovered);
void runOnce(ScanStage targetStage, ThrowingBiConsumer<Path, Mod, IOException> discovered);
}

View File

@ -2,6 +2,7 @@ package io.gitlab.jfronny.inceptum.launcher.system.mds.flow;
import io.gitlab.jfronny.commons.io.JFiles;
import io.gitlab.jfronny.commons.ref.R;
import io.gitlab.jfronny.commons.throwable.ThrowingBiConsumer;
import io.gitlab.jfronny.commons.tuple.Tuple;
import io.gitlab.jfronny.inceptum.common.Utils;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
@ -15,7 +16,7 @@ import java.io.IOException;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -111,17 +112,92 @@ public class FlowMds implements ModsDirScanner {
return scannedPaths.contains(path) || scannedPaths.contains(ModPath.appendImod(path));
}
private final Set<SavedTask> savedTasks = new HashSet<>();
record SavedTask(ScanStage at, ThrowingBiConsumer<Path, Mod, IOException> discovered, CompletableFuture<Void> future) implements ThrowingBiConsumer<Path, Mod, IOException> {
@Override
public void accept(Path var1, Mod var2) throws IOException {
try {
discovered.accept(var1, var2);
future.complete(null);
} catch (Throwable e) {
future.completeExceptionally(e);
}
}
}
private void waitUntilAfterScan(ScanStage at, ThrowingBiConsumer<Path, Mod, IOException> discovered) {
CompletableFuture<Void> future = new CompletableFuture<>();
synchronized (savedTasks) {
savedTasks.add(new SavedTask(at, discovered, future));
}
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
Utils.LOGGER.error("Could not wait for scan to finish", e);
}
}
private void scanTaskInternal() {
while (!disposed) {
runOnce(ScanStage.ALL, R::nop);
Map<ScanStage, SavedTask> tasks;
synchronized (savedTasks) {
tasks = savedTasks.stream().collect(Collectors.toMap(
SavedTask::at,
Function.identity()
));
savedTasks.clear();
}
try {
performScanTask(Map.of(ScanStage.ALL, R::nop));
Thread.sleep(1000);
} catch (InterruptedException e) {
} catch (IOException | InterruptedException e) {
for (SavedTask task : tasks.values()) {
task.future.completeExceptionally(e);
}
throw new RuntimeException(e);
}
}
}
private void performScanTask(Map<ScanStage, ThrowingBiConsumer<Path, Mod, IOException>> discovered) throws IOException {
if (!Files.isDirectory(instance.modsDir())) {
return;
}
MdsPipeline pipeline = new MdsPipeline();
pipeline.addTask(ScanStage.NONE, (path, mod) -> {
descriptions.put(path, mod);
scannedPaths.add(path);
});
ScanStage targetStage = discovered.keySet().stream()
.max(Comparator.naturalOrder())
.orElse(ScanStage.DISCOVER);
pipeline.addTask(ScanStage.NONE, discovered.get(ScanStage.NONE));
pipeline.addTask(ScanStage.NONE, discovered.get(ScanStage.DISCOVER));
if (targetStage.contains(ScanStage.DOWNLOAD)) {
pipeline.addTask(ScanStage.DOWNLOAD, new MdsDownloadTask(instance));
pipeline.addTask(ScanStage.NONE, discovered.get(ScanStage.DOWNLOAD));
}
if (targetStage.contains(ScanStage.CROSSREFERENCE)) {
pipeline.addTask(ScanStage.CROSSREFERENCE, new MdsCrossReferenceTask(instance));
pipeline.addTask(ScanStage.NONE, discovered.get(ScanStage.CROSSREFERENCE));
}
if (targetStage.contains(ScanStage.UPDATECHECK)) {
pipeline.addTask(ScanStage.UPDATECHECK, new MdsUpdateTask(instance, getGameVersion()));
pipeline.addTask(ScanStage.NONE, discovered.get(ScanStage.UPDATECHECK));
}
pipeline.addTask(ScanStage.NONE, discovered.get(ScanStage.ALL));
var futures1 = pipeline.run(factory, getToScan(), new MdsDiscoverTask(instance, getGameVersion()));
var futures2 = pipeline.run(factory, descriptions.entrySet().stream().map(Tuple::from).collect(Collectors.toSet()));
for (CompletableFuture<Void> future : Stream.concat(futures1.stream(), futures2.stream()).toList()) {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
Utils.LOGGER.error("Could not scan file for mod info", e);
}
}
runOnce(ScanStage.ALL, R::nop);
}
private Set<Path> getToScan() throws IOException {
if (descriptions.isEmpty()) return Set.copyOf(JFiles.list(instance.modsDir()));
Set<Path> toScan = new HashSet<>();
@ -141,34 +217,12 @@ public class FlowMds implements ModsDirScanner {
}
@Override
public void runOnce(ScanStage targetStage, BiConsumer<Path, Mod> discovered) {
public void runOnce(ScanStage targetStage, ThrowingBiConsumer<Path, Mod, IOException> discovered) {
try {
if (!Files.isDirectory(instance.modsDir())) {
return;
}
MdsPipeline pipeline = new MdsPipeline();
if (targetStage.contains(ScanStage.DOWNLOAD)) {
pipeline.addTask(ScanStage.DOWNLOAD, new MdsDownloadTask(instance));
}
if (targetStage.contains(ScanStage.CROSSREFERENCE)) {
pipeline.addTask(ScanStage.CROSSREFERENCE, new MdsCrossReferenceTask(instance));
}
if (targetStage.contains(ScanStage.UPDATECHECK)) {
pipeline.addTask(ScanStage.UPDATECHECK, new MdsUpdateTask(instance, getGameVersion()));
}
pipeline.addTask(ScanStage.NONE, (path, mod) -> {
scannedPaths.add(path);
descriptions.put(path, mod);
if (mod != null) discovered.accept(path, mod);
});
var futures1 = pipeline.run(factory, getToScan(), new MdsDiscoverTask(instance, getGameVersion()));
var futures2 = pipeline.run(factory, descriptions.entrySet().stream().map(Tuple::from).collect(Collectors.toSet()));
for (CompletableFuture<Void> future : Stream.concat(futures1.stream(), futures2.stream()).toList()) {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
Utils.LOGGER.error("Could not scan file for mod info", e);
}
if (!th.isAlive()) {
performScanTask(Map.of(targetStage, discovered));
} else {
waitUntilAfterScan(targetStage, discovered);
}
} catch (IOException e) {
Utils.LOGGER.error("Could not scan file for mod info", e);

View File

@ -3,23 +3,27 @@ package io.gitlab.jfronny.inceptum.launcher.system.mds.flow;
import io.gitlab.jfronny.commons.throwable.*;
import io.gitlab.jfronny.commons.tuple.Tuple;
import io.gitlab.jfronny.inceptum.launcher.system.mds.*;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
public class MdsPipeline {
private final List<Tuple<ScanStage, ThrowingConsumer<Tuple<Path, Mod>, IOException>>> functions = new ArrayList<>();
public void addTask(ScanStage stage, ThrowingConsumer<MdsMod, IOException> task) {
public void addTask(ScanStage stage, @Nullable ThrowingConsumer<MdsMod, IOException> task) {
if (task == null) return;
functions.add(Tuple.of(stage, tuple -> {
if (tuple.right() instanceof MdsMod mmod) task.accept(mmod);
}));
}
public void addTask(ScanStage stage, ThrowingBiConsumer<Path, MdsMod, IOException> task) {
public void addTask(ScanStage stage, @Nullable ThrowingBiConsumer<Path, ? super MdsMod, IOException> task) {
if (task == null) return;
functions.add(Tuple.of(stage, tuple -> {
if (tuple.right() instanceof MdsMod mmod) task.accept(tuple.left(), mmod);
}));

View File

@ -1,6 +1,7 @@
package io.gitlab.jfronny.inceptum.launcher.system.mds.noop;
import io.gitlab.jfronny.commons.ref.R;
import io.gitlab.jfronny.commons.throwable.ThrowingBiConsumer;
import io.gitlab.jfronny.inceptum.launcher.system.mds.*;
import io.gitlab.jfronny.inceptum.launcher.util.VoidClaimPool;
@ -54,6 +55,6 @@ public record NoopMds(String gameVersion) implements ModsDirScanner {
}
@Override
public void runOnce(ScanStage targetStage, BiConsumer<Path, Mod> discovered) {
public void runOnce(ScanStage targetStage, ThrowingBiConsumer<Path, Mod, IOException> discovered) {
}
}

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.inceptum.launcher.system.mds.threaded;
import io.gitlab.jfronny.commons.throwable.ThrowingBiConsumer;
import io.gitlab.jfronny.inceptum.common.GsonPreset;
import io.gitlab.jfronny.inceptum.common.Utils;
import io.gitlab.jfronny.inceptum.launcher.model.fabric.FabricModJson;
@ -14,9 +15,8 @@ import io.gitlab.jfronny.inceptum.launcher.system.source.ModSource;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.*;
import java.util.function.BiConsumer;
public record FileScanTask(ProtoInstance instance, Path file, BiConsumer<Path, Mod> discovered, String gameVersion) implements Runnable {
public record FileScanTask(ProtoInstance instance, Path file, ThrowingBiConsumer<Path, Mod, IOException> discovered, String gameVersion) implements Runnable {
@Override
public void run() {
if (!Files.exists(file)) return;

View File

@ -2,6 +2,7 @@ package io.gitlab.jfronny.inceptum.launcher.system.mds.threaded;
import io.gitlab.jfronny.commons.io.JFiles;
import io.gitlab.jfronny.commons.ref.R;
import io.gitlab.jfronny.commons.throwable.ThrowingBiConsumer;
import io.gitlab.jfronny.inceptum.common.Utils;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.launcher.system.mds.*;
@ -112,7 +113,7 @@ public class ThreadedMds implements ModsDirScanner {
}
@Override
public void runOnce(ScanStage targetStage, BiConsumer<Path, Mod> discovered) {
public void runOnce(ScanStage targetStage, ThrowingBiConsumer<Path, Mod, IOException> discovered) {
try {
if (!Files.isDirectory(instance.modsDir())) {
return;