From 3165ff33b3fc3ece729143b35ab452c43e3e2c71 Mon Sep 17 00:00:00 2001 From: Stef Date: Tue, 14 Oct 2025 13:21:17 +0200 Subject: [PATCH 01/14] Tests for SoundEffectManager --- .../audio/SoundEffectManagerTest.java | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 framework/src/test/java/org/toop/framework/audio/SoundEffectManagerTest.java diff --git a/framework/src/test/java/org/toop/framework/audio/SoundEffectManagerTest.java b/framework/src/test/java/org/toop/framework/audio/SoundEffectManagerTest.java new file mode 100644 index 0000000..302858f --- /dev/null +++ b/framework/src/test/java/org/toop/framework/audio/SoundEffectManagerTest.java @@ -0,0 +1,119 @@ +package org.toop.framework.audio; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.toop.framework.resource.ResourceMeta; +import org.toop.framework.resource.resources.BaseResource; +import org.toop.framework.resource.types.AudioResource; + +import java.io.File; +import java.util.List; +import java.util.ArrayList; +import java.util.Collection; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for SoundEffectManager. + */ +class MockSoundEffectResource extends BaseResource implements AudioResource { + boolean played = false; + boolean stopped = false; + + public MockSoundEffectResource(String name) { + super(new File(name)); + } + + @Override + public String getName() { + return getFile().getName(); + } + + @Override + public void play() { + played = true; + } + + @Override + public void stop() { + stopped = true; + } + + @Override + public void setOnEnd(Runnable callback) {} + + @Override + public void setOnError(Runnable callback) {} + + @Override + public void updateVolume(double volume) {} +} + +public class SoundEffectManagerTest { + + private SoundEffectManager manager; + private MockAudioResource sfx1; + private MockAudioResource sfx2; + private MockAudioResource sfx3; + + @BeforeEach + void setUp() { + sfx1 = new MockAudioResource("explosion.wav"); + sfx2 = new MockAudioResource("laser.wav"); + sfx3 = new MockAudioResource("jump.wav"); + + List> resources = List.of( + new ResourceMeta<>("explosion", sfx1), + new ResourceMeta<>("laser", sfx2), + new ResourceMeta<>("jump", sfx3) + ); + + manager = new SoundEffectManager<>(resources); + } + + @Test + void testPlayValidSound() { + manager.play("explosion", false); + assertTrue(sfx1.played, "Sound 'explosion' should be played"); + } + + @Test + void testPlayInvalidSoundLogsWarning() { + // Nothing should crash or throw + assertDoesNotThrow(() -> manager.play("nonexistent", false)); + } + + @Test + void testStopValidSound() { + manager.stop("laser"); + assertTrue(sfx2.stopped, "Sound 'laser' should be stopped"); + } + + @Test + void testStopInvalidSoundDoesNotThrow() { + assertDoesNotThrow(() -> manager.stop("does_not_exist")); + } + + @Test + void testGetActiveAudioReturnsAll() { + Collection active = manager.getActiveAudio(); + assertEquals(3, active.size(), "All three sounds should be in active audio list"); + assertTrue(active.containsAll(List.of(sfx1, sfx2, sfx3))); + } + + @Test + void testDuplicateResourceKeepsLast() { + MockAudioResource oldRes = new MockAudioResource("duplicate_old.wav"); + MockAudioResource newRes = new MockAudioResource("duplicate_new.wav"); + + List> list = new ArrayList<>(); + list.add(new ResourceMeta<>("dup", oldRes)); + list.add(new ResourceMeta<>("dup", newRes)); // duplicate key + + SoundEffectManager dupManager = new SoundEffectManager<>(list); + dupManager.play("dup", false); + + assertTrue(newRes.played, "New duplicate resource should override old one"); + assertFalse(oldRes.played, "Old duplicate resource should be discarded"); + } +} From cb7e3298fd376c3fb767833503e6d227d985295b Mon Sep 17 00:00:00 2001 From: lieght <49651652+BAFGdeJong@users.noreply.github.com> Date: Tue, 14 Oct 2025 19:57:07 +0200 Subject: [PATCH 02/14] Added shuffling on user request --- app/src/main/java/org/toop/Main.java | 2 +- .../main/java/org/toop/framework/audio/MusicManager.java | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/toop/Main.java b/app/src/main/java/org/toop/Main.java index 69b1b86..49ff87b 100644 --- a/app/src/main/java/org/toop/Main.java +++ b/app/src/main/java/org/toop/Main.java @@ -23,7 +23,7 @@ public final class Main { ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/assets")); new Thread(NetworkingClientManager::new).start(); new Thread(() -> { - MusicManager musicManager = new MusicManager<>(ResourceManager.getAllOfTypeAndRemoveWrapper(MusicAsset.class)); + MusicManager musicManager = new MusicManager<>(ResourceManager.getAllOfTypeAndRemoveWrapper(MusicAsset.class), true); SoundEffectManager soundEffectManager = new SoundEffectManager<>(ResourceManager.getAllOfType(SoundEffectAsset.class)); AudioVolumeManager audioVolumeManager = new AudioVolumeManager() .registerManager(VolumeControl.MASTERVOLUME, musicManager) diff --git a/framework/src/main/java/org/toop/framework/audio/MusicManager.java b/framework/src/main/java/org/toop/framework/audio/MusicManager.java index ea20e8e..38a6f3a 100644 --- a/framework/src/main/java/org/toop/framework/audio/MusicManager.java +++ b/framework/src/main/java/org/toop/framework/audio/MusicManager.java @@ -18,10 +18,13 @@ public class MusicManager implements org.toop.framework private int playingIndex = 0; private boolean playing = false; - public MusicManager(List resources) { + public MusicManager(List resources, boolean shuffleMusic) { this.dispatcher = new JavaFXDispatcher(); this.resources = resources; - createShuffled(); + // Shuffle if wanting to shuffle + if (shuffleMusic) createShuffled(); + else backgroundMusic.addAll(resources); + // ------------------------------ } /** From 8f7e78efb5ed744d9ce1c560f78740b05878daf6 Mon Sep 17 00:00:00 2001 From: lieght <49651652+BAFGdeJong@users.noreply.github.com> Date: Tue, 14 Oct 2025 23:27:12 +0200 Subject: [PATCH 03/14] Reworked NetworkingClientManager into SRP model. --- app/src/main/java/org/toop/Main.java | 14 +- .../toop/app/layer/layers/ConnectedLayer.java | 2 + .../framework/eventbus/GlobalEventBus.java | 6 +- .../NetworkingClientEventListener.java | 126 +++++++++++ .../networking/NetworkingClientManager.java | 210 +++--------------- .../TournamentNetworkingClient.java} | 71 ++---- .../networking/events/NetworkEvents.java | 11 +- .../NetworkingGameClientHandler.java | 2 +- .../NetworkingTicTacToeClientHandler.java | 12 - .../interfaces/NetworkingClient.java | 7 + .../interfaces/NetworkingClientManager.java | 11 + .../networking/types/ServerCommand.java | 3 + .../networking/types/ServerMessage.java | 3 + 13 files changed, 221 insertions(+), 257 deletions(-) create mode 100644 framework/src/main/java/org/toop/framework/networking/NetworkingClientEventListener.java rename framework/src/main/java/org/toop/framework/networking/{NetworkingClient.java => clients/TournamentNetworkingClient.java} (59%) rename framework/src/main/java/org/toop/framework/networking/{ => handlers}/NetworkingGameClientHandler.java (99%) delete mode 100644 framework/src/main/java/org/toop/framework/networking/handlers/NetworkingTicTacToeClientHandler.java create mode 100644 framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClient.java create mode 100644 framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClientManager.java create mode 100644 framework/src/main/java/org/toop/framework/networking/types/ServerCommand.java create mode 100644 framework/src/main/java/org/toop/framework/networking/types/ServerMessage.java diff --git a/app/src/main/java/org/toop/Main.java b/app/src/main/java/org/toop/Main.java index 49ff87b..efe348d 100644 --- a/app/src/main/java/org/toop/Main.java +++ b/app/src/main/java/org/toop/Main.java @@ -2,26 +2,24 @@ package org.toop; import org.toop.app.App; import org.toop.framework.audio.*; +import org.toop.framework.networking.NetworkingClientEventListener; import org.toop.framework.networking.NetworkingClientManager; -import org.toop.framework.networking.NetworkingInitializationException; import org.toop.framework.resource.ResourceLoader; import org.toop.framework.resource.ResourceManager; -import org.toop.framework.resource.ResourceMeta; import org.toop.framework.resource.resources.MusicAsset; import org.toop.framework.resource.resources.SoundEffectAsset; -import java.util.Arrays; -import java.util.List; - public final class Main { - static void main(String[] args) { + static void main(String[] args) throws InterruptedException { initSystems(); + Thread.sleep(200); App.run(args); } - private static void initSystems() throws NetworkingInitializationException { + private static void initSystems() { ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/assets")); - new Thread(NetworkingClientManager::new).start(); + new Thread(() -> new NetworkingClientEventListener(new NetworkingClientManager())).start(); + new Thread(() -> { MusicManager musicManager = new MusicManager<>(ResourceManager.getAllOfTypeAndRemoveWrapper(MusicAsset.class), true); SoundEffectManager soundEffectManager = new SoundEffectManager<>(ResourceManager.getAllOfType(SoundEffectAsset.class)); diff --git a/app/src/main/java/org/toop/app/layer/layers/ConnectedLayer.java b/app/src/main/java/org/toop/app/layer/layers/ConnectedLayer.java index b255c3d..a5db92c 100644 --- a/app/src/main/java/org/toop/app/layer/layers/ConnectedLayer.java +++ b/app/src/main/java/org/toop/app/layer/layers/ConnectedLayer.java @@ -19,6 +19,7 @@ import org.toop.app.layer.containers.HorizontalContainer; import org.toop.app.layer.containers.VerticalContainer; import org.toop.app.layer.layers.game.TicTacToeLayer; import org.toop.framework.eventbus.EventFlow; +import org.toop.framework.networking.clients.TournamentNetworkingClient; import org.toop.framework.networking.events.NetworkEvents; import org.toop.local.AppContext; @@ -111,6 +112,7 @@ public final class ConnectedLayer extends Layer { new EventFlow() .addPostEvent( NetworkEvents.StartClient.class, + TournamentNetworkingClient.class, information.serverIP(), Integer.parseInt(information.serverPort())) .onResponse( diff --git a/framework/src/main/java/org/toop/framework/eventbus/GlobalEventBus.java b/framework/src/main/java/org/toop/framework/eventbus/GlobalEventBus.java index 6c8745f..ee6bdfb 100644 --- a/framework/src/main/java/org/toop/framework/eventbus/GlobalEventBus.java +++ b/framework/src/main/java/org/toop/framework/eventbus/GlobalEventBus.java @@ -134,7 +134,8 @@ public final class GlobalEventBus { for (Consumer listener : classListeners) { try { listener.accept(event); - } catch (Throwable ignored) { + } catch (Throwable e) { +// e.printStackTrace(); } } } @@ -146,7 +147,8 @@ public final class GlobalEventBus { for (Consumer listener : genericListeners) { try { listener.accept(event); - } catch (Throwable ignored) { + } catch (Throwable e) { + // e.printStackTrace(); } } } diff --git a/framework/src/main/java/org/toop/framework/networking/NetworkingClientEventListener.java b/framework/src/main/java/org/toop/framework/networking/NetworkingClientEventListener.java new file mode 100644 index 0000000..1086c8c --- /dev/null +++ b/framework/src/main/java/org/toop/framework/networking/NetworkingClientEventListener.java @@ -0,0 +1,126 @@ +package org.toop.framework.networking; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.toop.framework.eventbus.EventFlow; +import org.toop.framework.networking.interfaces.NetworkingClient; +import org.toop.framework.networking.events.NetworkEvents; +import org.toop.framework.networking.interfaces.NetworkingClientManager; + +public class NetworkingClientEventListener { + + private static final Logger logger = LogManager.getLogger(NetworkingClientEventListener.class); + private final NetworkingClientManager clientManager; + + /** Starts a connection manager, to manage, connections. */ + public NetworkingClientEventListener(NetworkingClientManager clientManager) { + this.clientManager = clientManager; + new EventFlow() + .listen(this::handleStartClient) + .listen(this::handleCommand) + .listen(this::handleSendLogin) + .listen(this::handleSendLogout) + .listen(this::handleSendGetPlayerlist) + .listen(this::handleSendGetGamelist) + .listen(this::handleSendSubscribe) + .listen(this::handleSendMove) + .listen(this::handleSendChallenge) + .listen(this::handleSendAcceptChallenge) + .listen(this::handleSendForfeit) + .listen(this::handleSendMessage) + .listen(this::handleSendHelp) + .listen(this::handleSendHelpForCommand) + .listen(this::handleCloseClient) + .listen(this::handleChangeClientHost) + .listen(this::handleGetAllConnections) + .listen(this::handleShutdownAll); + } + + void handleStartClient(NetworkEvents.StartClient event) { + long clientId = clientManager.startClient(event.networkingClientClass(), event.host(), event.port()).orElse(-1); + logger.info("Client {} started", clientId); + try { + new EventFlow() + .addPostEvent(new NetworkEvents.StartClientResponse(clientId, event.getIdentifier())) + .asyncPostEvent(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + void handleCommand(NetworkEvents.SendCommand event) { + String args = String.join(" ", event.args()); + logger.error(args); + clientManager.sendCommand(event.clientId(), args); + } + + void handleSendLogin(NetworkEvents.SendLogin event) { + logger.error("{}", event.username()); + clientManager.sendCommand(event.clientId(), String.format("LOGIN %s", event.username())); + } + + private void handleSendLogout(NetworkEvents.SendLogout event) { + clientManager.sendCommand(event.clientId(), "LOGOUT"); + } + + private void handleSendGetPlayerlist(NetworkEvents.SendGetPlayerlist event) { + clientManager.sendCommand(event.clientId(), "GET PLAYERLIST"); + } + + private void handleSendGetGamelist(NetworkEvents.SendGetGamelist event) { + clientManager.sendCommand(event.clientId(), "GET GAMELIST"); + } + + private void handleSendSubscribe(NetworkEvents.SendSubscribe event) { + clientManager.sendCommand(event.clientId(), String.format("SUBSCRIBE %s", event.gameType())); + } + + private void handleSendMove(NetworkEvents.SendMove event) { + clientManager.sendCommand(event.clientId(), String.format("MOVE %d", event.moveNumber())); + } + + private void handleSendChallenge(NetworkEvents.SendChallenge event) { + clientManager.sendCommand( + event.clientId(), + String.format("CHALLENGE %s %s", event.usernameToChallenge(), event.gameType())); + } + + private void handleSendAcceptChallenge(NetworkEvents.SendAcceptChallenge event) { + clientManager.sendCommand(event.clientId(), String.format("CHALLENGE ACCEPT %d", event.challengeId())); + } + + private void handleSendForfeit(NetworkEvents.SendForfeit event) { + clientManager.sendCommand(event.clientId(), "FORFEIT"); + } + + private void handleSendMessage(NetworkEvents.SendMessage event) { + clientManager.sendCommand(event.clientId(), String.format("MESSAGE %s", event.message())); + } + + private void handleSendHelp(NetworkEvents.SendHelp event) { + clientManager.sendCommand(event.clientId(), "HELP"); + } + + private void handleSendHelpForCommand(NetworkEvents.SendHelpForCommand event) { + clientManager.sendCommand(event.clientId(), String.format("HELP %s", event.command())); + } + + private void handleChangeClientHost(NetworkEvents.ChangeClientHost event) { +// NetworkingClient client = this.networkClients.get(event.clientId()); +// client.closeConnection(); +// startClientRequest(event.ip(), event.port(), event.clientId()); + } + + void handleCloseClient(NetworkEvents.CloseClient event) { + this.clientManager.closeClient(event.clientId()); + } + + void handleGetAllConnections(NetworkEvents.RequestsAllClients request) { +// List a = new ArrayList<>(this.networkClients.values()); +// request.future().complete(a); + } + + public void handleShutdownAll(NetworkEvents.ForceCloseAllClients request) { + // TODO + } +} diff --git a/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java b/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java index d42ed9b..99fee84 100644 --- a/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java +++ b/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java @@ -5,193 +5,57 @@ import java.util.concurrent.ConcurrentHashMap; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.toop.framework.SnowflakeGenerator; -import org.toop.framework.eventbus.EventFlow; -import org.toop.framework.networking.events.NetworkEvents; - -public class NetworkingClientManager { +import org.toop.framework.networking.interfaces.NetworkingClient; +public class NetworkingClientManager implements org.toop.framework.networking.interfaces.NetworkingClientManager { private static final Logger logger = LogManager.getLogger(NetworkingClientManager.class); + private final Map networkClients = new ConcurrentHashMap<>(); - /** Map of serverId -> Server instances */ - final Map networkClients = new ConcurrentHashMap<>(); + public NetworkingClientManager() {} - /** Starts a connection manager, to manage, connections. */ - public NetworkingClientManager() throws NetworkingInitializationException { + @Override + public OptionalLong startClient(Class networkingClientClass, String host, int port) { + long clientId = SnowflakeGenerator.nextId(); try { - new EventFlow() - .listen(this::handleStartClient) - .listen(this::handleCommand) - .listen(this::handleSendLogin) - .listen(this::handleSendLogout) - .listen(this::handleSendGetPlayerlist) - .listen(this::handleSendGetGamelist) - .listen(this::handleSendSubscribe) - .listen(this::handleSendMove) - .listen(this::handleSendChallenge) - .listen(this::handleSendAcceptChallenge) - .listen(this::handleSendForfeit) - .listen(this::handleSendMessage) - .listen(this::handleSendHelp) - .listen(this::handleSendHelpForCommand) - .listen(this::handleCloseClient) - .listen(this::handleChangeClientHost) - .listen(this::handleGetAllConnections) - .listen(this::handleShutdownAll); - logger.info("NetworkingClientManager initialized"); + NetworkingClient networkingClient = networkingClientClass + .getConstructor(long.class, String.class, int.class).newInstance(clientId, host, port); + this.networkClients.put(clientId, networkingClient); + logger.info("New client started successfully for {}:{}", host, port); } catch (Exception e) { - logger.error("Failed to initialize the client manager", e); - throw e; + logger.error(e); // TODO Better error handling + return OptionalLong.empty(); } + return OptionalLong.of(clientId); } - long startClientRequest(String ip, int port) { - long connectionId = SnowflakeGenerator.nextId(); - try { - NetworkingClient client = - new NetworkingClient( - () -> new NetworkingGameClientHandler(connectionId), - ip, - port, - connectionId); - client.setConnectionId(connectionId); - this.networkClients.put(connectionId, client); - logger.info("New client started successfully for {}:{}", ip, port); - } catch (Exception e) { - logger.error(e); - } - return connectionId; + @Override + public boolean sendCommand(long id, String command) { + logger.info("Sending command to client for {}:{}", id, command); + if (command.isEmpty()) { return false; } + + NetworkingClient client = this.networkClients.get(id); + if (client == null) { return false; } // TODO: Create client not found exceptions. + + String toSend = command.trim(); + + if (toSend.endsWith("\n")) { client.writeAndFlush(toSend); } + else { client.writeAndFlush(toSend + "\n"); } + + return true; } - private long startClientRequest(String ip, int port, long clientId) { - try { // With EventFlow - NetworkingClient client = - new NetworkingClient( - () -> new NetworkingGameClientHandler(clientId), ip, port, clientId); - client.setConnectionId(clientId); - this.networkClients.replace(clientId, client); - logger.info( - "New client started successfully for {}:{}, replaced: {}", ip, port, clientId); - } catch (Exception e) { - logger.error(e); - } - logger.info("Client {} started", clientId); - return clientId; + @Override + public boolean reconnect(long id) { + return false; // TODO } - void handleStartClient(NetworkEvents.StartClient event) { - long id = this.startClientRequest(event.ip(), event.port()); - new Thread( - () -> - new EventFlow() - .addPostEvent( - NetworkEvents.StartClientResponse.class, - id, - event.eventSnowflake()) - .asyncPostEvent()) - .start(); + @Override + public boolean changeAddress(long id, String host, int port) { + return false; // TODO } - void handleCommand( - NetworkEvents.SendCommand - event) { // TODO: Move this to ServerConnection class, keep it internal. - NetworkingClient client = this.networkClients.get(event.clientId()); - String args = String.join(" ", event.args()); - sendCommand(client, args); - } - - void handleSendLogin(NetworkEvents.SendLogin event) { - NetworkingClient client = this.networkClients.get(event.clientId()); - sendCommand(client, String.format("LOGIN %s", event.username())); - } - - private void handleSendLogout(NetworkEvents.SendLogout event) { - NetworkingClient client = this.networkClients.get(event.clientId()); - sendCommand(client, "LOGOUT"); - } - - private void handleSendGetPlayerlist(NetworkEvents.SendGetPlayerlist event) { - NetworkingClient client = this.networkClients.get(event.clientId()); - sendCommand(client, "GET PLAYERLIST"); - } - - private void handleSendGetGamelist(NetworkEvents.SendGetGamelist event) { - NetworkingClient client = this.networkClients.get(event.clientId()); - sendCommand(client, "GET GAMELIST"); - } - - private void handleSendSubscribe(NetworkEvents.SendSubscribe event) { - NetworkingClient client = this.networkClients.get(event.clientId()); - sendCommand(client, String.format("SUBSCRIBE %s", event.gameType())); - } - - private void handleSendMove(NetworkEvents.SendMove event) { - NetworkingClient client = this.networkClients.get(event.clientId()); - sendCommand(client, String.format("MOVE %d", event.moveNumber())); - } - - private void handleSendChallenge(NetworkEvents.SendChallenge event) { - NetworkingClient client = this.networkClients.get(event.clientId()); - sendCommand( - client, - String.format("CHALLENGE %s %s", event.usernameToChallenge(), event.gameType())); - } - - private void handleSendAcceptChallenge(NetworkEvents.SendAcceptChallenge event) { - NetworkingClient client = this.networkClients.get(event.clientId()); - sendCommand(client, String.format("CHALLENGE ACCEPT %d", event.challengeId())); - } - - private void handleSendForfeit(NetworkEvents.SendForfeit event) { - NetworkingClient client = this.networkClients.get(event.clientId()); - sendCommand(client, "FORFEIT"); - } - - private void handleSendMessage(NetworkEvents.SendMessage event) { - NetworkingClient client = this.networkClients.get(event.clientId()); - sendCommand(client, String.format("MESSAGE %s", event.message())); - } - - private void handleSendHelp(NetworkEvents.SendHelp event) { - NetworkingClient client = this.networkClients.get(event.clientId()); - sendCommand(client, "HELP"); - } - - private void handleSendHelpForCommand(NetworkEvents.SendHelpForCommand event) { - NetworkingClient client = this.networkClients.get(event.clientId()); - sendCommand(client, String.format("HELP %s", event.command())); - } - - private void sendCommand(NetworkingClient client, String command) { - logger.info( - "Preparing to send command: {} to server: {}:{}. clientId: {}", - command.trim(), - client.getHost(), - client.getPort(), - client.getId()); - client.writeAndFlushnl(command); - } - - private void handleChangeClientHost(NetworkEvents.ChangeClientHost event) { - NetworkingClient client = this.networkClients.get(event.clientId()); - client.closeConnection(); - startClientRequest(event.ip(), event.port(), event.clientId()); - } - - void handleCloseClient(NetworkEvents.CloseClient event) { - NetworkingClient client = this.networkClients.get(event.clientId()); - client.closeConnection(); - this.networkClients.remove(event.clientId()); - logger.info("Client {} closed successfully.", event.clientId()); - } - - void handleGetAllConnections(NetworkEvents.RequestsAllClients request) { - List a = new ArrayList<>(this.networkClients.values()); - request.future().complete(a); - } - - public void handleShutdownAll(NetworkEvents.ForceCloseAllClients request) { - this.networkClients.values().forEach(NetworkingClient::closeConnection); - this.networkClients.clear(); - logger.info("All servers shut down"); + @Override + public boolean closeClient(long id) { + return false; // TODO } } diff --git a/framework/src/main/java/org/toop/framework/networking/NetworkingClient.java b/framework/src/main/java/org/toop/framework/networking/clients/TournamentNetworkingClient.java similarity index 59% rename from framework/src/main/java/org/toop/framework/networking/NetworkingClient.java rename to framework/src/main/java/org/toop/framework/networking/clients/TournamentNetworkingClient.java index fd99bf7..c678964 100644 --- a/framework/src/main/java/org/toop/framework/networking/NetworkingClient.java +++ b/framework/src/main/java/org/toop/framework/networking/clients/TournamentNetworkingClient.java @@ -1,4 +1,4 @@ -package org.toop.framework.networking; +package org.toop.framework.networking.clients; import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; @@ -9,27 +9,16 @@ import io.netty.handler.codec.LineBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.util.CharsetUtil; -import java.util.function.Supplier; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.toop.framework.eventbus.EventFlow; -import org.toop.framework.networking.events.NetworkEvents; +import org.toop.framework.networking.handlers.NetworkingGameClientHandler; +import org.toop.framework.networking.interfaces.NetworkingClient; -public class NetworkingClient { - private static final Logger logger = LogManager.getLogger(NetworkingClient.class); - - private long connectionId; - private String host; - private int port; +public class TournamentNetworkingClient implements NetworkingClient { + private static final Logger logger = LogManager.getLogger(TournamentNetworkingClient.class); private Channel channel; - private NetworkingGameClientHandler handler; - public NetworkingClient( - Supplier handlerFactory, - String host, - int port, - long connectionId) { - this.connectionId = connectionId; + public TournamentNetworkingClient(long clientId, String host, int port) { try { Bootstrap bootstrap = new Bootstrap(); EventLoopGroup workerGroup = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory()); @@ -40,7 +29,7 @@ public class NetworkingClient { new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) { - handler = handlerFactory.get(); + NetworkingGameClientHandler handler = new NetworkingGameClientHandler(clientId); ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new LineBasedFrameDecoder(1024)); // split at \n @@ -52,53 +41,28 @@ public class NetworkingClient { }); ChannelFuture channelFuture = bootstrap.connect(host, port).sync(); this.channel = channelFuture.channel(); - this.host = host; - this.port = port; } catch (Exception e) { logger.error("Failed to create networking client instance", e); } } - public NetworkingGameClientHandler getHandler() { - return this.handler; - } - - public String getHost() { - return this.host; - } - - public int getPort() { - return this.port; - } - - public void setConnectionId(long connectionId) { - this.connectionId = connectionId; - } - - public boolean isChannelActive() { + @Override + public boolean isActive() { return this.channel != null && this.channel.isActive(); } + @Override public void writeAndFlush(String msg) { String literalMsg = msg.replace("\n", "\\n").replace("\r", "\\r"); - if (isChannelActive()) { + if (isActive()) { this.channel.writeAndFlush(msg); - logger.info( - "Connection {} sent message: '{}' ", this.channel.remoteAddress(), literalMsg); + logger.info("Connection {} sent message: '{}' ", this.channel.remoteAddress(), literalMsg); } else { logger.warn("Cannot send message: '{}', connection inactive. ", literalMsg); } } - public void writeAndFlushnl(String msg) { - if (isChannelActive()) { - this.channel.writeAndFlush(msg + "\r\n"); - logger.info("Connection {} sent message: '{}'", this.channel.remoteAddress(), msg); - } else { - logger.warn("Cannot send message: '{}', connection inactive.", msg); - } - } - + @Override public void closeConnection() { if (this.channel != null && this.channel.isActive()) { this.channel @@ -109,11 +73,6 @@ public class NetworkingClient { logger.info( "Connection {} closed successfully", this.channel.remoteAddress()); - new EventFlow() - .addPostEvent( - new NetworkEvents.ClosedConnection( - this.connectionId)) - .asyncPostEvent(); } else { logger.error( "Error closing connection {}. Error: {}", @@ -123,8 +82,4 @@ public class NetworkingClient { }); } } - - public long getId() { - return this.connectionId; - } } diff --git a/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java b/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java index 6574016..4e983b6 100644 --- a/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java +++ b/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java @@ -8,7 +8,7 @@ import org.toop.framework.eventbus.events.ResponseToUniqueEvent; import org.toop.framework.eventbus.events.UniqueEvent; import org.toop.framework.eventbus.events.EventsBase; import org.toop.annotations.AutoResponseResult; -import org.toop.framework.networking.NetworkingClient; +import org.toop.framework.networking.interfaces.NetworkingClient; /** * A collection of networking-related event records for use with the {@link @@ -106,11 +106,16 @@ public class NetworkEvents extends EventsBase { * *

Carries IP, port, and a unique event ID for correlation with responses. * - * @param ip Server IP address. + * @param networkingClientClass The type of networking client to create. + * @param host Server IP address. * @param port Server port. * @param eventSnowflake Unique event identifier for correlation. */ - public record StartClient(String ip, int port, long eventSnowflake) implements UniqueEvent {} + public record StartClient( + Class networkingClientClass, + String host, + int port, + long identifier) implements UniqueEvent {} /** * Response confirming a client was started. diff --git a/framework/src/main/java/org/toop/framework/networking/NetworkingGameClientHandler.java b/framework/src/main/java/org/toop/framework/networking/handlers/NetworkingGameClientHandler.java similarity index 99% rename from framework/src/main/java/org/toop/framework/networking/NetworkingGameClientHandler.java rename to framework/src/main/java/org/toop/framework/networking/handlers/NetworkingGameClientHandler.java index 388e420..25f9df9 100644 --- a/framework/src/main/java/org/toop/framework/networking/NetworkingGameClientHandler.java +++ b/framework/src/main/java/org/toop/framework/networking/handlers/NetworkingGameClientHandler.java @@ -1,4 +1,4 @@ -package org.toop.framework.networking; +package org.toop.framework.networking.handlers; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; diff --git a/framework/src/main/java/org/toop/framework/networking/handlers/NetworkingTicTacToeClientHandler.java b/framework/src/main/java/org/toop/framework/networking/handlers/NetworkingTicTacToeClientHandler.java deleted file mode 100644 index e263072..0000000 --- a/framework/src/main/java/org/toop/framework/networking/handlers/NetworkingTicTacToeClientHandler.java +++ /dev/null @@ -1,12 +0,0 @@ -//package org.toop.frontend.networking.handlers; -// -//import io.netty.channel.ChannelHandlerContext; -//import org.apache.logging.log4j.LogManager; -//import org.apache.logging.log4j.Logger; -//import org.toop.frontend.networking.NetworkingGameClientHandler; -// -//public class NetworkingTicTacToeClientHandler extends NetworkingGameClientHandler { -// static final Logger logger = LogManager.getLogger(NetworkingTicTacToeClientHandler.class); -// -// -//} diff --git a/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClient.java b/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClient.java new file mode 100644 index 0000000..a00caac --- /dev/null +++ b/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClient.java @@ -0,0 +1,7 @@ +package org.toop.framework.networking.interfaces; + +public interface NetworkingClient { + boolean isActive(); + void writeAndFlush(String msg); + void closeConnection(); +} diff --git a/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClientManager.java b/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClientManager.java new file mode 100644 index 0000000..d6869fc --- /dev/null +++ b/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClientManager.java @@ -0,0 +1,11 @@ +package org.toop.framework.networking.interfaces; + +import java.util.OptionalLong; + +public interface NetworkingClientManager { + OptionalLong startClient(Class networkingClientClass, String host, int port); + boolean sendCommand(long id, String command); + boolean reconnect(long id); + boolean changeAddress(long id, String host, int port); + boolean closeClient(long id); +} diff --git a/framework/src/main/java/org/toop/framework/networking/types/ServerCommand.java b/framework/src/main/java/org/toop/framework/networking/types/ServerCommand.java new file mode 100644 index 0000000..e11bb61 --- /dev/null +++ b/framework/src/main/java/org/toop/framework/networking/types/ServerCommand.java @@ -0,0 +1,3 @@ +package org.toop.framework.networking.types; + +public record ServerCommand(long clientId, String command) {} diff --git a/framework/src/main/java/org/toop/framework/networking/types/ServerMessage.java b/framework/src/main/java/org/toop/framework/networking/types/ServerMessage.java new file mode 100644 index 0000000..606607d --- /dev/null +++ b/framework/src/main/java/org/toop/framework/networking/types/ServerMessage.java @@ -0,0 +1,3 @@ +package org.toop.framework.networking.types; + +public record ServerMessage(String message) {} From d0676d9363696430adcc4dd5eb2fb7e3c8cf64d9 Mon Sep 17 00:00:00 2001 From: lieght <49651652+BAFGdeJong@users.noreply.github.com> Date: Tue, 14 Oct 2025 23:27:52 +0200 Subject: [PATCH 04/14] Forgot to remove --- app/src/main/java/org/toop/Main.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/org/toop/Main.java b/app/src/main/java/org/toop/Main.java index efe348d..c929d5d 100644 --- a/app/src/main/java/org/toop/Main.java +++ b/app/src/main/java/org/toop/Main.java @@ -10,9 +10,8 @@ import org.toop.framework.resource.resources.MusicAsset; import org.toop.framework.resource.resources.SoundEffectAsset; public final class Main { - static void main(String[] args) throws InterruptedException { + static void main(String[] args) { initSystems(); - Thread.sleep(200); App.run(args); } From 444a81abc37fab287fed8218497980a101da816b Mon Sep 17 00:00:00 2001 From: lieght <49651652+BAFGdeJong@users.noreply.github.com> Date: Tue, 14 Oct 2025 23:44:45 +0200 Subject: [PATCH 05/14] Improved API for dependency injection --- .../org/toop/app/layer/layers/ConnectedLayer.java | 2 +- .../networking/NetworkingClientEventListener.java | 4 ++-- .../framework/networking/NetworkingClientManager.java | 11 ++++------- .../clients/TournamentNetworkingClient.java | 7 ++++++- .../framework/networking/events/NetworkEvents.java | 4 ++-- .../networking/interfaces/NetworkingClient.java | 1 + .../interfaces/NetworkingClientManager.java | 2 +- 7 files changed, 17 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/org/toop/app/layer/layers/ConnectedLayer.java b/app/src/main/java/org/toop/app/layer/layers/ConnectedLayer.java index a5db92c..c631932 100644 --- a/app/src/main/java/org/toop/app/layer/layers/ConnectedLayer.java +++ b/app/src/main/java/org/toop/app/layer/layers/ConnectedLayer.java @@ -112,7 +112,7 @@ public final class ConnectedLayer extends Layer { new EventFlow() .addPostEvent( NetworkEvents.StartClient.class, - TournamentNetworkingClient.class, + new TournamentNetworkingClient(), information.serverIP(), Integer.parseInt(information.serverPort())) .onResponse( diff --git a/framework/src/main/java/org/toop/framework/networking/NetworkingClientEventListener.java b/framework/src/main/java/org/toop/framework/networking/NetworkingClientEventListener.java index 1086c8c..f45c3dc 100644 --- a/framework/src/main/java/org/toop/framework/networking/NetworkingClientEventListener.java +++ b/framework/src/main/java/org/toop/framework/networking/NetworkingClientEventListener.java @@ -36,8 +36,8 @@ public class NetworkingClientEventListener { .listen(this::handleShutdownAll); } - void handleStartClient(NetworkEvents.StartClient event) { - long clientId = clientManager.startClient(event.networkingClientClass(), event.host(), event.port()).orElse(-1); + void handleStartClient(NetworkEvents.StartClient event) { + long clientId = clientManager.startClient(SnowflakeGenerator.nextId(), event.networkingClientClass(), event.host(), event.port()).orElse(-1); logger.info("Client {} started", clientId); try { new EventFlow() diff --git a/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java b/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java index 99fee84..cdd3a82 100644 --- a/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java +++ b/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java @@ -4,7 +4,6 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.toop.framework.SnowflakeGenerator; import org.toop.framework.networking.interfaces.NetworkingClient; public class NetworkingClientManager implements org.toop.framework.networking.interfaces.NetworkingClientManager { @@ -14,18 +13,16 @@ public class NetworkingClientManager implements org.toop.framework.networking.in public NetworkingClientManager() {} @Override - public OptionalLong startClient(Class networkingClientClass, String host, int port) { - long clientId = SnowflakeGenerator.nextId(); + public OptionalLong startClient(long id, NetworkingClient networkingClient, String host, int port) { try { - NetworkingClient networkingClient = networkingClientClass - .getConstructor(long.class, String.class, int.class).newInstance(clientId, host, port); - this.networkClients.put(clientId, networkingClient); + networkingClient.connect(id, host, port); + this.networkClients.put(id, networkingClient); logger.info("New client started successfully for {}:{}", host, port); } catch (Exception e) { logger.error(e); // TODO Better error handling return OptionalLong.empty(); } - return OptionalLong.of(clientId); + return OptionalLong.of(id); } @Override diff --git a/framework/src/main/java/org/toop/framework/networking/clients/TournamentNetworkingClient.java b/framework/src/main/java/org/toop/framework/networking/clients/TournamentNetworkingClient.java index c678964..76d26d6 100644 --- a/framework/src/main/java/org/toop/framework/networking/clients/TournamentNetworkingClient.java +++ b/framework/src/main/java/org/toop/framework/networking/clients/TournamentNetworkingClient.java @@ -18,7 +18,10 @@ public class TournamentNetworkingClient implements NetworkingClient { private static final Logger logger = LogManager.getLogger(TournamentNetworkingClient.class); private Channel channel; - public TournamentNetworkingClient(long clientId, String host, int port) { + public TournamentNetworkingClient() {} + + @Override + public boolean connect(long clientId, String host, int port) { try { Bootstrap bootstrap = new Bootstrap(); EventLoopGroup workerGroup = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory()); @@ -43,7 +46,9 @@ public class TournamentNetworkingClient implements NetworkingClient { this.channel = channelFuture.channel(); } catch (Exception e) { logger.error("Failed to create networking client instance", e); + return false; } + return true; } @Override diff --git a/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java b/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java index 4e983b6..63fb7fc 100644 --- a/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java +++ b/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java @@ -111,8 +111,8 @@ public class NetworkEvents extends EventsBase { * @param port Server port. * @param eventSnowflake Unique event identifier for correlation. */ - public record StartClient( - Class networkingClientClass, + public record StartClient( + NetworkingClient networkingClientClass, String host, int port, long identifier) implements UniqueEvent {} diff --git a/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClient.java b/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClient.java index a00caac..6cca1e9 100644 --- a/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClient.java +++ b/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClient.java @@ -1,6 +1,7 @@ package org.toop.framework.networking.interfaces; public interface NetworkingClient { + boolean connect(long clientId, String host, int port); boolean isActive(); void writeAndFlush(String msg); void closeConnection(); diff --git a/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClientManager.java b/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClientManager.java index d6869fc..0a2d70d 100644 --- a/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClientManager.java +++ b/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClientManager.java @@ -3,7 +3,7 @@ package org.toop.framework.networking.interfaces; import java.util.OptionalLong; public interface NetworkingClientManager { - OptionalLong startClient(Class networkingClientClass, String host, int port); + OptionalLong startClient(long id, NetworkingClient networkingClientClass, String host, int port); boolean sendCommand(long id, String command); boolean reconnect(long id); boolean changeAddress(long id, String host, int port); From 14e6785c6f0b2698ffda76267bfed6efb6651b6f Mon Sep 17 00:00:00 2001 From: lieght <49651652+BAFGdeJong@users.noreply.github.com> Date: Wed, 15 Oct 2025 00:01:41 +0200 Subject: [PATCH 06/14] Some better docs. --- .../eventbus/events/ResponseToUniqueEvent.java | 11 +++++++++++ .../toop/framework/eventbus/events/UniqueEvent.java | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/framework/src/main/java/org/toop/framework/eventbus/events/ResponseToUniqueEvent.java b/framework/src/main/java/org/toop/framework/eventbus/events/ResponseToUniqueEvent.java index 30328ce..6f65378 100644 --- a/framework/src/main/java/org/toop/framework/eventbus/events/ResponseToUniqueEvent.java +++ b/framework/src/main/java/org/toop/framework/eventbus/events/ResponseToUniqueEvent.java @@ -4,6 +4,17 @@ import java.lang.reflect.RecordComponent; import java.util.HashMap; import java.util.Map; +/** + * MUST HAVE long identifier at the end. + * e.g. + * + *

{@code
+ * public record uniqueEventResponse(String content, long identifier) implements ResponseToUniqueEvent {};
+ * public record uniqueEventResponse(long identifier) implements ResponseToUniqueEvent {};
+ * public record uniqueEventResponse(String content, int number, long identifier) implements ResponseToUniqueEvent {};
+ * }
+ * + */ public interface ResponseToUniqueEvent extends UniqueEvent { default Map result() { Map map = new HashMap<>(); diff --git a/framework/src/main/java/org/toop/framework/eventbus/events/UniqueEvent.java b/framework/src/main/java/org/toop/framework/eventbus/events/UniqueEvent.java index bb68f61..6042c83 100644 --- a/framework/src/main/java/org/toop/framework/eventbus/events/UniqueEvent.java +++ b/framework/src/main/java/org/toop/framework/eventbus/events/UniqueEvent.java @@ -1,5 +1,16 @@ package org.toop.framework.eventbus.events; +/** + * MUST HAVE long identifier at the end. + * e.g. + * + *
{@code
+ * public record uniqueEvent(String content, long identifier) implements UniqueEvent {};
+ * public record uniqueEvent(long identifier) implements UniqueEvent {};
+ * public record uniqueEvent(String content, int number, long identifier) implements UniqueEvent {};
+ * }
+ * + */ public interface UniqueEvent extends EventType { default long getIdentifier() { try { From dc81d9c3ca2dbcc69fe8417d70a08ab871c39369 Mon Sep 17 00:00:00 2001 From: lieght <49651652+BAFGdeJong@users.noreply.github.com> Date: Wed, 15 Oct 2025 02:58:14 +0200 Subject: [PATCH 07/14] Added exceptions. Added reconnect attempts and changeable address --- .idea/inspectionProfiles/Project_Default.xml | 2 +- .../NetworkingClientEventListener.java | 85 +++++---- .../networking/NetworkingClientManager.java | 173 ++++++++++++++++-- .../clients/TournamentNetworkingClient.java | 16 +- .../networking/events/NetworkEvents.java | 81 ++++---- .../exceptions/ClientNotFoundException.java | 25 +++ .../exceptions/CouldNotConnectException.java | 21 +++ .../NetworkingInitializationException.java | 2 +- .../interfaces/NetworkingClient.java | 7 +- .../interfaces/NetworkingClientManager.java | 21 ++- .../networking/types/NetworkingReconnect.java | 5 + 11 files changed, 333 insertions(+), 105 deletions(-) create mode 100644 framework/src/main/java/org/toop/framework/networking/exceptions/ClientNotFoundException.java create mode 100644 framework/src/main/java/org/toop/framework/networking/exceptions/CouldNotConnectException.java rename framework/src/main/java/org/toop/framework/networking/{ => exceptions}/NetworkingInitializationException.java (79%) create mode 100644 framework/src/main/java/org/toop/framework/networking/types/NetworkingReconnect.java diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index e917175..655cfae 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -2,7 +2,7 @@ diff --git a/framework/src/main/java/org/toop/framework/networking/NetworkingClientEventListener.java b/framework/src/main/java/org/toop/framework/networking/NetworkingClientEventListener.java index f45c3dc..9b8fb14 100644 --- a/framework/src/main/java/org/toop/framework/networking/NetworkingClientEventListener.java +++ b/framework/src/main/java/org/toop/framework/networking/NetworkingClientEventListener.java @@ -2,9 +2,10 @@ package org.toop.framework.networking; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.toop.framework.SnowflakeGenerator; import org.toop.framework.eventbus.EventFlow; -import org.toop.framework.networking.interfaces.NetworkingClient; import org.toop.framework.networking.events.NetworkEvents; +import org.toop.framework.networking.exceptions.ClientNotFoundException; import org.toop.framework.networking.interfaces.NetworkingClientManager; public class NetworkingClientEventListener { @@ -31,93 +32,111 @@ public class NetworkingClientEventListener { .listen(this::handleSendHelp) .listen(this::handleSendHelpForCommand) .listen(this::handleCloseClient) - .listen(this::handleChangeClientHost) + .listen(this::handleReconnect) + .listen(this::handleChangeAddress) .listen(this::handleGetAllConnections) .listen(this::handleShutdownAll); } void handleStartClient(NetworkEvents.StartClient event) { - long clientId = clientManager.startClient(SnowflakeGenerator.nextId(), event.networkingClientClass(), event.host(), event.port()).orElse(-1); - logger.info("Client {} started", clientId); + clientManager.startClient( + event.identifier(), + event.networkingClientClass(), + event.host(), + event.port(), + event.networkingReconnect() + ); + } + + private void sendCommand(long clientId, String command) { try { - new EventFlow() - .addPostEvent(new NetworkEvents.StartClientResponse(clientId, event.getIdentifier())) - .asyncPostEvent(); - } catch (Exception e) { - e.printStackTrace(); + clientManager.sendCommand(clientId, command); + } catch (ClientNotFoundException e) { + logger.error(e); } } - void handleCommand(NetworkEvents.SendCommand event) { + private void handleCommand(NetworkEvents.SendCommand event) { String args = String.join(" ", event.args()); - logger.error(args); - clientManager.sendCommand(event.clientId(), args); + sendCommand(event.clientId(), args); } - void handleSendLogin(NetworkEvents.SendLogin event) { - logger.error("{}", event.username()); - clientManager.sendCommand(event.clientId(), String.format("LOGIN %s", event.username())); + private void handleSendLogin(NetworkEvents.SendLogin event) { + sendCommand(event.clientId(), String.format("LOGIN %s", event.username())); } private void handleSendLogout(NetworkEvents.SendLogout event) { - clientManager.sendCommand(event.clientId(), "LOGOUT"); + sendCommand(event.clientId(), "LOGOUT"); } private void handleSendGetPlayerlist(NetworkEvents.SendGetPlayerlist event) { - clientManager.sendCommand(event.clientId(), "GET PLAYERLIST"); + sendCommand(event.clientId(), "GET PLAYERLIST"); } private void handleSendGetGamelist(NetworkEvents.SendGetGamelist event) { - clientManager.sendCommand(event.clientId(), "GET GAMELIST"); + sendCommand(event.clientId(), "GET GAMELIST"); } private void handleSendSubscribe(NetworkEvents.SendSubscribe event) { - clientManager.sendCommand(event.clientId(), String.format("SUBSCRIBE %s", event.gameType())); + sendCommand(event.clientId(), String.format("SUBSCRIBE %s", event.gameType())); } private void handleSendMove(NetworkEvents.SendMove event) { - clientManager.sendCommand(event.clientId(), String.format("MOVE %d", event.moveNumber())); + sendCommand(event.clientId(), String.format("MOVE %d", event.moveNumber())); } private void handleSendChallenge(NetworkEvents.SendChallenge event) { - clientManager.sendCommand( - event.clientId(), - String.format("CHALLENGE %s %s", event.usernameToChallenge(), event.gameType())); + sendCommand(event.clientId(), String.format("CHALLENGE %s %s", event.usernameToChallenge(), event.gameType())); } private void handleSendAcceptChallenge(NetworkEvents.SendAcceptChallenge event) { - clientManager.sendCommand(event.clientId(), String.format("CHALLENGE ACCEPT %d", event.challengeId())); + sendCommand(event.clientId(), String.format("CHALLENGE ACCEPT %d", event.challengeId())); } private void handleSendForfeit(NetworkEvents.SendForfeit event) { - clientManager.sendCommand(event.clientId(), "FORFEIT"); + sendCommand(event.clientId(), "FORFEIT"); } private void handleSendMessage(NetworkEvents.SendMessage event) { - clientManager.sendCommand(event.clientId(), String.format("MESSAGE %s", event.message())); + sendCommand(event.clientId(), String.format("MESSAGE %s", event.message())); } private void handleSendHelp(NetworkEvents.SendHelp event) { - clientManager.sendCommand(event.clientId(), "HELP"); + sendCommand(event.clientId(), "HELP"); } private void handleSendHelpForCommand(NetworkEvents.SendHelpForCommand event) { - clientManager.sendCommand(event.clientId(), String.format("HELP %s", event.command())); + sendCommand(event.clientId(), String.format("HELP %s", event.command())); } - private void handleChangeClientHost(NetworkEvents.ChangeClientHost event) { -// NetworkingClient client = this.networkClients.get(event.clientId()); -// client.closeConnection(); -// startClientRequest(event.ip(), event.port(), event.clientId()); + private void handleReconnect(NetworkEvents.Reconnect event) { + try { + clientManager.reconnect(event.clientId(), event.networkingReconnect()); + } catch (ClientNotFoundException e) { + logger.error(e); + } + } + + private void handleChangeAddress(NetworkEvents.ChangeAddress event) { + try { + clientManager.changeAddress(event.clientId(), event.ip(), event.port(), event.networkingReconnect()); + } catch (ClientNotFoundException e) { + logger.error(e); + } } void handleCloseClient(NetworkEvents.CloseClient event) { - this.clientManager.closeClient(event.clientId()); + try { + this.clientManager.closeClient(event.clientId()); + } catch (ClientNotFoundException e) { + logger.error(e); + } } void handleGetAllConnections(NetworkEvents.RequestsAllClients request) { // List a = new ArrayList<>(this.networkClients.values()); // request.future().complete(a); + // TODO } public void handleShutdownAll(NetworkEvents.ForceCloseAllClients request) { diff --git a/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java b/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java index cdd3a82..64d8082 100644 --- a/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java +++ b/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java @@ -1,10 +1,20 @@ package org.toop.framework.networking; +import java.net.InetSocketAddress; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.toop.framework.eventbus.EventFlow; +import org.toop.framework.networking.events.NetworkEvents; +import org.toop.framework.networking.exceptions.ClientNotFoundException; +import org.toop.framework.networking.exceptions.CouldNotConnectException; import org.toop.framework.networking.interfaces.NetworkingClient; +import org.toop.framework.networking.types.NetworkingReconnect; public class NetworkingClientManager implements org.toop.framework.networking.interfaces.NetworkingClientManager { private static final Logger logger = LogManager.getLogger(NetworkingClientManager.class); @@ -13,46 +23,169 @@ public class NetworkingClientManager implements org.toop.framework.networking.in public NetworkingClientManager() {} @Override - public OptionalLong startClient(long id, NetworkingClient networkingClient, String host, int port) { - try { - networkingClient.connect(id, host, port); - this.networkClients.put(id, networkingClient); - logger.info("New client started successfully for {}:{}", host, port); - } catch (Exception e) { - logger.error(e); // TODO Better error handling - return OptionalLong.empty(); - } - return OptionalLong.of(id); + public void startClient( + long id, + NetworkingClient networkingClient, + String host, int port, + NetworkingReconnect networkingReconnect) { + + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + + Runnable connectTask = new Runnable() { + int attempts = 0; + + @Override + public void run() { + try { + networkingClient.connect(id, host, port); + networkClients.put(id, networkingClient); + logger.info("New client started successfully for {}:{}", host, port); + new EventFlow().addPostEvent(new NetworkEvents.StartClientResponse(id, id)).postEvent(); + scheduler.shutdown(); + } catch (CouldNotConnectException e) { + attempts++; + if (attempts < networkingReconnect.reconnectAttempts()) { + logger.warn("Could not connect to {}:{}. Retrying in {} {}", + host, port, networkingReconnect.timeout(), networkingReconnect.timeUnit()); + scheduler.schedule(this, networkingReconnect.timeout(), networkingReconnect.timeUnit()); + } else { + logger.error("Failed to start client for {}:{} after {} attempts", host, port, attempts); + new EventFlow().addPostEvent(new NetworkEvents.StartClientResponse(-1, id)).postEvent(); + scheduler.shutdown(); + } + } catch (Exception e) { + logger.error("Unexpected exception during startClient", e); + scheduler.shutdown(); + } + } + }; + + scheduler.schedule(connectTask, 0, TimeUnit.MILLISECONDS); } @Override - public boolean sendCommand(long id, String command) { + public void sendCommand(long id, String command) throws ClientNotFoundException { logger.info("Sending command to client for {}:{}", id, command); - if (command.isEmpty()) { return false; } + if (command.isEmpty()) { + IllegalArgumentException e = new IllegalArgumentException("command is empty"); + logger.error("Invalid command received", e); + return; + } NetworkingClient client = this.networkClients.get(id); - if (client == null) { return false; } // TODO: Create client not found exceptions. + if (client == null) { + throw new ClientNotFoundException(id); + } String toSend = command.trim(); if (toSend.endsWith("\n")) { client.writeAndFlush(toSend); } else { client.writeAndFlush(toSend + "\n"); } - return true; } @Override - public boolean reconnect(long id) { - return false; // TODO + public void reconnect(long id, NetworkingReconnect networkingReconnect) throws ClientNotFoundException { + NetworkingClient client = this.networkClients.get(id); + if (client == null) { + throw new ClientNotFoundException(id); + } + + InetSocketAddress address = client.getAddress(); + + if (client.isActive()) { + client.closeConnection(); + } + + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + + Runnable connectTask = new Runnable() { + int attempts = 0; + + @Override + public void run() { + try { + client.connect(id, address.getHostName(), address.getPort()); + networkClients.put(id, client); + logger.info("Client {} reconnected to {}:{}", id, address.getHostName(), address.getPort()); + new EventFlow().addPostEvent(new NetworkEvents.ReconnectResponse(true, id)).postEvent().postEvent(); + scheduler.shutdown(); + } catch (CouldNotConnectException e) { + attempts++; + if (attempts < networkingReconnect.reconnectAttempts()) { + logger.warn("Could not reconnect client {} to {}:{}. Retrying in {} {}", + id, address.getHostName(), address.getPort(), networkingReconnect.timeout(), networkingReconnect.timeUnit()); + scheduler.schedule(this, networkingReconnect.timeout(), networkingReconnect.timeUnit()); + } else { + logger.error("Failed to reconnect client {} to {}:{} after {} attempts", id, address.getHostName(), address.getPort(), attempts); + new EventFlow().addPostEvent(new NetworkEvents.ReconnectResponse(false, id)).postEvent().postEvent(); + scheduler.shutdown(); + } + } catch (Exception e) { + logger.error("Unexpected exception during reconnect for client {}", id, e); + new EventFlow().addPostEvent(new NetworkEvents.ReconnectResponse(false, id)).postEvent().postEvent(); + scheduler.shutdown(); + } + } + }; + + scheduler.schedule(connectTask, 0, TimeUnit.MILLISECONDS); } @Override - public boolean changeAddress(long id, String host, int port) { - return false; // TODO + public void changeAddress(long id, String host, int port, NetworkingReconnect networkingReconnect) throws ClientNotFoundException { + NetworkingClient client = this.networkClients.get(id); + if (client == null) { + throw new ClientNotFoundException(id); + } + + if (client.isActive()) { + client.closeConnection(); + } + + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + + Runnable connectTask = new Runnable() { + int attempts = 0; + + @Override + public void run() { + try { + client.connect(id, host, port); + networkClients.put(id, client); + logger.info("Client {} changed address to {}:{}", id, host, port); + new EventFlow().addPostEvent(new NetworkEvents.ChangeAddressResponse(true, id)).postEvent().postEvent(); + scheduler.shutdown(); + } catch (CouldNotConnectException e) { + attempts++; + if (attempts < networkingReconnect.reconnectAttempts()) { + logger.warn("Could not connect client {} to {}:{}. Retrying in {} {}", + id, host, port, networkingReconnect.timeout(), networkingReconnect.timeUnit()); + scheduler.schedule(this, networkingReconnect.timeout(), networkingReconnect.timeUnit()); + } else { + logger.error("Failed to connect client {} to {}:{} after {} attempts", id, host, port, attempts); + new EventFlow().addPostEvent(new NetworkEvents.ChangeAddressResponse(false, id)).postEvent().postEvent(); + scheduler.shutdown(); + } + } catch (Exception e) { + logger.error("Unexpected exception during changeAddress for client {}", id, e); + new EventFlow().addPostEvent(new NetworkEvents.ChangeAddressResponse(false, id)).postEvent().postEvent(); + scheduler.shutdown(); + } + } + }; + + scheduler.schedule(connectTask, 0, TimeUnit.MILLISECONDS); } @Override - public boolean closeClient(long id) { - return false; // TODO + public void closeClient(long id) throws ClientNotFoundException { + NetworkingClient client = this.networkClients.get(id); + if (client == null) { + throw new ClientNotFoundException(id); + } + + client.closeConnection(); + } } diff --git a/framework/src/main/java/org/toop/framework/networking/clients/TournamentNetworkingClient.java b/framework/src/main/java/org/toop/framework/networking/clients/TournamentNetworkingClient.java index 76d26d6..74871e4 100644 --- a/framework/src/main/java/org/toop/framework/networking/clients/TournamentNetworkingClient.java +++ b/framework/src/main/java/org/toop/framework/networking/clients/TournamentNetworkingClient.java @@ -11,9 +11,12 @@ import io.netty.handler.codec.string.StringEncoder; import io.netty.util.CharsetUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.toop.framework.networking.exceptions.CouldNotConnectException; import org.toop.framework.networking.handlers.NetworkingGameClientHandler; import org.toop.framework.networking.interfaces.NetworkingClient; +import java.net.InetSocketAddress; + public class TournamentNetworkingClient implements NetworkingClient { private static final Logger logger = LogManager.getLogger(TournamentNetworkingClient.class); private Channel channel; @@ -21,7 +24,12 @@ public class TournamentNetworkingClient implements NetworkingClient { public TournamentNetworkingClient() {} @Override - public boolean connect(long clientId, String host, int port) { + public InetSocketAddress getAddress() { + return (InetSocketAddress) channel.remoteAddress(); + } + + @Override + public void connect(long clientId, String host, int port) throws CouldNotConnectException { try { Bootstrap bootstrap = new Bootstrap(); EventLoopGroup workerGroup = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory()); @@ -44,11 +52,9 @@ public class TournamentNetworkingClient implements NetworkingClient { }); ChannelFuture channelFuture = bootstrap.connect(host, port).sync(); this.channel = channelFuture.channel(); - } catch (Exception e) { - logger.error("Failed to create networking client instance", e); - return false; + } catch (Exception _) { + throw new CouldNotConnectException(clientId); } - return true; } @Override diff --git a/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java b/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java index 63fb7fc..e2281bd 100644 --- a/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java +++ b/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java @@ -9,6 +9,7 @@ import org.toop.framework.eventbus.events.UniqueEvent; import org.toop.framework.eventbus.events.EventsBase; import org.toop.annotations.AutoResponseResult; import org.toop.framework.networking.interfaces.NetworkingClient; +import org.toop.framework.networking.types.NetworkingReconnect; /** * A collection of networking-related event records for use with the {@link @@ -101,31 +102,6 @@ public class NetworkEvents extends EventsBase { /** Request to close a specific client connection. */ public record CloseClient(long clientId) implements GenericEvent {} - /** - * Event to start a new client connection. - * - *

Carries IP, port, and a unique event ID for correlation with responses. - * - * @param networkingClientClass The type of networking client to create. - * @param host Server IP address. - * @param port Server port. - * @param eventSnowflake Unique event identifier for correlation. - */ - public record StartClient( - NetworkingClient networkingClientClass, - String host, - int port, - long identifier) implements UniqueEvent {} - - /** - * Response confirming a client was started. - * - * @param clientId The client ID assigned to the new connection. - * @param identifier Event ID used for correlation. - */ - @AutoResponseResult - public record StartClientResponse(long clientId, long identifier) implements ResponseToUniqueEvent {} - /** Generic server response. */ public record ServerResponse(long clientId) implements GenericEvent {} @@ -137,8 +113,49 @@ public class NetworkEvents extends EventsBase { */ public record SendCommand(long clientId, String... args) implements GenericEvent {} + /** + * Event to start a new client connection. + * + *

Carries IP, port, and a unique event ID for correlation with responses. + * + * @param networkingClientClass The type of networking client to create. + * @param host Server IP address. + * @param port Server port. + * @param identifier Unique event identifier for correlation. + */ + public record StartClient( + NetworkingClient networkingClientClass, + String host, + int port, + NetworkingReconnect networkingReconnect, + long identifier) implements UniqueEvent {} + + /** + * Response confirming a client was started. + * + * @param clientId The client ID assigned to the new connection. + * @param identifier Event ID used for correlation. + */ + @AutoResponseResult + public record StartClientResponse(long clientId, long identifier) implements ResponseToUniqueEvent {} + /** WIP (Not working) Request to reconnect a client to a previous address. */ - public record Reconnect(long clientId) implements GenericEvent {} + public record Reconnect(long clientId, NetworkingReconnect networkingReconnect, long identifier) + implements UniqueEvent {} + + public record ReconnectResponse(boolean successful, long identifier) implements ResponseToUniqueEvent {} + + /** + * Request to change a client connection to a new server. + * + * @param clientId The client connection ID. + * @param ip The new server IP. + * @param port The new server port. + */ + public record ChangeAddress(long clientId, String ip, int port, NetworkingReconnect networkingReconnect, long identifier) + implements UniqueEvent {} + + public record ChangeAddressResponse(boolean successful, long identifier) implements ResponseToUniqueEvent {} /** * Response triggered when a message is received from a server. @@ -148,18 +165,6 @@ public class NetworkEvents extends EventsBase { */ public record ReceivedMessage(long clientId, String message) implements GenericEvent {} - /** - * Request to change a client connection to a new server. - * - * @param clientId The client connection ID. - * @param ip The new server IP. - * @param port The new server port. - */ - public record ChangeClientHost(long clientId, String ip, int port) implements GenericEvent {} - - /** WIP (Not working) Response indicating that the client could not connect. */ - public record CouldNotConnect(long clientId) implements GenericEvent {} - /** Event indicating a client connection was closed. */ public record ClosedConnection(long clientId) implements GenericEvent {} } diff --git a/framework/src/main/java/org/toop/framework/networking/exceptions/ClientNotFoundException.java b/framework/src/main/java/org/toop/framework/networking/exceptions/ClientNotFoundException.java new file mode 100644 index 0000000..2506b26 --- /dev/null +++ b/framework/src/main/java/org/toop/framework/networking/exceptions/ClientNotFoundException.java @@ -0,0 +1,25 @@ +package org.toop.framework.networking.exceptions; + +/** + * Thrown when an operation is attempted on a networking client + * that does not exist or has already been closed. + */ +public class ClientNotFoundException extends RuntimeException { + + private final long clientId; + + public ClientNotFoundException(long clientId) { + super("Networking client with ID " + clientId + " was not found."); + this.clientId = clientId; + } + + public ClientNotFoundException(long clientId, Throwable cause) { + super("Networking client with ID " + clientId + " was not found.", cause); + this.clientId = clientId; + } + + public long getClientId() { + return clientId; + } + +} \ No newline at end of file diff --git a/framework/src/main/java/org/toop/framework/networking/exceptions/CouldNotConnectException.java b/framework/src/main/java/org/toop/framework/networking/exceptions/CouldNotConnectException.java new file mode 100644 index 0000000..839fb0b --- /dev/null +++ b/framework/src/main/java/org/toop/framework/networking/exceptions/CouldNotConnectException.java @@ -0,0 +1,21 @@ +package org.toop.framework.networking.exceptions; + +public class CouldNotConnectException extends RuntimeException { + + private final long clientId; + + public CouldNotConnectException(long clientId) { + super("Networking client with ID " + clientId + " could not connect."); + this.clientId = clientId; + } + + public CouldNotConnectException(long clientId, Throwable cause) { + super("Networking client with ID " + clientId + " could not connect.", cause); + this.clientId = clientId; + } + + public long getClientId() { + return clientId; + } + +} \ No newline at end of file diff --git a/framework/src/main/java/org/toop/framework/networking/NetworkingInitializationException.java b/framework/src/main/java/org/toop/framework/networking/exceptions/NetworkingInitializationException.java similarity index 79% rename from framework/src/main/java/org/toop/framework/networking/NetworkingInitializationException.java rename to framework/src/main/java/org/toop/framework/networking/exceptions/NetworkingInitializationException.java index d9081d1..0ff430a 100644 --- a/framework/src/main/java/org/toop/framework/networking/NetworkingInitializationException.java +++ b/framework/src/main/java/org/toop/framework/networking/exceptions/NetworkingInitializationException.java @@ -1,4 +1,4 @@ -package org.toop.framework.networking; +package org.toop.framework.networking.exceptions; public class NetworkingInitializationException extends RuntimeException { public NetworkingInitializationException(String message, Throwable cause) { diff --git a/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClient.java b/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClient.java index 6cca1e9..09b215c 100644 --- a/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClient.java +++ b/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClient.java @@ -1,7 +1,12 @@ package org.toop.framework.networking.interfaces; +import org.toop.framework.networking.exceptions.CouldNotConnectException; + +import java.net.InetSocketAddress; + public interface NetworkingClient { - boolean connect(long clientId, String host, int port); + InetSocketAddress getAddress(); + void connect(long clientId, String host, int port) throws CouldNotConnectException; boolean isActive(); void writeAndFlush(String msg); void closeConnection(); diff --git a/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClientManager.java b/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClientManager.java index 0a2d70d..b9d8445 100644 --- a/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClientManager.java +++ b/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClientManager.java @@ -1,11 +1,20 @@ package org.toop.framework.networking.interfaces; -import java.util.OptionalLong; +import org.toop.framework.networking.exceptions.ClientNotFoundException; +import org.toop.framework.networking.exceptions.CouldNotConnectException; +import org.toop.framework.networking.types.NetworkingReconnect; + +import java.util.Optional; public interface NetworkingClientManager { - OptionalLong startClient(long id, NetworkingClient networkingClientClass, String host, int port); - boolean sendCommand(long id, String command); - boolean reconnect(long id); - boolean changeAddress(long id, String host, int port); - boolean closeClient(long id); + void startClient( + long id, + NetworkingClient networkingClientClass, + String host, + int port, + NetworkingReconnect networkingReconnect) throws CouldNotConnectException; + void sendCommand(long id, String command) throws ClientNotFoundException; + void reconnect(long id, NetworkingReconnect networkingReconnect) throws ClientNotFoundException; + void changeAddress(long id, String host, int port, NetworkingReconnect networkingReconnect) throws ClientNotFoundException; + void closeClient(long id) throws ClientNotFoundException; } diff --git a/framework/src/main/java/org/toop/framework/networking/types/NetworkingReconnect.java b/framework/src/main/java/org/toop/framework/networking/types/NetworkingReconnect.java new file mode 100644 index 0000000..7245124 --- /dev/null +++ b/framework/src/main/java/org/toop/framework/networking/types/NetworkingReconnect.java @@ -0,0 +1,5 @@ +package org.toop.framework.networking.types; + +import java.util.concurrent.TimeUnit; + +public record NetworkingReconnect(int reconnectAttempts, long timeout, TimeUnit timeUnit) {} \ No newline at end of file From bc6182e29aa0abbcb787d55b2de45c2e05dea3c1 Mon Sep 17 00:00:00 2001 From: lieght <49651652+BAFGdeJong@users.noreply.github.com> Date: Wed, 15 Oct 2025 03:26:19 +0200 Subject: [PATCH 08/14] Fixed event bug --- .../toop/framework/networking/NetworkingClientManager.java | 5 +++-- .../org/toop/framework/networking/events/NetworkEvents.java | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java b/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java index 64d8082..86d7085 100644 --- a/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java +++ b/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java @@ -40,7 +40,7 @@ public class NetworkingClientManager implements org.toop.framework.networking.in networkingClient.connect(id, host, port); networkClients.put(id, networkingClient); logger.info("New client started successfully for {}:{}", host, port); - new EventFlow().addPostEvent(new NetworkEvents.StartClientResponse(id, id)).postEvent(); + new EventFlow().addPostEvent(new NetworkEvents.StartClientResponse(id, false, id)).postEvent(); scheduler.shutdown(); } catch (CouldNotConnectException e) { attempts++; @@ -50,11 +50,12 @@ public class NetworkingClientManager implements org.toop.framework.networking.in scheduler.schedule(this, networkingReconnect.timeout(), networkingReconnect.timeUnit()); } else { logger.error("Failed to start client for {}:{} after {} attempts", host, port, attempts); - new EventFlow().addPostEvent(new NetworkEvents.StartClientResponse(-1, id)).postEvent(); + new EventFlow().addPostEvent(new NetworkEvents.StartClientResponse(-1, false, id)).postEvent(); scheduler.shutdown(); } } catch (Exception e) { logger.error("Unexpected exception during startClient", e); + new EventFlow().addPostEvent(new NetworkEvents.StartClientResponse(-1, false, id)).postEvent(); scheduler.shutdown(); } } diff --git a/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java b/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java index e2281bd..9740a17 100644 --- a/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java +++ b/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java @@ -133,11 +133,12 @@ public class NetworkEvents extends EventsBase { /** * Response confirming a client was started. * - * @param clientId The client ID assigned to the new connection. + * @param clientId The client ID assigned to the new connection. + * @param successful If successfully connected or not. If not clientId will also be -1. * @param identifier Event ID used for correlation. */ @AutoResponseResult - public record StartClientResponse(long clientId, long identifier) implements ResponseToUniqueEvent {} + public record StartClientResponse(long clientId, boolean successful, long identifier) implements ResponseToUniqueEvent {} /** WIP (Not working) Request to reconnect a client to a previous address. */ public record Reconnect(long clientId, NetworkingReconnect networkingReconnect, long identifier) From 798005de2ca9559780572e2cf54506a4d6af87f2 Mon Sep 17 00:00:00 2001 From: lieght <49651652+BAFGdeJong@users.noreply.github.com> Date: Wed, 15 Oct 2025 19:05:09 +0200 Subject: [PATCH 09/14] Refactor to make Events easier to work with. --- .../NetworkingClientEventListener.java | 35 ++-- .../networking/NetworkingClientManager.java | 153 +++++------------- .../networking/events/NetworkEvents.java | 22 +-- .../interfaces/NetworkingClientManager.java | 15 +- .../networking/types/NetworkingConnector.java | 5 + .../networking/types/NetworkingReconnect.java | 5 - 6 files changed, 78 insertions(+), 157 deletions(-) create mode 100644 framework/src/main/java/org/toop/framework/networking/types/NetworkingConnector.java delete mode 100644 framework/src/main/java/org/toop/framework/networking/types/NetworkingReconnect.java diff --git a/framework/src/main/java/org/toop/framework/networking/NetworkingClientEventListener.java b/framework/src/main/java/org/toop/framework/networking/NetworkingClientEventListener.java index 9b8fb14..ef51a46 100644 --- a/framework/src/main/java/org/toop/framework/networking/NetworkingClientEventListener.java +++ b/framework/src/main/java/org/toop/framework/networking/NetworkingClientEventListener.java @@ -39,12 +39,13 @@ public class NetworkingClientEventListener { } void handleStartClient(NetworkEvents.StartClient event) { + long clientId = SnowflakeGenerator.nextId(); clientManager.startClient( - event.identifier(), - event.networkingClientClass(), - event.host(), - event.port(), - event.networkingReconnect() + clientId, + event.networkingClient(), + event.networkingConnector(), + () -> new EventFlow().addPostEvent(new NetworkEvents.StartClientResponse(clientId, true, event.identifier())).postEvent(), + () -> new EventFlow().addPostEvent(new NetworkEvents.StartClientResponse(clientId, false, event.identifier())).postEvent() ); } @@ -110,19 +111,23 @@ public class NetworkingClientEventListener { } private void handleReconnect(NetworkEvents.Reconnect event) { - try { - clientManager.reconnect(event.clientId(), event.networkingReconnect()); - } catch (ClientNotFoundException e) { - logger.error(e); - } + clientManager.startClient( + event.clientId(), + event.networkingClient(), + event.networkingConnector(), + () -> new EventFlow().addPostEvent(new NetworkEvents.ReconnectResponse(true, event.identifier())).postEvent(), + () -> new EventFlow().addPostEvent(new NetworkEvents.ReconnectResponse(false, event.identifier())).postEvent() + ); } private void handleChangeAddress(NetworkEvents.ChangeAddress event) { - try { - clientManager.changeAddress(event.clientId(), event.ip(), event.port(), event.networkingReconnect()); - } catch (ClientNotFoundException e) { - logger.error(e); - } + clientManager.startClient( + event.clientId(), + event.networkingClient(), + event.networkingConnector(), + () -> new EventFlow().addPostEvent(new NetworkEvents.ChangeAddressResponse(true, event.identifier())).postEvent(), + () -> new EventFlow().addPostEvent(new NetworkEvents.ChangeAddressResponse(false, event.identifier())).postEvent() + ); } void handleCloseClient(NetworkEvents.CloseClient event) { diff --git a/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java b/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java index 86d7085..0f4538f 100644 --- a/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java +++ b/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java @@ -1,6 +1,5 @@ package org.toop.framework.networking; -import java.net.InetSocketAddress; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; @@ -9,12 +8,10 @@ import java.util.concurrent.TimeUnit; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.toop.framework.eventbus.EventFlow; -import org.toop.framework.networking.events.NetworkEvents; import org.toop.framework.networking.exceptions.ClientNotFoundException; import org.toop.framework.networking.exceptions.CouldNotConnectException; import org.toop.framework.networking.interfaces.NetworkingClient; -import org.toop.framework.networking.types.NetworkingReconnect; +import org.toop.framework.networking.types.NetworkingConnector; public class NetworkingClientManager implements org.toop.framework.networking.interfaces.NetworkingClientManager { private static final Logger logger = LogManager.getLogger(NetworkingClientManager.class); @@ -22,13 +19,13 @@ public class NetworkingClientManager implements org.toop.framework.networking.in public NetworkingClientManager() {} - @Override - public void startClient( + private void connectHelper( long id, - NetworkingClient networkingClient, - String host, int port, - NetworkingReconnect networkingReconnect) { - + NetworkingClient nClient, + NetworkingConnector nConnector, + Runnable onSuccess, + Runnable onFailure + ) { ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); Runnable connectTask = new Runnable() { @@ -36,26 +33,31 @@ public class NetworkingClientManager implements org.toop.framework.networking.in @Override public void run() { + + if (networkClients.get(id) != null) { + networkClients.remove(id); + } + try { - networkingClient.connect(id, host, port); - networkClients.put(id, networkingClient); - logger.info("New client started successfully for {}:{}", host, port); - new EventFlow().addPostEvent(new NetworkEvents.StartClientResponse(id, false, id)).postEvent(); + nClient.connect(id, nConnector.host(), nConnector.port()); + networkClients.put(id, nClient); + logger.info("New client started successfully for {}:{}", nConnector.host(), nConnector.port()); + onSuccess.run(); scheduler.shutdown(); } catch (CouldNotConnectException e) { attempts++; - if (attempts < networkingReconnect.reconnectAttempts()) { + if (attempts < nConnector.reconnectAttempts()) { logger.warn("Could not connect to {}:{}. Retrying in {} {}", - host, port, networkingReconnect.timeout(), networkingReconnect.timeUnit()); - scheduler.schedule(this, networkingReconnect.timeout(), networkingReconnect.timeUnit()); + nConnector.host(), nConnector.port(), nConnector.timeout(), nConnector.timeUnit()); + scheduler.schedule(this, nConnector.timeout(), nConnector.timeUnit()); } else { - logger.error("Failed to start client for {}:{} after {} attempts", host, port, attempts); - new EventFlow().addPostEvent(new NetworkEvents.StartClientResponse(-1, false, id)).postEvent(); + logger.error("Failed to start client for {}:{} after {} attempts", nConnector.host(), nConnector.port(), attempts); + onFailure.run(); scheduler.shutdown(); } } catch (Exception e) { logger.error("Unexpected exception during startClient", e); - new EventFlow().addPostEvent(new NetworkEvents.StartClientResponse(-1, false, id)).postEvent(); + onFailure.run(); scheduler.shutdown(); } } @@ -64,6 +66,23 @@ public class NetworkingClientManager implements org.toop.framework.networking.in scheduler.schedule(connectTask, 0, TimeUnit.MILLISECONDS); } + @Override + public void startClient( + long id, + NetworkingClient nClient, + NetworkingConnector nConnector, + Runnable onSuccess, + Runnable onFailure + ) { + connectHelper( + id, + nClient, + nConnector, + onSuccess, + onFailure + ); + } + @Override public void sendCommand(long id, String command) throws ClientNotFoundException { logger.info("Sending command to client for {}:{}", id, command); @@ -85,100 +104,6 @@ public class NetworkingClientManager implements org.toop.framework.networking.in } - @Override - public void reconnect(long id, NetworkingReconnect networkingReconnect) throws ClientNotFoundException { - NetworkingClient client = this.networkClients.get(id); - if (client == null) { - throw new ClientNotFoundException(id); - } - - InetSocketAddress address = client.getAddress(); - - if (client.isActive()) { - client.closeConnection(); - } - - ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); - - Runnable connectTask = new Runnable() { - int attempts = 0; - - @Override - public void run() { - try { - client.connect(id, address.getHostName(), address.getPort()); - networkClients.put(id, client); - logger.info("Client {} reconnected to {}:{}", id, address.getHostName(), address.getPort()); - new EventFlow().addPostEvent(new NetworkEvents.ReconnectResponse(true, id)).postEvent().postEvent(); - scheduler.shutdown(); - } catch (CouldNotConnectException e) { - attempts++; - if (attempts < networkingReconnect.reconnectAttempts()) { - logger.warn("Could not reconnect client {} to {}:{}. Retrying in {} {}", - id, address.getHostName(), address.getPort(), networkingReconnect.timeout(), networkingReconnect.timeUnit()); - scheduler.schedule(this, networkingReconnect.timeout(), networkingReconnect.timeUnit()); - } else { - logger.error("Failed to reconnect client {} to {}:{} after {} attempts", id, address.getHostName(), address.getPort(), attempts); - new EventFlow().addPostEvent(new NetworkEvents.ReconnectResponse(false, id)).postEvent().postEvent(); - scheduler.shutdown(); - } - } catch (Exception e) { - logger.error("Unexpected exception during reconnect for client {}", id, e); - new EventFlow().addPostEvent(new NetworkEvents.ReconnectResponse(false, id)).postEvent().postEvent(); - scheduler.shutdown(); - } - } - }; - - scheduler.schedule(connectTask, 0, TimeUnit.MILLISECONDS); - } - - @Override - public void changeAddress(long id, String host, int port, NetworkingReconnect networkingReconnect) throws ClientNotFoundException { - NetworkingClient client = this.networkClients.get(id); - if (client == null) { - throw new ClientNotFoundException(id); - } - - if (client.isActive()) { - client.closeConnection(); - } - - ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); - - Runnable connectTask = new Runnable() { - int attempts = 0; - - @Override - public void run() { - try { - client.connect(id, host, port); - networkClients.put(id, client); - logger.info("Client {} changed address to {}:{}", id, host, port); - new EventFlow().addPostEvent(new NetworkEvents.ChangeAddressResponse(true, id)).postEvent().postEvent(); - scheduler.shutdown(); - } catch (CouldNotConnectException e) { - attempts++; - if (attempts < networkingReconnect.reconnectAttempts()) { - logger.warn("Could not connect client {} to {}:{}. Retrying in {} {}", - id, host, port, networkingReconnect.timeout(), networkingReconnect.timeUnit()); - scheduler.schedule(this, networkingReconnect.timeout(), networkingReconnect.timeUnit()); - } else { - logger.error("Failed to connect client {} to {}:{} after {} attempts", id, host, port, attempts); - new EventFlow().addPostEvent(new NetworkEvents.ChangeAddressResponse(false, id)).postEvent().postEvent(); - scheduler.shutdown(); - } - } catch (Exception e) { - logger.error("Unexpected exception during changeAddress for client {}", id, e); - new EventFlow().addPostEvent(new NetworkEvents.ChangeAddressResponse(false, id)).postEvent().postEvent(); - scheduler.shutdown(); - } - } - }; - - scheduler.schedule(connectTask, 0, TimeUnit.MILLISECONDS); - } - @Override public void closeClient(long id) throws ClientNotFoundException { NetworkingClient client = this.networkClients.get(id); diff --git a/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java b/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java index 9740a17..8938721 100644 --- a/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java +++ b/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java @@ -9,7 +9,7 @@ import org.toop.framework.eventbus.events.UniqueEvent; import org.toop.framework.eventbus.events.EventsBase; import org.toop.annotations.AutoResponseResult; import org.toop.framework.networking.interfaces.NetworkingClient; -import org.toop.framework.networking.types.NetworkingReconnect; +import org.toop.framework.networking.types.NetworkingConnector; /** * A collection of networking-related event records for use with the {@link @@ -118,16 +118,12 @@ public class NetworkEvents extends EventsBase { * *

Carries IP, port, and a unique event ID for correlation with responses. * - * @param networkingClientClass The type of networking client to create. - * @param host Server IP address. - * @param port Server port. - * @param identifier Unique event identifier for correlation. + * @param networkingClient + * @param networkingConnector */ public record StartClient( - NetworkingClient networkingClientClass, - String host, - int port, - NetworkingReconnect networkingReconnect, + NetworkingClient networkingClient, + NetworkingConnector networkingConnector, long identifier) implements UniqueEvent {} /** @@ -135,13 +131,12 @@ public class NetworkEvents extends EventsBase { * * @param clientId The client ID assigned to the new connection. * @param successful If successfully connected or not. If not clientId will also be -1. - * @param identifier Event ID used for correlation. */ @AutoResponseResult public record StartClientResponse(long clientId, boolean successful, long identifier) implements ResponseToUniqueEvent {} /** WIP (Not working) Request to reconnect a client to a previous address. */ - public record Reconnect(long clientId, NetworkingReconnect networkingReconnect, long identifier) + public record Reconnect(long clientId, NetworkingClient networkingClient, NetworkingConnector networkingConnector, long identifier) implements UniqueEvent {} public record ReconnectResponse(boolean successful, long identifier) implements ResponseToUniqueEvent {} @@ -150,10 +145,9 @@ public class NetworkEvents extends EventsBase { * Request to change a client connection to a new server. * * @param clientId The client connection ID. - * @param ip The new server IP. - * @param port The new server port. + * @param networkingConnector */ - public record ChangeAddress(long clientId, String ip, int port, NetworkingReconnect networkingReconnect, long identifier) + public record ChangeAddress(long clientId, NetworkingClient networkingClient, NetworkingConnector networkingConnector, long identifier) implements UniqueEvent {} public record ChangeAddressResponse(boolean successful, long identifier) implements ResponseToUniqueEvent {} diff --git a/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClientManager.java b/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClientManager.java index b9d8445..c236080 100644 --- a/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClientManager.java +++ b/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClientManager.java @@ -2,19 +2,16 @@ package org.toop.framework.networking.interfaces; import org.toop.framework.networking.exceptions.ClientNotFoundException; import org.toop.framework.networking.exceptions.CouldNotConnectException; -import org.toop.framework.networking.types.NetworkingReconnect; - -import java.util.Optional; +import org.toop.framework.networking.types.NetworkingConnector; public interface NetworkingClientManager { void startClient( long id, - NetworkingClient networkingClientClass, - String host, - int port, - NetworkingReconnect networkingReconnect) throws CouldNotConnectException; + NetworkingClient nClient, + NetworkingConnector nConnector, + Runnable onSuccess, + Runnable onFailure + ) throws CouldNotConnectException; void sendCommand(long id, String command) throws ClientNotFoundException; - void reconnect(long id, NetworkingReconnect networkingReconnect) throws ClientNotFoundException; - void changeAddress(long id, String host, int port, NetworkingReconnect networkingReconnect) throws ClientNotFoundException; void closeClient(long id) throws ClientNotFoundException; } diff --git a/framework/src/main/java/org/toop/framework/networking/types/NetworkingConnector.java b/framework/src/main/java/org/toop/framework/networking/types/NetworkingConnector.java new file mode 100644 index 0000000..ee6ed44 --- /dev/null +++ b/framework/src/main/java/org/toop/framework/networking/types/NetworkingConnector.java @@ -0,0 +1,5 @@ +package org.toop.framework.networking.types; + +import java.util.concurrent.TimeUnit; + +public record NetworkingConnector(String host, int port, int reconnectAttempts, long timeout, TimeUnit timeUnit) {} \ No newline at end of file diff --git a/framework/src/main/java/org/toop/framework/networking/types/NetworkingReconnect.java b/framework/src/main/java/org/toop/framework/networking/types/NetworkingReconnect.java deleted file mode 100644 index 7245124..0000000 --- a/framework/src/main/java/org/toop/framework/networking/types/NetworkingReconnect.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.toop.framework.networking.types; - -import java.util.concurrent.TimeUnit; - -public record NetworkingReconnect(int reconnectAttempts, long timeout, TimeUnit timeUnit) {} \ No newline at end of file From 5beebf9663875ac0f3d38d6aaea29cdde1530483 Mon Sep 17 00:00:00 2001 From: lieght <49651652+BAFGdeJong@users.noreply.github.com> Date: Wed, 15 Oct 2025 19:08:18 +0200 Subject: [PATCH 10/14] Quick fix for closing connection. --- .../toop/framework/networking/NetworkingClientManager.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java b/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java index 0f4538f..02434fc 100644 --- a/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java +++ b/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java @@ -34,7 +34,9 @@ public class NetworkingClientManager implements org.toop.framework.networking.in @Override public void run() { - if (networkClients.get(id) != null) { + NetworkingClient qClient = networkClients.get(id); + if (qClient != null) { + qClient.closeConnection(); networkClients.remove(id); } From 1134649197140e3ad1ea8b04303979a0d9332936 Mon Sep 17 00:00:00 2001 From: lieght <49651652+BAFGdeJong@users.noreply.github.com> Date: Wed, 15 Oct 2025 20:11:43 +0200 Subject: [PATCH 11/14] Documentation --- .../networking/events/NetworkEvents.java | 232 +++++++++++------- 1 file changed, 142 insertions(+), 90 deletions(-) diff --git a/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java b/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java index 8938721..ac3de68 100644 --- a/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java +++ b/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java @@ -3,163 +3,215 @@ package org.toop.framework.networking.events; import java.util.*; import java.util.concurrent.CompletableFuture; -import org.toop.framework.eventbus.events.GenericEvent; -import org.toop.framework.eventbus.events.ResponseToUniqueEvent; -import org.toop.framework.eventbus.events.UniqueEvent; -import org.toop.framework.eventbus.events.EventsBase; import org.toop.annotations.AutoResponseResult; +import org.toop.framework.eventbus.events.*; import org.toop.framework.networking.interfaces.NetworkingClient; import org.toop.framework.networking.types.NetworkingConnector; /** - * A collection of networking-related event records for use with the {@link - * org.toop.framework.eventbus.GlobalEventBus}. + * Defines all event types related to the networking subsystem. + *

+ * These events are used in conjunction with the {@link org.toop.framework.eventbus.GlobalEventBus} + * and {@link org.toop.framework.eventbus.EventFlow} to communicate between components + * such as networking clients, managers, and listeners. + *

* - *

This class defines all the events that can be posted or listened to in the networking - * subsystem. Events are separated into those with unique IDs (UniqueEvent) and those without - * (GenericEvent). + *

Important

+ * For all {@link UniqueEvent} and {@link ResponseToUniqueEvent} types: + * the {@code identifier} field is automatically generated and injected + * by {@link org.toop.framework.eventbus.EventFlow}. It should never + * be manually assigned by user code. (Exceptions may apply) */ public class NetworkEvents extends EventsBase { + // ------------------------------------------------------ + // Generic Request & Response Events (no identifier) + // ------------------------------------------------------ + /** - * Requests all active client connections. - * - *

This is a blocking event. The result will be delivered via the provided {@link - * CompletableFuture}. - * - * @param future CompletableFuture to receive the list of active {@link NetworkingClient} - * instances. + * Requests a list of all active networking clients. + *

+ * This is a blocking request that returns the list asynchronously + * via the provided {@link CompletableFuture}. */ public record RequestsAllClients(CompletableFuture> future) implements GenericEvent {} - /** Forces all active client connections to close immediately. */ + /** Signals all active clients should be forcefully closed. */ public record ForceCloseAllClients() implements GenericEvent {} - /** Response indicating a challenge was cancelled. */ - public record ChallengeCancelledResponse(long clientId, String challengeId) implements GenericEvent {} + /** Indicates a challenge was cancelled by the server. */ + public record ChallengeCancelledResponse(long clientId, String challengeId) + implements GenericEvent {} - /** Response indicating a challenge was received. */ + /** Indicates an incoming challenge from another player. */ public record ChallengeResponse(long clientId, String challengerName, String challengeId, String gameType) implements GenericEvent {} - /** Response containing a list of players for a client. */ - public record PlayerlistResponse(long clientId, String[] playerlist) implements GenericEvent {} + /** Contains the list of players currently available on the server. */ + public record PlayerlistResponse(long clientId, String[] playerlist) + implements GenericEvent {} - /** Response containing a list of games for a client. */ - public record GamelistResponse(long clientId, String[] gamelist) implements GenericEvent {} + /** Contains the list of available game types for a client. */ + public record GamelistResponse(long clientId, String[] gamelist) + implements GenericEvent {} - /** Response indicating a game match information for a client. */ + /** Provides match information when a new game starts. */ public record GameMatchResponse(long clientId, String playerToMove, String gameType, String opponent) implements GenericEvent {} - /** Response indicating the result of a game. */ - public record GameResultResponse(long clientId, String condition) implements GenericEvent {} + /** Indicates the outcome or completion of a game. */ + public record GameResultResponse(long clientId, String condition) + implements GenericEvent {} - /** Response indicating a game move occurred. */ - public record GameMoveResponse(long clientId, String player, String move, String details) implements GenericEvent {} + /** Indicates that a game move has been processed or received. */ + public record GameMoveResponse(long clientId, String player, String move, String details) + implements GenericEvent {} - /** Response indicating it is the player's turn. */ + /** Indicates it is the current player's turn to move. */ public record YourTurnResponse(long clientId, String message) implements GenericEvent {} - /** Request to send login credentials for a client. */ - public record SendLogin(long clientId, String username) implements GenericEvent {} + /** Requests a login operation for the given client. */ + public record SendLogin(long clientId, String username) + implements GenericEvent {} - /** Request to log out a client. */ - public record SendLogout(long clientId) implements GenericEvent {} + /** Requests logout for the specified client. */ + public record SendLogout(long clientId) + implements GenericEvent {} - /** Request to retrieve the player list for a client. */ - public record SendGetPlayerlist(long clientId) implements GenericEvent {} + /** Requests the player list from the server. */ + public record SendGetPlayerlist(long clientId) + implements GenericEvent {} - /** Request to retrieve the game list for a client. */ - public record SendGetGamelist(long clientId) implements GenericEvent {} + /** Requests the game list from the server. */ + public record SendGetGamelist(long clientId) + implements GenericEvent {} - /** Request to subscribe a client to a game type. */ - public record SendSubscribe(long clientId, String gameType) implements GenericEvent {} + /** Requests a subscription to updates for a given game type. */ + public record SendSubscribe(long clientId, String gameType) + implements GenericEvent {} - /** Request to make a move in a game. */ - public record SendMove(long clientId, short moveNumber) implements GenericEvent {} + /** Sends a game move command to the server. */ + public record SendMove(long clientId, short moveNumber) + implements GenericEvent {} - /** Request to challenge another player. */ - public record SendChallenge(long clientId, String usernameToChallenge, String gameType) implements GenericEvent {} + /** Requests to challenge another player to a game. */ + public record SendChallenge(long clientId, String usernameToChallenge, String gameType) + implements GenericEvent {} - /** Request to accept a challenge. */ - public record SendAcceptChallenge(long clientId, int challengeId) implements GenericEvent {} + /** Requests to accept an existing challenge. */ + public record SendAcceptChallenge(long clientId, int challengeId) + implements GenericEvent {} - /** Request to forfeit a game. */ - public record SendForfeit(long clientId) implements GenericEvent {} + /** Requests to forfeit the current game. */ + public record SendForfeit(long clientId) + implements GenericEvent {} - /** Request to send a message from a client. */ - public record SendMessage(long clientId, String message) implements GenericEvent {} + /** Sends a chat or informational message from a client. */ + public record SendMessage(long clientId, String message) + implements GenericEvent {} - /** Request to display help to a client. */ - public record SendHelp(long clientId) implements GenericEvent {} + /** Requests general help information from the server. */ + public record SendHelp(long clientId) + implements GenericEvent {} - /** Request to display help for a specific command. */ - public record SendHelpForCommand(long clientId, String command) implements GenericEvent {} + /** Requests help information specific to a given command. */ + public record SendHelpForCommand(long clientId, String command) + implements GenericEvent {} - /** Request to close a specific client connection. */ - public record CloseClient(long clientId) implements GenericEvent {} + /** Requests to close an active client connection. */ + public record CloseClient(long clientId) + implements GenericEvent {} - /** Generic server response. */ - public record ServerResponse(long clientId) implements GenericEvent {} + /** A generic event indicating a raw server response. */ + public record ServerResponse(long clientId) + implements GenericEvent {} /** - * Request to send a command to a server. + * Sends a raw command string to the server. * - * @param clientId The client connection ID. + * @param clientId The client ID to send the command from. * @param args The command arguments. */ - public record SendCommand(long clientId, String... args) implements GenericEvent {} + public record SendCommand(long clientId, String... args) + implements GenericEvent {} + + /** Event fired when a message is received from the server. */ + public record ReceivedMessage(long clientId, String message) + implements GenericEvent {} + + /** Indicates that a client connection has been closed. */ + public record ClosedConnection(long clientId) + implements GenericEvent {} + + // ------------------------------------------------------ + // Unique Request & Response Events (with identifier) + // ------------------------------------------------------ /** - * Event to start a new client connection. + * Requests creation and connection of a new client. + *

+ * The {@code identifier} is automatically assigned by {@link org.toop.framework.eventbus.EventFlow} + * to correlate with its corresponding {@link StartClientResponse}. + *

* - *

Carries IP, port, and a unique event ID for correlation with responses. - * - * @param networkingClient - * @param networkingConnector + * @param networkingClient The client instance to start. + * @param networkingConnector Connection details (host, port, etc.). + * @param identifier Automatically injected unique identifier. */ public record StartClient( NetworkingClient networkingClient, NetworkingConnector networkingConnector, - long identifier) implements UniqueEvent {} + long identifier) + implements UniqueEvent {} /** - * Response confirming a client was started. + * Response confirming that a client has been successfully started. + *

+ * The {@code identifier} value is automatically propagated from + * the original {@link StartClient} request by {@link org.toop.framework.eventbus.EventFlow}. + *

* - * @param clientId The client ID assigned to the new connection. - * @param successful If successfully connected or not. If not clientId will also be -1. + * @param clientId The newly assigned client ID. + * @param successful Whether the connection succeeded. + * @param identifier Automatically injected correlation ID. */ @AutoResponseResult - public record StartClientResponse(long clientId, boolean successful, long identifier) implements ResponseToUniqueEvent {} - - /** WIP (Not working) Request to reconnect a client to a previous address. */ - public record Reconnect(long clientId, NetworkingClient networkingClient, NetworkingConnector networkingConnector, long identifier) - implements UniqueEvent {} - - public record ReconnectResponse(boolean successful, long identifier) implements ResponseToUniqueEvent {} + public record StartClientResponse(long clientId, boolean successful, long identifier) + implements ResponseToUniqueEvent {} /** - * Request to change a client connection to a new server. - * - * @param clientId The client connection ID. - * @param networkingConnector + * Requests reconnection of an existing client using its previous configuration. + *

+ * The {@code identifier} is automatically injected by {@link org.toop.framework.eventbus.EventFlow}. + *

*/ - public record ChangeAddress(long clientId, NetworkingClient networkingClient, NetworkingConnector networkingConnector, long identifier) + public record Reconnect( + long clientId, + NetworkingClient networkingClient, + NetworkingConnector networkingConnector, + long identifier) implements UniqueEvent {} - public record ChangeAddressResponse(boolean successful, long identifier) implements ResponseToUniqueEvent {} + /** Response to a {@link Reconnect} event, carrying the success result. */ + public record ReconnectResponse(boolean successful, long identifier) + implements ResponseToUniqueEvent {} /** - * Response triggered when a message is received from a server. - * - * @param clientId The connection ID that received the message. - * @param message The message content. + * Requests to change the connection target (host/port) for a client. + *

+ * The {@code identifier} is automatically injected by {@link org.toop.framework.eventbus.EventFlow}. + *

*/ - public record ReceivedMessage(long clientId, String message) implements GenericEvent {} + public record ChangeAddress( + long clientId, + NetworkingClient networkingClient, + NetworkingConnector networkingConnector, + long identifier) + implements UniqueEvent {} - /** Event indicating a client connection was closed. */ - public record ClosedConnection(long clientId) implements GenericEvent {} + /** Response to a {@link ChangeAddress} event, carrying the success result. */ + public record ChangeAddressResponse(boolean successful, long identifier) + implements ResponseToUniqueEvent {} } From cf9dbef8820b7b63c4620ab1d2c0cb5277e3e0b2 Mon Sep 17 00:00:00 2001 From: lieght <49651652+BAFGdeJong@users.noreply.github.com> Date: Wed, 15 Oct 2025 21:36:46 +0200 Subject: [PATCH 12/14] Correct client creation and user polling --- app/src/main/java/org/toop/app/Server.java | 58 +++++++++++++--------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/toop/app/Server.java b/app/src/main/java/org/toop/app/Server.java index 8ec565a..8a198ae 100644 --- a/app/src/main/java/org/toop/app/Server.java +++ b/app/src/main/java/org/toop/app/Server.java @@ -1,5 +1,6 @@ package org.toop.app; +import com.google.common.util.concurrent.AbstractScheduledService; import org.toop.app.game.ReversiGame; import org.toop.app.game.TicTacToeGame; import org.toop.app.view.ViewStack; @@ -9,13 +10,19 @@ import org.toop.app.view.views.OnlineView; import org.toop.app.view.views.SendChallengeView; import org.toop.app.view.views.ServerView; import org.toop.framework.eventbus.EventFlow; +import org.toop.framework.networking.clients.TournamentNetworkingClient; import org.toop.framework.networking.events.NetworkEvents; +import org.toop.framework.networking.interfaces.NetworkingClient; +import org.toop.framework.networking.types.NetworkingConnector; import org.toop.local.AppContext; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; public final class Server { private String user = ""; @@ -58,8 +65,12 @@ public final class Server { } new EventFlow() - .addPostEvent(NetworkEvents.StartClient.class, ip, parsedPort) + .addPostEvent(NetworkEvents.StartClient.class, + new TournamentNetworkingClient(), + new NetworkingConnector(ip, parsedPort, 10, 1, TimeUnit.SECONDS) + ) .onResponse(NetworkEvents.StartClientResponse.class, e -> { + // TODO add if unsuccessful this.user = user; clientId = e.clientId(); @@ -68,30 +79,20 @@ public final class Server { view = new ServerView(user, this::sendChallenge, this::disconnect); ViewStack.push(view); - startPopulateThread(); + startPopulateScheduler(); }).postEvent(); new EventFlow().listen(this::handleReceivedChallenge); } - private void populatePlayerList() { - new EventFlow().listen(NetworkEvents.PlayerlistResponse.class, e -> { - if (e.clientId() == clientId) { - onlinePlayers = new ArrayList(List.of(e.playerlist())); - onlinePlayers.removeIf(name -> name.equalsIgnoreCase(user)); + private void populatePlayerList(ScheduledExecutorService scheduler, Runnable populatingTask) { - view.update(onlinePlayers); - } - }); + final long DELAY = 5; - final EventFlow sendGetPlayerList = new EventFlow().addPostEvent(new NetworkEvents.SendGetPlayerlist(clientId)); - - while (isPolling) { - sendGetPlayerList.postEvent(); - - try { - Thread.sleep(5000); - } catch (InterruptedException _) {} + if (!isPolling) scheduler.shutdown(); + else { + populatingTask.run(); + scheduler.schedule(() -> populatePlayerList(scheduler, populatingTask), DELAY, TimeUnit.SECONDS); } } @@ -173,6 +174,7 @@ public final class Server { private void disconnect() { new EventFlow().addPostEvent(new NetworkEvents.CloseClient(clientId)).postEvent(); + isPolling = false; ViewStack.push(new OnlineView()); } @@ -184,15 +186,25 @@ public final class Server { forfeitGame(); ViewStack.push(view); - startPopulateThread(); + startPopulateScheduler(); } - private void startPopulateThread() { + private void startPopulateScheduler() { isPolling = true; - final Thread populateThread = new Thread(this::populatePlayerList); - populateThread.setDaemon(false); - populateThread.start(); + EventFlow getPlayerlistFlow = new EventFlow() + .addPostEvent(new NetworkEvents.SendGetPlayerlist(clientId)) + .listen(NetworkEvents.PlayerlistResponse.class, e -> { + if (e.clientId() == clientId) { + onlinePlayers = new ArrayList<>(List.of(e.playerlist())); + onlinePlayers.removeIf(name -> name.equalsIgnoreCase(user)); + + view.update(onlinePlayers); + } + }, false); + + final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + scheduler.schedule(() -> populatePlayerList(scheduler, getPlayerlistFlow::postEvent), 0, TimeUnit.MILLISECONDS); } public List getGamesList() { From 975207a6de683298a5add06821d43bd49baef6f9 Mon Sep 17 00:00:00 2001 From: lieght <49651652+BAFGdeJong@users.noreply.github.com> Date: Thu, 16 Oct 2025 00:37:20 +0200 Subject: [PATCH 13/14] Nuke everything on close. --- app/src/main/java/org/toop/app/App.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/toop/app/App.java b/app/src/main/java/org/toop/app/App.java index 64513d5..d9d1f7f 100644 --- a/app/src/main/java/org/toop/app/App.java +++ b/app/src/main/java/org/toop/app/App.java @@ -79,6 +79,7 @@ public final class App extends Application { public static void quit() { ViewStack.cleanup(); stage.close(); + System.exit(0); // TODO: This is like dropping a nuke } public static void reload() { From 3a120803e38e10931b40e69167566085e4d95a60 Mon Sep 17 00:00:00 2001 From: Bas de Jong Date: Thu, 16 Oct 2025 13:59:24 +0200 Subject: [PATCH 14/14] Fixes for garbage code by Omar --- app/src/main/java/org/toop/app/Server.java | 24 ++++++++++++------- .../app/view/views/SendChallengeView.java | 2 +- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/org/toop/app/Server.java b/app/src/main/java/org/toop/app/Server.java index 8a198ae..8d71c4a 100644 --- a/app/src/main/java/org/toop/app/Server.java +++ b/app/src/main/java/org/toop/app/Server.java @@ -29,6 +29,7 @@ public final class Server { private long clientId = -1; private List onlinePlayers = new CopyOnWriteArrayList(); + private List gameList = new CopyOnWriteArrayList<>(); private ServerView view; @@ -80,6 +81,9 @@ public final class Server { ViewStack.push(view); startPopulateScheduler(); + + populateGameList(); + }).postEvent(); new EventFlow().listen(this::handleReceivedChallenge); @@ -207,16 +211,18 @@ public final class Server { scheduler.schedule(() -> populatePlayerList(scheduler, getPlayerlistFlow::postEvent), 0, TimeUnit.MILLISECONDS); } - public List getGamesList() { - final List list = new ArrayList(); - list.add("tic-tac-toe"); // Todo: get games list from server and check if the game is supported - list.add("reversi"); + private void gamesListFromServerHandler(NetworkEvents.GamelistResponse event) { + gameList.addAll(List.of(event.gamelist())); + } + public void populateGameList() { new EventFlow().addPostEvent(new NetworkEvents.SendGetGamelist(clientId)) - .listen(NetworkEvents.GamelistResponse.class, e -> { - System.out.println(Arrays.toString(e.gamelist())); - }).postEvent(); - - return list; + .listen(NetworkEvents.GamelistResponse.class, + this::gamesListFromServerHandler, true + ).postEvent(); } + + public List getGameList() { + return gameList; + } } \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/view/views/SendChallengeView.java b/app/src/main/java/org/toop/app/view/views/SendChallengeView.java index a3bde9b..5d88a13 100644 --- a/app/src/main/java/org/toop/app/view/views/SendChallengeView.java +++ b/app/src/main/java/org/toop/app/view/views/SendChallengeView.java @@ -46,7 +46,7 @@ public final class SendChallengeView extends View { gameText.setText(AppContext.getString("to-a-game-of")); final ComboBox gamesCombobox = combobox(); - gamesCombobox.getItems().addAll(server.getGamesList()); + gamesCombobox.getItems().addAll(server.getGameList()); gamesCombobox.setValue(gamesCombobox.getItems().getFirst()); final Button sendButton = button();