Skip to content

Commit

Permalink
Ensure queue ui state is synced with server state
Browse files Browse the repository at this point in the history
  • Loading branch information
Sheikah45 committed Apr 23, 2023
1 parent 4c85e71 commit 4154145
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -114,7 +115,7 @@ public class TeamMatchmakingService implements InitializingBean {
private final ObservableMap<String, MatchmakerQueueBean> nameToQueue = FXCollections.synchronizedObservableMap(FXCollections.observableHashMap());
@Getter
private final ObservableList<MatchmakerQueueBean> queues = JavaFxUtil.attachListToMap(FXCollections.synchronizedObservableList(FXCollections.observableArrayList(queue -> new Observable[]{queue.selectedProperty(), queue.matchingStatusProperty()})), nameToQueue);
private final FilteredList<MatchmakerQueueBean> selectedQueues = new FilteredList<>(queues, MatchmakerQueueBean::isSelected);
private final FilteredList<MatchmakerQueueBean> selectedQueues = new FilteredList<>(queues);
private final BooleanBinding anyQueueSelected = Bindings.isNotEmpty(selectedQueues);
private final ReadOnlyBooleanWrapper partyMembersNotReady = new ReadOnlyBooleanWrapper();
private final SetProperty<PlayerBean> playersInGame = new SimpleSetProperty<>(FXCollections.observableSet());
Expand All @@ -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<Boolean>) this::onInQueueChange));

if (!newValue) {
searching.set(false);
if (!matchFoundAndWaitingForGameLaunch.get()) {
gameService.stopSearchMatchmaker();
}
}
}));

selectedQueues.addListener((ListChangeListener<MatchmakerQueueBean>) change -> {
boolean isSearching = searching.get();
ObservableSet<Integer> unselectedQueueIds = matchmakerPrefs.getUnselectedQueueIds();
while (change.next()) {
if (change.wasAdded()) {
List<MatchmakerQueueBean> added = List.copyOf(change.getAddedSubList());
added.stream().map(MatchmakerQueueBean::getId).forEach(unselectedQueueIds::remove);
if (isSearching) {
added.forEach(this::joinQueue);
}
}

if (change.wasRemoved()) {
List<MatchmakerQueueBean> 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)
Expand All @@ -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()
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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<? extends MatchmakerQueueBean> change) {
if (!Objects.equals(party.getOwner(), playerService.getCurrentPlayer())) {
return;
}

boolean searching = isSearching();

ObservableSet<Integer> unselectedQueueIds = matchmakerPrefs.getUnselectedQueueIds();
while (change.next()) {
if (change.wasAdded()) {
List<MatchmakerQueueBean> 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<MatchmakerQueueBean> 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);

Expand Down Expand Up @@ -358,12 +380,6 @@ public void leaveQueues() {
}

private CompletableFuture<Boolean> 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)
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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));
}
}

0 comments on commit 4154145

Please sign in to comment.