diff --git a/backend/src/main/java/fr/zelytra/session/SessionManager.java b/backend/src/main/java/fr/zelytra/session/SessionManager.java index ced13d27..022e81ad 100644 --- a/backend/src/main/java/fr/zelytra/session/SessionManager.java +++ b/backend/src/main/java/fr/zelytra/session/SessionManager.java @@ -7,9 +7,9 @@ import io.quarkus.logging.Log; import jakarta.annotation.Nullable; -import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; /** * Manages sessions for a multiplayer game, allowing players to create, join, and leave sessions. @@ -18,17 +18,17 @@ public class SessionManager { private static SessionManager instance; - private final HashMap sessions; + private final ConcurrentHashMap sessions; // SotServer cached to avoid API spam and faster server response - private final HashMap sotServers; + private final ConcurrentHashMap sotServers; /** * Private constructor for singleton pattern. */ private SessionManager() { - this.sessions = new HashMap<>(); - this.sotServers = new HashMap<>(); + this.sessions = new ConcurrentHashMap<>(); + this.sotServers = new ConcurrentHashMap<>(); } /** @@ -96,10 +96,14 @@ public Fleet joinSession(String sessionId, Player player) { */ public void leaveSession(Player player) { for (Fleet fleet : sessions.values()) { + + SotServer sotServer = getSotServerFromPlayer(player); + if (sotServer != null) { + playerLeaveSotServer(player, sotServer); + } + fleet.getPlayers().remove(player); - fleet.getServers().forEach((key, value) -> { - value.getConnectedPlayers().remove(player); - }); + fleet.getServers().forEach((key, value) -> value.getConnectedPlayers().remove(player)); SessionSocket.broadcastDataToSession(fleet.getSessionId(), MessageType.UPDATE, fleet); Log.info("[" + fleet.getSessionId() + "] " + player.getUsername() + " Leave the session !"); @@ -111,6 +115,33 @@ public void leaveSession(Player player) { } } + /** + * Retrieves the {@link SotServer} instance that a specified player is currently connected to. + *

+ * This method iterates through all sessions and their corresponding fleets to find the SotServer + * to which the specified player is connected. If the player is found within a SotServer's + * connected players list, that SotServer is returned. + *

+ * It is assumed that a player can only be connected to one SotServer at any given time. + * If the player is not connected to any SotServer, or if the player does not exist, + * this method returns {@code null}. + * + * @param player The {@link Player} whose SotServer connection is to be retrieved. + * @return The {@link SotServer} instance the player is connected to, or {@code null} if the player + * is not connected to any SotServer or does not exist. + */ + public SotServer getSotServerFromPlayer(Player player) { + for (Fleet fleet : sessions.values()) { + for (SotServer server : fleet.getServers().values()) { + if (server.getConnectedPlayers().contains(player)) { + return server; + } + } + } + return null; + } + + /** * Retrieves a Fleet instance for a given session ID, if it exists. * diff --git a/backend/src/main/java/fr/zelytra/session/SessionSocket.java b/backend/src/main/java/fr/zelytra/session/SessionSocket.java index ec57a6d0..92c4aa0c 100644 --- a/backend/src/main/java/fr/zelytra/session/SessionSocket.java +++ b/backend/src/main/java/fr/zelytra/session/SessionSocket.java @@ -7,7 +7,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import fr.zelytra.session.countdown.SessionCountDown; import fr.zelytra.session.fleet.Fleet; import fr.zelytra.session.player.Player; import fr.zelytra.session.server.SotServer; @@ -29,6 +28,7 @@ public class SessionSocket { private final ExecutorService executor = Executors.newSingleThreadExecutor(); private final ConcurrentHashMap> sessionTimeoutTasks = new ConcurrentHashMap<>(); + private static final int RISE_ANCHOR_TIMER = 3; // in seconds @OnOpen public void onOpen(Session session) { @@ -71,10 +71,7 @@ public void onMessage(String message, Session session, @PathParam("sessionId") S Player player = objectMapper.convertValue(socketMessage.data(), Player.class); handleLeaveMessage(player); } - case START_COUNTDOWN -> { - SessionCountDown countDown = objectMapper.convertValue(socketMessage.data(), SessionCountDown.class); - handleStartCountdown(session, countDown); - } + case START_COUNTDOWN -> handleStartCountdown(session); case CLEAR_STATUS -> handleClearStatus(session); case JOIN_SERVER -> { SotServer sotServer = objectMapper.convertValue(socketMessage.data(), SotServer.class); @@ -100,17 +97,15 @@ private void handleClearStatus(Session session) { broadcastDataToSession(fleet.getSessionId(), MessageType.UPDATE, fleet); } - private void handleStartCountdown(Session session, SessionCountDown countDown) { - - countDown.calculateClickTime(); + private void handleStartCountdown(Session session) { SessionManager manager = SessionManager.getInstance(); Player player = manager.getPlayerFromSessionId(session.getId()); Fleet fleet = manager.getFleetByPlayerName(player.getUsername()); fleet.getStats().addTry(); - Log.info("[" + fleet.getSessionId() + "] Starting countdown at " + countDown.getClickTime().toString()); - broadcastDataToSession(fleet.getSessionId(), MessageType.RUN_COUNTDOWN, countDown); + Log.info("[" + fleet.getSessionId() + "] Starting countdown in " + SessionSocket.RISE_ANCHOR_TIMER + "s"); + broadcastDataToSession(fleet.getSessionId(), MessageType.RUN_COUNTDOWN, SessionSocket.RISE_ANCHOR_TIMER); broadcastDataToSession(fleet.getSessionId(), MessageType.UPDATE, fleet); } @@ -172,35 +167,25 @@ private void handleLeaveMessage(Player player) { @OnClose public void onClose(Session session) { - // Clean up resources related to the session sessionTimeoutTasks.remove(session.getId()); SessionManager manager = SessionManager.getInstance(); Player player = manager.getPlayerFromSessionId(session.getId()); - if (player != null) { manager.leaveSession(player); Log.info("[" + player.getUsername() + "] Disconnected"); - return; + } else { + Log.warn("[UNDEFINED PLAYER] Disconnected"); } - Log.warn("[UNDEFINED PLAYER] Disconnected"); } @OnError public void onError(Session session, Throwable throwable) throws IOException { Log.error("WebSocket error for session " + session.getId() + ": " + throwable); - - SessionManager manager = SessionManager.getInstance(); - Player player = manager.getPlayerFromSessionId(session.getId()); - if (player != null) { - manager.leaveSession(player); - } - session.close(); } - /** * Broadcasts a message to all players within a session. *

diff --git a/backend/src/main/java/fr/zelytra/session/countdown/SessionCountDown.java b/backend/src/main/java/fr/zelytra/session/countdown/SessionCountDown.java deleted file mode 100644 index 6d83a2c9..00000000 --- a/backend/src/main/java/fr/zelytra/session/countdown/SessionCountDown.java +++ /dev/null @@ -1,37 +0,0 @@ -package fr.zelytra.session.countdown; - -import java.time.LocalTime; - -public class SessionCountDown { - private LocalTime startingTimer; - private LocalTime clickTime; - - public SessionCountDown() { - - } - - public SessionCountDown(LocalTime startingTimer) { - this.startingTimer = startingTimer; - calculateClickTime(); - } - - public void calculateClickTime() { - this.clickTime = startingTimer.plusSeconds(6); - } - - public LocalTime getStartingTimer() { - return startingTimer; - } - - public void setStartingTimer(LocalTime startingTimer) { - this.startingTimer = startingTimer; - } - - public LocalTime getClickTime() { - return clickTime; - } - - public void setClickTime(LocalTime clickTime) { - this.clickTime = clickTime; - } -} diff --git a/backend/src/main/java/fr/zelytra/session/fleet/FleetStats.java b/backend/src/main/java/fr/zelytra/session/fleet/FleetStats.java index c11cfc00..e45de025 100644 --- a/backend/src/main/java/fr/zelytra/session/fleet/FleetStats.java +++ b/backend/src/main/java/fr/zelytra/session/fleet/FleetStats.java @@ -1,7 +1,5 @@ package fr.zelytra.session.fleet; -import io.quarkus.logging.Log; - public class FleetStats { @@ -31,7 +29,6 @@ public void setSuccessPrediction(int successPrediction) { public void addTry() { this.tryAmount++; - Log.info(this.tryAmount); } } diff --git a/frontend/src/components/fleet/FleetLobby.vue b/frontend/src/components/fleet/FleetLobby.vue index 46f03d04..999ed8c4 100644 --- a/frontend/src/components/fleet/FleetLobby.vue +++ b/frontend/src/components/fleet/FleetLobby.vue @@ -96,7 +96,6 @@ import PlayerFleet from "@/vue/fleet/PlayerFleet.vue"; import {useI18n} from "vue-i18n"; import BannerTemplate from "@/vue/templates/BannerTemplate.vue"; import {UserStore} from "@/objects/stores/UserStore.ts"; -import {LocalTime} from "@js-joda/core"; import SessionCountdown from "@/components/fleet/SessionCountdown.vue"; import ServerContainer from "@/vue/templates/ServerContainer.vue"; @@ -126,9 +125,6 @@ function startSession() { if (!UserStore.player.isMaster) { return; } - UserStore.player.countDown = { - startingTimer: LocalTime.now().toJSON() - } props.session!.runCountDown() } diff --git a/frontend/src/components/fleet/SessionCountdown.vue b/frontend/src/components/fleet/SessionCountdown.vue index 21fc61ab..adb70821 100644 --- a/frontend/src/components/fleet/SessionCountdown.vue +++ b/frontend/src/components/fleet/SessionCountdown.vue @@ -22,8 +22,9 @@ const alerts = inject("alertProvider"); const updateTimer = setInterval(() => { if (!UserStore.player.countDown || !UserStore.player.countDown.clickTime) return; - const start: LocalTime = LocalTime.now(); - const click: LocalTime = LocalTime.parse(UserStore.player.countDown.clickTime); + + const start:LocalTime = LocalTime.now() + const click:LocalTime = UserStore.player.countDown.clickTime as LocalTime if (click.isBefore(start)) { UserStore.player.countDown = undefined; @@ -72,6 +73,7 @@ const props = defineProps({session: {type: Object as PropType, required: onUnmounted(() => { clearInterval(updateTimer); + UserStore.player.countDown = undefined; }) diff --git a/frontend/src/objects/Fleet.ts b/frontend/src/objects/Fleet.ts index 64c32949..1d0157b3 100644 --- a/frontend/src/objects/Fleet.ts +++ b/frontend/src/objects/Fleet.ts @@ -3,9 +3,9 @@ import {WebSocketMessage, WebSocketMessageType} from "@/objects/WebSocet.ts"; import {AlertType} from "@/vue/alert/Alert.ts"; import {alertProvider} from "@/main.ts"; import i18n from "@/objects/i18n"; -import {SessionRunner} from "@/objects/SessionRunner.ts"; import {Player} from "@/objects/Player.ts"; import {SotServer} from "@/objects/SotServer.ts"; +import {LocalTime} from "@js-joda/core"; const {t} = i18n.global; @@ -78,7 +78,7 @@ export class Fleet { break; } case WebSocketMessageType.RUN_COUNTDOWN: { - this.handleSessionRunner(message.data as SessionRunner); + this.handleSessionRunner(message.data as number); break; } default: { @@ -105,6 +105,7 @@ export class Fleet { type: AlertType.ERROR, }); UserStore.player.fleet!.sessionId = ""; + UserStore.player.countDown = undefined; // Reset timer to avoid app freeze } this.safeClose = false; } @@ -122,9 +123,8 @@ export class Fleet { UserStore.player.isReady = player.isReady; } - private handleSessionRunner(countdown: SessionRunner) { - UserStore.player.countDown = countdown - + private handleSessionRunner(countdown: number) { + UserStore.player.countDown = {clickTime: LocalTime.now().plusSeconds(countdown)} } leaveSession(): void { @@ -148,14 +148,14 @@ export class Fleet { runCountDown() { if (!this.socket) return; const message: WebSocketMessage = { - data: UserStore.player.countDown, + data: null, messageType: WebSocketMessageType.START_COUNTDOWN, }; this.socket.send(JSON.stringify(message)); } clearPlayersStatus() { - if (!this.socket) return; + if (!this.socket || !UserStore.player.isMaster) return; const message: WebSocketMessage = { data: undefined, messageType: WebSocketMessageType.CLEAR_STATUS, diff --git a/frontend/src/objects/SessionRunner.ts b/frontend/src/objects/SessionRunner.ts index 316be650..ffd2dbdc 100644 --- a/frontend/src/objects/SessionRunner.ts +++ b/frontend/src/objects/SessionRunner.ts @@ -1,4 +1,5 @@ +import {LocalTime} from "@js-joda/core"; + export interface SessionRunner { - startingTimer: string - clickTime?: string + clickTime: LocalTime } \ No newline at end of file