Skip to content

Commit

Permalink
Merge c188950 into 5c88d82
Browse files Browse the repository at this point in the history
  • Loading branch information
BlackYps committed Mar 19, 2023
2 parents 5c88d82 + c188950 commit 41e989f
Show file tree
Hide file tree
Showing 19 changed files with 431 additions and 289 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import com.faforever.client.patch.SimpleHttpFeaturedModUpdater;
import com.faforever.client.preferences.DataPrefs;
import com.faforever.client.preferences.ForgedAlliancePrefs;
import com.faforever.client.preferences.PreferencesService;
import com.faforever.client.task.TaskService;
import lombok.AllArgsConstructor;
import org.springframework.context.ApplicationContext;
Expand All @@ -21,13 +20,12 @@ public class FeaturedModUpdaterConfig {
private final ApplicationContext applicationContext;
private final TaskService taskService;
private final SimpleHttpFeaturedModUpdater httpFeaturedModUpdater;
private final PreferencesService preferencesService;
private final DataPrefs dataPrefs;
private final ForgedAlliancePrefs forgedAlliancePrefs;

@Bean
GameUpdater gameUpdater() {
return new GameUpdaterImpl(modService, applicationContext, taskService, preferencesService, dataPrefs, forgedAlliancePrefs)
.addFeaturedModUpdater(httpFeaturedModUpdater);
return new GameUpdaterImpl(modService, applicationContext, taskService, dataPrefs, forgedAlliancePrefs)
.setFeaturedModUpdater(httpFeaturedModUpdater);
}
}
21 changes: 18 additions & 3 deletions src/main/java/com/faforever/client/fa/ForgedAllianceService.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public Process startGameOnline(GameParameters gameParameters) throws IOException
public Process startReplay(Path path, @Nullable Integer replayId) throws IOException {
int checkedReplayId = Objects.requireNonNullElse(replayId, -1);

List<String> launchCommand = defaultLaunchCommand().replayFile(path)
List<String> launchCommand = replayLaunchCommand().replayFile(path)
.replayId(checkedReplayId)
.logFile(loggingService.getNewGameLogFile(checkedReplayId))
.build();
Expand All @@ -98,7 +98,7 @@ public Process startReplay(Path path, @Nullable Integer replayId) throws IOExcep


public Process startReplay(URI replayUri, Integer replayId) throws IOException {
List<String> launchCommand = defaultLaunchCommand().replayUri(replayUri)
List<String> launchCommand = replayLaunchCommand().replayUri(replayUri)
.replayId(replayId)
.logFile(loggingService.getNewGameLogFile(replayId))
.username(playerService.getCurrentPlayer().getUsername())
Expand All @@ -111,6 +111,10 @@ public Path getExecutablePath() {
return dataPrefs.getBinDirectory().resolve(FORGED_ALLIANCE_EXE);
}

public Path getReplayExecutablePath() {
return dataPrefs.getReplayBinDirectory().resolve(FORGED_ALLIANCE_EXE);
}

public Path getDebuggerExecutablePath() {
return dataPrefs.getBinDirectory().resolve(DEBUGGER_EXE);
}
Expand All @@ -120,10 +124,21 @@ private LaunchCommandBuilder defaultLaunchCommand() {
.executableDecorator(forgedAlliancePrefs.getExecutableDecorator())
.executable(getExecutablePath());

return addDebugger(baseCommandBuilder);
}

private LaunchCommandBuilder replayLaunchCommand() {
LaunchCommandBuilder baseCommandBuilder = LaunchCommandBuilder.create()
.executableDecorator(forgedAlliancePrefs.getExecutableDecorator())
.executable(getReplayExecutablePath());

return addDebugger(baseCommandBuilder);
}

private LaunchCommandBuilder addDebugger(LaunchCommandBuilder baseCommandBuilder) {
if (forgedAlliancePrefs.isRunFAWithDebugger() && Files.exists(getDebuggerExecutablePath())) {
baseCommandBuilder = baseCommandBuilder.debuggerExecutable(getDebuggerExecutablePath());
}

return baseCommandBuilder;
}

Expand Down
137 changes: 78 additions & 59 deletions src/main/java/com/faforever/client/game/GameService.java
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ public class GameService implements InitializingBean {

@VisibleForTesting
final BooleanProperty gameRunning = new SimpleBooleanProperty();
final BooleanProperty replayRunning = new SimpleBooleanProperty();
;
/** TODO: Explain why access needs to be synchronized. */
@VisibleForTesting
Expand All @@ -156,6 +157,7 @@ public class GameService implements InitializingBean {
private final ObservableList<GameBean> games = JavaFxUtil.attachListToMap(FXCollections.synchronizedObservableList(FXCollections.observableArrayList(game -> new Observable[]{game.statusProperty(), game.teamsProperty(), game.titleProperty(), game.mapFolderNameProperty(), game.simModsProperty(), game.passwordProtectedProperty()})), gameIdToGame);

private Process process;
private Process replayProcess;
private CompletableFuture<Void> matchmakerFuture;
private boolean gameKilled;
private boolean rehostRequested;
Expand Down Expand Up @@ -260,6 +262,12 @@ public void afterPropertiesSet() {
onLoggedIn();
}
});

try {
patchGamePrefsForMultiInstances();
} catch (Exception e) {
log.error("Game.prefs patch failed", e);
}
}

private Mono<GameBean> initializeGameBean(GameInfo gameInfo) {
Expand Down Expand Up @@ -441,7 +449,7 @@ public CompletableFuture<Void> runWithReplay(Path path, @Nullable Integer replay

return modService.getFeaturedMod(featuredMod)
.toFuture()
.thenCompose(featuredModBean -> updateGameIfNecessary(featuredModBean, simMods, featuredModFileVersions, baseFafVersion))
.thenCompose(featuredModBean -> updateReplayFilesIfNecessary(featuredModBean, simMods, featuredModFileVersions, baseFafVersion))
.thenCompose(aVoid -> downloadMapIfNecessary(mapFolderName).handleAsync((ignoredResult, throwable) -> {
try {
return askWhetherToStartWithOutMap(throwable);
Expand All @@ -451,13 +459,9 @@ public CompletableFuture<Void> runWithReplay(Path path, @Nullable Integer replay
}))
.thenRun(() -> {
try {
Process processForReplay = forgedAllianceService.startReplay(path, replayId);
if (forgedAlliancePrefs.isAllowReplaysWhileInGame() && isRunning()) {
return;
}
this.process = processForReplay;
setGameRunning(true);
spawnTerminationListener(this.process);
this.replayProcess = forgedAllianceService.startReplay(path, replayId);
setReplayRunning(true);
spawnReplayTerminationListener(this.replayProcess);
} catch (IOException e) {
notifyCantPlayReplay(replayId, e);
}
Expand All @@ -469,17 +473,9 @@ public CompletableFuture<Void> runWithReplay(Path path, @Nullable Integer replay
}

private boolean canStartReplay() {
if (isRunning() && !forgedAlliancePrefs.isAllowReplaysWhileInGame()) {
log.info("Forged Alliance is already running and experimental concurrent game feature not turned on, not starting replay");
notificationService.addImmediateWarnNotification("replay.gameRunning");
return false;
} else if (waitingForMatchMakerGame()) {
log.info("In matchmaker queue, not starting replay");
notificationService.addImmediateWarnNotification("replay.inQueue");
return false;
} else if (inOthersParty) {
log.info("In party, not starting replay");
notificationService.addImmediateWarnNotification("replay.inParty");
if (isReplayRunning()) {
log.info("Another replay is already running, not starting replay");
notificationService.addImmediateWarnNotification("replay.replayRunning");
return false;
}
return true;
Expand Down Expand Up @@ -537,7 +533,7 @@ public CompletableFuture<Void> runWithLiveReplay(URI replayUrl, Integer gameId,

return modService.getFeaturedMod(gameType)
.toFuture()
.thenCompose(featuredModBean -> updateGameIfNecessary(featuredModBean, simModUids))
.thenCompose(featuredModBean -> updateReplayFilesIfNecessary(featuredModBean, simModUids, null, null))
.thenCompose(aVoid -> downloadMapIfNecessary(mapName))
.thenRun(() -> {
Process processCreated;
Expand All @@ -546,12 +542,9 @@ public CompletableFuture<Void> runWithLiveReplay(URI replayUrl, Integer gameId,
} catch (IOException e) {
throw new GameLaunchException("Live replay could not be started", e, "replay.live.startError");
}
if (forgedAlliancePrefs.isAllowReplaysWhileInGame() && isRunning()) {
return;
}
this.process = processCreated;
setGameRunning(true);
spawnTerminationListener(this.process);
this.replayProcess = processCreated;
setReplayRunning(true);
spawnReplayTerminationListener(this.replayProcess);
})
.exceptionally(throwable -> {
throwable = ConcurrentUtil.unwrapIfCompletionException(throwable);
Expand Down Expand Up @@ -592,7 +585,14 @@ public void startSearchMatchmaker() {
.toFuture()
.thenAccept(featuredModBean -> updateGameIfNecessary(featuredModBean, Set.of()))
.thenCompose(aVoid -> fafServerAccessor.startSearchMatchmaker())
.thenCompose(gameLaunchResponse -> downloadMapIfNecessary(gameLaunchResponse.getMapName()).thenCompose(aVoid -> leaderboardService.getActiveLeagueEntryForPlayer(playerService.getCurrentPlayer(), gameLaunchResponse.getLeaderboard()))
.thenCompose(gameLaunchResponse -> downloadMapIfNecessary(gameLaunchResponse.getMapName()).thenCompose(aVoid -> {
// We need to kill the replay to free the lock on the game.prefs
if (isReplayRunning()) {
gameKilled = true;
replayProcess.destroy();
}
return leaderboardService.getActiveLeagueEntryForPlayer(playerService.getCurrentPlayer(), gameLaunchResponse.getLeaderboard());
})
.thenApply(leagueEntryOptional -> {
GameParameters parameters = gameMapper.map(gameLaunchResponse);
parameters.setDivision(leagueEntryOptional.map(bean -> bean.getSubdivision().getDivision().getNameKey())
Expand Down Expand Up @@ -644,13 +644,13 @@ private boolean isRunning() {
}

public CompletableFuture<Void> updateGameIfNecessary(FeaturedModBean featuredModBean, Set<String> simModUids) {
return updateGameIfNecessary(featuredModBean, simModUids, null, null);
return gameUpdater.update(featuredModBean, simModUids, null, null, false);
}

private CompletableFuture<Void> updateGameIfNecessary(FeaturedModBean featuredModBean, Set<String> simModUids,
@Nullable Map<String, Integer> featuredModFileVersions,
@Nullable Integer version) {
return gameUpdater.update(featuredModBean, simModUids, featuredModFileVersions, version);
private CompletableFuture<Void> updateReplayFilesIfNecessary(FeaturedModBean featuredModBean, Set<String> simModUids,
@Nullable Map<String, Integer> featuredModFileVersions,
@Nullable Integer version) {
return gameUpdater.update(featuredModBean, simModUids, featuredModFileVersions, version, true);
}

public boolean isGameRunning() {
Expand All @@ -665,6 +665,18 @@ private void setGameRunning(boolean running) {
}
}

public boolean isReplayRunning() {
synchronized (replayRunning) {
return replayRunning.get();
}
}

private void setReplayRunning(boolean running) {
synchronized (replayRunning) {
this.replayRunning.set(running);
}
}

private boolean waitingForMatchMakerGame() {
return matchmakerFuture != null && !matchmakerFuture.isDone();
}
Expand Down Expand Up @@ -714,7 +726,7 @@ private CompletableFuture<Void> startGame(GameParameters gameParameters) {
setGameRunning(false);
return null;
})
.thenCompose(this::spawnTerminationListener);
.thenCompose(process -> spawnTerminationListener(process, true));
}

private void onRecentlyPlayedGameEnded(GameBean game) {
Expand All @@ -725,35 +737,11 @@ private void onRecentlyPlayedGameEnded(GameBean game) {
notificationService.addNotification(new PersistentNotification(i18n.get("game.ended", game.getTitle()), Severity.INFO, singletonList(new Action(i18n.get("game.rate"), actionEvent -> eventBus.post(new ShowReplayEvent(game.getId()))))));
}

@VisibleForTesting
CompletableFuture<Void> spawnTerminationListener(Process process) {
return spawnTerminationListener(process, true);
}

@VisibleForTesting
CompletableFuture<Void> spawnTerminationListener(Process process, Boolean forOnlineGame) {
rehostRequested = false;
return process.onExit().thenAccept(finishedProcess -> {
fafServerAccessor.setPingIntervalSeconds(25);
int exitCode = finishedProcess.exitValue();
log.info("Forged Alliance terminated with exit code {}", exitCode);
Optional<Path> logFile = loggingService.getMostRecentGameLogFile();
logFile.ifPresent(file -> {
try {
Files.writeString(file, logMasker.maskMessage(Files.readString(file)));
} catch (IOException e) {
log.warn("Could not open log file", e);
}
});

if (exitCode != 0 && !gameKilled) {
if (exitCode == -1073741515) {
notificationService.addImmediateWarnNotification("game.crash.notInitialized");
} else {
notificationService.addNotification(new ImmediateNotification(i18n.get("errorTitle"), i18n.get("game.crash", exitCode, logFile.map(Path::toString)
.orElse("")), WARN, List.of(new Action(i18n.get("game.open.log"), event -> platformService.reveal(logFile.orElse(operatingSystem.getLoggingDirectory()))), new DismissAction(i18n))));
}
}
handleTermination(finishedProcess);

synchronized (gameRunning) {
gameRunning.set(false);
Expand All @@ -774,6 +762,37 @@ CompletableFuture<Void> spawnTerminationListener(Process process, Boolean forOnl
});
}

@VisibleForTesting
void spawnReplayTerminationListener(Process process) {
process.onExit().thenAccept(finishedProcess -> {
handleTermination(finishedProcess);
setReplayRunning(false);
});
}

private void handleTermination(Process finishedProcess) {
fafServerAccessor.setPingIntervalSeconds(25);
int exitCode = finishedProcess.exitValue();
log.info("Forged Alliance terminated with exit code {}", exitCode);
Optional<Path> logFile = loggingService.getMostRecentGameLogFile();
logFile.ifPresent(file -> {
try {
Files.writeString(file, logMasker.maskMessage(Files.readString(file)));
} catch (IOException e) {
log.warn("Could not open log file", e);
}
});

if (exitCode != 0 && !gameKilled) {
if (exitCode == -1073741515) {
notificationService.addImmediateWarnNotification("game.crash.notInitialized");
} else {
notificationService.addNotification(new ImmediateNotification(i18n.get("errorTitle"), i18n.get("game.crash", exitCode, logFile.map(Path::toString)
.orElse("")), WARN, List.of(new Action(i18n.get("game.open.log"), event -> platformService.reveal(logFile.orElse(operatingSystem.getLoggingDirectory()))), new DismissAction(i18n))));
}
}
}

private void rehost() {
synchronized (currentGame) {
GameBean game = currentGame.get();
Expand Down Expand Up @@ -812,7 +831,7 @@ private GameBean enhanceWithLastPasswordIfPasswordProtected(GameBean game) {
}

public void killGame() {
if (process != null && process.isAlive()) {
if (isRunning()) {
log.info("ForgedAlliance still running, destroying process");
process.destroy();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ public interface FeaturedModUpdater {
* Updates the specified featured mod to the specified version. If {@code version} is null, it will update to the
* latest version
*/
CompletableFuture<PatchResult> updateMod(FeaturedModBean featuredMod, @Nullable Integer version);
CompletableFuture<PatchResult> updateMod(FeaturedModBean featuredMod, @Nullable Integer version, boolean useReplayFolder);

/**
* Returns {@code true} if this updater is able to update the specified featured mod.
*/
boolean canUpdate(FeaturedModBean featuredModBean);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@

public interface GameBinariesUpdateTask extends PrioritizedCompletableTask<Void> {
void setVersion(ComparableVersion version);
void setForReplays(boolean forReplays);
}
Loading

0 comments on commit 41e989f

Please sign in to comment.