diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/Lobby.java b/Server/src/main/java/uulm/teamname/marvelous/server/lobby/Lobby.java index b96ebaf..40de790 100644 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/Lobby.java +++ b/Server/src/main/java/uulm/teamname/marvelous/server/lobby/Lobby.java @@ -75,9 +75,11 @@ public class Lobby { partyConfig.maxRoundTime, this::turnTimeout); - var response = this.game.startGame(player1Characters, player2Characters); + var eventsDescribingGameStart = this.game.startGame(player1Characters, player2Characters); - this.connection.broadcastEvents(response); + this.connection.broadcastEvents(eventsDescribingGameStart); + + updateTimer(); } /** @@ -86,28 +88,32 @@ public class Lobby { * @param source the player executing the requests */ public synchronized void receiveRequests(Request[] requests, Participant source) { + Logger.trace("Received {} requests from participant '{}' of type {}", + requests.length, + source.name, + source.type); if(activePlayer != source && source.type != ParticipantType.Spectator) { + Logger.trace("Resetting bad requests as new participant sent data"); activePlayer = source; badRequests = 0; } Logger.info("got {} requests from participant {}", requests.length, - source); + source.name); Logger.trace("Processing requests through pipeline"); - Optional resultingEvents = pipeline.processRequests(requests, source); - Logger.debug("generated {} events from the pipeline", resultingEvents.map(x -> x.length).orElse(0)); + Optional> resultingEvents = pipeline.processRequests(requests, source); + Logger.debug("generated {} events from the pipeline", resultingEvents.map(List::size).orElse(0)); //resultingEvents isEmpty when a wrong request appeared + Logger.trace("Checking whether resultingEvents (an optional) is empty"); if (resultingEvents.isEmpty()) { + Logger.debug("Rejecting requests from participant '{}'", source.name); reject(source); } else { - var events = resultingEvents.get(); - connection.sendEvents(source, events); - badRequests = 0; + accept(source, resultingEvents.get()); } - updateTimer(); } @@ -117,26 +123,41 @@ public class Lobby { */ void updateTimer() { var currentActiveCharacterType = game.state.getActiveCharacter().type; + Logger.trace("Updating turnTimer with current entityType {}", currentActiveCharacterType); if (pauseSegment.isPaused()) { + Logger.trace("Game is paused, clearing turnTimer"); turnTimer.clear(); } else if (currentActiveCharacterType == EntityType.P1) { + Logger.trace("Scheduling turnTimer for Player1"); turnTimer.startTurnTimer(connection.getPlayer1()); } else if (currentActiveCharacterType == EntityType.P2) { + Logger.trace("Scheduling turnTimer for Player2"); turnTimer.startTurnTimer(connection.getPlayer2()); } else { turnTimer.clear(); } } + private void accept(Participant source, List accepted) { + Logger.debug("Accepting requests from participant '{}', broadcasting events to all except source", + source.name); + connection.broadcastToAllExcept(source, accepted.toArray(new Event[0])); + + Logger.trace("Adding ack and sending back to originParticipant"); + accepted.add(0, new EventBuilder(EventType.Ack).buildGameStateEvent()); + connection.sendEvents(source, accepted.toArray(new Event[0])); + badRequests = 0; + } + /** * If the player executed a false request the request gets rejected. * @param source the executing player */ - private synchronized void reject(Participant source) { + private void reject(Participant source) { connection.sendEvents(source, new EventBuilder(EventType.Nack).buildGameEvent(), game.getGameStateEvent()); badRequests ++; //if the player sends 2 bad messages after one another, the player gets kicked out of the lobby. - if(badRequests == 2){ + if(badRequests >= 5){ connection.removeParticipant(source); if(connection.hasPlayer1()){ generateWin(connection.getPlayer1()); @@ -178,9 +199,12 @@ public class Lobby { } } - public synchronized void turnTimeout(Participant source){ - //source round is over next players turn - connection.sendEvents(source, game.endTurn().toArray(new Event[0])); + /** Skips the current turn, and starts a new one. Exclusively called in the {@link TurnTimer}. */ + private synchronized void turnTimeout(Participant source){ + var nextTurnEvents = game.endTurn(); + nextTurnEvents.add(game.getGameStateEvent()); + connection.broadcastEvents(); + updateTimer(); } /** @@ -189,7 +213,7 @@ public class Lobby { * * @param winner is the {@link Participant} that won */ - public synchronized void generateWin(Participant winner){ + public synchronized void generateWin(Participant winner){ connection.broadcastEvents( new EventBuilder(EventType.WinEvent) .withPlayerWon(winner.type.equals(ParticipantType.PlayerOne) ? 1 : 2) diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/TurnTimer.java b/Server/src/main/java/uulm/teamname/marvelous/server/lobby/TurnTimer.java index fc28f44..7e610d7 100644 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/TurnTimer.java +++ b/Server/src/main/java/uulm/teamname/marvelous/server/lobby/TurnTimer.java @@ -1,23 +1,34 @@ package uulm.teamname.marvelous.server.lobby; +import org.tinylog.Logger; import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType; import uulm.teamname.marvelous.server.lobbymanager.Participant; import java.util.Timer; import java.util.TimerTask; +import java.util.concurrent.*; import java.util.function.Consumer; /** * The {@link TurnTimer} class is called by the {@link Lobby} to limit the amount of time a player has per round. */ public class TurnTimer { - private final Timer timer; + private final ScheduledExecutorService timer; private final Consumer callback; private final int maxRoundTime; + private ScheduledFuture current; + public TurnTimer(int maxRoundTime, Consumer callback) { - this.timer = new Timer(); - this.maxRoundTime = maxRoundTime; + String lobbyThreadName = Thread.currentThread().getName(); + ThreadFactory threadFactory = new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + return new Thread(r, lobbyThreadName + "-timerThread"); + } + }; + this.timer = Executors.newSingleThreadScheduledExecutor(threadFactory); + this.maxRoundTime = 3; this.callback = callback; } @@ -26,21 +37,23 @@ public class TurnTimer { * @param participant the timer is for */ public void startTurnTimer(Participant participant) { - if (participant.type == ParticipantType.Spectator) + if (participant.type == ParticipantType.Spectator) { throw new IllegalStateException("Spectators don't have TurnTime"); + } - // cancel all current timers, and start a new timer - timer.cancel(); - timer.schedule(new TimerTask() { - @Override - public void run() { - callback.accept(participant); - } - }, maxRoundTime); + clear(); + Logger.debug("Starting turn timer for participant '{}' with role {}", + participant.name, participant.type); + current = timer.schedule(() -> {callback.accept(participant); return participant;}, + maxRoundTime, + TimeUnit.SECONDS); } /** cancels all currently running timers */ public void clear() { - timer.cancel(); + Logger.trace("Clearing timer"); + if (this.current != null) { + current.cancel(false); + } } } diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/Pipeline.java b/Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/Pipeline.java index 2ddcb01..7226ffc 100644 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/Pipeline.java +++ b/Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/Pipeline.java @@ -37,7 +37,7 @@ public class Pipeline { * out of the {@link Optional}, first check whether the {@link Optional} is empty by doing * {@link Optional#isEmpty()} or {@link Optional#isPresent()}, and act accordingly. */ - public Optional processRequests(Request[] requests, Participant origin) { + public Optional> processRequests(Request[] requests, Participant origin) { Logger.trace("Pipeline started RequestProcessing"); // The packet carries the requests, and gets smaller per segment Packet packet = new Packet(requests, origin); @@ -60,7 +60,7 @@ public class Pipeline { return Optional.empty(); } else { // else return an optional of the array Logger.trace("Pipeline executed normally. Returning {} events.", carrier.size()); - return Optional.of(carrier.toArray(new Event[0])); + return Optional.of(carrier); } }