From 4945381030da588d40b83234a8d6934853c4de7a Mon Sep 17 00:00:00 2001 From: JFronny Date: Sat, 22 Jun 2024 19:58:01 +0200 Subject: [PATCH] feat: implement new, flow-based MDS --- .../gitlab/jfronny/inceptum/common/GList.java | 2 +- .../InceptumEnvironmentInitializer.java | 1 + .../gitlab/jfronny/inceptum/common/Utils.java | 11 +- gradle/libs.versions.toml | 4 +- .../inceptum/cli/commands/ModCommand.java | 2 +- .../instance/InstanceSettingsWindow.kt | 5 + .../inceptum/imgui/window/AddModWindow.java | 2 +- .../imgui/window/edit/InstanceEditWindow.java | 10 + launcher/build.gradle.kts | 2 + .../gson/MicrosoftAccountAdapter.java | 4 +- .../gson/MinecraftArgumentAdapter.java | 4 +- .../launcher/gson/ModMetaSourcesAdapter.java | 4 +- .../launcher/gson/ModSourceAdapter.java | 4 +- .../inceptum/launcher/gson/RulesAdapter.java | 6 +- .../launcher/model/inceptum/ModMeta.java | 13 +- .../launcher/system/exporter/Exporter.java | 6 +- .../launcher/system/importer/Importers.java | 2 - .../launcher/system/instance/Instance.java | 6 +- .../system/launch/InstanceLauncher.java | 5 +- .../inceptum/launcher/system/mds/MdsMod.java | 18 +- .../launcher/system/mds/ModsDirScanner.java | 8 +- .../launcher/system/mds/ScanStage.java | 8 +- .../launcher/system/mds/flow/FlowMds.java | 195 ++++++++++++++++++ .../mds/flow/MdsCrossReferenceTask.java | 22 ++ .../system/mds/flow/MdsDiscoverTask.java | 34 +++ .../system/mds/flow/MdsDownloadTask.java | 53 +++++ .../launcher/system/mds/flow/MdsPipeline.java | 68 ++++++ .../system/mds/flow/MdsThreadFactory.java | 69 +++++++ .../system/mds/flow/MdsUpdateTask.java | 18 ++ .../launcher/system/mds/noop/NoopMds.java | 8 + .../launcher/system/mds/noop/NoopMod.java | 2 +- .../system/mds/threaded/FileScanTask.java | 10 +- .../system/mds/threaded/ThreadedMds.java | 6 + .../setup/steps/DownloadClientStep.java | 3 - .../util/VersionInfoLibraryResolver.java | 4 +- .../inceptum/launcher/util/VoidClaimPool.java | 39 ++++ launcher/src/main/java/module-info.java | 1 + 37 files changed, 601 insertions(+), 58 deletions(-) create mode 100644 launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/FlowMds.java create mode 100644 launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/MdsCrossReferenceTask.java create mode 100644 launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/MdsDiscoverTask.java create mode 100644 launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/MdsDownloadTask.java create mode 100644 launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/MdsPipeline.java create mode 100644 launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/MdsThreadFactory.java create mode 100644 launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/MdsUpdateTask.java create mode 100644 launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/util/VoidClaimPool.java diff --git a/common/src/main/java/io/gitlab/jfronny/inceptum/common/GList.java b/common/src/main/java/io/gitlab/jfronny/inceptum/common/GList.java index f1e5cc2..4ed0218 100644 --- a/common/src/main/java/io/gitlab/jfronny/inceptum/common/GList.java +++ b/common/src/main/java/io/gitlab/jfronny/inceptum/common/GList.java @@ -11,7 +11,7 @@ import java.util.LinkedList; import java.util.List; public class GList { - public static > List read(Reader reader, ThrowingFunction read) throws TEx { + public static > List read(Reader reader, ThrowingFunction read) throws TEx { if (reader.isLenient() && reader.peek() != Token.BEGIN_ARRAY) return List.of(read.apply(reader)); reader.beginArray(); List res = new LinkedList<>(); diff --git a/common/src/main/java/io/gitlab/jfronny/inceptum/common/InceptumEnvironmentInitializer.java b/common/src/main/java/io/gitlab/jfronny/inceptum/common/InceptumEnvironmentInitializer.java index 238dbd2..eab4678 100644 --- a/common/src/main/java/io/gitlab/jfronny/inceptum/common/InceptumEnvironmentInitializer.java +++ b/common/src/main/java/io/gitlab/jfronny/inceptum/common/InceptumEnvironmentInitializer.java @@ -9,6 +9,7 @@ import java.io.IOException; public class InceptumEnvironmentInitializer { public static void initialize() throws IOException { + HttpClient.scheduleOnlineCheck(); HotswapLoggerFinder.updateAllStrategies(InceptumEnvironmentInitializer::defaultFactory); HttpClient.setUserAgent("jfmods/inceptum/" + BuildMetadata.VERSION); InceptumConfig.load(); diff --git a/common/src/main/java/io/gitlab/jfronny/inceptum/common/Utils.java b/common/src/main/java/io/gitlab/jfronny/inceptum/common/Utils.java index 2b11648..25967ad 100644 --- a/common/src/main/java/io/gitlab/jfronny/inceptum/common/Utils.java +++ b/common/src/main/java/io/gitlab/jfronny/inceptum/common/Utils.java @@ -5,8 +5,7 @@ import io.gitlab.jfronny.commons.io.JFiles; import io.gitlab.jfronny.commons.logger.SystemLoggerPlus; import java.awt.*; -import java.io.File; -import java.io.IOException; +import java.io.*; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.FileSystem; @@ -46,8 +45,12 @@ public class Utils { } } - public static FileSystem openZipFile(Path zip, boolean create) throws IOException, URISyntaxException { - return JFiles.openZipFile(zip, create, SYSTEM_LOADER); + public static FileSystem openZipFile(Path zip, boolean create) throws IOException { + try { + return JFiles.openZipFile(zip, create, SYSTEM_LOADER); + } catch (URISyntaxException e) { + throw new IOException("Could not access file system", e); + } } @SuppressWarnings("unused") // Called through reflection from wrapper diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8f985f3..67f7fc6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -jf-commons = "1.7-SNAPSHOT" +jf-commons = "2.0.0-SNAPSHOT" annotations = "24.0.1" lwjgl = "3.3.2" imgui = "1.86.10" @@ -34,6 +34,8 @@ javagi-adw = { module = "io.github.jwharm.javagi:adw", version.ref = "javagi" } commons = { module = "io.gitlab.jfronny:commons", version.ref = "jf-commons" } commons-http-client = { module = "io.gitlab.jfronny:commons-http-client", version.ref = "jf-commons" } commons-http-server = { module = "io.gitlab.jfronny:commons-http-server", version.ref = "jf-commons" } +commons-flow = { module = "io.gitlab.jfronny:commons-flow", version.ref = "jf-commons" } +commons-flow-backend-unsafe = { module = "io.gitlab.jfronny:commons-flow-backend-unsafe", version.ref = "jf-commons" } commons-io = { module = "io.gitlab.jfronny:commons-io", version.ref = "jf-commons" } commons-logger = { module = "io.gitlab.jfronny:commons-logger", version.ref = "jf-commons" } commons-serialize-json = { module = "io.gitlab.jfronny:commons-serialize-json", version.ref = "jf-commons" } diff --git a/launcher-cli/src/main/java/io/gitlab/jfronny/inceptum/cli/commands/ModCommand.java b/launcher-cli/src/main/java/io/gitlab/jfronny/inceptum/cli/commands/ModCommand.java index c02b6aa..652e68f 100644 --- a/launcher-cli/src/main/java/io/gitlab/jfronny/inceptum/cli/commands/ModCommand.java +++ b/launcher-cli/src/main/java/io/gitlab/jfronny/inceptum/cli/commands/ModCommand.java @@ -50,7 +50,7 @@ public class ModCommand extends Command { return; } System.out.println("Scanning installed mods, this might take a while"); - instance.mds().runOnce(ScanStage.UPDATECHECK, (path, mod) -> { + instance.mds().runOnce(ScanStage.ALL, (path, mod) -> { boolean hasSources = !mod.getMetadata().sources().isEmpty(); boolean updatable = hasSources && mod.getMetadata().sources().values().stream().anyMatch(Optional::isPresent); if (filterUpdatable && !updatable) return; diff --git a/launcher-gtk/src/main/kotlin/io/gitlab/jfronny/inceptum/gtk/window/settings/instance/InstanceSettingsWindow.kt b/launcher-gtk/src/main/kotlin/io/gitlab/jfronny/inceptum/gtk/window/settings/instance/InstanceSettingsWindow.kt index 2ca22c6..10c04fd 100644 --- a/launcher-gtk/src/main/kotlin/io/gitlab/jfronny/inceptum/gtk/window/settings/instance/InstanceSettingsWindow.kt +++ b/launcher-gtk/src/main/kotlin/io/gitlab/jfronny/inceptum/gtk/window/settings/instance/InstanceSettingsWindow.kt @@ -6,8 +6,13 @@ import org.gnome.gtk.Application class InstanceSettingsWindow(val app: Application?, val instance: Instance) : SettingsWindow(app) { init { + val claim = instance.mds.focus() addTab(GeneralTab(this), "instance.settings.general", "preferences-other-symbolic") addTab(ModsTab(this), "instance.settings.mods", "package-x-generic-symbolic") addTab(ExportTab(this), "instance.settings.export", "send-to-symbolic") + onCloseRequest { + claim.close() + false + } } } diff --git a/launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/AddModWindow.java b/launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/AddModWindow.java index 758b511..e4e1b37 100644 --- a/launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/AddModWindow.java +++ b/launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/AddModWindow.java @@ -85,7 +85,7 @@ public class AddModWindow extends Window { ImGui.text(mod.description()); ImGui.tableNextColumn(); boolean alreadyPresent = false; - for (Mod mdsMod : instance.getMods(ScanStage.CROSSREFERENCE)) { + for (Mod mdsMod : instance.mds().getMods()) { alreadyPresent = mdsMod.getMetadata().sources().keySet().stream() .anyMatch(s -> s instanceof ModrinthModSource ms && ms.getModId().equals(projectId)); if (alreadyPresent) diff --git a/launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/edit/InstanceEditWindow.java b/launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/edit/InstanceEditWindow.java index 1897c41..7c67cee 100644 --- a/launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/edit/InstanceEditWindow.java +++ b/launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/edit/InstanceEditWindow.java @@ -1,16 +1,20 @@ package io.gitlab.jfronny.inceptum.imgui.window.edit; import imgui.ImGui; +import io.gitlab.jfronny.inceptum.common.Utils; import io.gitlab.jfronny.inceptum.imgui.control.Tab; import io.gitlab.jfronny.inceptum.imgui.window.GuiUtil; import io.gitlab.jfronny.inceptum.imgui.window.Window; +import io.gitlab.jfronny.inceptum.launcher.LauncherEnv; import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance; +import java.io.Closeable; import java.io.IOException; import java.util.List; public class InstanceEditWindow extends Window { protected final Instance instance; + protected final Closeable focus; private final List tabs; protected boolean reDownload = false; protected boolean lastTabWasMods = false; @@ -19,6 +23,7 @@ public class InstanceEditWindow extends Window { super(instance.getName() + " - Edit"); this.instance = instance; this.instance.mds().start(); + this.focus = instance.mds().focus(); this.tabs = List.of( new GeneralTab(this), new ArgumentsTab(this), @@ -46,6 +51,11 @@ public class InstanceEditWindow extends Window { @Override public void close() { super.close(); + try { + focus.close(); + } catch (IOException e) { + Utils.LOGGER.error("Could not release focus on MDS", e); + } if (reDownload) { GuiUtil.reload(instance); } diff --git a/launcher/build.gradle.kts b/launcher/build.gradle.kts index 9f51cc6..d7e7371 100644 --- a/launcher/build.gradle.kts +++ b/launcher/build.gradle.kts @@ -6,4 +6,6 @@ plugins { dependencies { api(projects.common) api(libs.commons.http.server) // required for launcher-gtk for some reason + api(libs.commons.flow) + api(libs.commons.flow.backend.unsafe) } diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/gson/MicrosoftAccountAdapter.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/gson/MicrosoftAccountAdapter.java index d907a0e..4be03b4 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/gson/MicrosoftAccountAdapter.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/gson/MicrosoftAccountAdapter.java @@ -6,11 +6,11 @@ import io.gitlab.jfronny.commons.serialize.SerializeWriter; import io.gitlab.jfronny.inceptum.launcher.api.account.MicrosoftAccount; public class MicrosoftAccountAdapter { - public static > void serialize(MicrosoftAccount value, Writer writer) throws TEx, MalformedDataException { + public static > void serialize(MicrosoftAccount value, Writer writer) throws TEx, MalformedDataException { GC_MicrosoftAccountMeta.serialize(value == null ? null : value.toMeta(), writer); } - public static > MicrosoftAccount deserialize(Reader reader) throws TEx, MalformedDataException { + public static > MicrosoftAccount deserialize(Reader reader) throws TEx, MalformedDataException { MicrosoftAccountMeta meta = GC_MicrosoftAccountMeta.deserialize(reader); return meta == null ? null : new MicrosoftAccount(meta); } diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/gson/MinecraftArgumentAdapter.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/gson/MinecraftArgumentAdapter.java index 6c58902..807f1af 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/gson/MinecraftArgumentAdapter.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/gson/MinecraftArgumentAdapter.java @@ -11,11 +11,11 @@ import java.util.List; import java.util.Set; public class MinecraftArgumentAdapter { - public static > void serialize(MinecraftArgument rules, Writer writer) throws TEx { + public static > void serialize(MinecraftArgument rules, Writer writer) throws TEx { throw new UnsupportedOperationException(); } - public static > MinecraftArgument deserialize(Reader reader) throws TEx, MalformedDataException { + public static > MinecraftArgument deserialize(Reader reader) throws TEx, MalformedDataException { if (reader.peek() == Token.STRING) return new MinecraftArgument(Set.of(reader.nextString())); Rules rules = null; List value = null; diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/gson/ModMetaSourcesAdapter.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/gson/ModMetaSourcesAdapter.java index c58efcd..ac4cd1f 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/gson/ModMetaSourcesAdapter.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/gson/ModMetaSourcesAdapter.java @@ -10,13 +10,13 @@ import io.gitlab.jfronny.inceptum.launcher.system.source.ModSource; import java.util.Optional; public class ModMetaSourcesAdapter { - public static > void serialize(Sources value, Writer writer) throws TEx, MalformedDataException { + public static > void serialize(Sources value, Writer writer) throws TEx, MalformedDataException { writer.beginArray(); for (ModSource source : value.keySet()) GC_ModSource.serialize(source, writer); writer.endArray(); } - public static > Sources deserialize(Reader reader) throws TEx, MalformedDataException { + public static > Sources deserialize(Reader reader) throws TEx, MalformedDataException { reader.beginArray(); Sources sources = new Sources(); while (reader.hasNext()) { diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/gson/ModSourceAdapter.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/gson/ModSourceAdapter.java index a2d8abc..601889c 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/gson/ModSourceAdapter.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/gson/ModSourceAdapter.java @@ -10,7 +10,7 @@ import java.util.LinkedHashSet; import java.util.Set; public class ModSourceAdapter { - public static > void serialize(ModSource src, Writer writer) throws TEx { + public static > void serialize(ModSource src, Writer writer) throws TEx { writer.beginObject(); switch (src) { case ModrinthModSource mo -> writer.name("type").value("modrinth") @@ -41,7 +41,7 @@ public class ModSourceAdapter { writer.endObject(); } - public static > ModSource deserialize(Reader reader) throws TEx, MalformedDataException { + public static > ModSource deserialize(Reader reader) throws TEx, MalformedDataException { String type = null; String mr$id = null; diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/gson/RulesAdapter.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/gson/RulesAdapter.java index fad19a4..92dbc29 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/gson/RulesAdapter.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/gson/RulesAdapter.java @@ -7,11 +7,11 @@ import io.gitlab.jfronny.commons.serialize.SerializeWriter; import io.gitlab.jfronny.inceptum.launcher.model.mojang.Rules; public class RulesAdapter { - public static > void serialize(Rules rules, Writer writer) throws TEx { + public static > void serialize(Rules rules, Writer writer) throws TEx { throw new UnsupportedOperationException(); } - public static > Rules deserialize(Reader reader) throws TEx, MalformedDataException { + public static > Rules deserialize(Reader reader) throws TEx, MalformedDataException { boolean valid = true; reader.beginArray(); while (reader.hasNext()) { @@ -49,7 +49,7 @@ public class RulesAdapter { throw new MalformedDataException("Unexpected action in argument: " + actionType); } if (hasFeatures) valid = false; - if (osName != null && !OSUtils.TYPE.getMojName().equals(osName)) valid = false; + if (osName != null && !OSUtils.TYPE.mojName.equals(osName)) valid = false; if (osVersion != null && !System.getProperty("os.version").matches(osVersion)) valid = false; if (actionType.equals("disallow")) valid = !valid; } diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/model/inceptum/ModMeta.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/model/inceptum/ModMeta.java index f091ea4..7488033 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/model/inceptum/ModMeta.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/model/inceptum/ModMeta.java @@ -98,18 +98,17 @@ public record ModMeta( return res; } - public boolean updateCheck(String gameVersion) { + public boolean crossReference() { boolean modrinth = false; boolean curseforge = false; for (ModSource source : sources.keySet().toArray(ModSource[]::new)) { if (source instanceof ModrinthModSource) modrinth = true; if (source instanceof CurseforgeModSource) curseforge = true; - checkAndAddSource(source, gameVersion); } boolean changed = false; if (!modrinth) { try { - checkAndAddSource(new ModrinthModSource(ModrinthApi.getVersionByHash(sha1).id()), gameVersion); + sources.put(new ModrinthModSource(ModrinthApi.getVersionByHash(sha1).id()), Optional.empty()); changed = true; } catch (IOException e) { // not found @@ -120,7 +119,7 @@ public record ModMeta( FingerprintMatchesResponse.Result cf = CurseforgeApi.checkFingerprint(murmur2); if (!cf.exactMatches().isEmpty()) { FingerprintMatchesResponse.Result.Match f = cf.exactMatches().getFirst(); - checkAndAddSource(new CurseforgeModSource(f.id(), f.file().id()), gameVersion); + sources.put(new CurseforgeModSource(f.id(), f.file().id()), Optional.empty()); changed = true; } } catch (IOException | URISyntaxException e) { @@ -130,6 +129,12 @@ public record ModMeta( return changed; } + public void updateCheck(String gameVersion) { + for (ModSource source : sources.keySet().toArray(ModSource[]::new)) { + checkAndAddSource(source, gameVersion); + } + } + private void checkAndAddSource(ModSource source, String gameVersion) { try { sources.put(source, source.getUpdate(gameVersion)); diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/exporter/Exporter.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/exporter/Exporter.java index 06aa09b..9ea281e 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/exporter/Exporter.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/exporter/Exporter.java @@ -9,7 +9,6 @@ import io.gitlab.jfronny.inceptum.launcher.util.ProcessState; import io.gitlab.jfronny.inceptum.launcher.util.gitignore.IgnoringWalk; import java.io.IOException; -import java.net.URISyntaxException; import java.nio.file.*; import java.util.Objects; @@ -37,7 +36,7 @@ public abstract class Exporter { Manifest manifest = generateManifests(root, instance, instance.meta().instanceVersion); if (instance.isFabric()) { state.incrementStep("Adding mods"); - addMods(root, instance, instance.getMods(ScanStage.CROSSREFERENCE).stream().filter(mod -> { + addMods(root, instance, instance.completeModsScan(ScanStage.CROSSREFERENCE).stream().filter(mod -> { if (!mod.isEnabled()) return false; state.updateStep(mod.getName()); return true; @@ -58,9 +57,6 @@ public abstract class Exporter { } state.incrementStep("Cleaning up"); Files.walkFileTree(root, new CleanupFileVisitor()); - } catch (URISyntaxException se) { - // Can only be thrown by openZipFile, the target file therefore cannot exist - throw new IOException("Could not open export path", se); } catch (Throwable t) { if (Files.exists(exportPath)) Files.delete(exportPath); throw t; diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/importer/Importers.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/importer/Importers.java index 9e688c7..d9d6133 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/importer/Importers.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/importer/Importers.java @@ -28,8 +28,6 @@ public class Importers { return importer.importPack(fs.getPath("."), state); } } - } catch (URISyntaxException e) { - throw new IOException("Could not open zip", e); } throw new IOException("Could not import pack: unsupported format"); } diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/instance/Instance.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/instance/Instance.java index 2fe8681..6f8f535 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/instance/Instance.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/instance/Instance.java @@ -75,8 +75,10 @@ public record Instance(String id, Path path, InstanceMeta meta, ModsDirScanner m return path.resolve("config"); } - public Set getMods(ScanStage stage) throws IOException { - mds.runOnce(stage, R::nop); + public Set completeModsScan(ScanStage stage) throws IOException { + try (var focus = mds.focus()) { + mds.runOnce(stage, R::nop); + } return mds.getMods(); } diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/launch/InstanceLauncher.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/launch/InstanceLauncher.java index 4901ad1..238a580 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/launch/InstanceLauncher.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/launch/InstanceLauncher.java @@ -19,7 +19,6 @@ import io.gitlab.jfronny.inceptum.launcher.util.VersionInfoLibraryResolver; import java.io.File; import java.io.IOException; -import java.net.URISyntaxException; import java.nio.file.*; import java.util.*; import java.util.concurrent.atomic.AtomicReference; @@ -101,7 +100,7 @@ public class InstanceLauncher { // Fabric imods if (instance.isFabric()) { StringBuilder fabricAddMods = new StringBuilder("-Dfabric.addMods="); - for (Mod mod : instance.getMods(ScanStage.DOWNLOAD)) { + for (Mod mod : instance.completeModsScan(ScanStage.DOWNLOAD)) { if (mod.isEnabled() && mod.getNeedsInject()) { fabricAddMods.append(mod.getJarPath()); fabricAddMods.append(File.pathSeparatorChar); @@ -197,7 +196,7 @@ public class InstanceLauncher { return line.substring(linePrefix.length()); } } - } catch (IOException | URISyntaxException e) { + } catch (IOException e) { throw new LaunchException("IO Exception while trying to identify entrypoint", e); } throw new LaunchException("Could not identify entrypoint"); diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/MdsMod.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/MdsMod.java index a0439df..c4bd5b4 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/MdsMod.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/MdsMod.java @@ -21,29 +21,31 @@ import java.util.stream.Collectors; public class MdsMod extends Mod { private final ProtoInstance instance; private final Path imodPath; + private final Path initialJarPath; private final ModMeta meta; private @NotNull ScanStage scanStage = ScanStage.DISCOVER; private @Nullable DownloadAux downloadAux = null; - public MdsMod(ProtoInstance instance, Path imodPath, ModMeta meta) { + public MdsMod(ProtoInstance instance, Path imodPath, Path initialJarPath, ModMeta meta) { this.instance = instance; this.imodPath = imodPath; + this.initialJarPath = initialJarPath; this.meta = meta; } - private record DownloadAux(Path jarPath, boolean managedJar, @Nullable FabricModJson fmj) {} + private record DownloadAux(Path imodPath, Path jarPath, boolean managedJar, @Nullable FabricModJson fmj) {} - public void markDownloaded(Path jarPath, boolean managedJar, @Nullable FabricModJson fmj) { - this.downloadAux = new DownloadAux(jarPath, managedJar, fmj); + public void markDownloaded(Path imodPath, Path jarPath, boolean managedJar, @Nullable FabricModJson fmj) { + this.downloadAux = new DownloadAux(imodPath, jarPath, managedJar, fmj); this.scanStage = ScanStage.DOWNLOAD; } public void markCrossReferenced() { - this.scanStage = ScanStage.CROSSREFERENCE; + if (!this.scanStage.contains(ScanStage.CROSSREFERENCE)) this.scanStage = ScanStage.CROSSREFERENCE; } public void markUpdateChecked() { - this.scanStage = ScanStage.UPDATECHECK; + if (!this.scanStage.contains(ScanStage.UPDATECHECK)) this.scanStage = ScanStage.UPDATECHECK; } private Optional getFmj() { @@ -80,12 +82,12 @@ public class MdsMod extends Mod { @Override public Path getJarPath() { - return requireDownload().jarPath; + return downloadAux == null ? initialJarPath : requireDownload().jarPath; } @Override public Path getMetadataPath() { - return imodPath; + return downloadAux == null ? imodPath : requireDownload().imodPath; } @Override diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/ModsDirScanner.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/ModsDirScanner.java index 6911f2f..62b979f 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/ModsDirScanner.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/ModsDirScanner.java @@ -1,8 +1,8 @@ package io.gitlab.jfronny.inceptum.launcher.system.mds; 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; -import io.gitlab.jfronny.inceptum.launcher.system.mds.threaded.ThreadedMds; import io.gitlab.jfronny.inceptum.launcher.util.GameVersionParser; import java.io.Closeable; @@ -14,18 +14,20 @@ import java.util.function.BiConsumer; public interface ModsDirScanner extends Closeable { static ModsDirScanner get(Path modsDir, InstanceMeta meta) throws IOException { - if (Files.exists(modsDir)) return ThreadedMds.get(modsDir, meta); + if (Files.exists(modsDir)) return FlowMds.get(modsDir, meta);//ThreadedMds.get(modsDir, meta); return new NoopMds(GameVersionParser.getGameVersion(meta.gameVersion)); } static void closeAll() { - ThreadedMds.closeAll(); + FlowMds.closeAll();//ThreadedMds.closeAll(); } boolean isComplete(ScanStage stage); void start(); + Closeable focus(); + String getGameVersion(); Set getMods() throws IOException; diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/ScanStage.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/ScanStage.java index b0d5dde..75083a3 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/ScanStage.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/ScanStage.java @@ -20,9 +20,13 @@ public enum ScanStage implements Comparable { /** * The mod(s) have been checked for updates */ - UPDATECHECK; + UPDATECHECK, + /** + * The mod(s) have been scanned in all stages + */ + ALL; - public boolean isComplete(ScanStage stage) { + public boolean contains(ScanStage stage) { return ordinal() >= stage.ordinal(); } diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/FlowMds.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/FlowMds.java new file mode 100644 index 0000000..6bba830 --- /dev/null +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/FlowMds.java @@ -0,0 +1,195 @@ +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.tuple.Tuple; +import io.gitlab.jfronny.inceptum.common.Utils; +import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta; +import io.gitlab.jfronny.inceptum.launcher.system.instance.ModPath; +import io.gitlab.jfronny.inceptum.launcher.system.mds.*; +import io.gitlab.jfronny.inceptum.launcher.system.mds.noop.NoopMod; +import io.gitlab.jfronny.inceptum.launcher.util.GameVersionParser; +import io.gitlab.jfronny.inceptum.launcher.util.VoidClaimPool; + +import java.io.IOException; +import java.nio.file.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.nio.file.StandardWatchEventKinds.*; + +public class FlowMds implements ModsDirScanner { + private static final Map SCANNERS = new HashMap<>(); + private boolean disposed = false; + + private final MdsThreadFactory factory = new MdsThreadFactory("mds"); + + private final ProtoInstance instance; + private final WatchService service; + private final Thread th; + private final Map descriptions = new HashMap<>(); + private final Set scannedPaths = new HashSet<>(); + + private FlowMds(Path modsDir, InstanceMeta instance) throws IOException { + this.instance = new ProtoInstance(modsDir, this, instance); + this.th = factory.newThread(this::scanTaskInternal, modsDir.getParent().getFileName().toString()); + this.service = FileSystems.getDefault().newWatchService(); + modsDir.register(service, ENTRY_MODIFY, ENTRY_CREATE, ENTRY_DELETE); + } + + public static FlowMds get(Path modsDir, InstanceMeta instance) throws IOException { + if (SCANNERS.containsKey(modsDir)) { + FlowMds mds = SCANNERS.get(modsDir); + if (mds.instance.meta().equals(instance)) return mds; + mds.close(); + } + FlowMds mds = new FlowMds(modsDir, instance); + SCANNERS.put(modsDir, mds); + return mds; + } + + @Override + public boolean isComplete(ScanStage stage) { + if (!Files.isDirectory(instance.modsDir())) return true; + try { + for (Path path : JFiles.list(instance.modsDir())) { + Mod mod = descriptions.get(path); + if (mod == null || mod.getScanStage().ordinal() < stage.ordinal()) return false; + } + } catch (IOException e) { + Utils.LOGGER.error("Could not list files in mods dir", e); + } + return true; + } + + @Override + public void start() { + if (!th.isAlive()) th.start(); + } + + @Override + public VoidClaimPool.Claim focus() { + return factory.focusClaim.claim(); + } + + @Override + public String getGameVersion() { + return GameVersionParser.getGameVersion(instance.meta().gameVersion); + } + + @Override + public Set getMods() throws IOException { + Set mods = new TreeSet<>(); + if (Files.isDirectory(instance.modsDir())) { + for (Path path : JFiles.list(instance.modsDir())) { + if (ModPath.isImod(path) && Files.exists(ModPath.trimImod(path))) + continue; + mods.add(get(path)); + } + } + return mods; + } + + @Override + public Mod get(Path path) { + if (!Files.isRegularFile(path)) return null; + if (!descriptions.containsKey(path)) return new NoopMod(path); // not yet scanned + return descriptions.get(path); + } + + @Override + public void invalidate(Path path) { + descriptions.remove(path); + scannedPaths.remove(path); + } + + @Override + public boolean hasScanned(Path path) { + return scannedPaths.contains(path) || scannedPaths.contains(ModPath.appendImod(path)); + } + + private void scanTaskInternal() { + while (!disposed) { + runOnce(ScanStage.ALL, R::nop); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + private Set getToScan() throws IOException { + if (descriptions.isEmpty()) return Set.copyOf(JFiles.list(instance.modsDir())); + Set toScan = new HashSet<>(); + WatchKey key = service.poll(); + if (key != null) { + for (WatchEvent event : key.pollEvents()) { + if (event.context() instanceof Path p) { + toScan.add(instance.modsDir().resolve(p)); + } + } + if (!key.reset()) Utils.LOGGER.error("Could not reset watch key"); + } + JFiles.listTo(instance.modsDir(), path -> { + if (!descriptions.containsKey(path)) toScan.add(path); + }); + return toScan; + } + + @Override + public void runOnce(ScanStage targetStage, BiConsumer 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 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); + } + } + } catch (IOException e) { + Utils.LOGGER.error("Could not scan file for mod info", e); + } + } + + public static void closeAll() { + for (FlowMds value : SCANNERS.values().toArray(FlowMds[]::new)) { + try { + value.close(); + } catch (IOException e) { + Utils.LOGGER.error("Could not close MDS", e); + } + } + MdsThreadFactory.scheduler.shutdown(); + } + + @Override + public void close() throws IOException { + disposed = true; + service.close(); + SCANNERS.remove(instance.modsDir()); + } +} diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/MdsCrossReferenceTask.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/MdsCrossReferenceTask.java new file mode 100644 index 0000000..9e16b7f --- /dev/null +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/MdsCrossReferenceTask.java @@ -0,0 +1,22 @@ +package io.gitlab.jfronny.inceptum.launcher.system.mds.flow; + +import io.gitlab.jfronny.commons.http.client.HttpClient; +import io.gitlab.jfronny.commons.throwable.ThrowingConsumer; +import io.gitlab.jfronny.inceptum.common.GsonPreset; +import io.gitlab.jfronny.inceptum.launcher.model.inceptum.GC_ModMeta; +import io.gitlab.jfronny.inceptum.launcher.system.mds.*; + +import java.io.IOException; + +public record MdsCrossReferenceTask(ProtoInstance instance) implements ThrowingConsumer { + @Override + public void accept(MdsMod mod) throws IOException { + if (mod.getScanStage().contains(ScanStage.CROSSREFERENCE)) return; + if (HttpClient.wasOnline()) { + if (mod.getMetadata().crossReference()) { + GC_ModMeta.serialize(mod.getMetadata(), mod.getMetadataPath(), GsonPreset.CONFIG); + } + mod.markCrossReferenced(); + } + } +} diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/MdsDiscoverTask.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/MdsDiscoverTask.java new file mode 100644 index 0000000..f03fdaf --- /dev/null +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/MdsDiscoverTask.java @@ -0,0 +1,34 @@ +package io.gitlab.jfronny.inceptum.launcher.system.mds.flow; + +import io.gitlab.jfronny.commons.throwable.ThrowingFunction; +import io.gitlab.jfronny.inceptum.common.GsonPreset; +import io.gitlab.jfronny.inceptum.launcher.model.inceptum.GC_ModMeta; +import io.gitlab.jfronny.inceptum.launcher.model.inceptum.ModMeta; +import io.gitlab.jfronny.inceptum.launcher.system.instance.ModPath; +import io.gitlab.jfronny.inceptum.launcher.system.mds.*; +import io.gitlab.jfronny.inceptum.launcher.system.mds.noop.NoopMod; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public record MdsDiscoverTask(ProtoInstance instance, String gameVersion) implements ThrowingFunction { + @Override + public Mod apply(Path file) throws IOException { + if (!Files.exists(file)) return null; + if (Files.isDirectory(file)) return null; // Directories are not supported + if (ModPath.isJar(file)) return discover(file, ModPath.appendImod(file)); + else if (ModPath.isImod(file)) return discover(ModPath.trimImod(file), file); + else return new NoopMod(file); + } + + private Mod discover(Path jarPath, Path imodPath) throws IOException { + ModMeta meta; + if (Files.exists(imodPath)) meta = GC_ModMeta.deserialize(imodPath, GsonPreset.CONFIG); + else { + meta = ModMeta.fromJar(jarPath); + GC_ModMeta.serialize(meta, imodPath, GsonPreset.CONFIG); + } + return new MdsMod(instance, imodPath, jarPath, meta); + } +} diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/MdsDownloadTask.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/MdsDownloadTask.java new file mode 100644 index 0000000..df19e01 --- /dev/null +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/MdsDownloadTask.java @@ -0,0 +1,53 @@ +package io.gitlab.jfronny.inceptum.launcher.system.mds.flow; + +import io.gitlab.jfronny.commons.http.client.HttpClient; +import io.gitlab.jfronny.commons.throwable.ThrowingConsumer; +import io.gitlab.jfronny.inceptum.common.GsonPreset; +import io.gitlab.jfronny.inceptum.common.Utils; +import io.gitlab.jfronny.inceptum.launcher.model.fabric.FabricModJson; +import io.gitlab.jfronny.inceptum.launcher.model.fabric.GC_FabricModJson; +import io.gitlab.jfronny.inceptum.launcher.system.instance.ModPath; +import io.gitlab.jfronny.inceptum.launcher.system.mds.*; +import io.gitlab.jfronny.inceptum.launcher.system.source.ModSource; + +import java.io.IOException; +import java.nio.file.*; + +public record MdsDownloadTask(ProtoInstance instance) implements ThrowingConsumer { + @Override + public void accept(MdsMod mod) throws IOException { + if (mod.getScanStage().contains(ScanStage.DOWNLOAD)) return; + ModSource selectedSource = null; + for (ModSource source : mod.getMetadata().sources().keySet()) { + if (!Files.exists(source.getJarPath()) && HttpClient.wasOnline()) source.download(); + selectedSource = source; + } + Path imodPath = mod.getMetadataPath(); + Path jarPath = mod.getJarPath(); + boolean managed = false; + if (selectedSource != null) { + if (jarPath.startsWith(instance.modsDir()) && Files.exists(jarPath)) { + if (Files.exists(selectedSource.getJarPath())) { + Files.delete(jarPath); + Path newImod = imodPath.getParent().resolve(selectedSource.getShortName() + ModPath.EXT_IMOD); + Files.move(imodPath, newImod); + imodPath = newImod; + jarPath = selectedSource.getJarPath(); + managed = true; + } + } else { + jarPath = selectedSource.getJarPath(); + managed = true; + } + } else if (!Files.exists(jarPath)) throw new IOException("Mod has no jar and no sources"); + + FabricModJson fmj; + try (FileSystem fs = Utils.openZipFile(jarPath, false)) { + Path fmjPath = fs.getPath("fabric.mod.json"); + if (Files.exists(fmjPath)) fmj = GC_FabricModJson.deserialize(fmjPath, GsonPreset.API); + else fmj = null; + } + + mod.markDownloaded(imodPath, jarPath, managed, fmj); + } +} diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/MdsPipeline.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/MdsPipeline.java new file mode 100644 index 0000000..243d7fe --- /dev/null +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/MdsPipeline.java @@ -0,0 +1,68 @@ +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 java.io.IOException; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +public class MdsPipeline { + private final Set, IOException>>> functions = new HashSet<>(); + + public void addTask(ScanStage stage, ThrowingConsumer task) { + functions.add(Tuple.of(stage, tuple -> { + if (tuple.right() instanceof MdsMod mmod) task.accept(mmod); + })); + } + + public void addTask(ScanStage stage, ThrowingBiConsumer task) { + functions.add(Tuple.of(stage, tuple -> { + if (tuple.right() instanceof MdsMod mmod) task.accept(tuple.left(), mmod); + })); + } + + private void work(MdsThreadFactory factory, Tuple tuple, Queue, IOException>>> tasks, Consumer fail) { + if (tasks.isEmpty()) return; + Tuple, IOException>> task = tasks.poll(); + factory.newThread(MdsThreadFactory.prioritize( + task.right() + .compose(() -> tuple) + .andThen(() -> { + work(factory, tuple, tasks, fail); + }).addHandler(fail), + task.left() + )).start(); + } + + public List> run(MdsThreadFactory factory, Set paths, ThrowingFunction seed) { + return paths.stream().map(s -> { + CompletableFuture finished = new CompletableFuture<>(); + Queue, IOException>>> taskQueue = new LinkedList<>(functions); + taskQueue.add(Tuple.of(ScanStage.NONE, tuple -> finished.complete(null))); + Consumer fail = finished::completeExceptionally; + factory.newThread(MdsThreadFactory.prioritize(() -> { + seed.andThen(mod -> { + work(factory, Tuple.of(s, mod), taskQueue, fail); + }).addHandler(fail).accept(s); + }, ScanStage.DISCOVER)).start(); + return finished; + }).toList(); + } + + public List> run(MdsThreadFactory factory, Set> mods) { + return mods.stream().map(s -> { + CompletableFuture finished = new CompletableFuture<>(); + Queue, IOException>>> taskQueue = new LinkedList<>(functions); + taskQueue.add(Tuple.of(ScanStage.NONE, tuple -> finished.complete(null))); + Consumer fail = finished::completeExceptionally; + factory.newThread(MdsThreadFactory.prioritize(() -> { + work(factory, s, taskQueue, fail); + }, ScanStage.DISCOVER)).start(); + return finished; + }).toList(); + } +} diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/MdsThreadFactory.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/MdsThreadFactory.java new file mode 100644 index 0000000..2049cec --- /dev/null +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/MdsThreadFactory.java @@ -0,0 +1,69 @@ +package io.gitlab.jfronny.inceptum.launcher.system.mds.flow; + +import io.gitlab.jfronny.commons.flow.*; +import io.gitlab.jfronny.inceptum.launcher.system.mds.ScanStage; +import io.gitlab.jfronny.inceptum.launcher.util.VoidClaimPool; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +public class MdsThreadFactory implements ThreadFactory { + protected static final ThreadPoolExecutor scheduler = DefaultSchedulers.createPriorityScheduler(); + + private static final AtomicInteger poolNumber = new AtomicInteger(1); + public final VoidClaimPool focusClaim = new VoidClaimPool(this::focus, this::defocus); + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final String namePrefix; + private final Set ownedRunnables = ConcurrentHashMap.newKeySet(); + + public MdsThreadFactory(String name) { + this.namePrefix = name + "-" + poolNumber.getAndIncrement() + "-"; + } + + public static Runnable prioritize(Runnable runnable, ScanStage stage) { + return PrioritizedRunnable.of(runnable, 5 - stage.ordinal()); + } + + @Override + public Thread newThread(@NotNull Runnable runnable) { + return newThread(runnable, null); + } + + public Thread newThread(@NotNull Runnable runnable, @Nullable String name) { + int priority = runnable instanceof PrioritizedRunnable pr ? pr.getPriority() : 0; + OwnedThread ownedRunnable = new OwnedThread(runnable, new Thread[1], priority, this); + Thread t = ScheduledVirtualThreadBuilder.ofVirtual(scheduler) + .name(this.namePrefix + this.threadNumber.getAndIncrement() + (name == null ? "": "-" + name)) + .unstarted(ownedRunnable); + ownedRunnable.pfThread[0] = t; + ownedRunnables.add(ownedRunnable); + ScheduledVirtualThreadBuilder.setPriority(t, priority); + return t; + } + + record OwnedThread(Runnable runnable, Thread[] pfThread, int basePriority, MdsThreadFactory pool) implements Runnable { + int priority(boolean focus) { + return focus ? basePriority + 5 : basePriority; + } + + @Override + public void run() { + try { + runnable.run(); + } finally { + pool.ownedRunnables.remove(this); + } + } + } + + private void focus() { + ownedRunnables.forEach(r -> ScheduledVirtualThreadBuilder.setPriority(r.pfThread[0], r.priority(true))); + } + + private void defocus() { + ownedRunnables.forEach(r -> ScheduledVirtualThreadBuilder.setPriority(r.pfThread[0], r.priority(false))); + } +} diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/MdsUpdateTask.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/MdsUpdateTask.java new file mode 100644 index 0000000..0dbfcff --- /dev/null +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/flow/MdsUpdateTask.java @@ -0,0 +1,18 @@ +package io.gitlab.jfronny.inceptum.launcher.system.mds.flow; + +import io.gitlab.jfronny.commons.http.client.HttpClient; +import io.gitlab.jfronny.commons.throwable.ThrowingConsumer; +import io.gitlab.jfronny.inceptum.launcher.system.mds.*; + +import java.io.IOException; + +public record MdsUpdateTask(ProtoInstance instance, String gameVersion) implements ThrowingConsumer { + @Override + public void accept(MdsMod mod) throws IOException { + if (mod.getScanStage().contains(ScanStage.UPDATECHECK)) return; + if (HttpClient.wasOnline()) { + mod.getMetadata().updateCheck(gameVersion); + mod.markUpdateChecked(); + } + } +} diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/noop/NoopMds.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/noop/NoopMds.java index 722b08d..78f701d 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/noop/NoopMds.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/noop/NoopMds.java @@ -1,7 +1,10 @@ package io.gitlab.jfronny.inceptum.launcher.system.mds.noop; +import io.gitlab.jfronny.commons.ref.R; import io.gitlab.jfronny.inceptum.launcher.system.mds.*; +import io.gitlab.jfronny.inceptum.launcher.util.VoidClaimPool; +import java.io.Closeable; import java.io.IOException; import java.nio.file.Path; import java.util.Set; @@ -21,6 +24,11 @@ public record NoopMds(String gameVersion) implements ModsDirScanner { public void start() { } + @Override + public Closeable focus() { + return R::nop; + } + @Override public String getGameVersion() { return gameVersion; diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/noop/NoopMod.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/noop/NoopMod.java index 7b1a757..b57d1dc 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/noop/NoopMod.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/noop/NoopMod.java @@ -48,7 +48,7 @@ public class NoopMod extends Mod { @Override public ScanStage getScanStage() { - return ScanStage.UPDATECHECK; + return ScanStage.DISCOVER; } @Override diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/threaded/FileScanTask.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/threaded/FileScanTask.java index 0766668..13b6561 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/threaded/FileScanTask.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/threaded/FileScanTask.java @@ -33,19 +33,20 @@ public record FileScanTask(ProtoInstance instance, Path file, BiConsumer artifacts = new LinkedHashSet<>(); for (VersionInfo.Library library : version.libraries) { if (library.rules() != null && !library.rules().allow()) continue; - if (library.downloads().classifiers() != null && library.natives() != null && library.natives().containsKey(OSUtils.TYPE.getMojName())) { - artifacts.add(new ArtifactInfo(library.downloads().classifiers().get(library.natives().get(OSUtils.TYPE.getMojName())), true)); + if (library.downloads().classifiers() != null && library.natives() != null && library.natives().containsKey(OSUtils.TYPE.mojName)) { + artifacts.add(new ArtifactInfo(library.downloads().classifiers().get(library.natives().get(OSUtils.TYPE.mojName)), true)); } if (library.downloads().artifact() == null) { Utils.LOGGER.info("Null library artifact @ " + library.name()); diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/util/VoidClaimPool.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/util/VoidClaimPool.java new file mode 100644 index 0000000..0649478 --- /dev/null +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/util/VoidClaimPool.java @@ -0,0 +1,39 @@ +package io.gitlab.jfronny.inceptum.launcher.util; + +import java.io.Closeable; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +public class VoidClaimPool { + private final AtomicInteger content = new AtomicInteger(0); + private final Runnable onClaim; + private final Runnable onRelease; + + public VoidClaimPool(Runnable onClaim, Runnable onRelease) { + this.onClaim = onClaim; + this.onRelease = onRelease; + } + + public Claim claim() { + return new Claim(); + } + + public boolean isEmpty() { + return content.get() == 0; + } + + public class Claim implements Closeable { + private final AtomicBoolean active = new AtomicBoolean(true); + + private Claim() { + if (content.getAndIncrement() == 0) onClaim.run(); + } + + @Override + public void close() { + if (!active.getAndSet(false)) + throw new UnsupportedOperationException("Cannot release claim that is already released"); + if (content.decrementAndGet() == 0) onRelease.run(); + } + } +} diff --git a/launcher/src/main/java/module-info.java b/launcher/src/main/java/module-info.java index 9773cf8..73de6f6 100644 --- a/launcher/src/main/java/module-info.java +++ b/launcher/src/main/java/module-info.java @@ -30,4 +30,5 @@ module io.gitlab.jfronny.inceptum.launcher { requires static org.jetbrains.annotations; requires static io.gitlab.jfronny.commons.serialize.generator.annotations; requires io.gitlab.jfronny.commons.serialize; + requires io.gitlab.jfronny.commons.flow; } \ No newline at end of file