From fbebb529d3fcccaa00e27f2d564d0753fccfefed Mon Sep 17 00:00:00 2001 From: Simon Dietz <114642490+ArtifactForms@users.noreply.github.com> Date: Wed, 18 Mar 2026 21:31:13 +0100 Subject: [PATCH] fix: qualify outbound queue collection types --- .../main/java/common/network/Connection.java | 14 +++- .../java/server/network/ServerConnection.java | 64 +++++++++++++++++++ 2 files changed, 75 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..4d4d6736 100644 --- a/voxels-server/src/main/java/server/network/ServerConnection.java +++ b/voxels-server/src/main/java/server/network/ServerConnection.java @@ -13,9 +13,15 @@ */ 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 java.util.concurrent.LinkedBlockingQueue outboundQueue = + new java.util.concurrent.LinkedBlockingQueue<>(); + private final Thread writerThread; /** * Initializes a new server connection, sets up the dispatcher, and starts the listener thread. @@ -29,6 +35,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 +58,58 @@ 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() { + java.util.List batch = new java.util.ArrayList<>(MAX_OUTBOUND_BATCH_SIZE); + + try { + while (running || !outboundQueue.isEmpty()) { + Packet first = + outboundQueue.poll( + WRITER_POLL_TIMEOUT_MS, java.util.concurrent.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(java.util.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 +118,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);