Feat: Accept ClientService and delegate to LobbyClient

Add constructor overload that accepts ClientService and constructs a LobbyClient.
Avoids implicit host/port lookup in UI code; caller controls network config.
Keeps rendering/status behavior unchanged (async updates, deterministic ordering).
This commit is contained in:
Jona Walpert
2026-04-02 22:26:14 +02:00
parent 1070b5d4b9
commit 76e62b3cff
@@ -38,15 +38,9 @@ public class LobbyButtonGridManager {
/** Manager for mapping ButtonID to LobbyID. */ /** Manager for mapping ButtonID to LobbyID. */
private final LobbyButtonTranslationManager translationManager; private final LobbyButtonTranslationManager translationManager;
/** Number of rows in the grid. */
private static final int ROWS = 2;
/** Number of columns in the grid. */ /** Number of columns in the grid. */
private static final int COLS = 4; private static final int COLS = 4;
/** Path to the button image. */
private static final String BUTTON_IMAGE_PATH = "/images/logo.png";
/** Image for a lobby in CREATED state. */ /** Image for a lobby in CREATED state. */
/** Default fallback image. */ /** Default fallback image. */
private static final String BUTTON_FALLBACK_IMAGE = "/images/lobbypictures/error.png"; private static final String BUTTON_FALLBACK_IMAGE = "/images/lobbypictures/error.png";
@@ -61,25 +55,17 @@ public class LobbyButtonGridManager {
/** Cache for loaded Images keyed by resource path. */ /** Cache for loaded Images keyed by resource path. */
private final ConcurrentHashMap<String, Image> imageCache = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, Image> imageCache = new ConcurrentHashMap<>();
/** Max random lobby id. */
private static final int MAX_RANDOM_LOBBY_ID = 10000;
private final LobbyClient lobbyClient; private final LobbyClient lobbyClient;
/** Executor for background status/network tasks. */
private final ExecutorService executor = Executors.newCachedThreadPool();
/** /**
* Constructor for the GridManager. * Constructor for the GridManager.
* *
* @param gridPane the GridPane for rendering * @param gridPane the GridPane for rendering
* @param translationManager the manager for mapping ButtonID to LobbyID * @param translationManager the manager for mapping ButtonID to LobbyID
*/ */
public LobbyButtonGridManager(
GridPane gridPane, LobbyButtonTranslationManager translationManager) {
this(gridPane, translationManager, createDefaultLobbyClient());
}
/**
* Constructor that accepts a `LobbyClient` implementation.
*/
public LobbyButtonGridManager( public LobbyButtonGridManager(
GridPane gridPane, LobbyButtonTranslationManager translationManager, LobbyClient lobbyClient) { GridPane gridPane, LobbyButtonTranslationManager translationManager, LobbyClient lobbyClient) {
this.gridPane = gridPane; this.gridPane = gridPane;
@@ -88,17 +74,20 @@ public class LobbyButtonGridManager {
this.lobbyClient = lobbyClient; this.lobbyClient = lobbyClient;
} }
private static LobbyClient createDefaultLobbyClient() { /**
String host = System.getProperty("casono.server.host", "127.0.0.1"); * Convenience constructor: accept a {@link ClientService} and build a
int port = 1337; * {@link LobbyClient} from it. This avoids any host/port System.getProperty
try { * lookups elsewhere — caller controls the ClientService.
port = Integer.parseInt(System.getProperty("casono.server.port", "1337")); */
} catch (NumberFormatException e) { public LobbyButtonGridManager(
// use default port if parsing fails GridPane gridPane, LobbyButtonTranslationManager translationManager, ClientService clientService) {
} this(gridPane, translationManager, new LobbyClient(clientService));
return new LobbyClient(new ClientService(host, port));
} }
// Default client creation removed to avoid implicit IP/port configuration.
// Applications must construct and provide a LobbyClient or ClientService
// explicitly.
/** /**
* Renders all lobby buttons in the grid. Creates a button for each mapping with * Renders all lobby buttons in the grid. Creates a button for each mapping with
* image and event * image and event
@@ -106,10 +95,8 @@ public class LobbyButtonGridManager {
*/ */
public void renderLobbyButtons() { public void renderLobbyButtons() {
gridPane.getChildren().clear(); gridPane.getChildren().clear();
int index = 0;
Map<Integer, Integer> mapping = translationManager.getButtonIdToLobbyId(); Map<Integer, Integer> mapping = translationManager.getButtonIdToLobbyId();
if (mapping.isEmpty()) { if (mapping.isEmpty()) {
// No buttons to render
return; return;
} }
List<Integer> buttonIds = new ArrayList<>(mapping.keySet()); List<Integer> buttonIds = new ArrayList<>(mapping.keySet());
@@ -140,21 +127,6 @@ if (targetLobbyId != null) {
} }
}); });
// async fetch status and update image // async fetch status and update image
});
// async fetch status and update image
CompletableFuture.supplyAsync(() -> lobbyClient.fetchLobbyStatusString(lobbyId), executor)
.thenAccept(statusStr -> {
LobbyStatus status = parseLobbyStatus(statusStr);
String path = getImagePathForButton(buttonId, status == null ? LobbyStatus.CREATED : status);
Image img = safeLoadImage(path);
javafx.application.Platform.runLater(() -> {
ImageView iv = new ImageView(img);
iv.setPreserveRatio(true);
iv.fitWidthProperty()
.bind(gridPane.widthProperty().divide(COLS).subtract(BUTTON_WIDTH_MARGIN));
iv.setSmooth(true);
btn.setGraphic(iv);
});
CompletableFuture.supplyAsync(() -> lobbyClient.fetchLobbyStatusString(lobbyId), executor) CompletableFuture.supplyAsync(() -> lobbyClient.fetchLobbyStatusString(lobbyId), executor)
.thenAccept(statusStr -> { .thenAccept(statusStr -> {
LobbyStatus status = parseLobbyStatus(statusStr); LobbyStatus status = parseLobbyStatus(statusStr);
@@ -214,12 +186,6 @@ if (targetLobbyId != null) {
} }
} }
private String getImagePathForStatus(LobbyStatus status) {
return status == LobbyStatus.CREATED
? String.format(BUTTON_IMAGE_TEMPLATE, 0, "created")
: String.format(BUTTON_IMAGE_TEMPLATE, 0, "running");
}
private String getImagePathForButton(int buttonId, LobbyStatus status) { private String getImagePathForButton(int buttonId, LobbyStatus status) {
String statusStr = status == LobbyStatus.CREATED ? "created" : "running"; String statusStr = status == LobbyStatus.CREATED ? "created" : "running";
return String.format(BUTTON_IMAGE_TEMPLATE, buttonId, statusStr); return String.format(BUTTON_IMAGE_TEMPLATE, buttonId, statusStr);
@@ -265,9 +231,6 @@ if (targetLobbyId != null) {
/** Update all lobby buttons' images according to the current lobby statuses. */ /** Update all lobby buttons' images according to the current lobby statuses. */
public void updateLobbyButtonImages() { public void updateLobbyButtonImages() {
// Run on FX thread to be safe
javafx.application.Platform.runLater(
() -> {
Map<Integer, Integer> mapping = translationManager.getButtonIdToLobbyId(); Map<Integer, Integer> mapping = translationManager.getButtonIdToLobbyId();
if (mapping.isEmpty()) { if (mapping.isEmpty()) {
return; return;
@@ -298,7 +261,6 @@ javafx.application.Platform.runLater(() -> {
}); });
}); });
} }
});
} }
/** /**
@@ -310,13 +272,13 @@ javafx.application.Platform.runLater(() -> {
try { try {
int lobbyId = lobbyClient.createLobby(); int lobbyId = lobbyClient.createLobby();
LOGGER.info("Lobby created via LobbyClient: {}", lobbyId); LOGGER.info("Lobby created via LobbyClient: {}", lobbyId);
if (lobbyId <= 0) {
throw new RuntimeException("LobbyClient returned invalid lobby id: " + lobbyId);
}
return lobbyId; return lobbyId;
} catch (Exception e) { } catch (Exception e) {
LOGGER.error("Failed to create lobby via LobbyClient: {}", e.getMessage()); LOGGER.error("Failed to create lobby via LobbyClient: {}", e.getMessage());
// Fallback: generate a local id so UI can continue to work in offline mode throw new RuntimeException("Failed to create lobby", e);
int lobbyId = (int) (Math.random() * MAX_RANDOM_LOBBY_ID + 1);
LOGGER.warn("Falling back to local generated lobby id: {}", lobbyId);
return lobbyId;
} }
} }
@@ -374,4 +336,12 @@ javafx.application.Platform.runLater(() -> {
return gridPane; return gridPane;
} }
/**
* Expose the configured LobbyClient so callers can invoke its methods
* directly (createLobby, fetchLobbyStatusString, joinLobby, ...).
*/
public LobbyClient getLobbyClient() {
return lobbyClient;
}
} }