diff --git a/src/main/java/org/terasology/launcher/game/GameService.java b/src/main/java/org/terasology/launcher/game/GameService.java index ad066401..10b6fe3a 100644 --- a/src/main/java/org/terasology/launcher/game/GameService.java +++ b/src/main/java/org/terasology/launcher/game/GameService.java @@ -118,7 +118,7 @@ public void restart() { * @throws RuntimeException when required files in the game directory are missing or inaccessible */ @Override - protected RunGameTask createTask() { + protected RunGameTask createTask() throws GameVersionNotSupportedException{ verifyNotNull(settings); GameStarter starter; diff --git a/src/main/java/org/terasology/launcher/game/GameStarter.java b/src/main/java/org/terasology/launcher/game/GameStarter.java index b300d306..00d2f5eb 100644 --- a/src/main/java/org/terasology/launcher/game/GameStarter.java +++ b/src/main/java/org/terasology/launcher/game/GameStarter.java @@ -26,10 +26,9 @@ final class GameStarter implements Callable { private static final Logger logger = LoggerFactory.getLogger(GameStarter.class); final ProcessBuilder processBuilder; - private final Semver engineVersion; /** - * @param installation the directory under which we will find {@code libs/Terasology.jar}, also used as the process's + * @param installation the directory under which we will find {@code libs/Terasology.jar}, also used as the process's * working directory * @param gameDataDirectory {@code -homedir}, the directory where Terasology's data files (saves & etc) are kept * @param heapMin java's {@code -Xms} @@ -39,14 +38,14 @@ final class GameStarter implements Callable { * @param logLevel the minimum level of log events Terasology will include on its output stream to us */ GameStarter(Installation installation, Path gameDataDirectory, JavaHeapSize heapMin, JavaHeapSize heapMax, - List javaParams, List gameParams, Level logLevel) throws IOException { - engineVersion = installation.getEngineVersion(); + List javaParams, List gameParams, Level logLevel) throws IOException, GameVersionNotSupportedException { + Semver engineVersion = installation.getEngineVersion(); var gamePath = installation.path; final boolean isMac = Platform.getPlatform().isMac(); final List processParameters = new ArrayList<>(); - processParameters.add(getRuntimePath().toString()); + processParameters.add(getRuntimePath(engineVersion).toString()); if (heapMin.isUsed()) { processParameters.add("-Xms" + heapMin.getSizeParameter()); @@ -69,12 +68,12 @@ final class GameStarter implements Callable { processParameters.add(installation.getGameJarPath().toString()); // Parameters after this are for the game facade, not the java runtime. - processParameters.add(homeDirParameter(gameDataDirectory)); + processParameters.add(homeDirParameter(gameDataDirectory, engineVersion)); processParameters.addAll(gameParams); if (isMac) { // splash screen uses awt, so no awt => no splash - processParameters.add(noSplashParameter()); + processParameters.add(noSplashParameter(engineVersion)); } processBuilder = new ProcessBuilder(processParameters) @@ -97,23 +96,27 @@ public Process call() throws IOException { /** * @return the executable {@code java} file to run the game with */ - Path getRuntimePath() { + Path getRuntimePath(Semver engineVersion) throws GameVersionNotSupportedException { + if (VersionHistory.JAVA17.isProvidedBy(engineVersion)) { + // throw exception as the version is not supported + throw new GameVersionNotSupportedException(engineVersion); + } return Paths.get(System.getProperty("java.home"), "bin", "java"); } - String homeDirParameter(Path gameDataDirectory) { - if (terasologyUsesPosixOptions()) { + String homeDirParameter(Path gameDataDirectory, Semver engineVersion) { + if (terasologyUsesPosixOptions(engineVersion)) { return "--homedir=" + gameDataDirectory.toAbsolutePath(); } else { return "-homedir=" + gameDataDirectory.toAbsolutePath(); } } - String noSplashParameter() { - return terasologyUsesPosixOptions() ? "--no-splash" : "-noSplash"; + String noSplashParameter(Semver engineVersion) { + return terasologyUsesPosixOptions(engineVersion) ? "--no-splash" : "-noSplash"; } - boolean terasologyUsesPosixOptions() { + boolean terasologyUsesPosixOptions(Semver engineVersion) { return VersionHistory.PICOCLI.isProvidedBy(engineVersion); } } diff --git a/src/main/java/org/terasology/launcher/game/GameVersionNotSupportedException.java b/src/main/java/org/terasology/launcher/game/GameVersionNotSupportedException.java new file mode 100644 index 00000000..fa08651b --- /dev/null +++ b/src/main/java/org/terasology/launcher/game/GameVersionNotSupportedException.java @@ -0,0 +1,19 @@ +// Copyright 2023 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.launcher.game; + +import com.vdurmont.semver4j.Semver; + +public class GameVersionNotSupportedException extends RuntimeException { + private final Semver engineVersion; + + public GameVersionNotSupportedException(Semver engineVersion) { + this.engineVersion = engineVersion; + } + + @Override + public String getMessage() { + return "Unsupported engine version: " + engineVersion.toString(); + } +} diff --git a/src/main/java/org/terasology/launcher/game/VersionHistory.java b/src/main/java/org/terasology/launcher/game/VersionHistory.java index fc4d3d56..16301ed4 100644 --- a/src/main/java/org/terasology/launcher/game/VersionHistory.java +++ b/src/main/java/org/terasology/launcher/game/VersionHistory.java @@ -13,10 +13,20 @@ public enum VersionHistory { /** * The preview release of v4.1.0-rc.1 is the first release with LWJGL v3. - * See https://github.com/MovingBlocks/Terasology/releases/tag/v4.1.0-rc.1 + * See v4.1.0-rc.1 */ LWJGL3("4.1.0-rc.1"), - PICOCLI("5.2.0-SNAPSHOT"); + + /** + * With the 5.2.0-rc.1 preview release Terasology switches to PICOCLI with POSIX-style command line options. + * See v5.2.0-rc.1 + */ + PICOCLI("5.2.0-SNAPSHOT"), + + /** Since 3aa68c04f192243575f7f78de5b6ce268bb2da1a Terasology requires at least Java 17. + * See 3aa68c04 + */ + JAVA17("6.0.0-SNAPSHOT"); public final Semver engineVersion; diff --git a/src/main/java/org/terasology/launcher/ui/ApplicationController.java b/src/main/java/org/terasology/launcher/ui/ApplicationController.java index 69eafc5e..848d015d 100644 --- a/src/main/java/org/terasology/launcher/ui/ApplicationController.java +++ b/src/main/java/org/terasology/launcher/ui/ApplicationController.java @@ -36,6 +36,7 @@ import org.terasology.launcher.LauncherConfiguration; import org.terasology.launcher.game.GameManager; import org.terasology.launcher.game.GameService; +import org.terasology.launcher.game.GameVersionNotSupportedException; import org.terasology.launcher.game.Installation; import org.terasology.launcher.model.Build; import org.terasology.launcher.model.GameIdentifier; @@ -59,7 +60,6 @@ import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -74,21 +74,21 @@ public class ApplicationController { private Settings launcherSettings; private GameManager gameManager; - private RepositoryManager repositoryManager; + private final GameService gameService; private final ExecutorService executor = Executors.newSingleThreadExecutor(); private DownloadTask downloadTask; private Stage stage; - private Property config; + private final Property config; - private Property selectedRelease; - private Property gameAction; - private BooleanProperty downloading; - private BooleanProperty showPreReleases; + private final Property selectedRelease; + private final Property gameAction; + private final BooleanProperty downloading; + private final BooleanProperty showPreReleases; - private ObservableSet installedGames; + private final ObservableSet installedGames; /** * Indicate whether the user's hard drive is running out of space for game downloads. @@ -331,7 +331,6 @@ public void update(final LauncherConfiguration configuration, final Stage stage, this.launcherSettings = configuration.getLauncherSettings(); this.showPreReleases.bind(launcherSettings.showPreReleases); - this.repositoryManager = configuration.getRepositoryManager(); this.gameManager = configuration.getGameManager(); this.stage = stage; @@ -411,7 +410,11 @@ protected void startGameAction() { Dialogs.showError(stage, I18N.getMessage("message_error_installationNotFound", release)); return; } - gameService.start(installation, launcherSettings); + try { + gameService.start(installation, launcherSettings); + } catch (GameVersionNotSupportedException e) { + Dialogs.showError(stage, e.getMessage()); + } } private void handleRunStarted(ObservableValue o, Boolean oldValue, Boolean newValue) { @@ -492,21 +495,6 @@ protected void deleteAction() { }); } - /** - * Select the first item matching given predicate, select the first item otherwise. - * - * @param comboBox the combo box to change the selection for - * @param predicate first item matching this predicate will be selected - */ - private void selectItem(final ComboBox comboBox, Predicate predicate) { - final T item = comboBox.getItems().stream() - .filter(predicate) - .findFirst() - .orElse(comboBox.getItems().get(0)); - - comboBox.getSelectionModel().select(item); - } - /** * Closes the launcher frame this Controller handles. The launcher frame Stage is determined by the enclosing anchor pane. */ diff --git a/src/test/java/org/terasology/launcher/game/TestGameStarter.java b/src/test/java/org/terasology/launcher/game/TestGameStarter.java index e964b633..b28ec9c0 100644 --- a/src/test/java/org/terasology/launcher/game/TestGameStarter.java +++ b/src/test/java/org/terasology/launcher/game/TestGameStarter.java @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 package org.terasology.launcher.game; +import com.vdurmont.semver4j.Semver; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -19,9 +20,11 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasItem; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.terasology.launcher.Matchers.hasItemsFrom; public class TestGameStarter { @@ -65,11 +68,12 @@ public void testConstruction() throws IOException { @Test public void testJre() throws IOException { + Semver engineVersion = new Semver("5.0.0"); GameStarter task = newStarter(); // This is the sort of test where the code under test and the expectation are just copies // of the same source. But since there's a plan to separate the launcher runtime from the // game runtime, the runtime location seemed like a good thing to specify in its own test. - assertTrue(task.getRuntimePath().startsWith(Path.of(System.getProperty("java.home")))); + assertTrue(task.getRuntimePath(engineVersion).startsWith(Path.of(System.getProperty("java.home")))); } static Stream provideJarPaths() { @@ -95,4 +99,18 @@ public void testBuildProcess(Path jarRelativePath) throws IOException { // could parameterize this test for the things that are optional? // heap min, heap max, log level, gameParams and javaParams are all optional. } + + @Test + public void testSupportedJava11() throws IOException { + Semver engineVersion = new Semver("5.3.0"); + GameStarter task = newStarter(); + assertDoesNotThrow(() -> task.getRuntimePath(engineVersion)); + } + + @Test + public void testUnsupportedJava17() throws IOException { + Semver engineVersion = new Semver("6.0.0"); + GameStarter task = newStarter(); + assertThrows(GameVersionNotSupportedException.class, () -> task.getRuntimePath(engineVersion)); + } }