Compare commits

...

10 Commits

Author SHA1 Message Date
mike fc0981852a added GameEngine to client scope 2023-04-15 22:12:14 -04:00
Brett 1d7b378b52 code cleanup 2023-04-15 21:21:40 -04:00
Brett 49a78142d5 ; 2023-04-15 21:17:31 -04:00
Brett 0a03b1f913 fix spam 2023-04-15 21:16:31 -04:00
Brett b336a143a6 Merge remote-tracking branch 'refs/remotes/origin/main' 2023-04-15 21:12:12 -04:00
Brett a2cbc5d3af fix sync issue 2023-04-15 21:12:05 -04:00
mike ae31c9f70b packet type-ing 2023-04-15 21:04:51 -04:00
Brett 5ce406f376 Merge remote-tracking branch 'refs/remotes/origin/main' 2023-04-15 20:35:28 -04:00
Brett ceeef4b90e server.java 2023-04-15 20:35:18 -04:00
mike 9c878306b4 Updated ConnectedClient 2023-04-15 19:36:36 -04:00
5 changed files with 121 additions and 27 deletions

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
</set>
</option>
</component>
</project>

View File

@ -139,15 +139,15 @@ public class GameEngine {
return score; return score;
} }
public void updateMap(Map map) { public synchronized void updateMap(Map map) {
for (Building b : map.contains){ for (int i = 0; i < map.contains.size(); i++) {
if ((b instanceof ResourceBuilding)) { if ((map.contains.get(i) instanceof ResourceBuilding)) {
((ResourceBuilding) b).update(map.getTownHall()); ((ResourceBuilding) map.contains.get(i)).update(map.getTownHall());
} }
} }
} }
public boolean build (Map map, String buildingArg) { public synchronized boolean build (Map map, String buildingArg) {
BuildingFactory bfactory = new BuildingFactory(); BuildingFactory bfactory = new BuildingFactory();
Building type = bfactory.getBuilding(buildingArg); Building type = bfactory.getBuilding(buildingArg);
return map.build(new Tile(), type); return map.build(new Tile(), type);
@ -159,7 +159,7 @@ public class GameEngine {
return map.train(type); return map.train(type);
} }
public boolean upgradeBuilding (Map map, int buildingIndex) { public synchronized boolean upgradeBuilding (Map map, int buildingIndex) {
return map.upgradeBuilding(buildingIndex); return map.upgradeBuilding(buildingIndex);
} }

View File

@ -29,17 +29,45 @@ public class Client implements Runnable {
sendMessage(new Message.Sent(PacketTable.CONNECT, 0, ++lastMessageID)); sendMessage(new Message.Sent(PacketTable.CONNECT, 0, ++lastMessageID));
String prompt;
while (running) { while (running) {
String prompt;
if ((prompt = view.nextInput()) != null) { if ((prompt = view.nextInput()) != null) {
if (prompt.trim().isEmpty()) if (prompt.trim().isEmpty())
continue; continue;
if (prompt.charAt(0) == '6') if (prompt.charAt(0) == '6')
break; break;
byte messageType;
switch (prompt.charAt(0)) {
case '1':
messageType = PacketTable.BUILD;
break;
case '2':
messageType = PacketTable.TRAIN;
break;
case '3':
messageType = PacketTable.UPGRADE;
break;
default:
System.err.println("> Invalid command input!");
return;
}
Message.Sent buildMessage = new Message.Sent(messageType,0,++lastMessageID);
buildMessage.getData().write(prompt.substring(1).getBytes());
sendMessage(buildMessage);
view.printGameMenu(); view.printGameMenu();
} }
ArrayList<Long> removes = new ArrayList<>();
for (HashMap.Entry<Long, Message.Sent> message : sentMessages.entrySet()){
Message.Sent sent = message.getValue();
if (!sent.isAcknowledged() && sent.getTimeSinceSent().get() > Server.MAX_PACKET_ACK_TIME_SECONDS) {
System.out.println("The server did not acknowledge our message, did they receive it?");
sendMessage(sent);
removes.add(message.getKey());
}
}
for (Long l : removes)
sentMessages.remove(l);
} }
clientSocket.close(); clientSocket.close();
} }
@ -57,14 +85,19 @@ public class Client implements Runnable {
long clientID = stream.readLong(); long clientID = stream.readLong();
long messageID = stream.readLong(); long messageID = stream.readLong();
System.out.println("Receiving message with ID " + messageID + " to client: " + clientID + " of type " + packetID);
switch (packetID) { switch (packetID) {
case PacketTable.ACK: case PacketTable.ACK:
Message.Sent message = sentMessages.get(messageID); Message.Sent message = sentMessages.get(messageID);
if (message == null) if (message == null)
throw new RuntimeException("Server message sync error!"); throw new RuntimeException("Server acknowledged a message we never sent! (" + messageID + ")");
message.acknowledged(); message.acknowledged();
sentMessages.remove(messageID); sentMessages.remove(messageID);
System.out.println("Message acknowledged " + messageID); System.out.println("Message acknowledged with ID: " + messageID);
System.out.println("Messages left: " + sentMessages.size());
for (HashMap.Entry<Long, Message.Sent> ms : sentMessages.entrySet())
System.out.println("MessageID: " + ms.getKey());
break; break;
case PacketTable.DISCONNECT: case PacketTable.DISCONNECT:
running = false; running = false;
@ -78,10 +111,12 @@ public class Client implements Runnable {
} }
private void sendMessage(Message.Sent message){ private void sendMessage(Message.Sent message){
sentMessages.put(message.getMessageID(), message); if (message.getPacketID() != PacketTable.ACK)
sentMessages.put(message.getMessageID(), message);
byte[] data = message.getData().toByteArray(); byte[] data = message.getData().toByteArray();
DatagramPacket sendPacket = new DatagramPacket(data, data.length, serverAddress, Server.SERVER_PORT); DatagramPacket sendPacket = new DatagramPacket(data, data.length, serverAddress, Server.SERVER_PORT);
try { try {
System.out.println("Sending message with ID " + message.getMessageID() + " to server from: " + message.getClientID() + " of type " + message.getPacketID());
clientSocket.send(sendPacket); clientSocket.send(sendPacket);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();

View File

@ -16,7 +16,13 @@ public class PacketTable {
public static final byte ACK = 0x3; public static final byte ACK = 0x3;
// messageHeader, UTF8 String with length information (use DOS.writeUTF/DIS.readUTF) // messageHeader, UTF8 String with length information (use DOS.writeUTF/DIS.readUTF)
public static final byte MESSAGE = 0x4; public static final byte MESSAGE = 0x4;
// messageHeader, build
public static final byte BUILD = 0x5;
// messageHeader, train
public static final byte TRAIN = 0x6;
// messageHeader, upgrade
public static final byte UPGRADE = 0x7;
// messageHeader, serial packets with map info // messageHeader, serial packets with map info
public static final byte MAP_DATA = 0x5; public static final byte USER_MAP_DATA = 0x8;
} }

View File

@ -5,11 +5,15 @@ import ca.cosc3p91.a4.game.Map;
import java.io.*; import java.io.*;
import java.net.*; import java.net.*;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.rmi.ServerException; import java.rmi.ServerException;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.PriorityQueue; import java.util.PriorityQueue;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
public class Server implements Runnable { public class Server implements Runnable {
@ -32,6 +36,8 @@ public class Server implements Runnable {
socket = new DatagramSocket(SERVER_PORT); socket = new DatagramSocket(SERVER_PORT);
ioThread = new Thread(this); ioThread = new Thread(this);
ioThread.start(); ioThread.start();
mainEngine = new GameEngine();
} }
public void run(){ public void run(){
@ -49,21 +55,25 @@ public class Server implements Runnable {
long clientID = stream.readLong(); long clientID = stream.readLong();
long messageID = stream.readLong(); long messageID = stream.readLong();
System.out.println("Receiving message with ID " + messageID + " to client: " + clientID + " of type " + packetID);
ConnectedClient client = clients.get(clientID); ConnectedClient client = clients.get(clientID);
// the server must handle connection requests while the client's processing thread will handle all other messages // the server must handle connection requests while the client's processing thread will handle all other messages
if (packetID == PacketTable.CONNECT){ if (packetID == PacketTable.CONNECT){
clients.put(++clientAssignmentID, new ConnectedClient(socket, mainEngine, clientID, messageID, receivePacket.getAddress(), receivePacket.getPort())); long cid = ++clientAssignmentID;
} else if (packetID == PacketTable.DISCONNECT) { System.out.println("A client has connected, his clientID is " + cid);
if (client == null) clients.put(cid, new ConnectedClient(socket, mainEngine, cid, messageID, receivePacket.getAddress(), receivePacket.getPort()));
throw new ServerException("Client disconnected with invalid client id! (" + clientID + ")"); continue;
client.halt();
clients.put(clientID, null);
} else {
if (client == null)
throw new ServerException("Client message with invalid client id! (" + clientID + ")");
client.handleRequest(new Message.Received(packetID, clientID, messageID, stream, receivePacket.getData()));
} }
if (client == null)
throw new ServerException("Client disconnected with invalid client id! (" + clientID + ")");
if (packetID == PacketTable.DISCONNECT) {
client.halt();
clients.remove(clientID);
continue;
}
client.handleRequest(new Message.Received(packetID, clientID, messageID, stream, receivePacket.getData()));
} catch (IOException | InterruptedException e) { } catch (IOException | InterruptedException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -84,11 +94,14 @@ public class Server implements Runnable {
private final int port; private final int port;
private final Queue<Message.Received> pendingRequests = new PriorityQueue<>(); private final Queue<Message.Received> pendingRequests = new PriorityQueue<>();
private final ReentrantLock requestLock = new ReentrantLock(); private final ReentrantLock requestLock = new ReentrantLock();
private final AtomicBoolean allowUpdate;
private final HashMap<Long, Message.Sent> sentMessages = new HashMap<>(); private final HashMap<Long, Message.Sent> sentMessages = new HashMap<>();
private final DatagramSocket serverSocket; private final DatagramSocket serverSocket;
private final long clientID; private final long clientID;
private volatile boolean running = true; private volatile boolean running = true;
private final Thread processingThread; private final Thread processingThread;
private final Thread gameEngineThread;
private final GameEngine usingEngine;
private final Map clientMap; private final Map clientMap;
public ConnectedClient(DatagramSocket serverSocket, GameEngine engine, long clientID, long messageID, InetAddress address, int port){ public ConnectedClient(DatagramSocket serverSocket, GameEngine engine, long clientID, long messageID, InetAddress address, int port){
@ -97,8 +110,18 @@ public class Server implements Runnable {
this.port = port; this.port = port;
this.clientID = clientID; this.clientID = clientID;
this.clientMap = engine.generateInitialMap(); this.clientMap = engine.generateInitialMap();
this.usingEngine = engine;
this.allowUpdate = new AtomicBoolean(true);
processingThread = new Thread(this); processingThread = new Thread(this);
processingThread.start(); processingThread.start();
gameEngineThread = new Thread(() -> {
while (running) {
if (this.allowUpdate.get()) {
engine.updateMap(clientMap);
}
}
});
gameEngineThread.start();
sendMessage(new Message.Sent(PacketTable.ACK, clientID, messageID)); sendMessage(new Message.Sent(PacketTable.ACK, clientID, messageID));
} }
@ -113,16 +136,27 @@ public class Server implements Runnable {
private void processRequest(Message.Received request){ private void processRequest(Message.Received request){
try { try {
switch (request.getPacketID()){ switch (request.getPacketID()) {
case PacketTable.ACK: case PacketTable.ACK:
Message.Sent message = sentMessages.get(request.getMessageID()); Message.Sent message = sentMessages.get(request.getMessageID());
if (message == null) if (message == null)
throw new RuntimeException("A message was acknowledged but does not exist!"); throw new RuntimeException("A message was acknowledged but does not exist!");
message.acknowledged(); message.acknowledged();
sentMessages.remove(request.getMessageID());
break; break;
case PacketTable.MESSAGE: case PacketTable.MESSAGE:
System.out.println(request.getReader().readUTF()); System.out.println(request.getReader().readUTF());
break; break;
case PacketTable.BUILD:
usingEngine.build(clientMap,new String(request.getData(), StandardCharsets.UTF_8));
break;
case PacketTable.TRAIN:
usingEngine.train(clientMap,new String(request.getData(), StandardCharsets.UTF_8));
break;
case PacketTable.UPGRADE:
usingEngine.upgradeBuilding(clientMap, ByteBuffer.wrap(request.getData()).getInt());
break;
} }
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@ -135,26 +169,35 @@ public class Server implements Runnable {
requestLock.lock(); requestLock.lock();
if (!pendingRequests.isEmpty()) { if (!pendingRequests.isEmpty()) {
Message.Received request = pendingRequests.remove(); Message.Received request = pendingRequests.remove();
allowUpdate.set(false);
processRequest(request); processRequest(request);
allowUpdate.set(true);
} }
requestLock.unlock(); requestLock.unlock();
ArrayList<Long> removes = new ArrayList<>();
for (HashMap.Entry<Long, Message.Sent> message : sentMessages.entrySet()){ for (HashMap.Entry<Long, Message.Sent> message : sentMessages.entrySet()){
if (message.getValue().getTimeSinceSent().get() > MAX_PACKET_ACK_TIME_SECONDS) { Message.Sent sent = message.getValue();
System.out.println("The server did not process our message, did they receive it?"); if (!sent.isAcknowledged() && sent.getTimeSinceSent().get() > MAX_PACKET_ACK_TIME_SECONDS) {
// todo: resend message System.out.println("The client did not acknowledge our message, did they receive it?");
sendMessage(sent);
removes.add(message.getKey());
} }
} }
for (Long l : removes)
sentMessages.remove(l);
} }
} }
public void sendMessage(Message.Sent message){ public void sendMessage(Message.Sent message){
this.sentMessages.put(message.getMessageID(), message); if (message.getPacketID() != PacketTable.ACK)
this.sentMessages.put(message.getMessageID(), message);
byte[] data = message.getData().toByteArray(); byte[] data = message.getData().toByteArray();
if (data.length > PACKET_SIZE) if (data.length > PACKET_SIZE)
throw new RuntimeException("Unable to send packet as it exceeds maximum packet size!"); throw new RuntimeException("Unable to send packet as it exceeds maximum packet size!");
DatagramPacket request = new DatagramPacket(data, data.length, address, port); DatagramPacket request = new DatagramPacket(data, data.length, address, port);
try { try {
System.out.println("Sending message with ID " + message.getMessageID() + " to client: " + message.getClientID() + " of type " + message.getPacketID());
serverSocket.send(request); serverSocket.send(request);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();