Implemented MessageRouter.
Added ConnectionToServerCommandFactory, and JoinSuccessfulCommand, RaceParticipantsFullCOmmand, ServerFullCommand. Added IncomingHeartBeatCommandFactory, and IncomingHeartBeatCommand. Added ConnectionToServerState, which represents the client's connection state to server. Renamed VisualiserInput to VisualiserRaceController. Added ConnectionToServer, which tracks the client's connection to server. Added ConnectionToServerController, which accepts JoinAcceptance messages, turns them into commands, and passes them to ConnectionToServer. Added IncomingHeartBeatService, which tracks the heart beat status of the connection. Added IncomingHeartBeatController, which accepts HeartBeat messages, turns them into commands, and passes them to IncomingHeartBeatService. Refactored ServerConnection a bit. #story[1095]
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
package visualiser.network;
|
||||
|
||||
|
||||
import mock.model.commandFactory.Command;
|
||||
import network.Messages.AC35Data;
|
||||
import network.Messages.Enums.RequestToJoinEnum;
|
||||
import network.Messages.JoinAcceptance;
|
||||
import network.Messages.RequestToJoin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import shared.model.RunnableWithFramePeriod;
|
||||
import visualiser.enums.ConnectionToServerState;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* This class tracks the state of the connection to a server.
|
||||
*/
|
||||
public class ConnectionToServer implements RunnableWithFramePeriod {
|
||||
|
||||
|
||||
/**
|
||||
* The state of the connection to the client.
|
||||
*/
|
||||
private ConnectionToServerState connectionState = ConnectionToServerState.UNKNOWN;
|
||||
|
||||
|
||||
/**
|
||||
* The type of join request to make to server.
|
||||
*/
|
||||
private RequestToJoinEnum requestType;
|
||||
|
||||
|
||||
/**
|
||||
* The queue to place outgoing messages on.
|
||||
*/
|
||||
private BlockingQueue<AC35Data> outgoingMessages;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The {@link JoinAcceptance} message that has been received, if any.
|
||||
*/
|
||||
@Nullable
|
||||
private JoinAcceptance joinAcceptance;
|
||||
|
||||
|
||||
/**
|
||||
* The incoming commands to execute.
|
||||
*/
|
||||
private BlockingQueue<Command> incomingCommands;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a ConnectionToServer with a given state.
|
||||
* @param connectionState The state of the connection.
|
||||
* @param requestType The type of join request to make to server.
|
||||
* @param incomingCommands The queue of commands to execute.
|
||||
* @param outgoingMessages The queue to place outgoing messages on.
|
||||
*/
|
||||
public ConnectionToServer(ConnectionToServerState connectionState, RequestToJoinEnum requestType, BlockingQueue<Command> incomingCommands, BlockingQueue<AC35Data> outgoingMessages) {
|
||||
this.connectionState = connectionState;
|
||||
this.requestType = requestType;
|
||||
this.incomingCommands = incomingCommands;
|
||||
this.outgoingMessages = outgoingMessages;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the state of this connection.
|
||||
* @return The state of this connection.
|
||||
*/
|
||||
public ConnectionToServerState getConnectionState() {
|
||||
return connectionState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state of this connection.
|
||||
* @param connectionState The new state of this connection.
|
||||
*/
|
||||
public void setConnectionState(ConnectionToServerState connectionState) {
|
||||
this.connectionState = connectionState;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the {@link JoinAcceptance} message received from the server, if any.
|
||||
* @return The JoinAcceptance message from server. Null if no response from server.
|
||||
*/
|
||||
@Nullable
|
||||
public JoinAcceptance getJoinAcceptance() {
|
||||
return joinAcceptance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link JoinAcceptance} message received from the server, if any.
|
||||
* @param joinAcceptance The new JoinAcceptance message from server.
|
||||
*/
|
||||
public void setJoinAcceptance(@NotNull JoinAcceptance joinAcceptance) {
|
||||
this.joinAcceptance = joinAcceptance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
try {
|
||||
sendRequestToJoinMessage(requestType);
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Logger.getGlobal().log(Level.SEVERE, "ConnectionToServer: " + this + " was interrupted on thread: " + Thread.currentThread() + " while sending RequestToJoin.", e);
|
||||
Thread.currentThread().interrupt();
|
||||
|
||||
}
|
||||
|
||||
while (!Thread.interrupted()) {
|
||||
|
||||
try {
|
||||
Command command = incomingCommands.take();
|
||||
command.execute();
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Logger.getGlobal().log(Level.SEVERE, "ConnectionToServer: " + this + " was interrupted on thread: " + Thread.currentThread() + " while reading command.", e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//If we get interrupted, we consider the connection to have timed-out.
|
||||
connectionState = ConnectionToServerState.TIMED_OUT;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sends the server a {@link RequestToJoin} message.
|
||||
* @param requestType The type of request to send
|
||||
* @throws InterruptedException Thrown if the thread is interrupted while placing message on the outgoing message queue.
|
||||
*/
|
||||
private void sendRequestToJoinMessage(RequestToJoinEnum requestType) throws InterruptedException {
|
||||
|
||||
//Send them the source ID.
|
||||
RequestToJoin requestToJoin = new RequestToJoin(requestType);
|
||||
|
||||
outgoingMessages.put(requestToJoin);
|
||||
|
||||
connectionState = ConnectionToServerState.REQUEST_SENT;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package visualiser.network;
|
||||
|
||||
|
||||
import mock.exceptions.CommandConstructionException;
|
||||
import mock.model.commandFactory.Command;
|
||||
import network.Messages.AC35Data;
|
||||
import shared.model.RunnableWithFramePeriod;
|
||||
import visualiser.Commands.ConnectionToServerCommands.ConnectionToServerCommandFactory;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
||||
/**
|
||||
* The controller for connection related messages, coming from the server to the client.
|
||||
*/
|
||||
public class ConnectionToServerController implements RunnableWithFramePeriod {
|
||||
|
||||
|
||||
/**
|
||||
* The incoming queue of messages to act on.
|
||||
*/
|
||||
private BlockingQueue<AC35Data> incomingMessages;
|
||||
|
||||
|
||||
/**
|
||||
* The connection we are acting on.
|
||||
*/
|
||||
private ConnectionToServer connectionToServer;
|
||||
|
||||
|
||||
/**
|
||||
* The queue to place commands on.
|
||||
*/
|
||||
private BlockingQueue<Command> outgoingCommands;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a {@link ConnectionToServer} controller with the given parameters.
|
||||
* This accepts connection related messages, converts them to commands, and passes them to an outgoing command queue.
|
||||
* @param incomingMessages The message queue to read from.
|
||||
* @param connectionToServer The ConnectionToServer (context) to act on.
|
||||
* @param outgoingCommands The queue to place outgoing commands on.
|
||||
*/
|
||||
public ConnectionToServerController(BlockingQueue<AC35Data> incomingMessages, ConnectionToServer connectionToServer, BlockingQueue<Command> outgoingCommands) {
|
||||
this.incomingMessages = incomingMessages;
|
||||
this.connectionToServer = connectionToServer;
|
||||
this.outgoingCommands = outgoingCommands;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
while (!Thread.interrupted()) {
|
||||
|
||||
try {
|
||||
AC35Data message = incomingMessages.take();
|
||||
Command command = ConnectionToServerCommandFactory.create(message, connectionToServer);
|
||||
outgoingCommands.put(command);
|
||||
|
||||
} catch (CommandConstructionException e) {
|
||||
Logger.getGlobal().log(Level.WARNING, "ConnectionToServerController: " + this + " could not create command from message.", e);
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Logger.getGlobal().log(Level.SEVERE, "ConnectionToServerController: " + this + " was interrupted on thread: " + Thread.currentThread(), e);
|
||||
Thread.currentThread().interrupt();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package visualiser.network;
|
||||
|
||||
import mock.exceptions.CommandConstructionException;
|
||||
import mock.model.commandFactory.Command;
|
||||
import network.Messages.AC35Data;
|
||||
import shared.model.RunnableWithFramePeriod;
|
||||
import visualiser.Commands.IncomingHeartBeatCommands.IncomingHeartBeatCommandFactory;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
||||
/**
|
||||
* The controller for heartbeat related messages, coming from the server to the client.
|
||||
*/
|
||||
public class IncomingHeartBeatController implements RunnableWithFramePeriod {
|
||||
|
||||
|
||||
/**
|
||||
* The incoming queue of messages to act on.
|
||||
*/
|
||||
private BlockingQueue<AC35Data> incomingMessages;
|
||||
|
||||
|
||||
/**
|
||||
* The heart beat service we are acting on.
|
||||
*/
|
||||
private IncomingHeartBeatService incomingHeartBeatService;
|
||||
|
||||
|
||||
/**
|
||||
* The queue to place commands on.
|
||||
*/
|
||||
private BlockingQueue<Command> outgoingCommands;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a {@link IncomingHeartBeatService} controller with the given parameters.
|
||||
* This accepts connection related messages, converts them to commands, and passes them to an outgoing command queue.
|
||||
* @param incomingMessages The message queue to read from.
|
||||
* @param incomingHeartBeatService The IncomingHeartBeatService (context) to act on.
|
||||
* @param outgoingCommands The queue to place outgoing commands on.
|
||||
*/
|
||||
public IncomingHeartBeatController(BlockingQueue<AC35Data> incomingMessages, IncomingHeartBeatService incomingHeartBeatService, BlockingQueue<Command> outgoingCommands) {
|
||||
this.incomingMessages = incomingMessages;
|
||||
this.incomingHeartBeatService = incomingHeartBeatService;
|
||||
this.outgoingCommands = outgoingCommands;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
while (!Thread.interrupted()) {
|
||||
|
||||
try {
|
||||
AC35Data message = incomingMessages.take();
|
||||
Command command = IncomingHeartBeatCommandFactory.create(message, incomingHeartBeatService);
|
||||
outgoingCommands.put(command);
|
||||
|
||||
} catch (CommandConstructionException e) {
|
||||
Logger.getGlobal().log(Level.WARNING, "IncomingHeartBeatController: " + this + " could not create command from message.", e);
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Logger.getGlobal().log(Level.SEVERE, "IncomingHeartBeatController: " + this + " was interrupted on thread: " + Thread.currentThread(), e);
|
||||
Thread.currentThread().interrupt();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package visualiser.network;
|
||||
|
||||
import mock.model.commandFactory.Command;
|
||||
import shared.model.RunnableWithFramePeriod;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
||||
/**
|
||||
* Tracks the heart beat status of a connection.
|
||||
*/
|
||||
public class IncomingHeartBeatService implements RunnableWithFramePeriod {
|
||||
|
||||
|
||||
/**
|
||||
* Timestamp of the last sent heartbeat message.
|
||||
*/
|
||||
private long lastHeartbeatTime;
|
||||
|
||||
|
||||
/**
|
||||
* Sequence number for heartbeat messages.
|
||||
*/
|
||||
private long lastHeartBeatSeqNum;
|
||||
|
||||
|
||||
/**
|
||||
* The incoming commands to execute.
|
||||
*/
|
||||
private BlockingQueue<Command> incomingCommands;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates an {@link IncomingHeartBeatService} which executes commands from a given queue.
|
||||
* @param incomingCommands Queue to read and execute commands from.
|
||||
*/
|
||||
public IncomingHeartBeatService(BlockingQueue<Command> incomingCommands) {
|
||||
this.incomingCommands = incomingCommands;
|
||||
|
||||
this.lastHeartbeatTime = System.currentTimeMillis();
|
||||
this.lastHeartBeatSeqNum = -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the last heart beat time to a given value.
|
||||
* @param lastHeartbeatTime Timestamp of heartbeat.
|
||||
*/
|
||||
public void setLastHeartbeatTime(long lastHeartbeatTime) {
|
||||
this.lastHeartbeatTime = lastHeartbeatTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the last heart beat sequence number to a given value.
|
||||
* @param lastHeartBeatSeqNum Sequence number of heartbeat.
|
||||
*/
|
||||
public void setLastHeartBeatSeqNum(long lastHeartBeatSeqNum) {
|
||||
this.lastHeartBeatSeqNum = lastHeartBeatSeqNum;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the time since last heartbeat, in milliseconds.
|
||||
*
|
||||
* @return Time since last heartbeat, in milliseconds..
|
||||
*/
|
||||
private long timeSinceHeartbeat() {
|
||||
long now = System.currentTimeMillis();
|
||||
return (now - lastHeartbeatTime);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether or not the heartBeat service considers the connection "alive".
|
||||
* Going 10,000ms without receiving a heartBeat means that the connection is "dead".
|
||||
* @return True if alive, false if dead.
|
||||
*/
|
||||
public boolean isAlive() {
|
||||
long heartBeatPeriod = 10000;
|
||||
|
||||
return (timeSinceHeartbeat() < heartBeatPeriod);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
|
||||
while (!Thread.interrupted()) {
|
||||
|
||||
try {
|
||||
Command command = incomingCommands.take();
|
||||
command.execute();
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Logger.getGlobal().log(Level.SEVERE, "IncomingHeartBeatService: " + this + " was interrupted on thread: " + Thread.currentThread() + " while reading command.", e);
|
||||
Thread.currentThread().interrupt();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,459 @@
|
||||
package visualiser.network;
|
||||
|
||||
|
||||
import mock.model.commandFactory.Command;
|
||||
import network.MessageRouters.MessageRouter;
|
||||
import network.Messages.AC35Data;
|
||||
import network.Messages.Enums.MessageType;
|
||||
import network.Messages.Enums.RequestToJoinEnum;
|
||||
import network.Messages.JoinAcceptance;
|
||||
import network.Messages.LatestMessages;
|
||||
import network.StreamRelated.MessageDeserialiser;
|
||||
import network.StreamRelated.MessageSerialiser;
|
||||
import shared.model.RunnableWithFramePeriod;
|
||||
import visualiser.model.VisualiserRaceController;
|
||||
import visualiser.enums.ConnectionToServerState;
|
||||
import visualiser.gameController.ControllerClient;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* This class handles the client-server connection handshake, and creation of VisualiserInput and ControllerClient.
|
||||
*/
|
||||
public class ServerConnection implements RunnableWithFramePeriod {
|
||||
|
||||
/**
|
||||
* The socket for the connection to server.
|
||||
*/
|
||||
private Socket socket;
|
||||
|
||||
/**
|
||||
* The source ID that has been allocated to the client.
|
||||
*/
|
||||
private int allocatedSourceID = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Latest snapshot of the race, received from the server.
|
||||
*/
|
||||
private LatestMessages latestMessages;
|
||||
|
||||
|
||||
/**
|
||||
* Used to send client input to server.
|
||||
*/
|
||||
private ControllerClient controllerClient;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Used to write messages to socket.
|
||||
*/
|
||||
private MessageSerialiser messageSerialiser;
|
||||
/**
|
||||
* The thread {@link #messageSerialiser} runs on.
|
||||
*/
|
||||
private Thread messageSerialiserThread;
|
||||
|
||||
/**
|
||||
* Used to read messages from socket.
|
||||
*/
|
||||
private MessageDeserialiser messageDeserialiser;
|
||||
/**
|
||||
* The thread {@link #messageDeserialiser} runs on.
|
||||
*/
|
||||
private Thread messageDeserialiserThread;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Router to route messages to correct queue.
|
||||
*/
|
||||
private MessageRouter messageRouter;
|
||||
/**
|
||||
* The thread {@link #messageRouter} runs on.
|
||||
*/
|
||||
private Thread messageRouterThread;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The state of the connection to the client.
|
||||
*/
|
||||
private ConnectionToServer connectionToServer;
|
||||
/**
|
||||
* The thread {@link #connectionToServer} runs on.
|
||||
*/
|
||||
private Thread connectionToServerThread;
|
||||
|
||||
/**
|
||||
* The controller which handles JoinAcceptance messages.
|
||||
*/
|
||||
private ConnectionToServerController connectionToServerController;
|
||||
/**
|
||||
* The thread {@link #connectionToServerController} runs on.
|
||||
*/
|
||||
private Thread connectionToServerControllerThread;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Tracks the heartBeat status of the connection.
|
||||
*/
|
||||
private IncomingHeartBeatService heartBeatService;
|
||||
/**
|
||||
* The thread {@link #heartBeatService} runs on.
|
||||
*/
|
||||
private Thread heartBeatServiceThread;
|
||||
|
||||
/**
|
||||
* Tracks the heartBeat status of the connection.
|
||||
*/
|
||||
private IncomingHeartBeatController heartBeatController;
|
||||
/**
|
||||
* The thread {@link #heartBeatController} runs on.
|
||||
*/
|
||||
private Thread heartBeatControllerThread;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Used to convert incoming messages into a race snapshot.
|
||||
*/
|
||||
private VisualiserRaceController visualiserRaceController;
|
||||
/**
|
||||
* The thread {@link #visualiserRaceController} runs on.
|
||||
*/
|
||||
private Thread visualiserInputThread;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a server connection, using a given socket.
|
||||
* @param socket The socket which connects to the client.
|
||||
* @param latestMessages Latest race snapshot to send to client.
|
||||
* @param requestType The type of join request to make.
|
||||
* @throws IOException Thrown if there is a problem with the client socket.
|
||||
*/
|
||||
public ServerConnection(Socket socket, LatestMessages latestMessages, RequestToJoinEnum requestType) throws IOException {
|
||||
this.socket = socket;
|
||||
this.latestMessages = latestMessages;
|
||||
|
||||
createMessageSerialiser(socket);
|
||||
createMessageDeserialiser(socket);
|
||||
|
||||
createRouter(messageDeserialiser.getMessagesRead());
|
||||
|
||||
createConnectionToServer(requestType);
|
||||
|
||||
|
||||
messageRouterThread.start();
|
||||
|
||||
|
||||
this.controllerClient = new ControllerClient(messageRouter.getIncomingMessageQueue());
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates this connection's {@link MessageRouter}, and gives it a queue to read from.
|
||||
* Does not start {@link #messageRouterThread}. Start it after setting up any initial routes.
|
||||
* @param inputQueue Queue for the MessageRouter to read from.
|
||||
*/
|
||||
private void createRouter(BlockingQueue<AC35Data> inputQueue) {
|
||||
this.messageRouter = new MessageRouter(inputQueue);
|
||||
|
||||
this.messageRouterThread = new Thread(messageRouter, "ServerConnection()->MessageRouter thread " + messageRouter);
|
||||
|
||||
//Unrouted messages get sent back to the router. Kind of ugly, but we do this to ensure that no messages are lost while initializing (e.g., XML message being received before setting up the route for it).
|
||||
messageRouter.addDefaultRoute(messageRouter.getIncomingMessageQueue());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates the {@link #connectionToServer} and {@link #connectionToServerController}, and starts their threads.
|
||||
* @param requestType The type of join request to make to server.
|
||||
*/
|
||||
private void createConnectionToServer(RequestToJoinEnum requestType) {
|
||||
|
||||
//ConnectionToServer executes these commands.
|
||||
BlockingQueue<Command> commands = new LinkedBlockingQueue<>();
|
||||
this.connectionToServer = new ConnectionToServer(ConnectionToServerState.UNKNOWN, requestType, commands, messageRouter.getIncomingMessageQueue());
|
||||
|
||||
//ConnectionToServerController receives messages, and places commands on the above command queue.
|
||||
BlockingQueue<AC35Data> incomingJoinMessages = new LinkedBlockingQueue<>();
|
||||
this.connectionToServerController = new ConnectionToServerController(incomingJoinMessages, connectionToServer, commands);
|
||||
|
||||
//Route JoinAcceptance messages to the controller, and RequestToJoin to the socket.
|
||||
this.messageRouter.addRoute(MessageType.JOIN_ACCEPTANCE, incomingJoinMessages);
|
||||
this.messageRouter.addRoute(MessageType.REQUEST_TO_JOIN, messageSerialiser.getMessagesToSend());
|
||||
|
||||
|
||||
//Start the above on new threads.
|
||||
this.connectionToServerThread = new Thread(connectionToServer, "ServerConnection()->ConnectionToServer thread " + connectionToServer);
|
||||
this.connectionToServerThread.start();
|
||||
|
||||
this.connectionToServerControllerThread = new Thread(connectionToServerController,"ServerConnection()->ConnectionToServerController thread " + connectionToServerController);
|
||||
this.connectionToServerControllerThread.start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates the {@link #messageSerialiser} and starts its thread.
|
||||
* @param socket The socket to write to.
|
||||
* @throws IOException Thrown if we cannot get an outputStream from the socket
|
||||
*/
|
||||
private void createMessageSerialiser(Socket socket) throws IOException {
|
||||
BlockingQueue<AC35Data> outputQueue = new LinkedBlockingQueue<>();
|
||||
this.messageSerialiser = new MessageSerialiser(socket.getOutputStream(), outputQueue);
|
||||
|
||||
this.messageSerialiserThread = new Thread(messageSerialiser, "ServerConnection()->MessageSerialiser thread " + messageSerialiser);
|
||||
this.messageSerialiserThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link #messageDeserialiser} and starts its thread.
|
||||
* @param socket The socket to read from.
|
||||
* @throws IOException Thrown if we cannot get an inputStream from the socket
|
||||
*/
|
||||
private void createMessageDeserialiser(Socket socket) throws IOException {
|
||||
BlockingQueue<AC35Data> inputQueue = new LinkedBlockingQueue<>();
|
||||
this.messageDeserialiser = new MessageDeserialiser(socket.getInputStream(), inputQueue);
|
||||
|
||||
this.messageDeserialiserThread = new Thread(messageDeserialiser, "ServerConnection()->MessageDeserialiser thread " + messageDeserialiser);
|
||||
this.messageDeserialiserThread.start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates the {@link #heartBeatService} and {@link #heartBeatController} and starts its thread.
|
||||
*/
|
||||
private void createHeartBeatService() {
|
||||
|
||||
//IncomingHeartBeatService executes these commands.
|
||||
BlockingQueue<Command> commands = new LinkedBlockingQueue<>();
|
||||
this.heartBeatService = new IncomingHeartBeatService(commands);
|
||||
|
||||
//IncomingHeartBeatController receives messages, and places commands on the above command queue.
|
||||
BlockingQueue<AC35Data> incomingHeartBeatMessages = new LinkedBlockingQueue<>();
|
||||
this.heartBeatController = new IncomingHeartBeatController(incomingHeartBeatMessages, heartBeatService, commands);
|
||||
|
||||
//Route HeartBeat messages to the controller.
|
||||
this.messageRouter.addRoute(MessageType.HEARTBEAT, incomingHeartBeatMessages);
|
||||
|
||||
|
||||
//Start the above on new threads.
|
||||
this.heartBeatServiceThread = new Thread(heartBeatService, "ServerConnection()->IncomingHeartBeatService thread " + connectionToServer);
|
||||
this.heartBeatServiceThread.start();
|
||||
|
||||
this.heartBeatControllerThread = new Thread(heartBeatController,"ServerConnection()->IncomingHeartBeatController thread " + connectionToServerController);
|
||||
this.heartBeatControllerThread.start();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void createVisualiserRace() {
|
||||
|
||||
BlockingQueue<AC35Data> incomingMessages = new LinkedBlockingQueue<>();
|
||||
|
||||
this.visualiserRaceController = new VisualiserRaceController(latestMessages, incomingMessages);
|
||||
this.visualiserInputThread = new Thread(visualiserRaceController, "ServerConnection()->VisualiserInput thread " + visualiserRaceController);
|
||||
this.visualiserInputThread.start();
|
||||
|
||||
//Routes.
|
||||
this.messageRouter.addRoute(MessageType.BOATLOCATION, incomingMessages);
|
||||
this.messageRouter.addRoute(MessageType.RACESTATUS, incomingMessages);
|
||||
this.messageRouter.addRoute(MessageType.RACESTARTSTATUS, incomingMessages);
|
||||
this.messageRouter.addRoute(MessageType.AVGWIND, incomingMessages);
|
||||
this.messageRouter.addRoute(MessageType.COURSEWIND, incomingMessages);
|
||||
this.messageRouter.addRoute(MessageType.CHATTERTEXT, incomingMessages);
|
||||
this.messageRouter.addRoute(MessageType.DISPLAYTEXTMESSAGE, incomingMessages);
|
||||
this.messageRouter.addRoute(MessageType.YACHTACTIONCODE, incomingMessages);
|
||||
this.messageRouter.addRoute(MessageType.YACHTEVENTCODE, incomingMessages);
|
||||
this.messageRouter.addRoute(MessageType.MARKROUNDING, incomingMessages);
|
||||
this.messageRouter.addRoute(MessageType.XMLMESSAGE, incomingMessages);
|
||||
this.messageRouter.removeDefaultRoute();
|
||||
|
||||
//TODO create VisualiserRace here or somewhere else?
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void createPlayerInputController() {
|
||||
|
||||
this.messageRouter.addRoute(MessageType.BOATACTION, messageSerialiser.getMessagesToSend());
|
||||
//TODO routes
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
//Monitor the connection state.
|
||||
|
||||
long previousFrameTime = System.currentTimeMillis();
|
||||
|
||||
while (!Thread.interrupted()) {
|
||||
|
||||
long currentFrameTime = System.currentTimeMillis();
|
||||
waitForFramePeriod(previousFrameTime, currentFrameTime, 100);
|
||||
previousFrameTime = currentFrameTime;
|
||||
|
||||
|
||||
ConnectionToServerState state = connectionToServer.getConnectionState();
|
||||
|
||||
switch (state) {
|
||||
|
||||
case CONNECTED:
|
||||
connected();
|
||||
break;
|
||||
|
||||
case DECLINED:
|
||||
declined();
|
||||
break;
|
||||
|
||||
case TIMED_OUT:
|
||||
timedOut();
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Called when the {@link #connectionToServer} state changes to {@link ConnectionToServerState#CONNECTED}.
|
||||
*/
|
||||
private void connected() {
|
||||
|
||||
JoinAcceptance joinAcceptance = connectionToServer.getJoinAcceptance();
|
||||
|
||||
allocatedSourceID = joinAcceptance.getSourceID();
|
||||
|
||||
|
||||
createHeartBeatService();
|
||||
|
||||
createVisualiserRace();
|
||||
|
||||
createPlayerInputController();
|
||||
|
||||
|
||||
//We interrupt as this thread's run() isn't needed anymore.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when the {@link #connectionToServer} state changes to {@link ConnectionToServerState#DECLINED}.
|
||||
*/
|
||||
private void declined() {
|
||||
Logger.getGlobal().log(Level.WARNING, "Server handshake failed. Connection was declined.");
|
||||
|
||||
terminate();
|
||||
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when the {@link #connectionToServer} state changes to {@link ConnectionToServerState#TIMED_OUT}.
|
||||
*/
|
||||
private void timedOut() {
|
||||
Logger.getGlobal().log(Level.WARNING, "Server handshake failed. Connection timed out.");
|
||||
|
||||
terminate();
|
||||
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether or not this connection is still alive.
|
||||
* This is based off whether the {@link #messageDeserialiser}, {@link #messageSerialiser}, and {@link #heartBeatService} are alive.
|
||||
* @return True if it is alive, false otherwise.
|
||||
*/
|
||||
public boolean isAlive() {
|
||||
return messageDeserialiser.isRunning() && messageSerialiser.isRunning() && heartBeatService.isAlive();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the controller client, which writes BoatAction messages to the outgoing queue.
|
||||
* @return The ControllerClient.
|
||||
*/
|
||||
public ControllerClient getControllerClient() {
|
||||
return controllerClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the source ID that has been allocated to the client.
|
||||
* @return Source ID allocated to the client. 0 if it hasn't been allocated.
|
||||
*/
|
||||
public int getAllocatedSourceID() {
|
||||
return allocatedSourceID;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Terminates the connection and any running threads.
|
||||
*/
|
||||
public void terminate() {
|
||||
|
||||
if (this.messageRouterThread != null) {
|
||||
this.messageRouterThread.interrupt();
|
||||
}
|
||||
|
||||
|
||||
if (this.messageSerialiserThread != null) {
|
||||
this.messageSerialiserThread.interrupt();
|
||||
}
|
||||
if (this.messageDeserialiserThread != null) {
|
||||
this.messageDeserialiserThread.interrupt();
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (this.connectionToServerThread != null) {
|
||||
this.connectionToServerThread.interrupt();
|
||||
}
|
||||
if (this.connectionToServerControllerThread != null) {
|
||||
this.connectionToServerControllerThread.interrupt();
|
||||
}
|
||||
|
||||
|
||||
if (this.heartBeatServiceThread != null) {
|
||||
this.heartBeatServiceThread.interrupt();
|
||||
}
|
||||
if (this.heartBeatControllerThread != null) {
|
||||
this.heartBeatControllerThread.interrupt();
|
||||
}
|
||||
|
||||
|
||||
if (this.visualiserInputThread != null) {
|
||||
this.visualiserInputThread.interrupt();
|
||||
}
|
||||
//TODO visualiser race?
|
||||
//TODO input controller?
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user