Added successful client connection flow.
This commit is contained in:
parent
1bf7074b76
commit
682f9f9bc2
|
@ -2,106 +2,101 @@ package nl.andrewl.aos2_client;
|
||||||
|
|
||||||
import nl.andrewl.aos2_client.render.ChunkMesh;
|
import nl.andrewl.aos2_client.render.ChunkMesh;
|
||||||
import nl.andrewl.aos2_client.render.ChunkRenderer;
|
import nl.andrewl.aos2_client.render.ChunkRenderer;
|
||||||
import nl.andrewl.aos2_client.render.WindowInfo;
|
import nl.andrewl.aos2_client.render.WindowUtils;
|
||||||
import nl.andrewl.aos_core.model.Chunk;
|
import nl.andrewl.aos_core.model.Chunk;
|
||||||
import org.joml.Vector3i;
|
import org.joml.Vector3i;
|
||||||
import org.lwjgl.glfw.Callbacks;
|
|
||||||
import org.lwjgl.glfw.GLFWErrorCallback;
|
|
||||||
import org.lwjgl.opengl.GL;
|
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import static org.lwjgl.glfw.GLFW.*;
|
import static org.lwjgl.glfw.GLFW.*;
|
||||||
import static org.lwjgl.opengl.GL46.*;
|
import static org.lwjgl.opengl.GL46.*;
|
||||||
|
|
||||||
public class Client {
|
public class Client implements Runnable {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) throws IOException {
|
||||||
var windowInfo = initUI();
|
InetAddress serverAddress = InetAddress.getByName(args[0]);
|
||||||
long windowHandle = windowInfo.windowHandle();
|
int serverPort = Integer.parseInt(args[1]);
|
||||||
|
String username = args[2].trim();
|
||||||
|
|
||||||
Camera cam = new Camera();
|
Client client = new Client(serverAddress, serverPort, username);
|
||||||
cam.setOrientationDegrees(90, 90);
|
client.run();
|
||||||
cam.setPosition(-3, 3, 0);
|
|
||||||
glfwSetCursorPosCallback(windowHandle, cam);
|
|
||||||
|
|
||||||
Chunk chunk = Chunk.random(new Vector3i(0, 0, 0), new Random(1));
|
|
||||||
Chunk chunk2 = Chunk.random(new Vector3i(1, 0, 0), new Random(1));
|
|
||||||
Chunk chunk3 = Chunk.random(new Vector3i(1, 0, 1), new Random(1));
|
|
||||||
Chunk chunk4 = Chunk.random(new Vector3i(0, 0, 1), new Random(1));
|
|
||||||
|
|
||||||
chunk.setBlockAt(0, 0, 0, (byte) 0);
|
// var windowInfo = WindowUtils.initUI();
|
||||||
|
// long windowHandle = windowInfo.windowHandle();
|
||||||
for (int x = 0; x < Chunk.SIZE; x++) {
|
//
|
||||||
for (int z = 0; z < Chunk.SIZE; z++) {
|
// Camera cam = new Camera();
|
||||||
chunk.setBlockAt(x, Chunk.SIZE - 1, z, (byte) 0);
|
// cam.setOrientationDegrees(90, 90);
|
||||||
}
|
// cam.setPosition(-3, 3, 0);
|
||||||
|
// glfwSetCursorPosCallback(windowHandle, cam);
|
||||||
|
//
|
||||||
|
// Chunk chunk = Chunk.random(new Vector3i(0, 0, 0), new Random(1));
|
||||||
|
// Chunk chunk2 = Chunk.random(new Vector3i(1, 0, 0), new Random(1));
|
||||||
|
// Chunk chunk3 = Chunk.random(new Vector3i(1, 0, 1), new Random(1));
|
||||||
|
// Chunk chunk4 = Chunk.random(new Vector3i(0, 0, 1), new Random(1));
|
||||||
|
//
|
||||||
|
// chunk.setBlockAt(0, 0, 0, (byte) 0);
|
||||||
|
//
|
||||||
|
// for (int x = 0; x < Chunk.SIZE; x++) {
|
||||||
|
// for (int z = 0; z < Chunk.SIZE; z++) {
|
||||||
|
// chunk.setBlockAt(x, Chunk.SIZE - 1, z, (byte) 0);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// ChunkRenderer chunkRenderer = new ChunkRenderer(windowInfo.width(), windowInfo.height());
|
||||||
|
// chunkRenderer.addChunkMesh(new ChunkMesh(chunk));
|
||||||
|
// chunkRenderer.addChunkMesh(new ChunkMesh(chunk2));
|
||||||
|
// chunkRenderer.addChunkMesh(new ChunkMesh(chunk3));
|
||||||
|
// chunkRenderer.addChunkMesh(new ChunkMesh(chunk4));
|
||||||
|
//
|
||||||
|
// while (!glfwWindowShouldClose(windowHandle)) {
|
||||||
|
// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
//
|
||||||
|
// chunkRenderer.draw(cam);
|
||||||
|
//
|
||||||
|
// glfwSwapBuffers(windowHandle);
|
||||||
|
// glfwPollEvents();
|
||||||
|
//
|
||||||
|
// if (glfwGetKey(windowHandle, GLFW_KEY_W) == GLFW_PRESS) cam.move(Camera.FORWARD);
|
||||||
|
// if (glfwGetKey(windowHandle, GLFW_KEY_S) == GLFW_PRESS) cam.move(Camera.BACKWARD);
|
||||||
|
// if (glfwGetKey(windowHandle, GLFW_KEY_A) == GLFW_PRESS) cam.move(Camera.LEFT);
|
||||||
|
// if (glfwGetKey(windowHandle, GLFW_KEY_D) == GLFW_PRESS) cam.move(Camera.RIGHT);
|
||||||
|
// if (glfwGetKey(windowHandle, GLFW_KEY_SPACE) == GLFW_PRESS) cam.move(Camera.UP);
|
||||||
|
// if (glfwGetKey(windowHandle, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) cam.move(Camera.DOWN);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// chunkRenderer.free();
|
||||||
|
// WindowUtils.clearUI(windowHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
ChunkRenderer chunkRenderer = new ChunkRenderer(windowInfo.width(), windowInfo.height());
|
private InetAddress serverAddress;
|
||||||
chunkRenderer.addChunkMesh(new ChunkMesh(chunk));
|
private int serverPort;
|
||||||
chunkRenderer.addChunkMesh(new ChunkMesh(chunk2));
|
private String username;
|
||||||
chunkRenderer.addChunkMesh(new ChunkMesh(chunk3));
|
private CommunicationHandler communicationHandler;
|
||||||
chunkRenderer.addChunkMesh(new ChunkMesh(chunk4));
|
private volatile boolean running;
|
||||||
|
|
||||||
while (!glfwWindowShouldClose(windowHandle)) {
|
public Client(InetAddress serverAddress, int serverPort, String username) {
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
this.serverAddress = serverAddress;
|
||||||
|
this.serverPort = serverPort;
|
||||||
chunkRenderer.draw(cam);
|
this.username = username;
|
||||||
|
this.communicationHandler = new CommunicationHandler();
|
||||||
glfwSwapBuffers(windowHandle);
|
|
||||||
glfwPollEvents();
|
|
||||||
|
|
||||||
if (glfwGetKey(windowHandle, GLFW_KEY_W) == GLFW_PRESS) cam.move(Camera.FORWARD);
|
|
||||||
if (glfwGetKey(windowHandle, GLFW_KEY_S) == GLFW_PRESS) cam.move(Camera.BACKWARD);
|
|
||||||
if (glfwGetKey(windowHandle, GLFW_KEY_A) == GLFW_PRESS) cam.move(Camera.LEFT);
|
|
||||||
if (glfwGetKey(windowHandle, GLFW_KEY_D) == GLFW_PRESS) cam.move(Camera.RIGHT);
|
|
||||||
if (glfwGetKey(windowHandle, GLFW_KEY_SPACE) == GLFW_PRESS) cam.move(Camera.UP);
|
|
||||||
if (glfwGetKey(windowHandle, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) cam.move(Camera.DOWN);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
chunkRenderer.free();
|
@Override
|
||||||
|
public void run() {
|
||||||
Callbacks.glfwFreeCallbacks(windowHandle);
|
running = false;
|
||||||
glfwDestroyWindow(windowHandle);
|
try {
|
||||||
glfwTerminate();
|
communicationHandler.establishConnection(serverAddress, serverPort, username);
|
||||||
glfwSetErrorCallback(null).free();
|
System.out.println("Established connection to the server.");
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
running = false;
|
||||||
}
|
}
|
||||||
|
while (running) {
|
||||||
private static WindowInfo initUI() {
|
// Do game stuff
|
||||||
GLFWErrorCallback.createPrint(System.err).set();
|
System.out.println("Running!");
|
||||||
if (!glfwInit()) throw new IllegalStateException("Could not initialize GLFW.");
|
|
||||||
glfwDefaultWindowHints();
|
|
||||||
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
|
||||||
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
|
|
||||||
|
|
||||||
var vidMode = glfwGetVideoMode(glfwGetPrimaryMonitor());
|
|
||||||
if (vidMode == null) throw new IllegalStateException("Could not get information about the primary monitory.");
|
|
||||||
long windowHandle = glfwCreateWindow(vidMode.width(), vidMode.height(), "Ace of Shades 2", glfwGetPrimaryMonitor(), 0);
|
|
||||||
if (windowHandle == 0) throw new RuntimeException("Failed to create GLFW window.");
|
|
||||||
|
|
||||||
glfwSetKeyCallback(windowHandle, (window, key, scancode, action, mods) -> {
|
|
||||||
if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) {
|
|
||||||
glfwSetWindowShouldClose(windowHandle, true);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
glfwSetInputMode(windowHandle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
|
||||||
glfwSetInputMode(windowHandle, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
|
|
||||||
|
|
||||||
glfwSetWindowPos(windowHandle, 0, 0);
|
|
||||||
glfwSetCursorPos(windowHandle, 0, 0);
|
|
||||||
|
|
||||||
glfwMakeContextCurrent(windowHandle);
|
|
||||||
glfwSwapInterval(1);
|
|
||||||
glfwShowWindow(windowHandle);
|
|
||||||
|
|
||||||
GL.createCapabilities();
|
|
||||||
// GLUtil.setupDebugMessageCallback(System.out);
|
|
||||||
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
|
||||||
glEnable(GL_CULL_FACE);
|
|
||||||
glEnable(GL_DEPTH_TEST);
|
|
||||||
glCullFace(GL_BACK);
|
|
||||||
|
|
||||||
return new WindowInfo(windowHandle, vidMode.width(), vidMode.height());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
package nl.andrewl.aos2_client;
|
||||||
|
|
||||||
|
import nl.andrewl.aos_core.Net;
|
||||||
|
import nl.andrewl.aos_core.net.*;
|
||||||
|
import nl.andrewl.aos_core.net.udp.DatagramInit;
|
||||||
|
import nl.andrewl.record_net.Message;
|
||||||
|
import nl.andrewl.record_net.util.ExtendedDataInputStream;
|
||||||
|
import nl.andrewl.record_net.util.ExtendedDataOutputStream;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.DatagramPacket;
|
||||||
|
import java.net.DatagramSocket;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
public class CommunicationHandler {
|
||||||
|
private Socket socket;
|
||||||
|
private DatagramSocket datagramSocket;
|
||||||
|
private ExtendedDataInputStream in;
|
||||||
|
private ExtendedDataOutputStream out;
|
||||||
|
private int clientId;
|
||||||
|
|
||||||
|
public int establishConnection(InetAddress address, int port, String username) throws IOException {
|
||||||
|
System.out.printf("Connecting to server at %s, port %d, with username \"%s\"...%n", address, port, username);
|
||||||
|
if (socket != null && !socket.isClosed()) {
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
socket = new Socket(address, port);
|
||||||
|
socket.setSoTimeout(1000);
|
||||||
|
in = Net.getInputStream(socket.getInputStream());
|
||||||
|
out = Net.getOutputStream(socket.getOutputStream());
|
||||||
|
Net.write(new ConnectRequestMessage(username), out);
|
||||||
|
Message response = Net.read(in);
|
||||||
|
socket.setSoTimeout(0);
|
||||||
|
if (response instanceof ConnectRejectMessage rejectMessage) {
|
||||||
|
throw new IOException("Attempt to connect rejected: " + rejectMessage.reason());
|
||||||
|
}
|
||||||
|
if (response instanceof ConnectAcceptMessage acceptMessage) {
|
||||||
|
this.clientId = acceptMessage.clientId();
|
||||||
|
new Thread(new TcpReceiver(in, this::handleMessage)).start();
|
||||||
|
establishDatagramConnection();
|
||||||
|
new Thread(new UdpReceiver(datagramSocket, this::handleUdpMessage)).start();
|
||||||
|
return acceptMessage.clientId();
|
||||||
|
} else {
|
||||||
|
throw new IOException("Server returned an unexpected message: " + response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendMessage(Message msg) {
|
||||||
|
try {
|
||||||
|
Net.write(msg, out);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendDatagramPacket(Message msg) {
|
||||||
|
try {
|
||||||
|
byte[] data = Net.write(msg);
|
||||||
|
DatagramPacket packet = new DatagramPacket(data, data.length, socket.getRemoteSocketAddress());
|
||||||
|
datagramSocket.send(packet);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void establishDatagramConnection() throws IOException {
|
||||||
|
datagramSocket = new DatagramSocket();
|
||||||
|
boolean connectionEstablished = false;
|
||||||
|
int attempts = 0;
|
||||||
|
while (!connectionEstablished && attempts < 100) {
|
||||||
|
sendDatagramPacket(new DatagramInit(clientId));
|
||||||
|
byte[] buffer = new byte[UdpReceiver.MAX_PACKET_SIZE];
|
||||||
|
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
|
||||||
|
datagramSocket.receive(packet);
|
||||||
|
Message msg = Net.read(buffer);
|
||||||
|
if (msg instanceof DatagramInit echo && echo.clientId() == clientId) {
|
||||||
|
connectionEstablished = true;
|
||||||
|
} else {
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!connectionEstablished) {
|
||||||
|
throw new IOException("Could not establish a datagram connection to the server after " + attempts + " attempts.");
|
||||||
|
}
|
||||||
|
System.out.println("Established datagram communication with the server.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleMessage(Message msg) {
|
||||||
|
System.out.println("Received message: " + msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleUdpMessage(Message msg, DatagramPacket packet) {
|
||||||
|
System.out.println("Received udp message: " + msg);
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ public class ChunkMesh {
|
||||||
private final int vaoId;
|
private final int vaoId;
|
||||||
private final int eboId;
|
private final int eboId;
|
||||||
|
|
||||||
private int indiciesCount;
|
private int indexCount;
|
||||||
|
|
||||||
private final int[] positionData;
|
private final int[] positionData;
|
||||||
private final Chunk chunk;
|
private final Chunk chunk;
|
||||||
|
@ -41,14 +41,14 @@ public class ChunkMesh {
|
||||||
long start = System.nanoTime();
|
long start = System.nanoTime();
|
||||||
var meshData = ChunkMeshGenerator.generateMesh(chunk);
|
var meshData = ChunkMeshGenerator.generateMesh(chunk);
|
||||||
double dur = (System.nanoTime() - start) / 1_000_000.0;
|
double dur = (System.nanoTime() - start) / 1_000_000.0;
|
||||||
this.indiciesCount = meshData.indexBuffer().limit();
|
this.indexCount = meshData.indexBuffer().limit();
|
||||||
// Print some debug information.
|
// Print some debug information.
|
||||||
System.out.printf(
|
System.out.printf(
|
||||||
"Generated mesh for chunk (%d, %d, %d) in %.3f ms. %d vertices, %d indices.%n",
|
"Generated mesh for chunk (%d, %d, %d) in %.3f ms. %d vertices, %d indices.%n",
|
||||||
chunk.getPosition().x, chunk.getPosition().y, chunk.getPosition().z,
|
chunk.getPosition().x, chunk.getPosition().y, chunk.getPosition().z,
|
||||||
dur,
|
dur,
|
||||||
meshData.vertexBuffer().limit() / 9,
|
meshData.vertexBuffer().limit() / 9,
|
||||||
indiciesCount
|
indexCount
|
||||||
);
|
);
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, vboId);
|
glBindBuffer(GL_ARRAY_BUFFER, vboId);
|
||||||
|
@ -80,7 +80,7 @@ public class ChunkMesh {
|
||||||
public void draw() {
|
public void draw() {
|
||||||
glBindVertexArray(vaoId);
|
glBindVertexArray(vaoId);
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboId);
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboId);
|
||||||
glDrawElements(GL_TRIANGLES, indiciesCount, GL_UNSIGNED_INT, 0);
|
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void free() {
|
public void free() {
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
package nl.andrewl.aos2_client.render;
|
||||||
|
|
||||||
|
import org.lwjgl.glfw.Callbacks;
|
||||||
|
import org.lwjgl.glfw.GLFWErrorCallback;
|
||||||
|
import org.lwjgl.opengl.GL;
|
||||||
|
|
||||||
|
import static org.lwjgl.glfw.GLFW.*;
|
||||||
|
import static org.lwjgl.opengl.GL11.*;
|
||||||
|
|
||||||
|
public class WindowUtils {
|
||||||
|
public static WindowInfo initUI() {
|
||||||
|
GLFWErrorCallback.createPrint(System.err).set();
|
||||||
|
if (!glfwInit()) throw new IllegalStateException("Could not initialize GLFW.");
|
||||||
|
glfwDefaultWindowHints();
|
||||||
|
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
||||||
|
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
|
||||||
|
|
||||||
|
var vidMode = glfwGetVideoMode(glfwGetPrimaryMonitor());
|
||||||
|
if (vidMode == null) throw new IllegalStateException("Could not get information about the primary monitory.");
|
||||||
|
long windowHandle = glfwCreateWindow(vidMode.width(), vidMode.height(), "Ace of Shades 2", glfwGetPrimaryMonitor(), 0);
|
||||||
|
if (windowHandle == 0) throw new RuntimeException("Failed to create GLFW window.");
|
||||||
|
|
||||||
|
glfwSetKeyCallback(windowHandle, (window, key, scancode, action, mods) -> {
|
||||||
|
if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) {
|
||||||
|
glfwSetWindowShouldClose(windowHandle, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
glfwSetInputMode(windowHandle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
||||||
|
glfwSetInputMode(windowHandle, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
|
||||||
|
|
||||||
|
glfwSetWindowPos(windowHandle, 0, 0);
|
||||||
|
glfwSetCursorPos(windowHandle, 0, 0);
|
||||||
|
|
||||||
|
glfwMakeContextCurrent(windowHandle);
|
||||||
|
glfwSwapInterval(1);
|
||||||
|
glfwShowWindow(windowHandle);
|
||||||
|
|
||||||
|
GL.createCapabilities();
|
||||||
|
// GLUtil.setupDebugMessageCallback(System.out);
|
||||||
|
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
|
glEnable(GL_CULL_FACE);
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
glCullFace(GL_BACK);
|
||||||
|
|
||||||
|
return new WindowInfo(windowHandle, vidMode.width(), vidMode.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clearUI(long windowHandle) {
|
||||||
|
Callbacks.glfwFreeCallbacks(windowHandle);
|
||||||
|
glfwDestroyWindow(windowHandle);
|
||||||
|
glfwTerminate();
|
||||||
|
glfwSetErrorCallback(null).free();
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,6 +35,12 @@
|
||||||
<artifactId>record-net</artifactId>
|
<artifactId>record-net</artifactId>
|
||||||
<version>v1.2.1</version>
|
<version>v1.2.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- https://github.com/OpenHFT/Zero-Allocation-Hashing -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.openhft</groupId>
|
||||||
|
<artifactId>zero-allocation-hashing</artifactId>
|
||||||
|
<version>0.15</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
|
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
package nl.andrewl.aos_core;
|
package nl.andrewl.aos_core;
|
||||||
|
|
||||||
|
import nl.andrewl.aos_core.net.ChunkHashMessage;
|
||||||
|
import nl.andrewl.aos_core.net.ConnectAcceptMessage;
|
||||||
|
import nl.andrewl.aos_core.net.ConnectRejectMessage;
|
||||||
import nl.andrewl.aos_core.net.ConnectRequestMessage;
|
import nl.andrewl.aos_core.net.ConnectRequestMessage;
|
||||||
|
import nl.andrewl.aos_core.net.udp.DatagramInit;
|
||||||
import nl.andrewl.record_net.Message;
|
import nl.andrewl.record_net.Message;
|
||||||
import nl.andrewl.record_net.Serializer;
|
import nl.andrewl.record_net.Serializer;
|
||||||
import nl.andrewl.record_net.util.ExtendedDataInputStream;
|
import nl.andrewl.record_net.util.ExtendedDataInputStream;
|
||||||
|
@ -20,6 +24,10 @@ public final class Net {
|
||||||
private static final Serializer serializer = new Serializer();
|
private static final Serializer serializer = new Serializer();
|
||||||
static {
|
static {
|
||||||
serializer.registerType(1, ConnectRequestMessage.class);
|
serializer.registerType(1, ConnectRequestMessage.class);
|
||||||
|
serializer.registerType(2, ConnectAcceptMessage.class);
|
||||||
|
serializer.registerType(3, ConnectRejectMessage.class);
|
||||||
|
serializer.registerType(4, DatagramInit.class);
|
||||||
|
serializer.registerType(5, ChunkHashMessage.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ExtendedDataInputStream getInputStream(InputStream in) {
|
public static ExtendedDataInputStream getInputStream(InputStream in) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package nl.andrewl.aos_core.model;
|
package nl.andrewl.aos_core.model;
|
||||||
|
|
||||||
|
import net.openhft.hashing.LongHashFunction;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
import org.joml.Vector3i;
|
import org.joml.Vector3i;
|
||||||
|
|
||||||
|
@ -104,6 +105,10 @@ public class Chunk {
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long blockHash() {
|
||||||
|
return LongHashFunction.xx3(0).hashBytes(blocks);
|
||||||
|
}
|
||||||
|
|
||||||
public static Chunk random(Vector3i position, Random rand) {
|
public static Chunk random(Vector3i position, Random rand) {
|
||||||
Chunk c = new Chunk(position);
|
Chunk c = new Chunk(position);
|
||||||
for (int i = 0; i < TOTAL_SIZE; i++) {
|
for (int i = 0; i < TOTAL_SIZE; i++) {
|
||||||
|
|
|
@ -8,11 +8,33 @@ public class Player {
|
||||||
private final Vector3f velocity;
|
private final Vector3f velocity;
|
||||||
private final Vector2f orientation;
|
private final Vector2f orientation;
|
||||||
private final String username;
|
private final String username;
|
||||||
|
private final int id;
|
||||||
|
|
||||||
public Player(String username) {
|
public Player(int id, String username) {
|
||||||
this.position = new Vector3f();
|
this.position = new Vector3f();
|
||||||
this.velocity = new Vector3f();
|
this.velocity = new Vector3f();
|
||||||
this.orientation = new Vector2f();
|
this.orientation = new Vector2f();
|
||||||
|
this.id = id;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Vector3f getPosition() {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3f getVelocity() {
|
||||||
|
return velocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2f getOrientation() {
|
||||||
|
return orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package nl.andrewl.aos_core.net;
|
||||||
|
|
||||||
|
import nl.andrewl.record_net.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message sent by the client, which contains a hash of a chunk, so that the
|
||||||
|
* server can determine if it's up-to-date, or if the server needs to send the
|
||||||
|
* latest chunk data to the user.
|
||||||
|
* @param cx The chunk x coordinate.
|
||||||
|
* @param cy The chunk y coordinate.
|
||||||
|
* @param cz The chunk z coordinate.
|
||||||
|
* @param hash The hash value of the chunk.
|
||||||
|
*/
|
||||||
|
public record ChunkHashMessage(
|
||||||
|
int cx, int cy, int cz,
|
||||||
|
long hash
|
||||||
|
) implements Message {}
|
|
@ -2,4 +2,11 @@ package nl.andrewl.aos_core.net;
|
||||||
|
|
||||||
import nl.andrewl.record_net.Message;
|
import nl.andrewl.record_net.Message;
|
||||||
|
|
||||||
public record ConnectAcceptMessage () implements Message {}
|
/**
|
||||||
|
* The message that's sent by the server to indicate that a connecting client
|
||||||
|
* has been accepted and can join the server.
|
||||||
|
* @param clientId The client's id.
|
||||||
|
*/
|
||||||
|
public record ConnectAcceptMessage (
|
||||||
|
int clientId
|
||||||
|
) implements Message {}
|
||||||
|
|
|
@ -2,4 +2,10 @@ package nl.andrewl.aos_core.net;
|
||||||
|
|
||||||
import nl.andrewl.record_net.Message;
|
import nl.andrewl.record_net.Message;
|
||||||
|
|
||||||
public record ConnectRejectMessage(String reason) implements Message {}
|
/**
|
||||||
|
* A message that's sent by the server when a connecting client is rejected.
|
||||||
|
* @param reason The reason for the rejection.
|
||||||
|
*/
|
||||||
|
public record ConnectRejectMessage(
|
||||||
|
String reason
|
||||||
|
) implements Message {}
|
||||||
|
|
|
@ -2,4 +2,4 @@ package nl.andrewl.aos_core.net;
|
||||||
|
|
||||||
import nl.andrewl.record_net.Message;
|
import nl.andrewl.record_net.Message;
|
||||||
|
|
||||||
public record ConnectRequestMessage(String username, int udpPort) implements Message {}
|
public record ConnectRequestMessage(String username) implements Message {}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
package nl.andrewl.aos_core.net;
|
||||||
|
|
||||||
|
import nl.andrewl.aos_core.Net;
|
||||||
|
import nl.andrewl.record_net.Message;
|
||||||
|
import nl.andrewl.record_net.util.ExtendedDataInputStream;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class TcpReceiver implements Runnable {
|
||||||
|
private final ExtendedDataInputStream in;
|
||||||
|
private final Consumer<Message> messageConsumer;
|
||||||
|
|
||||||
|
public TcpReceiver(ExtendedDataInputStream in, Consumer<Message> messageConsumer) {
|
||||||
|
this.in = in;
|
||||||
|
this.messageConsumer = messageConsumer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
Message msg = Net.read(in);
|
||||||
|
messageConsumer.accept(msg);
|
||||||
|
} catch (SocketException e) {
|
||||||
|
if (e.getMessage().equals("Socket closed")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (EOFException e) {
|
||||||
|
return;
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package nl.andrewl.aos_core.net;
|
||||||
|
|
||||||
|
import nl.andrewl.record_net.Message;
|
||||||
|
|
||||||
|
import java.net.DatagramPacket;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface UdpMessageHandler {
|
||||||
|
void handle(Message msg, DatagramPacket packet);
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package nl.andrewl.aos_core.net;
|
||||||
|
|
||||||
|
import nl.andrewl.aos_core.Net;
|
||||||
|
import nl.andrewl.record_net.Message;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.DatagramPacket;
|
||||||
|
import java.net.DatagramSocket;
|
||||||
|
import java.net.SocketException;
|
||||||
|
|
||||||
|
public class UdpReceiver implements Runnable {
|
||||||
|
public static final short MAX_PACKET_SIZE = 1400;
|
||||||
|
|
||||||
|
private final DatagramSocket socket;
|
||||||
|
private final UdpMessageHandler handler;
|
||||||
|
|
||||||
|
public UdpReceiver(DatagramSocket socket, UdpMessageHandler handler) {
|
||||||
|
this.socket = socket;
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
byte[] buffer = new byte[MAX_PACKET_SIZE];
|
||||||
|
DatagramPacket packet = new DatagramPacket(buffer, MAX_PACKET_SIZE);
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
socket.receive(packet);
|
||||||
|
Message msg = Net.read(buffer);
|
||||||
|
handler.handle(msg, packet);
|
||||||
|
} catch (SocketException e) {
|
||||||
|
if (e.getMessage().equals("Socket closed")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (EOFException e) {
|
||||||
|
return;
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package nl.andrewl.aos_core.net.udp;
|
||||||
|
|
||||||
|
import nl.andrewl.record_net.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message that's sent to clients when a block in a chunk is updated.
|
||||||
|
* @param cx The chunk x coordinate.
|
||||||
|
* @param cy The chunk y coordinate.
|
||||||
|
* @param cz The chunk z coordinate.
|
||||||
|
* @param lx The local x coordinate in the chunk.
|
||||||
|
* @param ly The local y coordinate in the chunk.
|
||||||
|
* @param lz The local z coordinate in the chunk.
|
||||||
|
* @param newBlock The new block data in the specified position.
|
||||||
|
*/
|
||||||
|
public record ChunkUpdateMessage(
|
||||||
|
int cx, int cy, int cz,
|
||||||
|
int lx, int ly, int lz,
|
||||||
|
byte newBlock
|
||||||
|
) implements Message {}
|
|
@ -2,4 +2,11 @@ package nl.andrewl.aos_core.net.udp;
|
||||||
|
|
||||||
import nl.andrewl.record_net.Message;
|
import nl.andrewl.record_net.Message;
|
||||||
|
|
||||||
public record DatagramInit() implements Message {}
|
/**
|
||||||
|
* The message that's sent initially by the client, and responded to by the
|
||||||
|
* server, when a client is establishing a UDP "connection" to the server.
|
||||||
|
* @param clientId The client's id.
|
||||||
|
*/
|
||||||
|
public record DatagramInit(
|
||||||
|
int clientId
|
||||||
|
) implements Message {}
|
||||||
|
|
|
@ -8,9 +8,9 @@ When referring to the names of packets, we will assume a common package name of
|
||||||
### Player Connection
|
### Player Connection
|
||||||
This workflow is involved in the establishment of a connection between the client and server.
|
This workflow is involved in the establishment of a connection between the client and server.
|
||||||
|
|
||||||
1. Player sends a `ConnectRequestMessage` via TCP, immediately upon opening a socket connection. It contains the player's desired `username`, and their `udpPort` that they will use to connect.
|
1. Player sends a `ConnectRequestMessage` via TCP, immediately upon opening a socket connection. It contains the player's desired `username`.
|
||||||
2. The server will respond with either a `ConnectRejectMessage` with a `reason` for the rejection, or a `ConnectAcceptMessage`.
|
2. The server will respond with either a `ConnectRejectMessage` with a `reason` for the rejection, or a `ConnectAcceptMessage` containing the client's `clientId`.
|
||||||
3. If the player received an acceptance message, they will then send a `DatagramInit` to the server's UDP socket (on the same address/port). The player should keep sending such an init message until they receive a `DatagramInit` message echoed back as a response. The player should then stop sending init messages, and expect to begin receiving normal communication data through the datagram socket.
|
3. If the player received an acceptance message, they will then send a `DatagramInit` to the server's UDP socket (on the same address/port) containing the `clientId` received in the `ConnectAcceptMessage`. The player should keep sending such an init message until they receive a `DatagramInit` message echoed back as a response. The player should then stop sending init messages, and expect to begin receiving normal communication data through the datagram socket.
|
||||||
|
|
||||||
### World Data
|
### World Data
|
||||||
A combination of TCP and UDP communication is used to ensure that all connected clients have the latest information about the state of the world.
|
A combination of TCP and UDP communication is used to ensure that all connected clients have the latest information about the state of the world.
|
||||||
|
|
|
@ -1,85 +1,73 @@
|
||||||
package nl.andrewl.aos2_server;
|
package nl.andrewl.aos2_server;
|
||||||
|
|
||||||
import nl.andrewl.aos_core.Net;
|
import nl.andrewl.aos_core.Net;
|
||||||
|
import nl.andrewl.aos_core.model.Player;
|
||||||
|
import nl.andrewl.aos_core.net.ConnectAcceptMessage;
|
||||||
import nl.andrewl.aos_core.net.ConnectRejectMessage;
|
import nl.andrewl.aos_core.net.ConnectRejectMessage;
|
||||||
import nl.andrewl.aos_core.net.ConnectRequestMessage;
|
import nl.andrewl.aos_core.net.ConnectRequestMessage;
|
||||||
|
import nl.andrewl.aos_core.net.TcpReceiver;
|
||||||
import nl.andrewl.record_net.Message;
|
import nl.andrewl.record_net.Message;
|
||||||
import nl.andrewl.record_net.util.ExtendedDataInputStream;
|
import nl.andrewl.record_net.util.ExtendedDataInputStream;
|
||||||
import nl.andrewl.record_net.util.ExtendedDataOutputStream;
|
import nl.andrewl.record_net.util.ExtendedDataOutputStream;
|
||||||
|
|
||||||
import java.io.EOFException;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.*;
|
import java.net.DatagramPacket;
|
||||||
|
import java.net.DatagramSocket;
|
||||||
public class ClientHandler extends Thread {
|
import java.net.InetAddress;
|
||||||
private static int nextThreadId = 1;
|
import java.net.Socket;
|
||||||
|
|
||||||
|
public class ClientCommunicationHandler {
|
||||||
private final Server server;
|
private final Server server;
|
||||||
private final Socket socket;
|
private final Socket socket;
|
||||||
private final DatagramSocket datagramSocket;
|
private final DatagramSocket datagramSocket;
|
||||||
private final ExtendedDataInputStream in;
|
private final ExtendedDataInputStream in;
|
||||||
private final ExtendedDataOutputStream out;
|
private final ExtendedDataOutputStream out;
|
||||||
|
|
||||||
private volatile boolean running;
|
|
||||||
private InetAddress clientAddress;
|
private InetAddress clientAddress;
|
||||||
private int clientUdpPort;
|
private int clientUdpPort;
|
||||||
|
private Player player;
|
||||||
|
|
||||||
public ClientHandler(Server server, Socket socket, DatagramSocket datagramSocket) throws IOException {
|
public ClientCommunicationHandler(Server server, Socket socket, DatagramSocket datagramSocket) throws IOException {
|
||||||
super("aos-client-handler-" + nextThreadId++);
|
|
||||||
this.server = server;
|
this.server = server;
|
||||||
this.socket = socket;
|
this.socket = socket;
|
||||||
this.datagramSocket = datagramSocket;
|
this.datagramSocket = datagramSocket;
|
||||||
this.in = Net.getInputStream(socket.getInputStream());
|
this.in = Net.getInputStream(socket.getInputStream());
|
||||||
this.out = Net.getOutputStream(socket.getOutputStream());
|
this.out = Net.getOutputStream(socket.getOutputStream());
|
||||||
|
establishConnection();
|
||||||
|
new Thread(new TcpReceiver(in, this::handleTcpMessage)).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
running = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
running = true;
|
|
||||||
establishConnection();
|
|
||||||
while (running) {
|
|
||||||
try {
|
try {
|
||||||
Message msg = Net.read(in);
|
socket.close();
|
||||||
} catch (SocketException e) {
|
|
||||||
if (e.getMessage().equals("Socket closed") | e.getMessage().equals("Connection reset")) {
|
|
||||||
shutdown();
|
|
||||||
} else {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
} catch (EOFException e) {
|
|
||||||
shutdown();
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
shutdown();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void establishConnection() {
|
public void setClientUdpPort(int port) {
|
||||||
try {
|
this.clientUdpPort = port;
|
||||||
socket.setSoTimeout(1000);
|
|
||||||
} catch (SocketException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleTcpMessage(Message msg) {
|
||||||
|
System.out.println("Message received from client " + player.getUsername() + ": " + msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void establishConnection() throws IOException {
|
||||||
|
socket.setSoTimeout(1000);
|
||||||
boolean connectionEstablished = false;
|
boolean connectionEstablished = false;
|
||||||
int attempts = 0;
|
int attempts = 0;
|
||||||
while (!connectionEstablished && attempts < 100) {
|
while (!connectionEstablished && attempts < 100) {
|
||||||
try {
|
try {
|
||||||
Message msg = Net.read(in);
|
Message msg = Net.read(in);
|
||||||
if (msg instanceof ConnectRequestMessage connectMsg) {
|
if (msg instanceof ConnectRequestMessage connectMsg) {
|
||||||
|
// Try to set the TCP timeout back to 0 now that we've got the correct request.
|
||||||
|
socket.setSoTimeout(0);
|
||||||
this.clientAddress = socket.getInetAddress();
|
this.clientAddress = socket.getInetAddress();
|
||||||
this.clientUdpPort = connectMsg.udpPort();
|
|
||||||
System.out.println("Player connected: " + connectMsg.username());
|
System.out.println("Player connected: " + connectMsg.username());
|
||||||
connectionEstablished = true;
|
connectionEstablished = true;
|
||||||
try {
|
this.player = server.registerPlayer(this, connectMsg.username());
|
||||||
socket.setSoTimeout(0);
|
Net.write(new ConnectAcceptMessage(player.getId()), out);
|
||||||
} catch (SocketException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
@ -93,11 +81,11 @@ public class ClientHandler extends Thread {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
System.out.println("Player couldn't connect after " + attempts + " attempts. Aborting.");
|
System.out.println("Player couldn't connect after " + attempts + " attempts. Aborting.");
|
||||||
shutdown();
|
socket.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendDatagramPacket(Message msg) {
|
public void sendDatagramPacket(Message msg) {
|
||||||
try {
|
try {
|
||||||
sendDatagramPacket(Net.write(msg));
|
sendDatagramPacket(Net.write(msg));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -105,12 +93,12 @@ public class ClientHandler extends Thread {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendDatagramPacket(byte[] data) {
|
public void sendDatagramPacket(byte[] data) {
|
||||||
DatagramPacket packet = new DatagramPacket(data, data.length, clientAddress, clientUdpPort);
|
DatagramPacket packet = new DatagramPacket(data, data.length, clientAddress, clientUdpPort);
|
||||||
sendDatagramPacket(packet);
|
sendDatagramPacket(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendDatagramPacket(DatagramPacket packet) {
|
public void sendDatagramPacket(DatagramPacket packet) {
|
||||||
try {
|
try {
|
||||||
packet.setAddress(clientAddress);
|
packet.setAddress(clientAddress);
|
||||||
packet.setPort(clientUdpPort);
|
packet.setPort(clientUdpPort);
|
|
@ -1,22 +1,26 @@
|
||||||
package nl.andrewl.aos2_server;
|
package nl.andrewl.aos2_server;
|
||||||
|
|
||||||
|
import nl.andrewl.aos_core.model.Player;
|
||||||
|
import nl.andrewl.aos_core.net.UdpReceiver;
|
||||||
|
import nl.andrewl.aos_core.net.udp.DatagramInit;
|
||||||
|
import nl.andrewl.record_net.Message;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.DatagramSocket;
|
import java.net.*;
|
||||||
import java.net.ServerSocket;
|
import java.util.HashMap;
|
||||||
import java.net.Socket;
|
|
||||||
import java.net.SocketException;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class Server implements Runnable {
|
public class Server implements Runnable {
|
||||||
private final ServerSocket serverSocket;
|
private final ServerSocket serverSocket;
|
||||||
private final DatagramSocket datagramSocket;
|
private final DatagramSocket datagramSocket;
|
||||||
private volatile boolean running;
|
private volatile boolean running;
|
||||||
private Set<ClientHandler> clientHandlers;
|
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
private int nextClientId = 1;
|
||||||
new Server().run();
|
private final Set<ClientCommunicationHandler> clientHandlers;
|
||||||
}
|
private final Map<Integer, Player> players;
|
||||||
|
private final Map<Integer, ClientCommunicationHandler> playerClientHandlers;
|
||||||
|
|
||||||
public Server() throws IOException {
|
public Server() throws IOException {
|
||||||
this.serverSocket = new ServerSocket(24464, 5);
|
this.serverSocket = new ServerSocket(24464, 5);
|
||||||
|
@ -24,22 +28,49 @@ public class Server implements Runnable {
|
||||||
this.datagramSocket = new DatagramSocket(24464);
|
this.datagramSocket = new DatagramSocket(24464);
|
||||||
this.datagramSocket.setReuseAddress(true);
|
this.datagramSocket.setReuseAddress(true);
|
||||||
this.clientHandlers = new HashSet<>();
|
this.clientHandlers = new HashSet<>();
|
||||||
|
this.players = new HashMap<>();
|
||||||
|
this.playerClientHandlers = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
running = true;
|
running = true;
|
||||||
|
new Thread(new UdpReceiver(datagramSocket, this::handleUdpMessage)).start();
|
||||||
System.out.println("Started AOS2-Server on TCP/UDP port " + serverSocket.getLocalPort() + "; now accepting connections.");
|
System.out.println("Started AOS2-Server on TCP/UDP port " + serverSocket.getLocalPort() + "; now accepting connections.");
|
||||||
while (running) {
|
while (running) {
|
||||||
acceptClientConnection();
|
acceptClientConnection();
|
||||||
}
|
}
|
||||||
|
datagramSocket.close();
|
||||||
|
for (var handler : clientHandlers) handler.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleUdpMessage(Message msg, DatagramPacket packet) {
|
||||||
|
// Echo any init message from known clients.
|
||||||
|
if (msg instanceof DatagramInit init) {
|
||||||
|
var handler = getHandler(init.clientId());
|
||||||
|
if (handler != null) {
|
||||||
|
handler.setClientUdpPort(packet.getPort());
|
||||||
|
handler.sendDatagramPacket(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized Player registerPlayer(ClientCommunicationHandler handler, String username) {
|
||||||
|
Player player = new Player(nextClientId++, username);
|
||||||
|
players.put(player.getId(), player);
|
||||||
|
playerClientHandlers.put(player.getId(), handler);
|
||||||
|
System.out.println("Registered player " + username + " with id " + player.getId());
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientCommunicationHandler getHandler(int id) {
|
||||||
|
return playerClientHandlers.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void acceptClientConnection() {
|
private void acceptClientConnection() {
|
||||||
try {
|
try {
|
||||||
Socket clientSocket = serverSocket.accept();
|
Socket clientSocket = serverSocket.accept();
|
||||||
ClientHandler handler = new ClientHandler(this, clientSocket, datagramSocket);
|
ClientCommunicationHandler handler = new ClientCommunicationHandler(this, clientSocket, datagramSocket);
|
||||||
handler.start();
|
|
||||||
clientHandlers.add(handler);
|
clientHandlers.add(handler);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (e instanceof SocketException && !this.running && e.getMessage().equalsIgnoreCase("Socket closed")) {
|
if (e instanceof SocketException && !this.running && e.getMessage().equalsIgnoreCase("Socket closed")) {
|
||||||
|
@ -48,4 +79,8 @@ public class Server implements Runnable {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
new Server().run();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue