527 lines
21 KiB
HTML
527 lines
21 KiB
HTML
|
||
|
||
|
||
<!DOCTYPE html>
|
||
<html id="htmlId">
|
||
<head>
|
||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
|
||
<title>Coverage Report > GameEventProcessor</title>
|
||
<style type="text/css">
|
||
@import "../../css/coverage.css";
|
||
@import "../../css/idea.min.css";
|
||
</style>
|
||
<script type="text/javascript" src="../../js/highlight.min.js"></script>
|
||
<script type="text/javascript" src="../../js/highlightjs-line-numbers.min.js"></script>
|
||
</head>
|
||
|
||
<body>
|
||
<div class="content">
|
||
<div class="breadCrumbs">
|
||
Current scope: <a href="../../index.html">all classes</a>
|
||
<span class="separator">|</span>
|
||
<a href="../index.html">it.polimi.ingsw.gc14</a>
|
||
</div>
|
||
|
||
<h1>Coverage Summary for Class: GameEventProcessor (it.polimi.ingsw.gc14)</h1>
|
||
|
||
<table class="coverageStats">
|
||
|
||
<tr>
|
||
<th class="name">Class</th>
|
||
<th class="coverageStat
|
||
">
|
||
Method, %
|
||
</th>
|
||
<th class="coverageStat
|
||
">
|
||
Branch, %
|
||
</th>
|
||
<th class="coverageStat
|
||
">
|
||
Line, %
|
||
</th>
|
||
</tr>
|
||
<tr>
|
||
<td class="name">GameEventProcessor</td>
|
||
<td class="coverageStat">
|
||
<span class="percent">
|
||
0%
|
||
</span>
|
||
<span class="absValue">
|
||
(0/19)
|
||
</span>
|
||
</td>
|
||
<td class="coverageStat">
|
||
<span class="percent">
|
||
0%
|
||
</span>
|
||
<span class="absValue">
|
||
(0/48)
|
||
</span>
|
||
</td>
|
||
<td class="coverageStat">
|
||
<span class="percent">
|
||
0%
|
||
</span>
|
||
<span class="absValue">
|
||
(0/110)
|
||
</span>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td class="name">GameEventProcessor$1</td>
|
||
<td class="coverageStat">
|
||
<span class="percent">
|
||
0%
|
||
</span>
|
||
<span class="absValue">
|
||
(0/1)
|
||
</span>
|
||
</td>
|
||
<td class="coverageStat">
|
||
<span class="percent">
|
||
0%
|
||
</span>
|
||
<span class="absValue">
|
||
(0/1)
|
||
</span>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td class="name"><strong>Total</strong></td>
|
||
<td class="coverageStat">
|
||
<span class="percent">
|
||
0%
|
||
</span>
|
||
<span class="absValue">
|
||
(0/20)
|
||
</span>
|
||
</td>
|
||
<td class="coverageStat">
|
||
<span class="percent">
|
||
0%
|
||
</span>
|
||
<span class="absValue">
|
||
(0/48)
|
||
</span>
|
||
</td>
|
||
<td class="coverageStat">
|
||
<span class="percent">
|
||
0%
|
||
</span>
|
||
<span class="absValue">
|
||
(0/111)
|
||
</span>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
|
||
<br/>
|
||
<br/>
|
||
|
||
|
||
<pre>
|
||
<code class="sourceCode" id="sourceCode"> package it.polimi.ingsw.gc14;
|
||
|
||
import it.polimi.ingsw.gc14.Controller.GameController;
|
||
import it.polimi.ingsw.gc14.Model.Game;
|
||
import it.polimi.ingsw.gc14.Model.GamePackage.GameStages;
|
||
import it.polimi.ingsw.gc14.Network.CompositeClientBroadcaster;
|
||
import it.polimi.ingsw.gc14.Network.EventType;
|
||
import it.polimi.ingsw.gc14.Network.NetworkEvent;
|
||
import it.polimi.ingsw.gc14.Network.NetworkEvents.*;
|
||
|
||
import java.util.ArrayList;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
import java.util.concurrent.*;
|
||
import java.util.stream.Collectors;
|
||
|
||
/**
|
||
* Processes game events from the action queue and routes each one
|
||
* based on the current game state.
|
||
*
|
||
* <p>The three top-level states that determine routing are:
|
||
* <ul>
|
||
* <li><b>Inactive</b> – no game model is present, or the game has ended.
|
||
* Only disconnection cleanup is performed.</li>
|
||
* <li><b>Suspended</b> – a forfeit timer is running because exactly one
|
||
* player remains online. Only reconnection events are accepted.</li>
|
||
* <li><b>Active</b> – normal gameplay; every event is applied, saved,
|
||
* and broadcast.</li>
|
||
* </ul>
|
||
*
|
||
* <p>This class is not thread-safe by itself: it relies on the caller
|
||
* (the game loop in {@code ServerLauncher}) to drive it from a single
|
||
* thread via {@link #doFirstEvent()}.
|
||
*/
|
||
public class GameEventProcessor {
|
||
/** Queue from which incoming client events are consumed one at a time. */
|
||
private final BlockingQueue<NetworkEvent> actionQueue;
|
||
/** Server-side game controller; all model mutations go through this. */
|
||
private final GameController gameController;
|
||
/** Shared map tracking each player's online status ({@code true} = online). */
|
||
private final LimitedMap<String, Boolean> playerList;
|
||
/** Broadcaster used to push events and model snapshots to all connected clients. */
|
||
private final CompositeClientBroadcaster broadcaster;
|
||
/** Persists and deletes game save files. */
|
||
private final SaveManager saveManager;
|
||
|
||
/** Single-thread executor used exclusively for the forfeit timer. Daemon so it does not block JVM shutdown. */
|
||
<b class="nc"> private final ScheduledExecutorService timerExecutor =</b>
|
||
<b class="nc"> Executors.newSingleThreadScheduledExecutor(r -> {</b>
|
||
<b class="nc"> Thread t = new Thread(r, "forfeit-timer");</b>
|
||
<b class="nc"> t.setDaemon(true);</b>
|
||
<b class="nc"> return t;</b>
|
||
});
|
||
|
||
/**
|
||
* Handle to the running forfeit timer, or {@code null} when no timer is active.
|
||
* A non-null value signals that the game is in the suspended state.
|
||
*/
|
||
private ScheduledFuture<?> disconnectionTimer;
|
||
|
||
/**
|
||
* Constructs a {@code GameEventProcessor} with all required dependencies.
|
||
*
|
||
* @param actionQueue the queue from which incoming events are consumed.
|
||
* @param gameController the server-side game controller.
|
||
* @param playerList the shared map tracking each player's online status.
|
||
* @param broadcaster the broadcaster used to notify all connected clients.
|
||
* @param saveManager the save manager used to persist the game state.
|
||
*/
|
||
public GameEventProcessor(
|
||
BlockingQueue<NetworkEvent> actionQueue,
|
||
GameController gameController,
|
||
LimitedMap<String, Boolean> playerList,
|
||
CompositeClientBroadcaster broadcaster,
|
||
<b class="nc"> SaveManager saveManager) {</b>
|
||
<b class="nc"> this.actionQueue = actionQueue;</b>
|
||
<b class="nc"> this.gameController = gameController;</b>
|
||
<b class="nc"> this.playerList = playerList;</b>
|
||
<b class="nc"> this.broadcaster = broadcaster;</b>
|
||
<b class="nc"> this.saveManager = saveManager;</b>
|
||
}
|
||
|
||
|
||
/**
|
||
* Blocks until one event is available in the queue, then routes it to the
|
||
* appropriate handler based on the current game state.
|
||
*
|
||
* @throws InterruptedException if the thread is interrupted while waiting
|
||
* for the next event.
|
||
*/
|
||
public void doFirstEvent() throws InterruptedException {
|
||
<b class="nc"> NetworkEvent event = actionQueue.take();</b>
|
||
|
||
<b class="nc"> if (!isGameActive()) {</b>
|
||
<b class="nc"> handleInactiveGame(event);</b>
|
||
<b class="nc"> } else if (isSuspended()) {</b>
|
||
<b class="nc"> handleSuspendedGame(event);</b>
|
||
} else {
|
||
<b class="nc"> applyAndBroadcast(event);</b>
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* Returns {@code true} when there is an ongoing game that has not yet ended.
|
||
*/
|
||
private boolean isGameActive() {
|
||
<b class="nc"> Game model = gameController.getModel();</b>
|
||
<b class="nc"> return model != null</b>
|
||
<b class="nc"> && model.getCurrentState().getGameStage() != GameStages.ENDED;</b>
|
||
}
|
||
|
||
/**
|
||
* Returns {@code true} when the forfeit timer is running, meaning only one
|
||
* player is currently online and the game is waiting for a reconnection.
|
||
*/
|
||
private boolean isSuspended() {
|
||
<b class="nc"> return disconnectionTimer != null;</b>
|
||
}
|
||
|
||
|
||
/**
|
||
* Handles events that arrive when no active game exists (not yet started,
|
||
* or already ended). Only disconnection cleanup is relevant here.
|
||
*
|
||
* @param event the incoming event.
|
||
*/
|
||
private void handleInactiveGame(NetworkEvent event) {
|
||
<b class="nc"> if (event.getEventType() != EventType.DISCONNECTED_PLAYER) return;</b>
|
||
|
||
<b class="nc"> synchronized (gameController) {</b>
|
||
<b class="nc"> playerList.remove(event.getUsername());</b>
|
||
<b class="nc"> if (playerList.isEmpty()) {</b>
|
||
<b class="nc"> gameController.setModel(null);</b>
|
||
<b class="nc"> System.out.println("\n!!! Player list is now empty, ready for a new game init !!!\n");</b>
|
||
}
|
||
<b class="nc"> }</b>
|
||
}
|
||
|
||
|
||
/**
|
||
* Routes events while the game is suspended waiting for a reconnection.
|
||
* <ul>
|
||
* <li>A second disconnection while suspended means no player remains
|
||
* online: the game is aborted entirely.</li>
|
||
* <li>Any non-reconnection event is rejected with an error.</li>
|
||
* <li>A reconnection event is allowed through to {@link #applyAndBroadcast}.</li>
|
||
* </ul>
|
||
*
|
||
* @param event the incoming event.
|
||
*/
|
||
private void handleSuspendedGame(NetworkEvent event) {
|
||
<b class="nc"> switch (event.getEventType()) {</b>
|
||
<b class="nc"> case DISCONNECTED_PLAYER -> abortGame();</b>
|
||
<b class="nc"> case RECONNECT_PLAYER -> applyAndBroadcast(event);</b>
|
||
<b class="nc"> default -> rejectEvent(event);</b>
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Cancels the forfeit timer, clears the player list, and resets the model.
|
||
* Called when the last remaining player disconnects while the game is suspended.
|
||
*/
|
||
private void abortGame() {
|
||
<b class="nc"> disconnectionTimer.cancel(true);</b>
|
||
<b class="nc"> disconnectionTimer = null;</b>
|
||
<b class="nc"> playerList.clear();</b>
|
||
<b class="nc"> gameController.setModel(null);</b>
|
||
<b class="nc"> System.out.println("\n!!! All players disconnected — game aborted, ready for a new game init !!!\n");</b>
|
||
}
|
||
|
||
/**
|
||
* Marks the event as an error and broadcasts it back to the requesting
|
||
* player. Used to reject actions that are not permitted in the current state.
|
||
*
|
||
* @param event the event to reject.
|
||
*/
|
||
private void rejectEvent(NetworkEvent event) {
|
||
<b class="nc"> event.setIsError(true);</b>
|
||
<b class="nc"> broadcaster.notifyAll(event);</b>
|
||
}
|
||
|
||
|
||
/**
|
||
* Applies the event to the game controller, saves the updated state,
|
||
* and broadcasts the result to connected clients.
|
||
*
|
||
* <p>The entire method body is synchronized on {@code gameController} to
|
||
* prevent concurrent modification of the game model by the network threads.
|
||
*
|
||
* @param event the event to apply.
|
||
*/
|
||
private void applyAndBroadcast(NetworkEvent event) {
|
||
<b class="nc"> synchronized (gameController) {</b>
|
||
<b class="nc"> int roundBefore = gameController.getModel().getCurrentState().getRound();</b>
|
||
|
||
<b class="nc"> event.setIsError(!event.apply(gameController));</b>
|
||
<b class="nc"> Game game = gameController.getModel();</b>
|
||
|
||
<b class="nc"> if (event.isError()) {</b>
|
||
<b class="nc"> broadcaster.notifyAll(event);</b>
|
||
<b class="nc"> return;</b>
|
||
}
|
||
|
||
<b class="nc"> cancelForfeitTimerIfReconnect(event);</b>
|
||
|
||
<b class="nc"> if (!saveManager.save(game)) {</b>
|
||
<b class="nc"> System.out.println("\n!!! Save failed !!!\n");</b>
|
||
}
|
||
else {
|
||
<b class="nc"> System.out.println(event.getUsername()+": "+ event.getEventType() + " save successful.");</b>
|
||
}
|
||
|
||
// During the lobby phase a disconnection only removes the player
|
||
// from the list; no broadcast is needed.
|
||
<b class="nc"> if (event.getEventType() == EventType.DISCONNECTED_PLAYER</b>
|
||
<b class="nc"> && game.getCurrentState().getGameStage() == GameStages.WAITING) {</b>
|
||
<b class="nc"> playerList.remove(event.getUsername());</b>
|
||
<b class="nc"> return;</b>
|
||
}
|
||
|
||
<b class="nc"> enrichEvent(event, game);</b>
|
||
<b class="nc"> startForfeitTimerIfNeeded(event, game);</b>
|
||
<b class="nc"> broadcastResult(event, game, roundBefore);</b>
|
||
<b class="nc"> }</b>
|
||
}
|
||
|
||
/**
|
||
* Cancels the forfeit timer if the event is a successful reconnection.
|
||
*
|
||
* @param event the event that was just successfully applied.
|
||
*/
|
||
private void cancelForfeitTimerIfReconnect(NetworkEvent event) {
|
||
<b class="nc"> if (event.getEventType() == EventType.RECONNECT_PLAYER</b>
|
||
&& disconnectionTimer != null
|
||
<b class="nc"> && !disconnectionTimer.isDone()) {</b>
|
||
<b class="nc"> disconnectionTimer.cancel(false);</b>
|
||
<b class="nc"> disconnectionTimer = null;</b>
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Populates the event with the current game state so that clients can
|
||
* update their mini-model after receiving it.
|
||
*
|
||
* <p>Each event subclass overrides {@link NetworkEvent#enrichWithGameState}
|
||
* to append any type-specific extra fields (available totems, etc.).
|
||
*
|
||
* @param event the event to enrich.
|
||
* @param game the current game model.
|
||
*/
|
||
private void enrichEvent(NetworkEvent event, Game game) {
|
||
<b class="nc"> event.enrichWithGameState(game, buildDisconnectedList(game));</b>
|
||
}
|
||
|
||
/**
|
||
* Schedules the 60-second forfeit timer if a disconnection has left
|
||
* exactly one player online.
|
||
*
|
||
* <p>If a previous timer is still pending it is canceled first to avoid
|
||
* duplicate timers.
|
||
*
|
||
* @param event the event that was just applied.
|
||
* @param game the current game model.
|
||
*/
|
||
private void startForfeitTimerIfNeeded(NetworkEvent event, Game game) {
|
||
<b class="nc"> if (event.getEventType() != EventType.DISCONNECTED_PLAYER) return;</b>
|
||
<b class="nc"> if (game.getCurrentState().getGameStage() == GameStages.ENDED) return;</b>
|
||
<b class="nc"> if (onlinePlayerCount() != 1) return;</b>
|
||
|
||
<b class="nc"> if (disconnectionTimer != null && !disconnectionTimer.isDone()) {</b>
|
||
<b class="nc"> disconnectionTimer.cancel(false);</b>
|
||
<b class="nc"> System.out.println("Disconnection TIMER reset");</b>
|
||
}
|
||
|
||
<b class="nc"> disconnectionTimer = timerExecutor.schedule(</b>
|
||
<b class="nc"> () -> endGameForfeit(game),</b>
|
||
1, TimeUnit.MINUTES
|
||
);
|
||
<b class="nc"> System.out.println("Disconnection TIMER started, 60 seconds from now..." );</b>
|
||
}
|
||
|
||
/**
|
||
* Ends the game by forfeit when the timer expires without a reconnection.
|
||
* Broadcasts an {@link EndedGame} event, deletes the save, and resets state.
|
||
*
|
||
* @param game the game model captured when the timer was scheduled.
|
||
*/
|
||
private void endGameForfeit(Game game) {
|
||
<b class="nc"> synchronized (gameController) {</b>
|
||
<b class="nc"> gameController.endGameForfeit();</b>
|
||
<b class="nc"> EndedGame forfeitEnd = new EndedGame(</b>
|
||
<b class="nc"> game.getSlotMap(), game.getOrderLogicCard(),</b>
|
||
<b class="nc"> game.getCurrentState(), game.getPlayerStanding()</b>
|
||
);
|
||
<b class="nc"> broadcaster.notifyAll(forfeitEnd);</b>
|
||
<b class="nc"> System.out.println("Timer expired: no player reconnected in 60 s.");</b>
|
||
<b class="nc"> removeOfflinePlayers();</b>
|
||
<b class="nc"> if (!saveManager.delete()) {</b>
|
||
<b class="nc"> System.out.println("\n!!! Couldn't delete save !!!\n");</b>
|
||
}
|
||
<b class="nc"> disconnectionTimer = null;</b>
|
||
<b class="nc"> }</b>
|
||
}
|
||
|
||
/**
|
||
* Determines what to broadcast after a successful event application:
|
||
* <ul>
|
||
* <li>If the round advanced, an {@link ApplyNextRound} event (with updated
|
||
* card lists) replaces the original event.</li>
|
||
* <li>If the game has ended, an {@link EndedGame} event is sent and the
|
||
* save file is deleted.</li>
|
||
* <li>Otherwise the original event is broadcast as-is.</li>
|
||
* </ul>
|
||
*
|
||
* @param event the event that was applied.
|
||
* @param game the current game model (post-apply).
|
||
* @param roundBefore the round number before the event was applied.
|
||
*/
|
||
private void broadcastResult(NetworkEvent event, Game game, int roundBefore) {
|
||
<b class="nc"> broadcaster.notifyAll(event);</b>
|
||
<b class="nc"> if (game.getCurrentState().getRound() != roundBefore) {</b>
|
||
<b class="nc"> ApplyNextRound nextRound = new ApplyNextRound(</b>
|
||
<b class="nc"> game.getSlotMap(), game.getOrderLogicCard(), game.getCurrentState(),</b>
|
||
<b class="nc"> game.getPlayers(),</b>
|
||
<b class="nc"> game.getUpperListTribeCards(), game.getLowerListTribeCards(),</b>
|
||
<b class="nc"> game.getUpperListBuilding(), game.getLowerListBuilding()</b>
|
||
);
|
||
<b class="nc"> broadcaster.notifyAll(nextRound);</b>
|
||
<b class="nc"> } else if (game.getCurrentState().getGameStage() == GameStages.ENDED) {</b>
|
||
<b class="nc"> EndedGame endedGame = new EndedGame(</b>
|
||
<b class="nc"> game.getSlotMap(), game.getOrderLogicCard(),</b>
|
||
<b class="nc"> game.getCurrentState(), game.getPlayerStanding()</b>
|
||
);
|
||
<b class="nc"> broadcaster.notifyAll(endedGame);</b>
|
||
<b class="nc"> if (!saveManager.delete()) {</b>
|
||
<b class="nc"> System.out.println("\n!!! Couldn't delete save !!!\n");</b>
|
||
}
|
||
<b class="nc"> removeOfflinePlayers();</b>
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* Returns the number of players currently marked as online in the player list.
|
||
*/
|
||
private long onlinePlayerCount() {
|
||
<b class="nc"> return playerList.values().stream().filter(v -> v).count();</b>
|
||
}
|
||
|
||
/**
|
||
* Builds the list of usernames of players currently marked as disconnected
|
||
* in the game model.
|
||
*
|
||
* @param game the current game model.
|
||
* @return a new {@link ArrayList} of disconnected usernames.
|
||
*/
|
||
private ArrayList<String> buildDisconnectedList(Game game) {
|
||
<b class="nc"> return game.getDisconnectedPlayers().entrySet().stream()</b>
|
||
<b class="nc"> .filter(Map.Entry::getValue)</b>
|
||
<b class="nc"> .map(e -> e.getKey().getUserName())</b>
|
||
<b class="nc"> .collect(Collectors.toCollection(ArrayList::new));</b>
|
||
}
|
||
|
||
/**
|
||
* Removes all offline entries (value {@code false}) from the player list.
|
||
* Online players remain until they disconnect naturally.
|
||
*/
|
||
private void removeOfflinePlayers() {
|
||
<b class="nc"> List<String> toRemove = playerList.entrySet().stream()</b>
|
||
<b class="nc"> .filter(e -> !e.getValue())</b>
|
||
<b class="nc"> .map(Map.Entry::getKey)</b>
|
||
<b class="nc"> .toList();</b>
|
||
<b class="nc"> toRemove.forEach(playerList::remove);</b>
|
||
}
|
||
}
|
||
</code>
|
||
</pre>
|
||
</div>
|
||
|
||
<script type="text/javascript">
|
||
(function() {
|
||
var msie = false, msie9 = false;
|
||
/*@cc_on
|
||
msie = true;
|
||
@if (@_jscript_version >= 9)
|
||
msie9 = true;
|
||
@end
|
||
@*/
|
||
|
||
if (!msie || msie && msie9) {
|
||
hljs.highlightAll()
|
||
hljs.initLineNumbersOnLoad();
|
||
}
|
||
})();
|
||
</script>
|
||
|
||
<div class="footer">
|
||
|
||
<div style="float:right;">generated on 2026-06-19 22:53</div>
|
||
</div>
|
||
</body>
|
||
</html>
|