Feat: Expiry broadcast + client event handling and robustness #278

Merged
jona.walpert merged 3 commits from feat/lobby-expiry-broadcast into main 2026-04-12 14:57:43 +02:00
Showing only changes of commit 3e1d787b69 - Show all commits
@@ -1,6 +1,9 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby; package ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.Lobby.AddResult; import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.Lobby.AddResult;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -17,6 +20,7 @@ public class LobbyManager {
private final Map<LobbyId, Lobby> activeLobbies = new ConcurrentHashMap<>(); private final Map<LobbyId, Lobby> activeLobbies = new ConcurrentHashMap<>();
private final Map<String, LobbyId> playerToLobby = new ConcurrentHashMap<>(); private final Map<String, LobbyId> playerToLobby = new ConcurrentHashMap<>();
private final Map<LobbyId, Instant> creationTimes = new ConcurrentHashMap<>();
private final List<LobbyEventListener> listeners = new CopyOnWriteArrayList<>(); private final List<LobbyEventListener> listeners = new CopyOnWriteArrayList<>();
private final int maxPlayersPerLobby; private final int maxPlayersPerLobby;
private static final Logger LOGGER = Logger.getLogger(LobbyManager.class.getName()); private static final Logger LOGGER = Logger.getLogger(LobbyManager.class.getName());
@@ -39,6 +43,7 @@ public class LobbyManager {
if (!activeLobbies.containsKey(id)) { if (!activeLobbies.containsKey(id)) {
Lobby lobby = new Lobby(id, name == null ? ("Room " + i) : name); Lobby lobby = new Lobby(id, name == null ? ("Room " + i) : name);
activeLobbies.put(id, lobby); activeLobbies.put(id, lobby);
creationTimes.put(id, Instant.now());
return id; return id;
} }
} }
@@ -136,6 +141,35 @@ public class LobbyManager {
return activeLobbies.values(); return activeLobbies.values();
} }
/** Find all empty lobbies that were created more than the given {@code age} ago. */
public List<LobbyId> findEmptyLobbiesOlderThan(Duration age) {
List<LobbyId> result = new ArrayList<>();
Instant cutoff = Instant.now().minus(age);
for (Map.Entry<LobbyId, Lobby> e : activeLobbies.entrySet()) {
LobbyId id = e.getKey();
Lobby l = e.getValue();
if (l.getPlayerNames().isEmpty()) {
Instant created = creationTimes.get(id);
if (created != null && created.isBefore(cutoff)) {
result.add(id);
}
}
}
return result;
}
/** Remove a lobby and clean up internal mappings. */
public void removeLobby(LobbyId id) {
Lobby removed = activeLobbies.remove(id);
creationTimes.remove(id);
if (removed == null) {
return;
}
for (String username : removed.getPlayerNames()) {
playerToLobby.remove(username);
}
}
/** /**
* Apply the given action to every player username in the lobby identified by {@code lobbyId}. * Apply the given action to every player username in the lobby identified by {@code lobbyId}.
* This is a small helper that keeps iteration logic centralized and avoids leaking internal * This is a small helper that keeps iteration logic centralized and avoids leaking internal