diff --git a/src/main/java/com/faforever/client/game/GameRunner.java b/src/main/java/com/faforever/client/game/GameRunner.java index ed8f5706a5..7277efd7f6 100644 --- a/src/main/java/com/faforever/client/game/GameRunner.java +++ b/src/main/java/com/faforever/client/game/GameRunner.java @@ -38,6 +38,7 @@ import com.faforever.client.preferences.PreferencesService; import com.faforever.client.remote.FafServerAccessor; import com.faforever.client.replay.ReplayServer; +import com.faforever.client.task.TaskService; import com.faforever.client.theme.UiService; import com.faforever.client.ui.StageHolder; import com.faforever.client.util.ConcurrentUtil; @@ -118,6 +119,7 @@ public class GameRunner implements InitializingBean { private final NotificationPrefs notificationPrefs; private final FxApplicationThreadExecutor fxApplicationThreadExecutor; private final LogAnalyzerService logAnalyzerService; + private final TaskService taskService; private final MaskPatternLayout logMasker = new MaskPatternLayout(); private final SimpleObjectProperty runningGameId = new SimpleObjectProperty<>(); @@ -148,7 +150,7 @@ public void afterPropertiesSet() { fafServerAccessor.getEvents(NoticeInfo.class) .filter(notice -> Objects.equals(notice.getStyle(), "kill")) - .doOnNext(notice -> { + .doOnNext(_ -> { log.info("Game close requested by server"); String linksRules = clientProperties.getLinks().get("linksRules"); ImmediateNotification notification = new ImmediateNotification(i18n.get("game.kicked.title"), @@ -163,8 +165,8 @@ public void afterPropertiesSet() { .subscribe(); - fafServerAccessor.connectionStateProperty().addListener((observable, oldValue, newValue) -> { - if (isRunning() && newValue == ConnectionState.CONNECTED && oldValue != ConnectionState.CONNECTED) { + fafServerAccessor.connectionStateProperty().subscribe((oldState, newState) -> { + if (isRunning() && newState == ConnectionState.CONNECTED && oldState != ConnectionState.CONNECTED) { fafServerAccessor.restoreGameSession(runningGameId.get()); } }); @@ -183,16 +185,16 @@ CompletableFuture startOnlineGame(GameLaunchResponse gameLaunchResponse) { String mapFolderName = gameLaunchResponse.getMapName(); CompletableFuture downloadMapFuture = mapFolderName == null ? completedFuture( null) : mapService.downloadIfNecessary(mapFolderName).toFuture(); - CompletableFuture leagueFuture = hasLeague ? completedFuture(null) : getDivisionInfo( - leaderboard).toFuture(); - CompletableFuture startReplayServerFuture = replayServer.start(uid); - CompletableFuture startIceAdapterFuture = startIceAdapter(uid); - - return CompletableFuture.allOf(downloadMapFuture, leagueFuture, startIceAdapterFuture, startReplayServerFuture) - .thenApply(_ -> gameMapper.map(gameLaunchResponse, leagueFuture.join())) - .thenApply(parameters -> launchOnlineGame(parameters, startIceAdapterFuture.join(), - startReplayServerFuture.join())) - .whenCompleteAsync((process, throwable) -> { + + CompletableFuture loadLeagueInfoFuture = hasLeague ? completedFuture(null) : taskService.submitFutureTask("league.loadInfo", () -> getDivisionInfo(leaderboard).toFuture()); + CompletableFuture runReplayServerFuture = taskService.submitFutureTask("replayServer.connecting", () -> replayServer.start(uid)); + CompletableFuture runIceAdapterFuture = taskService.submitFutureTask("iceAdapter.connecting", () -> startIceAdapter(uid)); + + return CompletableFuture.allOf(downloadMapFuture, loadLeagueInfoFuture, runReplayServerFuture, runIceAdapterFuture) + .thenApply(_ -> gameMapper.map(gameLaunchResponse, loadLeagueInfoFuture.join())) + .thenApply(parameters -> launchOnlineGame(parameters, runIceAdapterFuture.join(), + runReplayServerFuture.join())) + .whenCompleteAsync((process, _) -> { if (process != null) { this.process.set(process); runningGameId.set(uid); @@ -483,7 +485,7 @@ private Optional getAnalysisButtonIfNecessary(Optional logFileCo infoIcon.getStyleClass().add("info-icon"); final Button showAnalysisBtn = new Button(i18n.get("game.log.analysis.solutionBtn"), infoIcon); showAnalysisBtn.setDefaultButton(true); - showAnalysisBtn.setOnAction(event -> notificationService.addNotification( + showAnalysisBtn.setOnAction(_ -> notificationService.addNotification( new ImmediateNotification(i18n.get("game.log.analysis"), message.toString(), WARN, actions))); return showAnalysisBtn; @@ -509,7 +511,7 @@ public void launchTutorial(MapVersion mapVersion, String technicalMapName) { } if (!preferencesService.hasValidGamePath()) { - gamePathHandler.chooseAndValidateGameDirectory().thenAccept(path -> launchTutorial(mapVersion, technicalMapName)); + gamePathHandler.chooseAndValidateGameDirectory().thenAccept(_ -> launchTutorial(mapVersion, technicalMapName)); return; } @@ -547,12 +549,12 @@ public void startOffline() { } if (!preferencesService.hasValidGamePath()) { - gamePathHandler.chooseAndValidateGameDirectory().thenAccept(path -> startOffline()); + gamePathHandler.chooseAndValidateGameDirectory().thenAccept(_ -> startOffline()); return; } CompletableFuture.supplyAsync(() -> forgedAllianceLaunchService.launchOfflineGame(null)) - .whenCompleteAsync((process, throwable) -> { + .whenCompleteAsync((process, _) -> { if (process != null) { this.process.set(process); } diff --git a/src/main/java/com/faforever/client/task/CompletableTask.java b/src/main/java/com/faforever/client/task/CompletableTask.java index 01fdff90dd..a999d5ff25 100644 --- a/src/main/java/com/faforever/client/task/CompletableTask.java +++ b/src/main/java/com/faforever/client/task/CompletableTask.java @@ -12,12 +12,16 @@ public abstract class CompletableTask extends Task implements PrioritizedC private final CompletableFuture future; private Priority priority; + public CompletableTask() { + this(Priority.MEDIUM); + } + public CompletableTask(Priority priority) { this.priority = priority; this.future = new CompletableFuture<>(); - setOnCancelled(event -> future.cancel(true)); - setOnFailed(event -> future.completeExceptionally(getException())); - setOnSucceeded(event -> future.complete(getValue())); + setOnCancelled(_ -> future.cancel(true)); + setOnFailed(_ -> future.completeExceptionally(getException())); + setOnSucceeded(_ -> future.complete(getValue())); } @Override diff --git a/src/main/java/com/faforever/client/task/SimpleTask.java b/src/main/java/com/faforever/client/task/SimpleTask.java new file mode 100644 index 0000000000..7dbcf0080b --- /dev/null +++ b/src/main/java/com/faforever/client/task/SimpleTask.java @@ -0,0 +1,27 @@ +package com.faforever.client.task; + +import org.apache.commons.lang3.StringUtils; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.function.Supplier; + +public class SimpleTask extends CompletableTask { + + private final Supplier> futureSupplier; + + public SimpleTask(Supplier> futureSupplier) { + this(null, futureSupplier); + } + + public SimpleTask(String title, Supplier> futureSupplier) { + this.futureSupplier = futureSupplier; + updateTitle(StringUtils.defaultIfBlank(title, StringUtils.EMPTY)); + } + + @Override + protected V call() throws Exception { + Future task = futureSupplier.get(); + return task.get(); + } +} \ No newline at end of file diff --git a/src/main/java/com/faforever/client/task/TaskService.java b/src/main/java/com/faforever/client/task/TaskService.java index 4a96ccf56b..a4d1e2d187 100644 --- a/src/main/java/com/faforever/client/task/TaskService.java +++ b/src/main/java/com/faforever/client/task/TaskService.java @@ -1,6 +1,7 @@ package com.faforever.client.task; import com.faforever.client.fx.FxApplicationThreadExecutor; +import com.faforever.client.i18n.I18n; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.concurrent.Worker; @@ -9,7 +10,9 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; +import java.util.function.Supplier; /** * Enqueues and runs tasks in background. Services that need to run a task (tasks that finish, not long-running @@ -25,6 +28,7 @@ public class TaskService { private final ExecutorService taskExecutor; private final FxApplicationThreadExecutor fxApplicationThreadExecutor; + private final I18n i18n; private final ObservableList> activeTasks = FXCollections.synchronizedObservableList(FXCollections.observableArrayList()); private final ObservableList> unmodifiableObservableList = FXCollections.unmodifiableObservableList(activeTasks); @@ -36,8 +40,8 @@ public class TaskService { * @param task the task to execute */ public > T submitTask(T task) { - task.getFuture().whenComplete((o, throwable) -> { - activeTasks.remove(task); + task.getFuture().whenComplete((_, throwable) -> { + fxApplicationThreadExecutor.execute(() -> activeTasks.remove(task)); if (throwable != null) { log.error("Task failed", throwable); } @@ -50,6 +54,15 @@ public > T submitTask(T task) { return task; } + public CompletableFuture submitFutureTask(Supplier> task) { + return submitFutureTask(null, task); + } + + public CompletableFuture submitFutureTask(String titleI18nKey, Supplier> task) { + String title = titleI18nKey != null ? i18n.get(titleI18nKey) : null; + return submitTask(new SimpleTask<>(title, task)).getFuture(); + } + public ObservableList> getActiveWorkers() { return unmodifiableObservableList; } diff --git a/src/main/java/com/faforever/client/ui/statusbar/StatusBarController.java b/src/main/java/com/faforever/client/ui/statusbar/StatusBarController.java index 536f508d0e..b114794faf 100644 --- a/src/main/java/com/faforever/client/ui/statusbar/StatusBarController.java +++ b/src/main/java/com/faforever/client/ui/statusbar/StatusBarController.java @@ -92,7 +92,7 @@ protected void onInitialize() { } })); - JavaFxUtil.addListener(taskService.getActiveWorkers(), (Observable observable) -> { + JavaFxUtil.addListener(taskService.getActiveWorkers(), (Observable _) -> { Collection> runningWorkers = taskService.getActiveWorkers(); if (runningWorkers.isEmpty()) { setCurrentWorkerInStatusBar(null); diff --git a/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties index d4190da6fa..388d9e5929 100644 --- a/src/main/resources/i18n/messages.properties +++ b/src/main/resources/i18n/messages.properties @@ -1314,3 +1314,6 @@ teammatchmaking.searchButton.memberInGame = Can't join matchmaking while someone teammatchmaking.searchButton.inParty = The party leader will start matchmaking teammatchmaking.searchButton.inQueue = Your party is now searching for a game leaderboard.tmm_3v3.name = 3v3 +iceAdapter.connecting = Connecting to ICE adapter +replayServer.connecting = Connecting to replay server +league.loadInfo = Loading a league information diff --git a/src/test/java/com/faforever/client/game/GameRunnerTest.java b/src/test/java/com/faforever/client/game/GameRunnerTest.java index b24171b4df..d3c891e771 100644 --- a/src/test/java/com/faforever/client/game/GameRunnerTest.java +++ b/src/test/java/com/faforever/client/game/GameRunnerTest.java @@ -37,6 +37,8 @@ import com.faforever.client.preferences.PreferencesService; import com.faforever.client.remote.FafServerAccessor; import com.faforever.client.replay.ReplayServer; +import com.faforever.client.task.SimpleTask; +import com.faforever.client.task.TaskService; import com.faforever.client.test.ServiceTest; import com.faforever.client.theme.UiService; import com.faforever.client.ui.StageHolder; @@ -51,6 +53,7 @@ import org.junit.jupiter.api.Test; import org.mapstruct.factory.Mappers; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Spy; @@ -62,6 +65,7 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; +import java.util.function.Supplier; import static java.util.concurrent.CompletableFuture.completedFuture; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -135,6 +139,8 @@ public class GameRunnerTest extends ServiceTest { @Mock private FxApplicationThreadExecutor fxApplicationThreadExecutor; @Mock + private TaskService taskService; + @Mock private GamePathHandler gamePathHandler; @Spy private GameMapper gameMapper = Mappers.getMapper(GameMapper.class); @@ -144,6 +150,8 @@ public class GameRunnerTest extends ServiceTest { private LastGamePrefs lastGamePrefs; @Spy private NotificationPrefs notificationPrefs; + @Captor + private ArgumentCaptor>> simpleTaskCaptor; @Mock private EnterPasswordController enterPasswordController; @@ -194,6 +202,11 @@ private void mockStartGameProcess(GameLaunchResponse gameLaunchResponse) throws lenient().when(iceAdapter.start(anyInt(), anyBoolean())).thenReturn(completedFuture(GPG_PORT)); lenient().when(coturnService.getIceSession(anyInt())) .thenReturn(Mono.just(new IceSession("someSessionId", false, List.of()))); + lenient().when(taskService.submitFutureTask(anyString(), simpleTaskCaptor.capture())).thenAnswer(_ -> { + CompletableFuture task = simpleTaskCaptor.getValue().get(); + task.join(); + return task; + }); lenient().when(process.onExit()).thenReturn(new CompletableFuture<>()); lenient().when(process.exitValue()).thenReturn(0); lenient().when(process.isAlive()).thenReturn(true);