fix: correctly disaply lobbei and update them in Lobyb Screen
This commit is contained in:
@@ -24,8 +24,10 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* The ClientService class is responsible for managing the connection to the server, sending
|
||||
* commands, and receiving responses. It uses a TcpTransport to communicate with the server and an
|
||||
* The ClientService class is responsible for managing the connection to the
|
||||
* server, sending
|
||||
* commands, and receiving responses. It uses a TcpTransport to communicate with
|
||||
* the server and an
|
||||
* ExecutorService to handle asynchronous requests.
|
||||
*/
|
||||
public class ClientService {
|
||||
@@ -39,20 +41,20 @@ public class ClientService {
|
||||
private final AtomicInteger idGenerator;
|
||||
private Logger logger;
|
||||
|
||||
private final Map<Integer, ArrayBlockingQueue<ParsedResponse>> pendingResponses =
|
||||
new ConcurrentHashMap<>();
|
||||
private final CopyOnWriteArrayList<Consumer<List<String>>> eventListeners =
|
||||
new CopyOnWriteArrayList<>();
|
||||
private final Map<Integer, ArrayBlockingQueue<ParsedResponse>> pendingResponses = new ConcurrentHashMap<>();
|
||||
private final CopyOnWriteArrayList<Consumer<List<String>>> eventListeners = new CopyOnWriteArrayList<>();
|
||||
private Thread readerThread = null;
|
||||
private final AtomicBoolean running = new AtomicBoolean(false);
|
||||
private static final int READER_JOIN_TIMEOUT_MS = 500;
|
||||
|
||||
/**
|
||||
* Constructs a ClientService with the given server IP and port. It establishes a socket
|
||||
* connection to the server and initializes the TcpTransport and ExecutorService for
|
||||
* Constructs a ClientService with the given server IP and port. It establishes
|
||||
* a socket
|
||||
* connection to the server and initializes the TcpTransport and ExecutorService
|
||||
* for
|
||||
* communication.
|
||||
*
|
||||
* @param ip The IP address of the server to connect to.
|
||||
* @param ip The IP address of the server to connect to.
|
||||
* @param port The port number of the server to connect to.
|
||||
*/
|
||||
public ClientService(String ip, int port) {
|
||||
@@ -77,25 +79,24 @@ public class ClientService {
|
||||
|
||||
private void startReaderThread() {
|
||||
running.set(true);
|
||||
readerThread =
|
||||
new Thread(
|
||||
() -> {
|
||||
while (running.get()) {
|
||||
try {
|
||||
RawPacket rp = clienttcptransport.read();
|
||||
processRawPacket(rp);
|
||||
} catch (IOException e) {
|
||||
if (running.get()) {
|
||||
logger.warn("IO error on transport reader", e);
|
||||
}
|
||||
break;
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
readerThread = new Thread(
|
||||
() -> {
|
||||
while (running.get()) {
|
||||
try {
|
||||
RawPacket rp = clienttcptransport.read();
|
||||
processRawPacket(rp);
|
||||
} catch (IOException e) {
|
||||
if (running.get()) {
|
||||
logger.warn("IO error on transport reader", e);
|
||||
}
|
||||
},
|
||||
"casono-client-reader");
|
||||
break;
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
"casono-client-reader");
|
||||
readerThread.setDaemon(true);
|
||||
readerThread.start();
|
||||
}
|
||||
@@ -111,13 +112,14 @@ public class ClientService {
|
||||
|
||||
for (String rawLine : responseText.split("\\n")) {
|
||||
String line = rawLine;
|
||||
String trimmed = line.trim();
|
||||
if (!hasStatus) {
|
||||
if ("+OK".equals(line)) {
|
||||
if ("+OK".equals(trimmed)) {
|
||||
success = true;
|
||||
hasStatus = true;
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith("-ERR") || line.startsWith("-ERROR")) {
|
||||
if (trimmed.startsWith("-ERR") || trimmed.startsWith("-ERROR")) {
|
||||
success = false;
|
||||
hasStatus = true;
|
||||
continue;
|
||||
@@ -125,13 +127,12 @@ public class ClientService {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ("END".equals(line)) {
|
||||
if ("END".equals(trimmed)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.startsWith("\t")) {
|
||||
line = line.substring(1);
|
||||
}
|
||||
// remove leading indentation (tabs/spaces) so parameters match regex parsing
|
||||
line = line.replaceFirst("^\\s+", "");
|
||||
lines.add(line);
|
||||
}
|
||||
|
||||
@@ -152,6 +153,7 @@ public class ClientService {
|
||||
if (rid == 0) {
|
||||
for (Consumer<List<String>> l : eventListeners) {
|
||||
try {
|
||||
logger.debug("Parsed response lines: {}", lines);
|
||||
l.accept(List.copyOf(lines));
|
||||
} catch (Exception e) {
|
||||
logger.warn("Event listener threw", e);
|
||||
@@ -168,7 +170,8 @@ public class ClientService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a ClientService in offline mode. No network connection will be attempted and calls
|
||||
* Constructs a ClientService in offline mode. No network connection will be
|
||||
* attempted and calls
|
||||
* to processCommand will throw a RuntimeException.
|
||||
*
|
||||
* @param offline true to create an offline (no-network) client service
|
||||
@@ -181,19 +184,21 @@ public class ClientService {
|
||||
this.executor = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
|
||||
/** Returns true if this ClientService is running in offline mode (no network). */
|
||||
/**
|
||||
* Returns true if this ClientService is running in offline mode (no network).
|
||||
*/
|
||||
public boolean isOffline() {
|
||||
return offlineMode;
|
||||
}
|
||||
|
||||
// Allow primitive values to contain hyphens (UUIDs) in addition to
|
||||
// digits/words/colons
|
||||
static Pattern responseRex =
|
||||
Pattern.compile(
|
||||
"(?<key>\\w+)=(('(?<string>([^']|\\')+)')|(?<primVal>[+-]?[-\\d\\w:]+))");
|
||||
static Pattern responseRex = Pattern.compile(
|
||||
"(?<key>\\w+)=(('(?<string>([^']|\\')+)')|(?<primVal>[+-]?[-\\d\\w:]+))");
|
||||
|
||||
/**
|
||||
* Removes escape characters from a string, specifically converting escaped single quotes (\')
|
||||
* Removes escape characters from a string, specifically converting escaped
|
||||
* single quotes (\')
|
||||
* back to regular single quotes (').
|
||||
*
|
||||
* @param input The escaped string to process.
|
||||
@@ -204,8 +209,10 @@ public class ClientService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a list of raw string parameters into a list of {@link RequestParameter} objects. It
|
||||
* uses a regex matcher to distinguish between quoted strings (which are unescaped) and
|
||||
* Converts a list of raw string parameters into a list of
|
||||
* {@link RequestParameter} objects. It
|
||||
* uses a regex matcher to distinguish between quoted strings (which are
|
||||
* unescaped) and
|
||||
* primitive values.
|
||||
*
|
||||
* @param input A list of raw strings to be parsed.
|
||||
@@ -230,10 +237,12 @@ public class ClientService {
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static record ParsedResponse(boolean success, List<String> lines) {}
|
||||
private static record ParsedResponse(boolean success, List<String> lines) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an event listener that receives unsolicited event payload lines (no status prefix).
|
||||
* Register an event listener that receives unsolicited event payload lines (no
|
||||
* status prefix).
|
||||
*/
|
||||
public void addEventListener(Consumer<List<String>> listener) {
|
||||
eventListeners.add(listener);
|
||||
@@ -244,14 +253,18 @@ public class ClientService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a command to the server and processes the multi-line response. It handles the protocol
|
||||
* handshake (expecting +OK), strips leading tabs from response lines, and collects them until
|
||||
* Sends a command to the server and processes the multi-line response. It
|
||||
* handles the protocol
|
||||
* handshake (expecting +OK), strips leading tabs from response lines, and
|
||||
* collects them until
|
||||
* the "END" marker is reached.
|
||||
*
|
||||
* @param message The raw command string to be sent to the transport layer.
|
||||
* @return A list of response lines received from the server (excluding protocol markers).
|
||||
* @throws RuntimeException if the server responds with an error or if a communication failure
|
||||
* occurs.
|
||||
* @return A list of response lines received from the server (excluding protocol
|
||||
* markers).
|
||||
* @throws RuntimeException if the server responds with an error or if a
|
||||
* communication failure
|
||||
* occurs.
|
||||
*/
|
||||
protected List<String> processCommand(String message) {
|
||||
if (offlineMode) {
|
||||
@@ -262,15 +275,14 @@ public class ClientService {
|
||||
ArrayBlockingQueue<ParsedResponse> q = new ArrayBlockingQueue<>(1);
|
||||
pendingResponses.put(reqId, q);
|
||||
|
||||
Future<?> writeFuture =
|
||||
executor.submit(
|
||||
() -> {
|
||||
try {
|
||||
clienttcptransport.write(new RawPacket(reqId, message));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
Future<?> writeFuture = executor.submit(
|
||||
() -> {
|
||||
try {
|
||||
clienttcptransport.write(new RawPacket(reqId, message));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
writeFuture.get();
|
||||
@@ -302,11 +314,15 @@ public class ClientService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to send a request to the server using the ExecutorService. It submits the
|
||||
* request as a Runnable task and waits for its completion. If the task is interrupted or
|
||||
* encounters an execution exception, it throws a RuntimeException with the appropriate cause.
|
||||
* Helper method to send a request to the server using the ExecutorService. It
|
||||
* submits the
|
||||
* request as a Runnable task and waits for its completion. If the task is
|
||||
* interrupted or
|
||||
* encounters an execution exception, it throws a RuntimeException with the
|
||||
* appropriate cause.
|
||||
*
|
||||
* @param request The Runnable task representing the request to be sent to the server.
|
||||
* @param request The Runnable task representing the request to be sent to the
|
||||
* server.
|
||||
*/
|
||||
private void sendRequest(Runnable request) {
|
||||
Future<?> future = executor.submit(request);
|
||||
@@ -320,9 +336,12 @@ public class ClientService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to extract the cause of an exception and return it as a RuntimeException. If
|
||||
* the cause is null, it returns the original exception as a RuntimeException. If the cause is
|
||||
* already a RuntimeException, it returns it directly. Otherwise, it wraps the cause in a new
|
||||
* Helper method to extract the cause of an exception and return it as a
|
||||
* RuntimeException. If
|
||||
* the cause is null, it returns the original exception as a RuntimeException.
|
||||
* If the cause is
|
||||
* already a RuntimeException, it returns it directly. Otherwise, it wraps the
|
||||
* cause in a new
|
||||
* RuntimeException and returns it.
|
||||
*
|
||||
* @param e The exception from which to extract the cause.
|
||||
@@ -340,8 +359,10 @@ public class ClientService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the socket connection to the server and shuts down the ExecutorService. It also closes
|
||||
* the TcpTransport used for communication. If any IOException occurs during this process, it
|
||||
* Closes the socket connection to the server and shuts down the
|
||||
* ExecutorService. It also closes
|
||||
* the TcpTransport used for communication. If any IOException occurs during
|
||||
* this process, it
|
||||
* prints the exception to the console.
|
||||
*/
|
||||
public void closeSocket() {
|
||||
|
||||
+153
-33
@@ -85,6 +85,8 @@ public class LobbyButtonGridManager {
|
||||
} catch (NumberFormatException ex) {
|
||||
return;
|
||||
}
|
||||
LOGGER.info("LOBBY_CLOSED event for lobby {} — mapping before: {}", lid,
|
||||
translationManager.getButtonIdToLobbyId());
|
||||
// find button id(s) for this lobby and remove mapping
|
||||
Map<Integer, Integer> mapping = translationManager.getButtonIdToLobbyId();
|
||||
Integer toRemove = null;
|
||||
@@ -95,9 +97,46 @@ public class LobbyButtonGridManager {
|
||||
}
|
||||
}
|
||||
if (toRemove != null) {
|
||||
LOGGER.info("Removing mapping for button {} -> lobby {} (LOBBY_CLOSED event)", toRemove,
|
||||
lid);
|
||||
translationManager.removeLobbyButton(toRemove);
|
||||
LOGGER.debug("Mapping after removal: {}", translationManager.getButtonIdToLobbyId());
|
||||
javafx.application.Platform.runLater(this::renderLobbyButtons);
|
||||
}
|
||||
} else if (evt != null && "LOBBY_CREATED".equalsIgnoreCase(evt) && lidStr != null) {
|
||||
int lid;
|
||||
try {
|
||||
lid = Integer.parseInt(lidStr);
|
||||
} catch (NumberFormatException ex) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.info("LOBBY_CREATED event for lobby {} — mapping before: {}", lid,
|
||||
translationManager.getButtonIdToLobbyId());
|
||||
|
||||
Map<Integer, Integer> mapping = translationManager.getButtonIdToLobbyId();
|
||||
|
||||
// If this lobby is already known, nothing to do
|
||||
if (mapping.containsValue(lid)) {
|
||||
LOGGER.debug("Lobby {} already mapped, skipping", lid);
|
||||
return;
|
||||
}
|
||||
|
||||
// find next free button id (1..8)
|
||||
for (int candidate = 1; candidate <= 8; candidate++) {
|
||||
if (!mapping.containsKey(candidate)) {
|
||||
try {
|
||||
translationManager.addLobbyButton(candidate, lid);
|
||||
LOGGER.info("Mapped new lobby {} to button {}", lid, candidate);
|
||||
LOGGER.debug("Mapping after add: {}", translationManager.getButtonIdToLobbyId());
|
||||
javafx.application.Platform.runLater(this::renderLobbyButtons);
|
||||
break;
|
||||
} catch (Exception ex) {
|
||||
// grid full? try next candidate
|
||||
LOGGER.debug("Could not map created lobby {}: {}", lid, ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -110,8 +149,29 @@ public class LobbyButtonGridManager {
|
||||
private void refreshMappings() {
|
||||
Map<Integer, Integer> mapping = translationManager.getButtonIdToLobbyId();
|
||||
|
||||
// Always attempt to sync with server-side lobby list so clients that start
|
||||
// after a lobby was created will discover it. This keeps the local mapping
|
||||
// in sync with the server without requiring a restart.
|
||||
List<LobbyClient.LobbyInfo> serverLobbies = null;
|
||||
java.util.Set<Integer> serverIds = new java.util.HashSet<>();
|
||||
try {
|
||||
serverLobbies = lobbyClient.getLobbyList();
|
||||
if (serverLobbies != null) {
|
||||
for (var li : serverLobbies) {
|
||||
serverIds.add(li.id);
|
||||
}
|
||||
}
|
||||
reconcileWithServerLobbies(serverLobbies);
|
||||
} catch (Exception ex) {
|
||||
LOGGER.debug("Could not fetch lobby list: {}", ex.getMessage());
|
||||
}
|
||||
|
||||
if (mapping.isEmpty()) {
|
||||
return;
|
||||
// mapping may still be empty after reconciliation
|
||||
if (translationManager.getButtonIdToLobbyId().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
mapping = translationManager.getButtonIdToLobbyId();
|
||||
}
|
||||
|
||||
List<Map.Entry<Integer, Integer>> entries = new ArrayList<>(mapping.entrySet());
|
||||
@@ -125,26 +185,91 @@ public class LobbyButtonGridManager {
|
||||
int lobbyId = lobbyIdObj.intValue();
|
||||
|
||||
CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
try {
|
||||
return lobbyClient.fetchLobbyStatusString(lobbyId);
|
||||
} catch (Exception ex) {
|
||||
LOGGER.info("Lobby {} missing: {}", lobbyId, ex.getMessage());
|
||||
return null;
|
||||
}
|
||||
},
|
||||
executor)
|
||||
() -> {
|
||||
try {
|
||||
return lobbyClient.fetchLobbyStatusString(lobbyId);
|
||||
} catch (Exception ex) {
|
||||
LOGGER.info("Lobby {} missing: {}", lobbyId, ex.getMessage());
|
||||
return null;
|
||||
}
|
||||
},
|
||||
executor)
|
||||
.thenAccept(
|
||||
status -> {
|
||||
// Do not remove a mapping just because the status fetch
|
||||
// temporarily returned null. Only remove mappings when
|
||||
// the server no longer lists the lobby (reconcileWithServerLobbies
|
||||
// handles removals based on authoritative server list).
|
||||
if (status == null) {
|
||||
translationManager.removeLobbyButton(buttonId);
|
||||
|
||||
javafx.application.Platform.runLater(this::renderLobbyButtons);
|
||||
if (!serverIds.contains(lobbyId)) {
|
||||
translationManager.removeLobbyButton(buttonId);
|
||||
javafx.application.Platform.runLater(this::renderLobbyButtons);
|
||||
} else {
|
||||
// treat as created/running later; keep mapping
|
||||
LOGGER.debug("Temporary missing status for lobby {} - keeping mapping",
|
||||
lobbyId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void reconcileWithServerLobbies(List<LobbyClient.LobbyInfo> serverLobbies) {
|
||||
if (serverLobbies == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<Integer, Integer> mapping = translationManager.getButtonIdToLobbyId();
|
||||
|
||||
// Build set of lobby ids returned by server
|
||||
java.util.Set<Integer> serverIds = new java.util.HashSet<>();
|
||||
for (var li : serverLobbies) {
|
||||
serverIds.add(li.id);
|
||||
}
|
||||
|
||||
LOGGER.debug("Reconciling mappings. serverIds={} mapping={}", serverIds, mapping);
|
||||
|
||||
// Remove mappings for lobbies that no longer exist
|
||||
java.util.List<Integer> toRemove = new java.util.ArrayList<>();
|
||||
for (var e : mapping.entrySet()) {
|
||||
Integer lid = e.getValue();
|
||||
if (lid == null) {
|
||||
continue;
|
||||
}
|
||||
if (!serverIds.contains(lid)) {
|
||||
toRemove.add(e.getKey());
|
||||
}
|
||||
}
|
||||
for (Integer bid : toRemove) {
|
||||
LOGGER.info("Reconciling: removing mapping for button {} (server no longer lists lobby)", bid);
|
||||
translationManager.removeLobbyButton(bid);
|
||||
}
|
||||
|
||||
// Add server lobbies that are not yet mapped
|
||||
for (var li : serverLobbies) {
|
||||
if (mapping.containsValue(li.id)) {
|
||||
continue;
|
||||
}
|
||||
// find next free button id (1..8)
|
||||
for (int candidate = 1; candidate <= 8; candidate++) {
|
||||
if (!mapping.containsKey(candidate)) {
|
||||
try {
|
||||
translationManager.addLobbyButton(candidate, li.id);
|
||||
LOGGER.info("Reconciling: added mapping button {} -> lobby {}", candidate, li.id);
|
||||
break;
|
||||
} catch (Exception ex) {
|
||||
// grid full? try next candidate
|
||||
LOGGER.debug("Could not add mapping for lobby {}: {}", li.id, ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!toRemove.isEmpty() || !serverLobbies.isEmpty()) {
|
||||
javafx.application.Platform.runLater(this::renderLobbyButtons);
|
||||
}
|
||||
}
|
||||
|
||||
public void renderLobbyButtons() {
|
||||
gridPane.getChildren().clear();
|
||||
|
||||
@@ -215,10 +340,8 @@ public class LobbyButtonGridManager {
|
||||
statusStr -> {
|
||||
LobbyStatus status = parseLobbyStatus(statusStr);
|
||||
|
||||
String path =
|
||||
getImagePathForButton(
|
||||
buttonId,
|
||||
status == null ? LobbyStatus.CREATED : status);
|
||||
String path = getImagePathForLobby(
|
||||
lobbyId, status == null ? LobbyStatus.CREATED : status);
|
||||
|
||||
Image img = safeLoadImage(path);
|
||||
|
||||
@@ -253,11 +376,11 @@ public class LobbyButtonGridManager {
|
||||
}
|
||||
}
|
||||
|
||||
private String getImagePathForButton(int buttonId, LobbyStatus status) {
|
||||
private String getImagePathForLobby(int lobbyId, LobbyStatus status) {
|
||||
|
||||
String statusStr = status == LobbyStatus.CREATED ? "created" : "running";
|
||||
|
||||
return String.format(BUTTON_IMAGE_TEMPLATE, buttonId, statusStr);
|
||||
return String.format(BUTTON_IMAGE_TEMPLATE, lobbyId, statusStr);
|
||||
}
|
||||
|
||||
private Image safeLoadImage(String path) {
|
||||
@@ -305,32 +428,30 @@ public class LobbyButtonGridManager {
|
||||
int lobbyId = lobbyIdObj.intValue();
|
||||
|
||||
CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
String statusStr = lobbyClient.fetchLobbyStatusString(lobbyId);
|
||||
() -> {
|
||||
String statusStr = lobbyClient.fetchLobbyStatusString(lobbyId);
|
||||
|
||||
LobbyStatus status = parseLobbyStatus(statusStr);
|
||||
LobbyStatus status = parseLobbyStatus(statusStr);
|
||||
|
||||
return status == null ? LobbyStatus.CREATED : status;
|
||||
},
|
||||
executor)
|
||||
return status == null ? LobbyStatus.CREATED : status;
|
||||
},
|
||||
executor)
|
||||
.thenAccept(
|
||||
status -> {
|
||||
String path = getImagePathForButton(buttonId, status);
|
||||
String path = getImagePathForLobby(lobbyId, status);
|
||||
|
||||
javafx.application.Platform.runLater(
|
||||
() -> {
|
||||
for (Node node : gridPane.getChildren()) {
|
||||
|
||||
boolean isButton = node instanceof Button;
|
||||
boolean idMatches =
|
||||
("lobbyBtn-" + buttonId)
|
||||
.equals(node.getId());
|
||||
boolean idMatches = ("lobbyBtn-" + buttonId)
|
||||
.equals(node.getId());
|
||||
|
||||
if (isButton && idMatches) {
|
||||
Button btn = (Button) node;
|
||||
|
||||
ImageView iv =
|
||||
new ImageView(safeLoadImage(path));
|
||||
ImageView iv = new ImageView(safeLoadImage(path));
|
||||
|
||||
iv.setPreserveRatio(true);
|
||||
iv.fitWidthProperty()
|
||||
@@ -375,8 +496,7 @@ public class LobbyButtonGridManager {
|
||||
|
||||
javafx.application.Platform.runLater(
|
||||
() -> {
|
||||
javafx.stage.Stage currentStage =
|
||||
(javafx.stage.Stage) gridPane.getScene().getWindow();
|
||||
javafx.stage.Stage currentStage = (javafx.stage.Stage) gridPane.getScene().getWindow();
|
||||
|
||||
currentStage.hide();
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ public class ServerApp {
|
||||
private static final int USER_CLEANUP_JOB_RECONNECT_THRESHOLD = 10;
|
||||
private static final int SESSION_DISCONNECT_JOB_DELAY = 0;
|
||||
private static final int SESSION_DISCONNECT_JOB_PERIOD = 2;
|
||||
private static final int SESSION_DISCONNECT_JOB_TIMEOUT = 5;
|
||||
private static final int SESSION_DISCONNECT_JOB_TIMEOUT = 30000;
|
||||
private static final int LOBBY_EXPIRY_SECONDS = 30;
|
||||
private static final int LOBBY_CLEANUP_INITIAL_DELAY_SECONDS = 5;
|
||||
private static final int LOBBY_CLEANUP_PERIOD_SECONDS = 5;
|
||||
@@ -94,7 +94,7 @@ public class ServerApp {
|
||||
TimeUnit.SECONDS);
|
||||
|
||||
LobbyManager lobbyManager = new LobbyManager();
|
||||
registerCommands(dispatcher, router, responseDispatcher, userRegistry, lobbyManager);
|
||||
registerCommands(dispatcher, router, responseDispatcher, userRegistry, lobbyManager, sessionManager);
|
||||
|
||||
// Periodic cleanup: remove empty lobbies older than 30s and notify affected
|
||||
// users
|
||||
@@ -147,7 +147,8 @@ public class ServerApp {
|
||||
CommandRouter commandRouter,
|
||||
ResponseDispatcher responseDispatcher,
|
||||
UserRegistry userRegistry,
|
||||
LobbyManager lobbyManager) {
|
||||
LobbyManager lobbyManager,
|
||||
SessionManager sessionManager) {
|
||||
parserDispatcher.register("PING", new PingParser());
|
||||
commandRouter.register(PingRequest.class, new PingHandler(responseDispatcher));
|
||||
|
||||
@@ -292,8 +293,8 @@ public class ServerApp {
|
||||
(ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler<
|
||||
ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby
|
||||
.create_lobby.CreateLobbyRequest>)
|
||||
new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.create_lobby
|
||||
.CreateLobbyHandler(responseDispatcher, lobbyManager));
|
||||
new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.create_lobby
|
||||
.CreateLobbyHandler(responseDispatcher, lobbyManager, sessionManager));
|
||||
|
||||
// JOIN_LOBBY registration
|
||||
parserDispatcher.register(
|
||||
|
||||
+23
-1
@@ -4,14 +4,22 @@ import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.LobbyId;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.LobbyManager;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.ErrorResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.SuccessResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBody;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.Session;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionManager;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
|
||||
public class CreateLobbyHandler extends CommandHandler<CreateLobbyRequest> {
|
||||
private final LobbyManager lobbyManager;
|
||||
private final SessionManager sessionManager;
|
||||
|
||||
public CreateLobbyHandler(ResponseDispatcher responseDispatcher, LobbyManager lobbyManager) {
|
||||
public CreateLobbyHandler(
|
||||
ResponseDispatcher responseDispatcher, LobbyManager lobbyManager, SessionManager sessionManager) {
|
||||
super(responseDispatcher);
|
||||
this.lobbyManager = lobbyManager;
|
||||
this.sessionManager = sessionManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -28,5 +36,19 @@ public class CreateLobbyHandler extends CommandHandler<CreateLobbyRequest> {
|
||||
}
|
||||
|
||||
responseDispatcher.dispatch(new CreateLobbyResponse(request.getContext(), id.value()));
|
||||
|
||||
// broadcast LOBBY_CREATED event to all connected sessions (requestId=0)
|
||||
for (Session s : sessionManager.getAllSessions()) {
|
||||
RequestContext ctx = new RequestContext(s.getId(), 0);
|
||||
SuccessResponse ev =
|
||||
new SuccessResponse(
|
||||
ctx,
|
||||
ResponseBody.builder()
|
||||
.param("EVENT", "LOBBY_CREATED")
|
||||
.param("LOBBY_ID", id.value())
|
||||
.build()) {};
|
||||
|
||||
responseDispatcher.dispatch(ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user