feat: cache detected game version

This commit is contained in:
huanghongxun 2021-08-21 14:41:56 +08:00
parent ddd0f83936
commit 7da9d0f283
13 changed files with 62 additions and 28 deletions

View File

@ -133,7 +133,7 @@ public final class LauncherHelper {
HMCLGameRepository repository = profile.getRepository(); HMCLGameRepository repository = profile.getRepository();
DefaultDependencyManager dependencyManager = profile.getDependency(); DefaultDependencyManager dependencyManager = profile.getDependency();
Version version = MaintainTask.maintain(repository, repository.getResolvedVersion(selectedVersion)); Version version = MaintainTask.maintain(repository, repository.getResolvedVersion(selectedVersion));
Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version)); Optional<String> gameVersion = repository.getGameVersion(version);
boolean integrityCheck = repository.unmarkVersionLaunchedAbnormally(selectedVersion); boolean integrityCheck = repository.unmarkVersionLaunchedAbnormally(selectedVersion);
CountDownLatch launchingLatch = new CountDownLatch(1); CountDownLatch launchingLatch = new CountDownLatch(1);
@ -311,7 +311,7 @@ public final class LauncherHelper {
// Without onAccept called, the launching operation will be terminated. // Without onAccept called, the launching operation will be terminated.
VersionNumber gameVersion = VersionNumber.asVersion(GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse("Unknown")); VersionNumber gameVersion = VersionNumber.asVersion(profile.getRepository().getGameVersion(version).orElse("Unknown"));
JavaVersion java = setting.getJavaVersion(); JavaVersion java = setting.getJavaVersion();
if (java == null) { if (java == null) {
Controllers.dialog(i18n("launch.wrong_javadir"), i18n("message.warning"), MessageType.WARNING, onAccept); Controllers.dialog(i18n("launch.wrong_javadir"), i18n("message.warning"), MessageType.WARNING, onAccept);

View File

@ -26,7 +26,6 @@ import javafx.scene.control.Control;
import javafx.scene.control.Skin; import javafx.scene.control.Skin;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.mod.ModpackConfiguration; import org.jackhuang.hmcl.mod.ModpackConfiguration;
import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.util.i18n.I18n;
@ -41,8 +40,6 @@ import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.MINECRAFT;
import static org.jackhuang.hmcl.util.Lang.handleUncaught; import static org.jackhuang.hmcl.util.Lang.handleUncaught;
import static org.jackhuang.hmcl.util.Lang.threadPool; import static org.jackhuang.hmcl.util.Lang.threadPool;
import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
import static org.jackhuang.hmcl.util.StringUtils.removeSuffix;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class GameItem extends Control { public class GameItem extends Control {
@ -61,7 +58,7 @@ public class GameItem extends Control {
this.version = id; this.version = id;
// GameVersion.minecraftVersion() is a time-costing job (up to ~200 ms) // GameVersion.minecraftVersion() is a time-costing job (up to ~200 ms)
CompletableFuture.supplyAsync(() -> GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse(i18n("message.unknown")), POOL_VERSION_RESOLVE) CompletableFuture.supplyAsync(() -> profile.getRepository().getGameVersion(id).orElse(i18n("message.unknown")), POOL_VERSION_RESOLVE)
.thenAcceptAsync(game -> { .thenAcceptAsync(game -> {
StringBuilder libraries = new StringBuilder(game); StringBuilder libraries = new StringBuilder(game);
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(id)); LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(id));
@ -72,7 +69,7 @@ public class GameItem extends Control {
if (I18n.hasKey("install.installer." + libraryId)) { if (I18n.hasKey("install.installer." + libraryId)) {
libraries.append(", ").append(i18n("install.installer." + libraryId)); libraries.append(", ").append(i18n("install.installer." + libraryId));
if (libraryVersion != null) if (libraryVersion != null)
libraries.append(": ").append(modifyVersion("", libraryVersion.replaceAll("(?i)" + libraryId, ""))); libraries.append(": ").append(LibraryAnalyzer.modifyVersion(game, libraryVersion.replaceAll("(?i)" + libraryId, "")));
} }
} }
@ -123,8 +120,4 @@ public class GameItem extends Control {
public ObjectProperty<Image> imageProperty() { public ObjectProperty<Image> imageProperty() {
return image; return image;
} }
private static String modifyVersion(String gameVersion, String version) {
return removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_");
}
} }

View File

@ -22,7 +22,6 @@ import javafx.scene.Node;
import javafx.scene.control.Skin; import javafx.scene.control.Skin;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Schedulers;
@ -70,7 +69,7 @@ public class InstallerListPage extends ListPageBase<InstallerItem> implements Ve
this.gameVersion = null; this.gameVersion = null;
CompletableFuture.supplyAsync(() -> { CompletableFuture.supplyAsync(() -> {
gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse(null); gameVersion = profile.getRepository().getGameVersion(version).orElse(null);
return LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(versionId)); return LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(versionId));
}).thenAcceptAsync(analyzer -> { }).thenAcceptAsync(analyzer -> {

View File

@ -23,7 +23,6 @@ import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.game.World; import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.setting.Theme;
@ -87,7 +86,7 @@ public class WorldListPage extends ListPageBase<WorldListItem> implements Versio
setLoading(true); setLoading(true);
return CompletableFuture return CompletableFuture
.runAsync(() -> gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse(null)) .runAsync(() -> gameVersion = profile.getRepository().getGameVersion(id).orElse(null))
.thenApplyAsync(unused -> { .thenApplyAsync(unused -> {
try (Stream<World> stream = World.getWorlds(savesDir)) { try (Stream<World> stream = World.getWorlds(savesDir)) {
return stream.parallel().collect(Collectors.toList()); return stream.parallel().collect(Collectors.toList());

View File

@ -23,7 +23,6 @@ import org.jackhuang.hmcl.download.game.GameDownloadTask;
import org.jackhuang.hmcl.download.game.GameLibrariesTask; import org.jackhuang.hmcl.download.game.GameLibrariesTask;
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask; import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
import org.jackhuang.hmcl.game.DefaultGameRepository; import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.game.Library; import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
@ -99,7 +98,7 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
return Task.composeAsync(() -> { return Task.composeAsync(() -> {
List<Task<?>> tasks = new ArrayList<>(); List<Task<?>> tasks = new ArrayList<>();
Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version)); Optional<String> gameVersion = repository.getGameVersion(version);
if (!gameVersion.isPresent()) return null; if (!gameVersion.isPresent()) return null;
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version.resolvePreservingPatches(getGameRepository())); LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version.resolvePreservingPatches(getGameRepository()));

View File

@ -29,6 +29,8 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.jackhuang.hmcl.util.Pair.pair; import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
import static org.jackhuang.hmcl.util.StringUtils.removeSuffix;
public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMark> { public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMark> {
private Version version; private Version version;
@ -153,6 +155,10 @@ public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMa
return mainClass != null && (LAUNCH_WRAPPER_MAIN.equals(mainClass) || mainClass.startsWith("net.fabricmc") || mainClass.startsWith("cpw.mods")); return mainClass != null && (LAUNCH_WRAPPER_MAIN.equals(mainClass) || mainClass.startsWith("net.fabricmc") || mainClass.startsWith("cpw.mods"));
} }
public static String modifyVersion(String gameVersion, String version) {
return removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_");
}
public enum LibraryType { public enum LibraryType {
MINECRAFT(true, "game", Pattern.compile("^$"), Pattern.compile("^$")), MINECRAFT(true, "game", Pattern.compile("^$"), Pattern.compile("^$")),
FABRIC(true, "fabric", Pattern.compile("net\\.fabricmc"), Pattern.compile("fabric-loader")), FABRIC(true, "fabric", Pattern.compile("net\\.fabricmc"), Pattern.compile("fabric-loader")),

View File

@ -22,7 +22,6 @@ import org.jackhuang.hmcl.download.DependencyManager;
import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.download.VersionMismatchException; import org.jackhuang.hmcl.download.VersionMismatchException;
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask; import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
@ -132,7 +131,7 @@ public final class ForgeInstallTask extends Task<Version> {
* @throws VersionMismatchException if required game version of installer does not match the actual one. * @throws VersionMismatchException if required game version of installer does not match the actual one.
*/ */
public static boolean detectForgeInstallerType(DependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException { public static boolean detectForgeInstallerType(DependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException {
Optional<String> gameVersion = GameVersion.minecraftVersion(dependencyManager.getGameRepository().getVersionJar(version)); Optional<String> gameVersion = dependencyManager.getGameRepository().getGameVersion(version);
if (!gameVersion.isPresent()) throw new IOException(); if (!gameVersion.isPresent()) throw new IOException();
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {
String installProfileText = FileUtils.readText(fs.getPath("install_profile.json")); String installProfileText = FileUtils.readText(fs.getPath("install_profile.json"));
@ -165,7 +164,7 @@ public final class ForgeInstallTask extends Task<Version> {
* @throws VersionMismatchException if required game version of installer does not match the actual one. * @throws VersionMismatchException if required game version of installer does not match the actual one.
*/ */
public static Task<Version> install(DefaultDependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException { public static Task<Version> install(DefaultDependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException {
Optional<String> gameVersion = GameVersion.minecraftVersion(dependencyManager.getGameRepository().getVersionJar(version)); Optional<String> gameVersion = dependencyManager.getGameRepository().getGameVersion(version);
if (!gameVersion.isPresent()) throw new IOException(); if (!gameVersion.isPresent()) throw new IOException();
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {
String installProfileText = FileUtils.readText(fs.getPath("install_profile.json")); String installProfileText = FileUtils.readText(fs.getPath("install_profile.json"));

View File

@ -208,8 +208,7 @@ public final class OptiFineInstallTask extends Task<Version> {
* @throws VersionMismatchException if required game version of installer does not match the actual one. * @throws VersionMismatchException if required game version of installer does not match the actual one.
*/ */
public static Task<Version> install(DefaultDependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException { public static Task<Version> install(DefaultDependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException {
File jar = dependencyManager.getGameRepository().getVersionJar(version); Optional<String> gameVersion = dependencyManager.getGameRepository().getGameVersion(version);
Optional<String> gameVersion = GameVersion.minecraftVersion(jar);
if (!gameVersion.isPresent()) throw new IOException(); if (!gameVersion.isPresent()) throw new IOException();
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {
Path configClass = fs.getPath("Config.class"); Path configClass = fs.getPath("Config.class");

View File

@ -50,6 +50,7 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -64,6 +65,7 @@ public class DefaultGameRepository implements GameRepository {
private File baseDirectory; private File baseDirectory;
protected Map<String, Version> versions; protected Map<String, Version> versions;
private ConcurrentHashMap<File, Optional<String>> gameVersions = new ConcurrentHashMap<>();
public DefaultGameRepository(File baseDirectory) { public DefaultGameRepository(File baseDirectory) {
this.baseDirectory = baseDirectory; this.baseDirectory = baseDirectory;
@ -136,6 +138,21 @@ public class DefaultGameRepository implements GameRepository {
return new File(getVersionRoot(id), id + ".jar"); return new File(getVersionRoot(id), id + ".jar");
} }
@Override
public Optional<String> getGameVersion(Version version) {
// This implementation may cause multiple flows against the same version entering
// this function, which is accepted because GameVersion::minecraftVersion should
// be consistent.
File versionJar = getVersionJar(version);
if (gameVersions.containsKey(versionJar)) {
return gameVersions.get(versionJar);
} else {
Optional<String> gameVersion = GameVersion.minecraftVersion(versionJar);
gameVersions.put(versionJar, gameVersion);
return gameVersion;
}
}
@Override @Override
public File getNativeDirectory(String id) { public File getNativeDirectory(String id) {
return new File(getVersionRoot(id), "natives"); return new File(getVersionRoot(id), "natives");
@ -321,6 +338,7 @@ public class DefaultGameRepository implements GameRepository {
} }
} }
this.gameVersions.clear();
this.versions = versions; this.versions = versions;
} }

View File

@ -24,6 +24,7 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Optional;
/** /**
* Supports operations on versioning. * Supports operations on versioning.
@ -134,6 +135,30 @@ public interface GameRepository extends VersionProvider {
*/ */
File getVersionJar(Version version); File getVersionJar(Version version);
/**
* Detect game version.
*
* This method is time-consuming, but the result will be cached.
* Consider running this job in IO scheduler.
*
* @param version version
* @return game version, or empty if an error occurred in detection.
*/
Optional<String> getGameVersion(Version version);
/**
* Detect game version.
*
* This method is time-consuming, but the result will be cached.
* Consider running this job in IO scheduler.
*
* @param versionId id of version
* @return game version, or empty if an error occurred in detection.
*/
default Optional<String> getGameVersion(String versionId) throws VersionNotFoundException {
return getGameVersion(getVersion(versionId));
}
/** /**
* Get minecraft jar * Get minecraft jar
* *

View File

@ -19,7 +19,6 @@ package org.jackhuang.hmcl.mod.mcbbs;
import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.game.DefaultGameRepository; import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.game.Library; import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.mod.ModAdviser; import org.jackhuang.hmcl.mod.ModAdviser;
import org.jackhuang.hmcl.mod.Modpack; import org.jackhuang.hmcl.mod.Modpack;
@ -81,7 +80,7 @@ public class McbbsModpackExportTask extends Task<Void> {
}); });
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(version)); LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(version));
String gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version)) String gameVersion = repository.getGameVersion(version)
.orElseThrow(() -> new IOException("Cannot parse the version of " + version)); .orElseThrow(() -> new IOException("Cannot parse the version of " + version));
List<McbbsModpackManifest.Addon> addons = new ArrayList<>(); List<McbbsModpackManifest.Addon> addons = new ArrayList<>();

View File

@ -19,7 +19,6 @@ package org.jackhuang.hmcl.mod.multimc;
import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.game.DefaultGameRepository; import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.mod.ModAdviser; import org.jackhuang.hmcl.mod.ModAdviser;
import org.jackhuang.hmcl.mod.Modpack; import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.mod.ModpackExportInfo; import org.jackhuang.hmcl.mod.ModpackExportInfo;
@ -72,7 +71,7 @@ public class MultiMCModpackExportTask extends Task<Void> {
zip.putDirectory(repository.getRunDirectory(versionId).toPath(), ".minecraft", path -> Modpack.acceptFile(path, blackList, whitelist)); zip.putDirectory(repository.getRunDirectory(versionId).toPath(), ".minecraft", path -> Modpack.acceptFile(path, blackList, whitelist));
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(versionId)); LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(versionId));
String gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(versionId)) String gameVersion = repository.getGameVersion(versionId)
.orElseThrow(() -> new IOException("Cannot parse the version of " + versionId)); .orElseThrow(() -> new IOException("Cannot parse the version of " + versionId));
List<MultiMCManifest.MultiMCManifestComponent> components = new ArrayList<>(); List<MultiMCManifest.MultiMCManifestComponent> components = new ArrayList<>();
components.add(new MultiMCManifest.MultiMCManifestComponent(true, false, "net.minecraft", gameVersion)); components.add(new MultiMCManifest.MultiMCManifestComponent(true, false, "net.minecraft", gameVersion));

View File

@ -19,7 +19,6 @@ package org.jackhuang.hmcl.mod.server;
import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.game.DefaultGameRepository; import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.mod.ModAdviser; import org.jackhuang.hmcl.mod.ModAdviser;
import org.jackhuang.hmcl.mod.Modpack; import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.mod.ModpackConfiguration; import org.jackhuang.hmcl.mod.ModpackConfiguration;
@ -81,7 +80,7 @@ public class ServerModpackExportTask extends Task<Void> {
}); });
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(versionId)); LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(versionId));
String gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(versionId)) String gameVersion = repository.getGameVersion(versionId)
.orElseThrow(() -> new IOException("Cannot parse the version of " + versionId)); .orElseThrow(() -> new IOException("Cannot parse the version of " + versionId));
List<ServerModpackManifest.Addon> addons = new ArrayList<>(); List<ServerModpackManifest.Addon> addons = new ArrayList<>();
addons.add(new ServerModpackManifest.Addon(MINECRAFT.getPatchId(), gameVersion)); addons.add(new ServerModpackManifest.Addon(MINECRAFT.getPatchId(), gameVersion));