Feat: Add addPlayer command on sever side #258

Merged
jona.walpert merged 13 commits from feat/97-add-joinlobby-command-on-server-side into main 2026-04-11 19:49:57 +02:00
4 changed files with 120 additions and 99 deletions
Showing only changes of commit 7788bbc5d3 - Show all commits
@@ -24,6 +24,7 @@ import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping.PingRequest;
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.send_message.SendMessageHandler; import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.send_message.SendMessageHandler;
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.send_message.SendMessageParser; import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.send_message.SendMessageParser;
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.send_message.SendMessageRequest; import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.send_message.SendMessageRequest;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.LobbyManager;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserCleanupJob; import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserCleanupJob;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry; import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry;
import ch.unibas.dmi.dbis.cs108.casono.server.network.NetworkManager; import ch.unibas.dmi.dbis.cs108.casono.server.network.NetworkManager;
@@ -44,97 +45,121 @@ import org.apache.logging.log4j.Logger;
/** Application class for starting the server. */ /** Application class for starting the server. */
public class ServerApp { public class ServerApp {
private static final int USER_CLEANUP_JOB_DELAY = 0; private static final int USER_CLEANUP_JOB_DELAY = 0;
private static final int USER_CLEANUP_JOB_PERIOD = 10; private static final int USER_CLEANUP_JOB_PERIOD = 10;
private static final int USER_CLEANUP_JOB_RECONNECT_THRESHOLD = 10; 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_DELAY = 0;
private static final int SESSION_DISCONNECT_JOB_PERIOD = 2; 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 = 5;
public static void start(String arg) { public static void start(String arg) {
int port = Integer.parseInt(arg); int port = Integer.parseInt(arg);
Logger logger = LogManager.getLogger(ServerApp.class); Logger logger = LogManager.getLogger(ServerApp.class);
logger.info("Starting server at port {}", port); logger.info("Starting server at port {}", port);
EventBus eventBus = new EventBus(); EventBus eventBus = new EventBus();
CommandParserDispatcher dispatcher = new CommandParserDispatcher(); CommandParserDispatcher dispatcher = new CommandParserDispatcher();
SessionManager sessionManager = new SessionManager(eventBus, dispatcher); SessionManager sessionManager = new SessionManager(eventBus, dispatcher);
ResponseDispatcher responseDispatcher = new ResponseDispatcher(sessionManager); ResponseDispatcher responseDispatcher = new ResponseDispatcher(sessionManager);
CommandHandlerExecutor handlerExecutor = new CommandHandlerExecutor(responseDispatcher); CommandHandlerExecutor handlerExecutor = new CommandHandlerExecutor(responseDispatcher);
CommandRouter router = new CommandRouter(handlerExecutor); CommandRouter router = new CommandRouter(handlerExecutor);
eventBus.subscribe(DisconnectEvent.class, event -> sessionManager.onDisconnect(event)); eventBus.subscribe(DisconnectEvent.class, event -> sessionManager.onDisconnect(event));
UserRegistry userRegistry = new UserRegistry(); UserRegistry userRegistry = new UserRegistry();
eventBus.subscribe( eventBus.subscribe(
DisconnectEvent.class, event -> userRegistry.onDisconnect(event.sessionId())); DisconnectEvent.class, event -> userRegistry.onDisconnect(event.sessionId()));
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate( scheduler.scheduleAtFixedRate(
new UserCleanupJob( new UserCleanupJob(
userRegistry, Duration.ofSeconds(USER_CLEANUP_JOB_RECONNECT_THRESHOLD)), userRegistry, Duration.ofSeconds(USER_CLEANUP_JOB_RECONNECT_THRESHOLD)),
USER_CLEANUP_JOB_DELAY, USER_CLEANUP_JOB_DELAY,
USER_CLEANUP_JOB_PERIOD, USER_CLEANUP_JOB_PERIOD,
TimeUnit.SECONDS); TimeUnit.SECONDS);
scheduler.scheduleAtFixedRate( scheduler.scheduleAtFixedRate(
new SessionDisconnectJob( new SessionDisconnectJob(
sessionManager, sessionManager,
eventBus, eventBus,
Duration.ofSeconds(SESSION_DISCONNECT_JOB_TIMEOUT)), Duration.ofSeconds(SESSION_DISCONNECT_JOB_TIMEOUT)),
SESSION_DISCONNECT_JOB_DELAY, SESSION_DISCONNECT_JOB_DELAY,
SESSION_DISCONNECT_JOB_PERIOD, SESSION_DISCONNECT_JOB_PERIOD,
TimeUnit.SECONDS); TimeUnit.SECONDS);
registerCommands(dispatcher, router, responseDispatcher, userRegistry); LobbyManager lobbyManager = new LobbyManager();
registerCommands(dispatcher, router, responseDispatcher, userRegistry, lobbyManager);
NetworkManager networkManager = new NetworkManager(port, sessionManager, router); NetworkManager networkManager = new NetworkManager(port, sessionManager, router);
networkManager.start(); networkManager.start();
} }
/** /**
* Registers command parsers and handlers. * Registers command parsers and handlers.
* *
* @param parserDispatcher the dispatcher responsible for parsing incoming commands * @param parserDispatcher the dispatcher responsible for parsing incoming
* @param commandRouter the router that dispatches parsed commands to appropriate handlers * commands
* @param responseDispatcher the dispatcher responsible for sending responses back to clients * @param commandRouter the router that dispatches parsed commands to
*/ * appropriate handlers
private static void registerCommands( * @param responseDispatcher the dispatcher responsible for sending responses
CommandParserDispatcher parserDispatcher, * back to clients
CommandRouter commandRouter, */
ResponseDispatcher responseDispatcher, private static void registerCommands(
UserRegistry userRegistry) { CommandParserDispatcher parserDispatcher,
parserDispatcher.register("PING", new PingParser()); CommandRouter commandRouter,
commandRouter.register(PingRequest.class, new PingHandler(responseDispatcher)); ResponseDispatcher responseDispatcher,
UserRegistry userRegistry,
LobbyManager lobbyManager) {
parserDispatcher.register("PING", new PingParser());
commandRouter.register(PingRequest.class, new PingHandler(responseDispatcher));
parserDispatcher.register("CHECK_USERNAME", new CheckUsernameParser()); parserDispatcher.register("CHECK_USERNAME", new CheckUsernameParser());
commandRouter.register( commandRouter.register(
CheckUsernameRequest.class, CheckUsernameRequest.class,
new CheckUsernameHandler(responseDispatcher, userRegistry)); new CheckUsernameHandler(responseDispatcher, userRegistry));
parserDispatcher.register("LOGIN", new LoginParser()); parserDispatcher.register("LOGIN", new LoginParser());
commandRouter.register( commandRouter.register(
LoginRequest.class, new LoginHandler(responseDispatcher, userRegistry)); LoginRequest.class, new LoginHandler(responseDispatcher, userRegistry));
parserDispatcher.register("LOGOUT", new LogoutParser()); parserDispatcher.register("LOGOUT", new LogoutParser());
commandRouter.register( commandRouter.register(
LogoutRequest.class, new LogoutHandler(responseDispatcher, userRegistry)); LogoutRequest.class, new LogoutHandler(responseDispatcher, userRegistry));
parserDispatcher.register("SEND_MESSAGE", new SendMessageParser()); parserDispatcher.register("SEND_MESSAGE", new SendMessageParser());
commandRouter.register( commandRouter.register(
SendMessageRequest.class, new SendMessageHandler(responseDispatcher, userRegistry)); SendMessageRequest.class, new SendMessageHandler(responseDispatcher, userRegistry));
parserDispatcher.register("GET_MESSAGE_COUNT", new GetMessageCountParser()); parserDispatcher.register("GET_MESSAGE_COUNT", new GetMessageCountParser());
commandRouter.register( commandRouter.register(
GetMessageCountRequest.class, GetMessageCountRequest.class,
new GetMessageCountHandler(responseDispatcher, userRegistry)); new GetMessageCountHandler(responseDispatcher, userRegistry));
parserDispatcher.register("GET_NEXT_MESSAGE", new GetNextMessageParser()); parserDispatcher.register("GET_NEXT_MESSAGE", new GetNextMessageParser());
commandRouter.register( commandRouter.register(
GetNextMessageRequest.class, GetNextMessageRequest.class,
new GetNextMessageHandler(responseDispatcher, userRegistry)); new GetNextMessageHandler(responseDispatcher, userRegistry));
parserDispatcher.register("LIST_USERS", new ListUsersParser()); parserDispatcher.register("LIST_USERS", new ListUsersParser());
commandRouter.register( commandRouter.register(
ListUsersRequest.class, new ListUsersHandler(responseDispatcher, userRegistry)); ListUsersRequest.class, new ListUsersHandler(responseDispatcher, userRegistry));
}
// Lobby commands
parserDispatcher.register(
"JOIN_LOBBY",
new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby.JoinLobbyParser());
commandRouter.register(
ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby.JoinLobbyRequest.class,
new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby.JoinLobbyHandler(
responseDispatcher, lobbyManager, userRegistry));
// Game state: allow client to request game snapshot for their lobby
parserDispatcher.register(
"GET_GAME_STATE",
new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.get_game_state.GetGameStateParser());
commandRouter.register(
ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.get_game_state.GetGameStateRequest.class,
new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.get_game_state.GetGameStateHandler(
responseDispatcher, lobbyManager, userRegistry));
}
} }
@@ -13,12 +13,10 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatch
/** /**
* Handler for the `JOIN_LOBBY` command. * Handler for the `JOIN_LOBBY` command.
* *
* <p> * <p>Resolves the username from the session (requires {@link UserLoggedInCheck}), looks up the
* Resolves the username from the session (requires {@link UserLoggedInCheck}), * target lobby and attempts to add the user. On success an `+OK` response is dispatched; on failure
* looks up the target lobby and attempts to add the user. On success an * an appropriate {@link ErrorResponse} with one of the error codes `LOBBY_NOT_FOUND`,
* `+OK` response is dispatched; on failure an appropriate {@link ErrorResponse} * `USER_NOT_LOGGED_IN` or `LOBBY_FULL_OR_ALREADY_IN` is returned.
* with one of the error codes `LOBBY_NOT_FOUND`, `USER_NOT_LOGGED_IN` or
* `LOBBY_FULL_OR_ALREADY_IN` is returned.
*/ */
public class JoinLobbyHandler extends CommandHandler<JoinLobbyRequest> { public class JoinLobbyHandler extends CommandHandler<JoinLobbyRequest> {
private final LobbyManager lobbyManager; private final LobbyManager lobbyManager;
@@ -27,10 +25,9 @@ public class JoinLobbyHandler extends CommandHandler<JoinLobbyRequest> {
/** /**
* Create a new {@link JoinLobbyHandler}. * Create a new {@link JoinLobbyHandler}.
* *
* @param responseDispatcher dispatcher used to send responses back to the * @param responseDispatcher dispatcher used to send responses back to the client
* client * @param lobbyManager manager providing lobby state and operations
* @param lobbyManager manager providing lobby state and operations * @param userRegistry registry to resolve session -> user mappings
* @param userRegistry registry to resolve session -> user mappings
*/ */
public JoinLobbyHandler( public JoinLobbyHandler(
ResponseDispatcher responseDispatcher, ResponseDispatcher responseDispatcher,
@@ -7,15 +7,14 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor.
/** /**
* Parser for the `JOIN_LOBBY` command. * Parser for the `JOIN_LOBBY` command.
* *
* <p> * <p>Expected request parameters:
* Expected request parameters: *
* <ul> * <ul>
* <li>`ID` (int) — numeric lobby identifier (required) * <li>`ID` (int) — numeric lobby identifier (required)
* </ul> * </ul>
* *
* <p> * <p>The parser builds a {@link JoinLobbyRequest} containing the parsed lobby id and the original
* The parser builds a {@link JoinLobbyRequest} containing the parsed lobby id * request context.
* and the original request context.
*/ */
public class JoinLobbyParser implements CommandParser<JoinLobbyRequest> { public class JoinLobbyParser implements CommandParser<JoinLobbyRequest> {
/** /**
@@ -26,7 +25,8 @@ public class JoinLobbyParser implements CommandParser<JoinLobbyRequest> {
*/ */
@Override @Override
public JoinLobbyRequest parse(PrimitiveRequest primitiveRequest) { public JoinLobbyRequest parse(PrimitiveRequest primitiveRequest) {
RequestParameterAccessor accessor = new RequestParameterAccessor(primitiveRequest.parameters()); RequestParameterAccessor accessor =
new RequestParameterAccessor(primitiveRequest.parameters());
int id = accessor.require("ID", Integer::parseInt); int id = accessor.require("ID", Integer::parseInt);
return new JoinLobbyRequest(primitiveRequest.context(), id); return new JoinLobbyRequest(primitiveRequest.context(), id);
} }
@@ -6,9 +6,8 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestCo
/** /**
* Request data for the `JOIN_LOBBY` command. * Request data for the `JOIN_LOBBY` command.
* *
* <p> * <p>Contains the lobby id the client wants to join and inherits the {@link Request} contextual
* Contains the lobby id the client wants to join and inherits the * information.
* {@link Request} contextual information.
*/ */
public class JoinLobbyRequest extends Request { public class JoinLobbyRequest extends Request {
private final int id; private final int id;
@@ -17,7 +16,7 @@ public class JoinLobbyRequest extends Request {
* Create a new {@link JoinLobbyRequest}. * Create a new {@link JoinLobbyRequest}.
* *
* @param context the request context * @param context the request context
* @param id numeric lobby id to join * @param id numeric lobby id to join
*/ */
public JoinLobbyRequest(RequestContext context, int id) { public JoinLobbyRequest(RequestContext context, int id) {
super(context); super(context);