feat: partial implementation of UserManager
This commit is contained in:
parent
cb2ad7e445
commit
ddba210025
@ -5,9 +5,8 @@ import org.tinylog.Logger;
|
|||||||
import uulm.teamname.marvelous.gamelibrary.json.JSON;
|
import uulm.teamname.marvelous.gamelibrary.json.JSON;
|
||||||
import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage;
|
import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage;
|
||||||
import uulm.teamname.marvelous.gamelibrary.messages.ErrorMessage;
|
import uulm.teamname.marvelous.gamelibrary.messages.ErrorMessage;
|
||||||
import uulm.teamname.marvelous.gamelibrary.messages.client.HelloServerMessage;
|
import uulm.teamname.marvelous.gamelibrary.messages.client.*;
|
||||||
import uulm.teamname.marvelous.server.lobbymanager.Participant;
|
import uulm.teamname.marvelous.server.lobbymanager.Participant;
|
||||||
import uulm.teamname.marvelous.server.netconnector.UserMetadata;
|
|
||||||
|
|
||||||
import org.java_websocket.WebSocket;
|
import org.java_websocket.WebSocket;
|
||||||
|
|
||||||
@ -15,21 +14,21 @@ import java.util.*;
|
|||||||
|
|
||||||
public class UserManager {
|
public class UserManager {
|
||||||
|
|
||||||
/** A set of users that aren't assigned to lobbies or character selection yet, and their resource descriptors */
|
/** A set of users that aren't assigned to lobbies or character selection yet */
|
||||||
private final HashMap<WebSocket, UserMetadata> newUsers;
|
private final HashSet<WebSocket> newUsers;
|
||||||
|
|
||||||
/** A set of users that can reconnect if they wish to do so, and their matching Participants */
|
/** A set of users that can reconnect if they wish to do so, and their matching Participants */
|
||||||
private final HashMap<WebSocket, Participant> readyToReconnect;
|
private final HashMap<WebSocket, Participant> readyToReconnect;
|
||||||
|
|
||||||
/** A set of users that only have to send the
|
/** A set of users that only have to send the
|
||||||
* {@link uulm.teamname.marvelous.gamelibrary.messages.client.PlayerReadyMessage} to be assigned
|
* {@link uulm.teamname.marvelous.gamelibrary.messages.client.PlayerReadyMessage} to be assigned
|
||||||
* to a lobby
|
* to a lobby, containing WebSockets mapped to their user's usernames
|
||||||
*/
|
*/
|
||||||
private final HashMap<WebSocket, UserMetadata> readyToConnect;
|
private final HashMap<WebSocket, SUID> readyToConnect;
|
||||||
|
|
||||||
|
|
||||||
/** A set of users that are already assigned to lobbies or character selection */
|
/** A set of users that are already assigned to lobbies or character selection, mapped to their participants */
|
||||||
private final HashSet<WebSocket> inGame;
|
private final HashMap<WebSocket, Participant> inGame;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A map mapping {@link SUID SUIDs} to {@link Participant Participants} to assert whether (and where to) reconnect
|
* A map mapping {@link SUID SUIDs} to {@link Participant Participants} to assert whether (and where to) reconnect
|
||||||
@ -41,19 +40,18 @@ public class UserManager {
|
|||||||
|
|
||||||
/** Constructs a new, empty UserManager */
|
/** Constructs a new, empty UserManager */
|
||||||
public UserManager(JSON json) {
|
public UserManager(JSON json) {
|
||||||
this.newUsers = new HashMap<>();
|
this.newUsers = new HashSet<>();
|
||||||
this.readyToConnect = new HashMap<>();
|
this.readyToConnect = new HashMap<>();
|
||||||
this.readyToReconnect = new HashMap<>();
|
this.readyToReconnect = new HashMap<>();
|
||||||
this.inGame = new HashSet<>();
|
this.inGame = new HashMap<>();
|
||||||
this.activeParticipants = new HashMap<>();
|
this.activeParticipants = new HashMap<>();
|
||||||
this.json = json;
|
this.json = json;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Called on a new WebSocket connection. Places the WebSocket and its ResourceDescriptor in a HashMap. */
|
/** Called on a new WebSocket connection. Places the WebSocket and its ResourceDescriptor in a HashMap. */
|
||||||
public void connectUser(WebSocket conn, ClientHandshake handshake) {
|
public void connectUser(WebSocket conn, ClientHandshake handshake) {
|
||||||
var metadata = new UserMetadata(conn, handshake.getResourceDescriptor());
|
|
||||||
synchronized (newUsers) {
|
synchronized (newUsers) {
|
||||||
newUsers.put(conn, metadata);
|
newUsers.add(conn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +71,7 @@ public class UserManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void handshake(WebSocket conn, HelloServerMessage message) {
|
void handshake(WebSocket conn, HelloServerMessage message) {
|
||||||
if (!newUsers.containsKey(conn)) {
|
if (!newUsers.contains(conn)) {
|
||||||
Logger.debug("websocket {} sent HelloServerMessage outside of handshake", conn);
|
Logger.debug("websocket {} sent HelloServerMessage outside of handshake", conn);
|
||||||
sendError(conn, "Invalid message, as Handshake is already completed");
|
sendError(conn, "Invalid message, as Handshake is already completed");
|
||||||
return;
|
return;
|
||||||
@ -90,20 +88,46 @@ public class UserManager {
|
|||||||
readyToReconnect.put(conn, participant);
|
readyToReconnect.put(conn, participant);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var metadata = newUsers.get(conn);
|
|
||||||
metadata.username = message.name;
|
|
||||||
|
|
||||||
Logger.trace("removing handshaking user from newUsers");
|
Logger.trace("removing handshaking user from newUsers");
|
||||||
synchronized (newUsers) {
|
synchronized (newUsers) {
|
||||||
newUsers.remove(conn);
|
newUsers.remove(conn);
|
||||||
}
|
}
|
||||||
Logger.trace("adding handshaking user to readyToConnect");
|
Logger.trace("adding handshaking user to readyToConnect");
|
||||||
synchronized (readyToConnect) {
|
synchronized (readyToConnect) {
|
||||||
readyToConnect.put(conn, metadata);
|
readyToConnect.put(conn, clientID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void reconnectClient(WebSocket conn, ReconnectMessage message) {
|
||||||
|
if (!readyToConnect.containsKey(conn)) {
|
||||||
|
Logger.debug("Non-reconnect-allowed client has sent reconnect message, sending error");
|
||||||
|
sendError(conn, "Reconnect is not possible");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Logger.info("Reconnecting client {} to their lobby");
|
||||||
|
var participantToRestore = readyToReconnect.get(conn);
|
||||||
|
synchronized (participantToRestore) {
|
||||||
|
participantToRestore.setConnection(conn);
|
||||||
|
}
|
||||||
|
synchronized (readyToReconnect) {
|
||||||
|
readyToReconnect.remove(conn);
|
||||||
|
}
|
||||||
|
synchronized (inGame) {
|
||||||
|
inGame.put(conn, participantToRestore);
|
||||||
|
}
|
||||||
|
// activeParticipants remains the same, as no players have been removed from the game
|
||||||
|
}
|
||||||
|
|
||||||
|
public void assignLobby(WebSocket conn, PlayerReadyMessage message) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void charactersSelected(WebSocket conn, CharacterSelectionMessage message) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void relayRequestMessage(Participant conn, RequestMessage message) {
|
||||||
|
}
|
||||||
|
|
||||||
/** Sends an {@link uulm.teamname.marvelous.gamelibrary.messages.ErrorMessage} to the specified user. */
|
/** Sends an {@link uulm.teamname.marvelous.gamelibrary.messages.ErrorMessage} to the specified user. */
|
||||||
public void sendError(WebSocket conn, String error) {
|
public void sendError(WebSocket conn, String error) {
|
||||||
Logger.debug("Sending error message '{}' to WebSocket {}", error, conn);
|
Logger.debug("Sending error message '{}' to WebSocket {}", error, conn);
|
||||||
@ -123,33 +147,38 @@ public class UserManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUserConnected(WebSocket user) {
|
public boolean isUserConnected(WebSocket user) {
|
||||||
return newUsers.containsKey(user) ||
|
return newUsers.contains(user) ||
|
||||||
readyToReconnect.containsKey(user) ||
|
readyToReconnect.containsKey(user) ||
|
||||||
readyToConnect.containsKey(user) ||
|
readyToConnect.containsKey(user) ||
|
||||||
inGame.contains(user);
|
inGame.containsKey(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getUserCount() {
|
public int getUserCount() {
|
||||||
return newUsers.size() + readyToConnect.size() + readyToReconnect.size() + inGame.size();
|
return newUsers.size() + readyToConnect.size() + readyToReconnect.size() + inGame.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<WebSocket, UserMetadata> getNewUsers() {
|
/** Package-private getter for mutable newUsers HashSet, meant for testing */
|
||||||
return Collections.unmodifiableMap(newUsers);
|
HashSet<WebSocket> getNewUsers() {
|
||||||
|
return newUsers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<WebSocket, Participant> getReadyToReconnect() {
|
/** Package-private getter for mutable readyToReconnect HashMap, meant for testing */
|
||||||
return Collections.unmodifiableMap(readyToReconnect);
|
HashMap<WebSocket, Participant> getReadyToReconnect() {
|
||||||
|
return readyToReconnect;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<WebSocket, UserMetadata> getReadyToConnect() {
|
/** Package-private getter for mutable readyToConnect HashMap, meant for testing */
|
||||||
return Collections.unmodifiableMap(readyToConnect);
|
HashMap<WebSocket, SUID> getReadyToConnect() {
|
||||||
|
return readyToConnect;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<WebSocket> getInGame() {
|
/** Package-private getter for mutable inGame HashMap, meant for testing */
|
||||||
return Collections.unmodifiableSet(inGame);
|
HashMap<WebSocket, Participant> getInGame() {
|
||||||
|
return inGame;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<SUID, Participant> getActiveParticipants() {
|
/** Package-private getter for mutable activeParticipants HashMap, meant for testing */
|
||||||
return Collections.unmodifiableMap(activeParticipants);
|
HashMap<SUID, Participant> getActiveParticipants() {
|
||||||
|
return activeParticipants;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
package uulm.teamname.marvelous.server.netconnector;
|
|
||||||
|
|
||||||
import org.java_websocket.WebSocket;
|
|
||||||
|
|
||||||
class UserMetadata {
|
|
||||||
public String username;
|
|
||||||
public final WebSocket conn;
|
|
||||||
public final String resourceDescriptor;
|
|
||||||
|
|
||||||
public UserMetadata(WebSocket conn, String resourceDescriptor) {
|
|
||||||
this.conn = conn;
|
|
||||||
this.resourceDescriptor = resourceDescriptor;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,11 @@
|
|||||||
package uulm.teamname.marvelous.server.netconnector;
|
package uulm.teamname.marvelous.server.netconnector;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
class MarvelousServerTest {
|
class MarvelousServerTest {
|
||||||
@ -15,8 +18,9 @@ class MarvelousServerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void mockitoTestThing() {
|
void runServerForTesting() throws InterruptedException {
|
||||||
// verify(server).onMessage(null, "hi");
|
// server = new MarvelousServer(new InetSocketAddress(1234));
|
||||||
|
// Thread serverThread = new Thread(() -> server.start());
|
||||||
|
// serverThread.join();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,129 @@
|
|||||||
|
package uulm.teamname.marvelous.server.netconnector;
|
||||||
|
|
||||||
|
import org.java_websocket.WebSocket;
|
||||||
|
import org.java_websocket.handshake.ClientHandshake;
|
||||||
|
import org.java_websocket.handshake.ClientHandshakeBuilder;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import uulm.teamname.marvelous.gamelibrary.json.JSON;
|
||||||
|
import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType;
|
||||||
|
import uulm.teamname.marvelous.gamelibrary.messages.client.*;
|
||||||
|
import uulm.teamname.marvelous.server.lobbymanager.Participant;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.assertj.core.api.Assertions.*;
|
||||||
|
|
||||||
|
class UserManagerTest {
|
||||||
|
|
||||||
|
UserManager manager;
|
||||||
|
WebSocket connection;
|
||||||
|
Participant participant;
|
||||||
|
ClientHandshake handshake;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void beforeEach() {
|
||||||
|
connection = mock(WebSocket.class);
|
||||||
|
// when(connection.hashCode()).thenReturn(1); // just for testing, of course
|
||||||
|
manager = spy(new UserManager(new JSON(null))); // FIXME: This shouldn't be null, of course
|
||||||
|
|
||||||
|
handshake = mock(ClientHandshake.class);
|
||||||
|
when(handshake.getResourceDescriptor()).thenReturn("/someDescriptor");
|
||||||
|
|
||||||
|
participant = spy(new Participant(connection, ParticipantType.PlayerOne, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void userIsConnectedTest() {
|
||||||
|
assertThat(manager.getUserCount()).isEqualTo(0);
|
||||||
|
assertThat(manager.getNewUsers()).doesNotContain(connection);
|
||||||
|
manager.connectUser(connection, handshake);
|
||||||
|
assertThat(manager.getUserCount()).isEqualTo(1);
|
||||||
|
assertThat(manager.isUserConnected(connection)).isTrue();
|
||||||
|
assertThat(manager.getNewUsers()).containsOnly(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void helloServerMessagesGetAssignedProperly() {
|
||||||
|
manager.messageReceived(
|
||||||
|
connection,
|
||||||
|
"{\"messageType\":\"HELLO_SERVER\",\"name\":\"SomeAwesomeName\",\"deviceID\":\"YAY\"}");
|
||||||
|
|
||||||
|
verify(manager).handshake(
|
||||||
|
eq(connection),
|
||||||
|
any(HelloServerMessage.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void handshakeGetsCompletedProperlyTest() {
|
||||||
|
manager.connectUser(connection, handshake);
|
||||||
|
assertThat(manager.getUserCount()).isEqualTo(1);
|
||||||
|
assertThat(manager.getNewUsers()).containsOnly(connection);
|
||||||
|
assertThat(manager.getReadyToConnect()).isEmpty();
|
||||||
|
|
||||||
|
var message = new HelloServerMessage();
|
||||||
|
message.name = "Some Awesome Name";
|
||||||
|
message.deviceID = "Some Interesting ID";
|
||||||
|
|
||||||
|
manager.handshake(connection, message);
|
||||||
|
|
||||||
|
assertThat(manager.getUserCount()).isEqualTo(1);
|
||||||
|
assertThat(manager.getNewUsers()).isEmpty();
|
||||||
|
assertThat(manager.getReadyToConnect()).containsOnlyKeys(connection);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void reconnectMessagesGetAssignedProperly() {
|
||||||
|
manager.messageReceived(
|
||||||
|
connection,
|
||||||
|
"{\"messageType\":\"RECONNECT\",\"reconnect\":\"TRUE\"}");
|
||||||
|
|
||||||
|
verify(manager).reconnectClient(
|
||||||
|
eq(connection),
|
||||||
|
any(ReconnectMessage.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Disabled
|
||||||
|
void reconnectWorksProperly() {
|
||||||
|
// TODO: create test for this case
|
||||||
|
}
|
||||||
|
|
||||||
|
void playerReadyMessagesGetAssignedProperly() {
|
||||||
|
manager.messageReceived(
|
||||||
|
connection,
|
||||||
|
"{\"messageType\":\"PLAYER_READY\",\"startGame\":\"false\",\"role\":\"Player\"}");
|
||||||
|
|
||||||
|
verify(manager).assignLobby(
|
||||||
|
eq(connection),
|
||||||
|
any(PlayerReadyMessage.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void characterSelectionMessageGetsAssignedProperly() {
|
||||||
|
manager.messageReceived(
|
||||||
|
connection,
|
||||||
|
"{\"messageType\":\"CHARACTER_SELECTION\",\"characters\":" +
|
||||||
|
"[true, true, false, true, false, false, true, true, false, false, false, true]}");
|
||||||
|
|
||||||
|
verify(manager).charactersSelected(
|
||||||
|
eq(connection),
|
||||||
|
any(CharacterSelectionMessage.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
void requestMessagesGetRelayedProperly() {
|
||||||
|
manager.messageReceived(
|
||||||
|
connection,
|
||||||
|
"{\"messageType\":\"REQUESTS\",\"messages\":[]}");
|
||||||
|
|
||||||
|
verify(manager).relayRequestMessage(
|
||||||
|
eq(participant),
|
||||||
|
any(RequestMessage.class));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user