diff --git a/src/main/java/com/faforever/client/teammatchmaking/TeamMatchmakingService.java b/src/main/java/com/faforever/client/teammatchmaking/TeamMatchmakingService.java index 0e5ab98337..5cd76c175b 100644 --- a/src/main/java/com/faforever/client/teammatchmaking/TeamMatchmakingService.java +++ b/src/main/java/com/faforever/client/teammatchmaking/TeamMatchmakingService.java @@ -9,6 +9,7 @@ import com.faforever.client.exception.NotifiableException; import com.faforever.client.fx.JavaFxService; import com.faforever.client.fx.JavaFxUtil; +import com.faforever.client.fx.SimpleChangeListener; import com.faforever.client.fx.SimpleInvalidationListener; import com.faforever.client.game.GameService; import com.faforever.client.game.PlayerStatus; @@ -62,7 +63,7 @@ import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleSetProperty; import javafx.collections.FXCollections; -import javafx.collections.ListChangeListener; +import javafx.collections.ListChangeListener.Change; import javafx.collections.ObservableList; import javafx.collections.ObservableMap; import javafx.collections.ObservableSet; @@ -114,7 +115,7 @@ public class TeamMatchmakingService implements InitializingBean { private final ObservableMap nameToQueue = FXCollections.synchronizedObservableMap(FXCollections.observableHashMap()); @Getter private final ObservableList queues = JavaFxUtil.attachListToMap(FXCollections.synchronizedObservableList(FXCollections.observableArrayList(queue -> new Observable[]{queue.selectedProperty(), queue.matchingStatusProperty()})), nameToQueue); - private final FilteredList selectedQueues = new FilteredList<>(queues, MatchmakerQueueBean::isSelected); + private final FilteredList selectedQueues = new FilteredList<>(queues); private final BooleanBinding anyQueueSelected = Bindings.isNotEmpty(selectedQueues); private final ReadOnlyBooleanWrapper partyMembersNotReady = new ReadOnlyBooleanWrapper(); private final SetProperty playersInGame = new SimpleSetProperty<>(FXCollections.observableSet()); @@ -125,45 +126,17 @@ public class TeamMatchmakingService implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { + selectedQueues.predicateProperty() + .bind(Bindings.createObjectBinding(() -> queue -> queue.isSelected() && party.getMembers() + .size() <= queue.getTeamSize(), party.getMembers())); + inQueue.bind(Bindings.createBooleanBinding(() -> queues.stream() .map(MatchmakerQueueBean::getMatchingStatus) .anyMatch(MatchingStatus.SEARCHING::equals), queues)); - inQueue.addListener(((observable, oldValue, newValue) -> { - if (newValue) { - searching.set(true); - gameService.startSearchMatchmaker(); - } + inQueue.addListener(((SimpleChangeListener) this::onInQueueChange)); - if (!newValue) { - searching.set(false); - if (!matchFoundAndWaitingForGameLaunch.get()) { - gameService.stopSearchMatchmaker(); - } - } - })); - - selectedQueues.addListener((ListChangeListener) change -> { - boolean isSearching = searching.get(); - ObservableSet unselectedQueueIds = matchmakerPrefs.getUnselectedQueueIds(); - while (change.next()) { - if (change.wasAdded()) { - List added = List.copyOf(change.getAddedSubList()); - added.stream().map(MatchmakerQueueBean::getId).forEach(unselectedQueueIds::remove); - if (isSearching) { - added.forEach(this::joinQueue); - } - } - - if (change.wasRemoved()) { - List removed = List.copyOf(change.getRemoved()); - removed.stream().map(MatchmakerQueueBean::getId).forEach(unselectedQueueIds::add); - if (isSearching) { - removed.forEach(this::leaveQueue); - } - } - } - }); + selectedQueues.addListener(this::onSelectedQueueChange); fafServerAccessor.getEvents(GameLaunchResponse.class) .map(GameLaunchResponse::getGameType) @@ -178,13 +151,7 @@ public void afterPropertiesSet() throws Exception { fafServerAccessor.getEvents(SearchInfo.class) .publishOn(javaFxService.getFxApplicationScheduler()) - .doOnNext(message -> { - MatchmakerQueueBean matchmakingQueue = nameToQueue.get(message.getQueueName()); - if (matchmakingQueue != null) { - matchmakingQueue.setMatchingStatus(message.getState() - .equals(MatchmakerState.START) ? MatchingStatus.SEARCHING : null); - } - }) + .doOnNext(this::onSearchInfo) .publishOn(javaFxService.getSingleScheduler()) .doOnError(throwable -> log.error("Error processing search info", throwable)) .retry() @@ -217,10 +184,8 @@ public void afterPropertiesSet() throws Exception { .doOnNext(ignored -> matchFoundAndWaitingForGameLaunch.set(true)) .doOnNext(ignored -> notifyMatchFound()) .publishOn(javaFxService.getFxApplicationScheduler()) - .doOnNext(ignored -> { - queues.forEach(matchmakingQueue -> matchmakingQueue.setMatchingStatus(null)); - searching.set(false); - }) + .doOnNext(ignored -> queues.forEach(matchmakingQueue -> matchmakingQueue.setMatchingStatus(null))) + .doOnNext(ignored -> searching.set(false)) .doOnNext(this::setFoundStatusForQueue) .publishOn(javaFxService.getSingleScheduler()) .doOnError(throwable -> log.error("Error processing found response", throwable)) @@ -252,6 +217,63 @@ public void afterPropertiesSet() throws Exception { eventBus.register(this); } + private void onSearchInfo(SearchInfo message) { + MatchmakerQueueBean matchmakingQueue = nameToQueue.get(message.getQueueName()); + if (matchmakingQueue != null) { + boolean searchStarted = message.getState().equals(MatchmakerState.START); + matchmakingQueue.setMatchingStatus(searchStarted ? MatchingStatus.SEARCHING : null); + + if (isSearching() && Objects.equals(party.getOwner(), playerService.getCurrentPlayer())) { + matchmakingQueue.setSelected(searchStarted); + } + } + } + + private void onInQueueChange(Boolean newValue) { + if (newValue) { + searching.set(true); + gameService.startSearchMatchmaker(); + } + + if (!newValue) { + searching.set(false); + if (!matchFoundAndWaitingForGameLaunch.get()) { + gameService.stopSearchMatchmaker(); + } + } + } + + private void onSelectedQueueChange(Change change) { + if (!Objects.equals(party.getOwner(), playerService.getCurrentPlayer())) { + return; + } + + boolean searching = isSearching(); + + ObservableSet unselectedQueueIds = matchmakerPrefs.getUnselectedQueueIds(); + while (change.next()) { + if (change.wasAdded()) { + List added = List.copyOf(change.getAddedSubList()); + added.stream().map(MatchmakerQueueBean::getId).forEach(unselectedQueueIds::remove); + if (searching) { + added.stream() + .filter(queue -> queue.getMatchingStatus() != MatchingStatus.SEARCHING) + .forEach(this::joinQueue); + } + } + + if (change.wasRemoved()) { + List removed = List.copyOf(change.getRemoved()); + removed.stream().map(MatchmakerQueueBean::getId).forEach(unselectedQueueIds::add); + if (searching) { + removed.stream() + .filter(queue -> queue.getMatchingStatus() == MatchingStatus.SEARCHING) + .forEach(this::leaveQueue); + } + } + } + } + private void sendInviteNotifications(PlayerBean player) { ActionCallback callback = event -> this.acceptPartyInvite(player); @@ -358,12 +380,6 @@ public void leaveQueues() { } private CompletableFuture joinQueue(MatchmakerQueueBean queue) { - if (party.getMembers().size() > queue.getTeamSize()) { - log.debug("Party of {} larger than queue size of {}, not queueing", party.getMembers() - .size(), queue.getTeamSize()); - return CompletableFuture.completedFuture(false); - } - return mapService.downloadAllMatchmakerMaps(queue) .thenRun(() -> fafServerAccessor.gameMatchmaking(queue, MatchmakerState.START)) .thenApply(aVoid -> true) @@ -391,7 +407,7 @@ public void onPartyInfo(PartyInfo message) { @VisibleForTesting void acceptPartyInvite(PlayerBean player) { - if (isInQueue()) { + if (isInQueue() || isSearching()) { notificationService.addImmediateWarnNotification("teammatchmaking.notification.joinAlreadyInQueue.message"); return; } @@ -410,7 +426,7 @@ void acceptPartyInvite(PlayerBean player) { } public void invitePlayer(String player) { - if (isInQueue()) { + if (isInQueue() || isSearching()) { notificationService.addImmediateWarnNotification("teammatchmaking.notification.inviteAlreadyInQueue.message"); return; } diff --git a/src/test/java/com/faforever/client/teammatchmaking/TeamMatchmakingServiceTest.java b/src/test/java/com/faforever/client/teammatchmaking/TeamMatchmakingServiceTest.java index 047d7ae0a9..17d8e75db0 100644 --- a/src/test/java/com/faforever/client/teammatchmaking/TeamMatchmakingServiceTest.java +++ b/src/test/java/com/faforever/client/teammatchmaking/TeamMatchmakingServiceTest.java @@ -46,6 +46,7 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mapstruct.factory.Mappers; @@ -68,6 +69,7 @@ import static com.faforever.client.notification.Severity.INFO; import static com.faforever.commons.api.elide.ElideNavigator.qBuilder; +import static org.hamcrest.CoreMatchers.hasItems; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; @@ -556,4 +558,34 @@ public void testJoinQueueWhileGameRunning() { verify(notificationService).addImmediateWarnNotification(anyString()); assertThat(success, is(false)); } + + @Test + public void testSelectDeselectQueuesWhileSearching() { + instance.getParty().setOwner(player); + matchmakerInfoTestPublisher.next(createMatchmakerInfoMessage()); + instance.setSearching(true); + + when(mapService.downloadAllMatchmakerMaps(any())).thenReturn(CompletableFuture.completedFuture(null)); + when(modService.getFeaturedMod(anyString())).thenReturn(Mono.just(new FeaturedModBean())); + when(gameService.updateGameIfNecessary(any(), any())).thenReturn(CompletableFuture.completedFuture(null)); + + SearchInfo message1 = new SearchInfo("queue1", MatchmakerState.START); + SearchInfo message2 = new SearchInfo("queue2", MatchmakerState.START); + searchInfoTestPublisher.next(message1, message2); + + instance.getQueues().forEach(queue -> queue.setSelected(false)); + + assertThat(matchmakerPrefs.getUnselectedQueueIds(), hasItems(1, 2)); + verify(fafServerAccessor, times(2)).gameMatchmaking(any(), eq(MatchmakerState.STOP)); + + SearchInfo message3 = new SearchInfo("queue1", MatchmakerState.STOP); + SearchInfo message4 = new SearchInfo("queue2", MatchmakerState.STOP); + searchInfoTestPublisher.next(message3, message4); + + instance.setSearching(true); + instance.getQueues().forEach(queue -> queue.setSelected(true)); + + Assertions.assertTrue(matchmakerPrefs.getUnselectedQueueIds().isEmpty()); + verify(fafServerAccessor, times(2)).gameMatchmaking(any(), eq(MatchmakerState.START)); + } }