diff --git a/.idea/FuzzierSettings.xml b/.idea/FuzzierSettings.xml
new file mode 100644
index 0000000..4426b5e
--- /dev/null
+++ b/.idea/FuzzierSettings.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 5c84731..8fa91a7 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -2,6 +2,7 @@
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 86993b2..1044c37 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -8,5 +8,5 @@
-
+
\ No newline at end of file
diff --git a/common/pom.xml b/common/pom.xml
index 7303d3b..6c1d95c 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -24,5 +24,12 @@
jackson-dataformat-yaml
3.0.4
+
+
+ org.projectlombok
+ lombok
+ 1.18.42
+ provided
+
diff --git a/common/src/main/java/cz/jzitnik/common/socket/SocketMessage.java b/common/src/main/java/cz/jzitnik/common/socket/SocketMessage.java
new file mode 100644
index 0000000..3134c92
--- /dev/null
+++ b/common/src/main/java/cz/jzitnik/common/socket/SocketMessage.java
@@ -0,0 +1,6 @@
+package cz.jzitnik.common.socket;
+
+import java.io.Serializable;
+
+public interface SocketMessage extends Serializable {
+}
diff --git a/common/src/main/java/cz/jzitnik/common/socket/messages/Test.java b/common/src/main/java/cz/jzitnik/common/socket/messages/Test.java
new file mode 100644
index 0000000..00d3703
--- /dev/null
+++ b/common/src/main/java/cz/jzitnik/common/socket/messages/Test.java
@@ -0,0 +1,6 @@
+package cz.jzitnik.common.socket.messages;
+
+import cz.jzitnik.common.socket.SocketMessage;
+
+public class Test implements SocketMessage {
+}
diff --git a/game/.idea/encodings.xml b/game/.idea/encodings.xml
index aa00ffa..fd66ed6 100644
--- a/game/.idea/encodings.xml
+++ b/game/.idea/encodings.xml
@@ -1,6 +1,12 @@
+
+
+
+
+
+
diff --git a/game/.idea/misc.xml b/game/.idea/misc.xml
index 3202223..1044c37 100644
--- a/game/.idea/misc.xml
+++ b/game/.idea/misc.xml
@@ -1,17 +1,5 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/game/pom.xml b/game/pom.xml
index 759b5da..6056ef3 100644
--- a/game/pom.xml
+++ b/game/pom.xml
@@ -179,5 +179,11 @@
jvm
2.5
+
+
+ org.glassfish.tyrus.bundles
+ tyrus-standalone-client
+ 2.2.2
+
diff --git a/game/src/main/java/cz/jzitnik/client/Cli.java b/game/src/main/java/cz/jzitnik/client/Cli.java
index 897412f..073d3a8 100644
--- a/game/src/main/java/cz/jzitnik/client/Cli.java
+++ b/game/src/main/java/cz/jzitnik/client/Cli.java
@@ -10,6 +10,7 @@ import cz.jzitnik.client.annotations.injectors.InjectState;
import cz.jzitnik.client.events.KeyboardPressEvent;
import cz.jzitnik.client.events.MouseAction;
import cz.jzitnik.client.events.TerminalResizeEvent;
+import cz.jzitnik.client.socket.SocketEventManager;
import cz.jzitnik.client.states.RunningState;
import cz.jzitnik.client.states.TerminalState;
import cz.jzitnik.client.utils.events.EventManager;
@@ -23,6 +24,9 @@ public class Cli implements Runnable {
@InjectDependency
private EventManager eventManager;
+ @InjectDependency
+ private SocketEventManager socketEventManager;
+
@InjectState
private TerminalState terminalState;
@@ -31,7 +35,9 @@ public class Cli implements Runnable {
@Override
public void run() {
- eventManager.start(); // Start event manager thread
+ // Start event manager thread
+ eventManager.start();
+ socketEventManager.start();
try (TerminalScreen terminal = new DefaultTerminalFactory()
.setMouseCaptureMode(MouseCaptureMode.CLICK_RELEASE_DRAG_MOVE)
diff --git a/game/src/main/java/cz/jzitnik/client/Game.java b/game/src/main/java/cz/jzitnik/client/Game.java
index d6601e5..b7c5496 100644
--- a/game/src/main/java/cz/jzitnik/client/Game.java
+++ b/game/src/main/java/cz/jzitnik/client/Game.java
@@ -2,10 +2,12 @@ package cz.jzitnik.client;
import cz.jzitnik.client.annotations.injectors.InjectDependency;
import cz.jzitnik.client.game.setup.GameSetup;
+import cz.jzitnik.client.socket.Client;
import cz.jzitnik.client.utils.DependencyManager;
import cz.jzitnik.client.utils.GlobalIOHandlerRepository;
import cz.jzitnik.client.utils.ScheduledTaskManager;
import cz.jzitnik.client.utils.ThreadManager;
+import jakarta.websocket.DeploymentException;
import org.reflections.Reflections;
import java.io.IOException;
@@ -23,15 +25,23 @@ public class Game {
private ScheduledTaskManager scheduledTaskManager;
@InjectDependency
private GlobalIOHandlerRepository globalIOHandlerRepository;
+ @InjectDependency
+ private Client client;
public void start() throws IOException {
dependencyManager.inject(this);
- gameSetup.setup();
- threadManager.startAll();
- scheduledTaskManager.startAll();
- globalIOHandlerRepository.setup();
+ try {
+ client.connect();
- cli.run();
+ gameSetup.setup();
+ threadManager.startAll();
+ scheduledTaskManager.startAll();
+ globalIOHandlerRepository.setup();
+
+ cli.run();
+ } catch (DeploymentException e) {
+ throw new RuntimeException(e);
+ }
}
}
diff --git a/game/src/main/java/cz/jzitnik/client/annotations/SocketEventHandler.java b/game/src/main/java/cz/jzitnik/client/annotations/SocketEventHandler.java
new file mode 100644
index 0000000..bf6fdd9
--- /dev/null
+++ b/game/src/main/java/cz/jzitnik/client/annotations/SocketEventHandler.java
@@ -0,0 +1,14 @@
+package cz.jzitnik.client.annotations;
+
+import cz.jzitnik.common.socket.SocketMessage;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface SocketEventHandler {
+ Class extends SocketMessage> value();
+}
diff --git a/game/src/main/java/cz/jzitnik/client/events/SendSocketMessageEvent.java b/game/src/main/java/cz/jzitnik/client/events/SendSocketMessageEvent.java
new file mode 100644
index 0000000..291299a
--- /dev/null
+++ b/game/src/main/java/cz/jzitnik/client/events/SendSocketMessageEvent.java
@@ -0,0 +1,7 @@
+package cz.jzitnik.client.events;
+
+import cz.jzitnik.client.utils.events.Event;
+import cz.jzitnik.common.socket.SocketMessage;
+
+public record SendSocketMessageEvent(SocketMessage message) implements Event {
+}
diff --git a/game/src/main/java/cz/jzitnik/client/events/handlers/SendSocketMessageEventHandler.java b/game/src/main/java/cz/jzitnik/client/events/handlers/SendSocketMessageEventHandler.java
new file mode 100644
index 0000000..d5be9c4
--- /dev/null
+++ b/game/src/main/java/cz/jzitnik/client/events/handlers/SendSocketMessageEventHandler.java
@@ -0,0 +1,24 @@
+package cz.jzitnik.client.events.handlers;
+
+import cz.jzitnik.client.annotations.EventHandler;
+import cz.jzitnik.client.annotations.injectors.InjectDependency;
+import cz.jzitnik.client.events.SendSocketMessageEvent;
+import cz.jzitnik.client.socket.Client;
+import cz.jzitnik.client.utils.events.AbstractEventHandler;
+
+import java.io.IOException;
+
+@EventHandler(SendSocketMessageEvent.class)
+public class SendSocketMessageEventHandler extends AbstractEventHandler {
+ @InjectDependency
+ private Client client;
+
+ @Override
+ public void handle(SendSocketMessageEvent event) {
+ try {
+ client.send(event.message());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/game/src/main/java/cz/jzitnik/client/socket/AbstractSocketEventHandler.java b/game/src/main/java/cz/jzitnik/client/socket/AbstractSocketEventHandler.java
new file mode 100644
index 0000000..223136f
--- /dev/null
+++ b/game/src/main/java/cz/jzitnik/client/socket/AbstractSocketEventHandler.java
@@ -0,0 +1,7 @@
+package cz.jzitnik.client.socket;
+
+import cz.jzitnik.common.socket.SocketMessage;
+
+public abstract class AbstractSocketEventHandler {
+ public abstract void handle(T event);
+}
diff --git a/game/src/main/java/cz/jzitnik/client/socket/Client.java b/game/src/main/java/cz/jzitnik/client/socket/Client.java
new file mode 100644
index 0000000..36a16a9
--- /dev/null
+++ b/game/src/main/java/cz/jzitnik/client/socket/Client.java
@@ -0,0 +1,50 @@
+package cz.jzitnik.client.socket;
+
+import cz.jzitnik.client.annotations.Dependency;
+import cz.jzitnik.client.annotations.injectors.InjectDependency;
+import cz.jzitnik.common.socket.SocketMessage;
+import jakarta.websocket.*;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.*;
+import java.net.URI;
+import java.nio.ByteBuffer;
+
+@Slf4j
+@Dependency
+@ClientEndpoint
+public class Client {
+ private Session session;
+
+ @InjectDependency
+ private SocketEventManager socketEventManager;
+
+ @OnOpen
+ public void onOpen(Session session) {
+ this.session = session;
+ }
+
+ @OnMessage
+ public void onMessage(ByteBuffer buffer) {
+ try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(buffer.array()))) {
+ SocketMessage message = (SocketMessage) ois.readObject();
+ socketEventManager.emitEvent(message);
+ } catch (IOException | ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void send(SocketMessage message) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(message);
+ oos.flush();
+
+ session.getBasicRemote().sendBinary(ByteBuffer.wrap(baos.toByteArray()));
+ }
+
+ public void connect() throws DeploymentException, IOException {
+ WebSocketContainer container = ContainerProvider.getWebSocketContainer();
+ container.connectToServer(this, URI.create("ws://localhost:8025/ws"));
+ }
+}
diff --git a/game/src/main/java/cz/jzitnik/client/socket/SocketEventManager.java b/game/src/main/java/cz/jzitnik/client/socket/SocketEventManager.java
new file mode 100644
index 0000000..154802f
--- /dev/null
+++ b/game/src/main/java/cz/jzitnik/client/socket/SocketEventManager.java
@@ -0,0 +1,94 @@
+package cz.jzitnik.client.socket;
+
+import cz.jzitnik.client.annotations.Dependency;
+import cz.jzitnik.client.annotations.SocketEventHandler;
+import cz.jzitnik.client.annotations.injectors.InjectConfig;
+import cz.jzitnik.client.annotations.injectors.InjectState;
+import cz.jzitnik.client.config.ThreadPoolConfig;
+import cz.jzitnik.client.states.RunningState;
+import cz.jzitnik.client.utils.DependencyManager;
+import cz.jzitnik.common.socket.SocketMessage;
+import lombok.extern.slf4j.Slf4j;
+import org.reflections.Reflections;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+
+@Slf4j
+@Dependency
+public class SocketEventManager extends Thread {
+ private final DependencyManager dependencyManager;
+
+ private ExecutorService eventExecutor;
+ private final HashMap, AbstractSocketEventHandler>> handlers = new HashMap<>();
+ private final BlockingQueue eventQueue = new LinkedBlockingQueue<>();
+
+ @InjectConfig
+ private ThreadPoolConfig threadPoolConfig;
+
+ @InjectState
+ private RunningState runningState;
+
+ public void emitEvent(SocketMessage event) {
+ eventQueue.add(event);
+ }
+
+ public SocketEventManager(Reflections reflections, DependencyManager dependencyManager) {
+ this.dependencyManager = dependencyManager;
+ setDaemon(true);
+
+ var classes = reflections.getTypesAnnotatedWith(SocketEventHandler.class);
+
+ for (var clazz : classes) {
+ SocketEventHandler eventHandler = clazz.getAnnotation(SocketEventHandler.class);
+ try {
+ var instance = (AbstractSocketEventHandler>) clazz.getDeclaredConstructor().newInstance();
+ handlers.put(eventHandler.value(), instance);
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException |
+ NoSuchMethodException e) {
+ log.error("Failed to instantiate socket event handler: {}", clazz.getName(), e);
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ for (Object instance : handlers.values()) {
+ dependencyManager.inject(instance);
+ }
+
+ eventExecutor = Executors.newFixedThreadPool(threadPoolConfig.eventThreadCount());
+ while (runningState.isRunning()) {
+ try {
+ SocketMessage event = eventQueue.take();
+ handleEvent(event);
+ } catch (InterruptedException e) {
+ // The game is shutting down.
+ eventExecutor.shutdownNow();
+ Thread.currentThread().interrupt();
+ }
+ }
+ eventExecutor.shutdown();
+ }
+
+ @SuppressWarnings("unchecked")
+ private AbstractSocketEventHandler getHandler(Class type) {
+ return (AbstractSocketEventHandler) handlers.get(type);
+ }
+
+ @SuppressWarnings("unchecked")
+ private void handleEvent(SocketMessage event) {
+ eventExecutor.submit(() -> {
+ try {
+ AbstractSocketEventHandler handler = getHandler((Class) event.getClass());
+ handler.handle(event);
+ } catch (Exception e) {
+ log.error("Error", e);
+ }
+ });
+ }
+}
diff --git a/game/src/main/java/cz/jzitnik/client/socket/events/TestHandler.java b/game/src/main/java/cz/jzitnik/client/socket/events/TestHandler.java
new file mode 100644
index 0000000..14e181d
--- /dev/null
+++ b/game/src/main/java/cz/jzitnik/client/socket/events/TestHandler.java
@@ -0,0 +1,15 @@
+package cz.jzitnik.client.socket.events;
+
+import cz.jzitnik.client.annotations.SocketEventHandler;
+import cz.jzitnik.client.socket.AbstractSocketEventHandler;
+import cz.jzitnik.common.socket.messages.Test;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@SocketEventHandler(Test.class)
+public class TestHandler extends AbstractSocketEventHandler {
+ @Override
+ public void handle(Test event) {
+ log.debug("Got test: {}", event);
+ }
+}
diff --git a/game/src/main/java/cz/jzitnik/client/utils/events/AbstractEventHandler.java b/game/src/main/java/cz/jzitnik/client/utils/events/AbstractEventHandler.java
index 51dd639..e5924d3 100644
--- a/game/src/main/java/cz/jzitnik/client/utils/events/AbstractEventHandler.java
+++ b/game/src/main/java/cz/jzitnik/client/utils/events/AbstractEventHandler.java
@@ -1,5 +1,5 @@
package cz.jzitnik.client.utils.events;
-public abstract class AbstractEventHandler {
+public abstract class AbstractEventHandler {
public abstract void handle(T event);
}
diff --git a/game/src/main/java/cz/jzitnik/client/utils/events/EventManager.java b/game/src/main/java/cz/jzitnik/client/utils/events/EventManager.java
index d1a7b03..ddcf8d9 100644
--- a/game/src/main/java/cz/jzitnik/client/utils/events/EventManager.java
+++ b/game/src/main/java/cz/jzitnik/client/utils/events/EventManager.java
@@ -24,7 +24,7 @@ public class EventManager extends Thread {
private ThreadPoolConfig threadPoolConfig;
private ExecutorService eventExecutor;
- private final HashMap, AbstractEventHandler extends Event>> handlers = new HashMap<>();
+ private final HashMap, AbstractEventHandler>> handlers = new HashMap<>();
private final BlockingQueue eventQueue = new LinkedBlockingQueue<>();
private final DependencyManager dependencyManager;
@@ -74,7 +74,7 @@ public class EventManager extends Thread {
for (var clazz : classes) {
EventHandler eventHandler = clazz.getAnnotation(EventHandler.class);
try {
- var instance = (AbstractEventHandler extends Event>) clazz.getDeclaredConstructor().newInstance();
+ var instance = (AbstractEventHandler>) clazz.getDeclaredConstructor().newInstance();
handlers.put(eventHandler.value(), instance);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException |
NoSuchMethodException e) {
diff --git a/server/pom.xml b/server/pom.xml
index b335cca..6b9d574 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -18,5 +18,49 @@
common
1.0-SNAPSHOT
+
+
+ org.projectlombok
+ lombok
+ 1.18.42
+ provided
+
+
+
+ jakarta.websocket
+ jakarta.websocket-api
+ 2.3.0-M2
+
+
+
+ org.glassfish.tyrus
+ tyrus-server
+ 2.2.2
+ compile
+
+
+
+ org.glassfish.tyrus
+ tyrus-container-grizzly-server
+ 2.2.2
+
+
+
+ org.glassfish.tyrus
+ tyrus-client
+ 2.2.2
+
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.17
+
+
+
+ ch.qos.logback
+ logback-classic
+ 1.5.25
+
diff --git a/server/src/main/java/cz/jzitnik/server/Main.java b/server/src/main/java/cz/jzitnik/server/Main.java
index 64fd88d..a752d0a 100644
--- a/server/src/main/java/cz/jzitnik/server/Main.java
+++ b/server/src/main/java/cz/jzitnik/server/Main.java
@@ -1,7 +1,18 @@
package cz.jzitnik.server;
-public class Main {
- static void main() {
+import jakarta.websocket.DeploymentException;
+import org.glassfish.tyrus.server.Server;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Scanner;
+
+public class Main {
+ static void main() throws DeploymentException {
+ Map properties = new HashMap<>();
+ Server server = new Server("localhost", 8025, "/", properties, WebSocket.class);
+
+ server.start();
+ new Scanner(System.in).nextLine();
}
}
diff --git a/server/src/main/java/cz/jzitnik/server/WebSocket.java b/server/src/main/java/cz/jzitnik/server/WebSocket.java
new file mode 100644
index 0000000..5fa6558
--- /dev/null
+++ b/server/src/main/java/cz/jzitnik/server/WebSocket.java
@@ -0,0 +1,53 @@
+package cz.jzitnik.server;
+
+import cz.jzitnik.common.socket.SocketMessage;
+import cz.jzitnik.common.socket.messages.Test;
+import jakarta.websocket.*;
+import jakarta.websocket.server.ServerEndpoint;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.*;
+
+@Slf4j
+@ServerEndpoint("/ws")
+public class WebSocket {
+
+ @OnOpen
+ public void onOpen(Session session) {
+ log.debug("Client connected: " + session.getId());
+
+ try {
+ SocketMessage response = new Test();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(response);
+ oos.flush();
+ session.getBasicRemote().sendBinary(java.nio.ByteBuffer.wrap(baos.toByteArray()));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ // Receive binary data from client
+ @OnMessage
+ public void onMessage(byte[] bytes, Session session) {
+ try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+ ObjectInputStream ois = new ObjectInputStream(bais)) {
+
+ SocketMessage socketMessage = (SocketMessage) ois.readObject();
+ System.out.println("Received: " + socketMessage);
+ } catch (IOException | ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @OnClose
+ public void onClose(Session session, CloseReason reason) {
+ System.out.println("Connection closed: " + reason);
+ }
+
+ @OnError
+ public void onError(Session session, Throwable throwable) {
+ throwable.printStackTrace();
+ }
+}