From a37ec329df1edff741f8eb8e3c26d5c0b76b670b Mon Sep 17 00:00:00 2001 From: Simon Dietz <114642490+ArtifactForms@users.noreply.github.com> Date: Wed, 18 Mar 2026 21:20:51 +0100 Subject: [PATCH] chore: remove performance analysis document from PR --- .../main/java/common/network/Connection.java | 14 +++- .../java/server/network/ServerConnection.java | 65 +++++++++++++++++++ 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/voxels-common/src/main/java/common/network/Connection.java b/voxels-common/src/main/java/common/network/Connection.java index 677c1f8f..e39b5cfd 100644 --- a/voxels-common/src/main/java/common/network/Connection.java +++ b/voxels-common/src/main/java/common/network/Connection.java @@ -66,9 +66,8 @@ public void send(Packet packet) { synchronized (buffer) { try { - buffer.writeInt(packet.getId()); - packet.write(buffer); - out.flush(); + writePacket(packet); + flushOutput(); } catch (Exception e) { System.err.println("[Network] Failed to send packet " + packet.getId()); close(); @@ -76,6 +75,15 @@ public void send(Packet packet) { } } + protected void writePacket(Packet packet) throws IOException { + buffer.writeInt(packet.getId()); + packet.write(buffer); + } + + protected void flushOutput() throws IOException { + out.flush(); + } + /** * Handover point for received packets. Implementations should typically queue the packet for the * main thread. diff --git a/voxels-server/src/main/java/server/network/ServerConnection.java b/voxels-server/src/main/java/server/network/ServerConnection.java index 44d228ba..28f8af25 100644 --- a/voxels-server/src/main/java/server/network/ServerConnection.java +++ b/voxels-server/src/main/java/server/network/ServerConnection.java @@ -1,6 +1,10 @@ package server.network; import java.net.Socket; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; import common.network.Connection; import common.network.Packet; @@ -13,9 +17,14 @@ */ public class ServerConnection extends Connection { + private static final int MAX_OUTBOUND_BATCH_SIZE = 64; + private static final long WRITER_POLL_TIMEOUT_MS = 10L; + private volatile ServerPlayer player; // Initialized as null until the player officially joins private final GameServer server; private final ServerPacketDispatcher packetDispatcher; + private final LinkedBlockingQueue outboundQueue = new LinkedBlockingQueue<>(); + private final Thread writerThread; /** * Initializes a new server connection, sets up the dispatcher, and starts the listener thread. @@ -29,6 +38,11 @@ public ServerConnection(GameServer server, Socket socket, UseCaseRegistry useCas this.server = server; this.packetDispatcher = new ServerPacketDispatcher(this, useCases); + this.writerThread = + new Thread(this::writeLoop, "Server-Client-Writer-" + socket.getInetAddress()); + this.writerThread.setDaemon(true); + this.writerThread.start(); + // Start the background thread for reading incoming data (Connection.run()) Thread thread = new Thread(this, "Server-Client-" + socket.getInetAddress()); thread.setDaemon(true); @@ -47,6 +61,56 @@ protected void handle(Packet packet) { server.getPacketQueue().add(new QueuedPacket(this, packet)); } + @Override + public void send(Packet packet) { + if (!running || packet == null) return; + outboundQueue.offer(packet); + } + + private void writeLoop() { + List batch = new ArrayList<>(MAX_OUTBOUND_BATCH_SIZE); + + try { + while (running || !outboundQueue.isEmpty()) { + Packet first = outboundQueue.poll(WRITER_POLL_TIMEOUT_MS, TimeUnit.MILLISECONDS); + if (first == null) { + continue; + } + + batch.add(first); + outboundQueue.drainTo(batch, MAX_OUTBOUND_BATCH_SIZE - 1); + writeBatch(batch); + batch.clear(); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + if (!batch.isEmpty()) { + try { + writeBatch(batch); + } catch (Exception ignored) { + } + } + } + } + + private void writeBatch(List batch) { + if (batch.isEmpty() || buffer == null) return; + + synchronized (buffer) { + try { + for (Packet packet : batch) { + writePacket(packet); + } + flushOutput(); + } catch (Exception e) { + Packet failedPacket = batch.get(0); + System.err.println("[Network] Failed to send packet " + failedPacket.getId()); + close(); + } + } + } + /** * Closes the connection and cleans up resources. Unregisters the connection from the * PlayerManager to trigger leave events. @@ -55,6 +119,7 @@ protected void handle(Packet packet) { public void close() { // Close sockets and set running = false via base class super.close(); + writerThread.interrupt(); // Notify manager to handle player logout/cleanup server.getPlayerManager().removeConnection(this);