package de.towerdefence.server.match.confirmation; import de.towerdefence.server.match.MatchService; import de.towerdefence.server.player.Player; import lombok.AllArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.concurrent.*; @AllArgsConstructor @Service public class MatchConfirmationService { private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); private final Map unconfirmedMatch = new HashMap<>(); final Map> matchAbortTasks = new ConcurrentHashMap<>(); @Autowired private final MatchService matchService; public UnconfirmedMatch createMatch(Player player1, Player player2, ConfirmationCallbacks player1Callbacks, ConfirmationCallbacks player2Callbacks) { UnconfirmedMatch match = new UnconfirmedMatch( player1, player2, player1Callbacks, player2Callbacks); unconfirmedMatch.put(player1, match); unconfirmedMatch.put(player2, match); ScheduledFuture scheduledTask = scheduler.schedule( () -> { matchAbortTasks.remove(match); unconfirmedMatch.remove(match.getPlayer1()); unconfirmedMatch.remove(match.getPlayer2()); if (match.getPlayer1State() == PlayerMatchConfirmState.CONFIRMED ) { player1Callbacks.getRequeueCallback().call( match.getPlayer1(), match.getMatchId(), player1Callbacks.getFoundCallback(), player1Callbacks.getQueuedCallback(), player1Callbacks.getAbortCallback(), player1Callbacks.getEstablishedCallback()); } else { player1Callbacks.getAbortCallback().call( match.getPlayer1(), match.getMatchId() ); } if (match.getPlayer2State() == PlayerMatchConfirmState.CONFIRMED) { player2Callbacks.getRequeueCallback().call( match.getPlayer2(), match.getMatchId(), player2Callbacks.getFoundCallback(), player2Callbacks.getQueuedCallback(), player2Callbacks.getAbortCallback(), player2Callbacks.getEstablishedCallback()); } else { player2Callbacks.getAbortCallback().call( match.getPlayer2(), match.getMatchId() ); } }, UnconfirmedMatch.TTL, TimeUnit.MILLISECONDS ); matchAbortTasks.put(match, scheduledTask); return match; } public void accept(Player player, String matchId) { setPlayerAcceptState(player, matchId, PlayerMatchConfirmState.CONFIRMED); } public void decline(Player player, String matchId) { setPlayerAcceptState(player, matchId, PlayerMatchConfirmState.ABORTED); } private void setPlayerAcceptState(Player player, String matchId, PlayerMatchConfirmState state) { Optional optionalMatch = getPlayerMatch(player, matchId); if (optionalMatch.isEmpty()) { return; } UnconfirmedMatch match = optionalMatch.get(); Optional optionalPlayerState = match.getPlayerState(player); if (optionalPlayerState.isEmpty()) { unconfirmedMatch.remove(player); return; } if (optionalPlayerState.get() != PlayerMatchConfirmState.UNKNOWN) { return; } Optional matchState = match.setPlayerConfirmState(player, state); if (matchState.isEmpty()) { unconfirmedMatch.remove(player); return; } handleMatchConfirmation(match, matchState.get()); } private void handleMatchConfirmation(UnconfirmedMatch match, UnconfirmedMatchState state) { if (state == UnconfirmedMatchState.WAITING) { return; } matchAbortTasks.get(match).cancel(true); matchAbortTasks.remove(match); unconfirmedMatch.remove(match.getPlayer1()); unconfirmedMatch.remove(match.getPlayer2()); ConfirmationCallbacks player1Callbacks = match.getPlayer1Callbacks(); ConfirmationCallbacks player2Callbacks = match.getPlayer2Callbacks(); switch (state) { case ABORTED -> { if (match.getPlayer1State() != PlayerMatchConfirmState.ABORTED ) { player1Callbacks.getRequeueCallback().call( match.getPlayer1(), match.getMatchId(), player1Callbacks.getFoundCallback(), player1Callbacks.getQueuedCallback(), player1Callbacks.getAbortCallback(), player1Callbacks.getEstablishedCallback()); } else { player1Callbacks.getAbortCallback().call( match.getPlayer1(), match.getMatchId() ); } if (match.getPlayer2State() != PlayerMatchConfirmState.ABORTED) { player2Callbacks.getRequeueCallback().call( match.getPlayer2(), match.getMatchId(), player2Callbacks.getFoundCallback(), player2Callbacks.getQueuedCallback(), player2Callbacks.getAbortCallback(), player2Callbacks.getEstablishedCallback()); } else { player2Callbacks.getAbortCallback().call( match.getPlayer2(), match.getMatchId() ); } } case CONFIRMED -> { boolean player1successful = sendPlayerEstablished( match.getMatchId(), player1Callbacks.getEstablishedCallback(), match.getPlayer1(), match.getPlayer2() ); boolean player2successful = sendPlayerEstablished( match.getMatchId(), player2Callbacks.getEstablishedCallback(), match.getPlayer2(), match.getPlayer1() ); if (!player1successful || !player2successful) { if (player1successful) { player1Callbacks.getRequeueCallback().call( match.getPlayer1(), match.getMatchId(), player1Callbacks.getFoundCallback(), player1Callbacks.getQueuedCallback(), player1Callbacks.getAbortCallback(), player1Callbacks.getEstablishedCallback()); } if (player2successful) { player2Callbacks.getRequeueCallback().call( match.getPlayer2(), match.getMatchId(), player2Callbacks.getFoundCallback(), player2Callbacks.getQueuedCallback(), player2Callbacks.getAbortCallback(), player2Callbacks.getEstablishedCallback()); } return; } matchService.createMatch(match.getMatchId(), match.getPlayer1(), match.getPlayer2()); } } } /** * @return if successful */ private boolean sendPlayerEstablished( String matchId, EstablishedCallback callback, Player player, Player opponent ) { try { callback.call(player, matchId, opponent); } catch (IOException ignored) { return false; } return true; } private Optional getPlayerMatch(Player player, String matchId) { UnconfirmedMatch match = unconfirmedMatch.get(player); if (match == null) { return Optional.empty(); } if (!match.getMatchId().equals(matchId)) { unconfirmedMatch.remove(player); return Optional.empty(); } return Optional.of(match); } }