From 4041c8f7d2c4b9f21bc60bf2ee0d4503f2a27411 Mon Sep 17 00:00:00 2001
From: jzitnik-dev <email@jzitnik.dev>
Date: Wed, 12 Mar 2025 09:41:13 +0100
Subject: [PATCH] feat: Better thread handling

---
 src/main/java/cz/jzitnik/Main.java            | 13 +---
 .../game/annotations/ThreadAnnotation.java    | 11 +++
 .../game/mobs/EntitySpawnProvider.java        |  4 -
 .../jzitnik/game/threads/ThreadProvider.java  | 74 +++++++++++++++++++
 .../{ => list}/HealthRegenerationThread.java  |  4 +-
 .../threads/{ => list}/HungerDrainThread.java |  4 +-
 .../{ => list}/InputHandlerThread.java        |  7 +-
 .../game/threads/list/NoHungerThread.java     | 25 +++++++
 8 files changed, 124 insertions(+), 18 deletions(-)
 create mode 100644 src/main/java/cz/jzitnik/game/annotations/ThreadAnnotation.java
 create mode 100644 src/main/java/cz/jzitnik/game/threads/ThreadProvider.java
 rename src/main/java/cz/jzitnik/game/threads/{ => list}/HealthRegenerationThread.java (80%)
 rename src/main/java/cz/jzitnik/game/threads/{ => list}/HungerDrainThread.java (79%)
 rename src/main/java/cz/jzitnik/game/threads/{ => list}/InputHandlerThread.java (95%)
 create mode 100644 src/main/java/cz/jzitnik/game/threads/list/NoHungerThread.java

diff --git a/src/main/java/cz/jzitnik/Main.java b/src/main/java/cz/jzitnik/Main.java
index 2aa20ba..1131fba 100644
--- a/src/main/java/cz/jzitnik/Main.java
+++ b/src/main/java/cz/jzitnik/Main.java
@@ -3,9 +3,7 @@ package cz.jzitnik;
 import cz.jzitnik.game.GameSaver;
 import cz.jzitnik.game.logic.CustomLogicProvider;
 import cz.jzitnik.game.mobs.EntityLogicProvider;
-import cz.jzitnik.game.threads.HealthRegenerationThread;
-import cz.jzitnik.game.threads.HungerDrainThread;
-import cz.jzitnik.game.threads.InputHandlerThread;
+import cz.jzitnik.game.threads.ThreadProvider;
 import cz.jzitnik.game.ui.*;
 import cz.jzitnik.game.SpriteLoader;
 import cz.jzitnik.tui.ScreenRenderer;
@@ -32,16 +30,11 @@ public class Main {
 
             final boolean[] isRunning = { true };
 
-            Thread inputHandlerThread = new InputHandlerThread(game, terminal, screenRenderer, isRunning);
-            Thread healingThread = new HealthRegenerationThread(game.getPlayer());
-            Thread hungerDrainThread = new HungerDrainThread(game.getPlayer());
             EntityLogicProvider entityLogicProvider = new EntityLogicProvider();
             CustomLogicProvider customLogicProvider = new CustomLogicProvider();
 
-            // Start all threads
-            healingThread.start();
-            hungerDrainThread.start();
-            inputHandlerThread.start();
+            ThreadProvider threadProvider = new ThreadProvider(game, screenRenderer, terminal, isRunning);
+            threadProvider.start();
 
             while (isRunning[0]) {
                 try {
diff --git a/src/main/java/cz/jzitnik/game/annotations/ThreadAnnotation.java b/src/main/java/cz/jzitnik/game/annotations/ThreadAnnotation.java
new file mode 100644
index 0000000..9a2a6fc
--- /dev/null
+++ b/src/main/java/cz/jzitnik/game/annotations/ThreadAnnotation.java
@@ -0,0 +1,11 @@
+package cz.jzitnik.game.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface ThreadAnnotation {
+}
diff --git a/src/main/java/cz/jzitnik/game/mobs/EntitySpawnProvider.java b/src/main/java/cz/jzitnik/game/mobs/EntitySpawnProvider.java
index 481f278..f0b55a6 100644
--- a/src/main/java/cz/jzitnik/game/mobs/EntitySpawnProvider.java
+++ b/src/main/java/cz/jzitnik/game/mobs/EntitySpawnProvider.java
@@ -6,9 +6,6 @@ import java.util.Set;
 
 import cz.jzitnik.game.Game;
 import cz.jzitnik.game.annotations.EntitySpawn;
-import cz.jzitnik.game.entities.Block;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
 import org.jline.terminal.Terminal;
 import org.reflections.Reflections;
 
@@ -21,7 +18,6 @@ public class EntitySpawnProvider {
         int playerY = playerLocation[1];
 
         for (EntitySpawnInterface entitySpawnInterface : spawnList) {
-
             entitySpawnInterface.spawn(playerX, playerY, game, terminal);
         }
     }
diff --git a/src/main/java/cz/jzitnik/game/threads/ThreadProvider.java b/src/main/java/cz/jzitnik/game/threads/ThreadProvider.java
new file mode 100644
index 0000000..08351a8
--- /dev/null
+++ b/src/main/java/cz/jzitnik/game/threads/ThreadProvider.java
@@ -0,0 +1,74 @@
+package cz.jzitnik.game.threads;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import cz.jzitnik.game.Game;
+import cz.jzitnik.game.annotations.ThreadAnnotation;
+import cz.jzitnik.game.entities.Player;
+import cz.jzitnik.tui.ScreenRenderer;
+
+import org.jline.terminal.Terminal;
+import org.reflections.Reflections;
+
+public class ThreadProvider {
+    private final Game game;
+    private final ScreenRenderer screenRenderer;
+    private final Terminal terminal;
+    private final boolean[] isRunning;
+    private final List<Thread> list = new ArrayList<>();
+
+    public void start() {
+        for (Thread thread : list) {
+            thread.start();
+        }
+    }
+
+    public ThreadProvider(Game game, ScreenRenderer screenRenderer, Terminal terminal, boolean[] isRunning) {
+        this.game = game;
+        this.screenRenderer = screenRenderer;
+        this.terminal = terminal;
+        this.isRunning = isRunning;
+        registerHandlers();
+    }
+
+    private void registerHandlers() {
+        Reflections reflections = new Reflections("cz.jzitnik.game.threads");
+        Set<Class<?>> handlerClasses = reflections.getTypesAnnotatedWith(ThreadAnnotation.class);
+
+        for (Class<?> clazz : handlerClasses) {
+            if (Thread.class.isAssignableFrom(clazz)) {
+                try {
+                    for (var constructor : clazz.getDeclaredConstructors()) {
+                        var paramTypes = constructor.getParameterTypes();
+                        var params = new Object[paramTypes.length];
+                        boolean suitable = true;
+
+                        for (int i = 0; i < paramTypes.length; i++) {
+                            Class<?> type = paramTypes[i];
+                            if (type == Game.class) params[i] = game;
+                            else if (type == ScreenRenderer.class) params[i] = screenRenderer;
+                            else if (type == Terminal.class) params[i] = terminal;
+                            else if (type == boolean[].class) params[i] = isRunning;
+                            else if (type == Player.class) params[i] = game.getPlayer();
+                            else {
+                                suitable = false;
+                                break;
+                            }
+                        }
+
+                        if (suitable) {
+                            constructor.setAccessible(true);
+                            Thread instance = (Thread) constructor.newInstance(params);
+                            list.add(instance);
+                            break; // Found a matching constructor, go to next class
+                        }
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/cz/jzitnik/game/threads/HealthRegenerationThread.java b/src/main/java/cz/jzitnik/game/threads/list/HealthRegenerationThread.java
similarity index 80%
rename from src/main/java/cz/jzitnik/game/threads/HealthRegenerationThread.java
rename to src/main/java/cz/jzitnik/game/threads/list/HealthRegenerationThread.java
index 451b523..d7f7bc5 100644
--- a/src/main/java/cz/jzitnik/game/threads/HealthRegenerationThread.java
+++ b/src/main/java/cz/jzitnik/game/threads/list/HealthRegenerationThread.java
@@ -1,9 +1,11 @@
-package cz.jzitnik.game.threads;
+package cz.jzitnik.game.threads.list;
 
+import cz.jzitnik.game.annotations.ThreadAnnotation;
 import cz.jzitnik.game.entities.Player;
 import lombok.AllArgsConstructor;
 
 @AllArgsConstructor
+@ThreadAnnotation
 public class HealthRegenerationThread extends Thread {
     private final Player player;
 
diff --git a/src/main/java/cz/jzitnik/game/threads/HungerDrainThread.java b/src/main/java/cz/jzitnik/game/threads/list/HungerDrainThread.java
similarity index 79%
rename from src/main/java/cz/jzitnik/game/threads/HungerDrainThread.java
rename to src/main/java/cz/jzitnik/game/threads/list/HungerDrainThread.java
index 397f44a..28deca4 100644
--- a/src/main/java/cz/jzitnik/game/threads/HungerDrainThread.java
+++ b/src/main/java/cz/jzitnik/game/threads/list/HungerDrainThread.java
@@ -1,9 +1,11 @@
-package cz.jzitnik.game.threads;
+package cz.jzitnik.game.threads.list;
 
+import cz.jzitnik.game.annotations.ThreadAnnotation;
 import cz.jzitnik.game.entities.Player;
 import lombok.AllArgsConstructor;
 
 @AllArgsConstructor
+@ThreadAnnotation
 public class HungerDrainThread extends Thread {
     private final Player player;
 
diff --git a/src/main/java/cz/jzitnik/game/threads/InputHandlerThread.java b/src/main/java/cz/jzitnik/game/threads/list/InputHandlerThread.java
similarity index 95%
rename from src/main/java/cz/jzitnik/game/threads/InputHandlerThread.java
rename to src/main/java/cz/jzitnik/game/threads/list/InputHandlerThread.java
index 6647246..0e92837 100644
--- a/src/main/java/cz/jzitnik/game/threads/InputHandlerThread.java
+++ b/src/main/java/cz/jzitnik/game/threads/list/InputHandlerThread.java
@@ -1,4 +1,4 @@
-package cz.jzitnik.game.threads;
+package cz.jzitnik.game.threads.list;
 
 import cz.jzitnik.game.Game;
 import cz.jzitnik.game.blocks.Chest;
@@ -9,8 +9,11 @@ import cz.jzitnik.tui.ScreenRenderer;
 import org.jline.terminal.MouseEvent;
 import org.jline.terminal.Terminal;
 import java.io.IOException;
+import java.nio.BufferUnderflowException;
 import java.util.Optional;
+import cz.jzitnik.game.annotations.ThreadAnnotation;
 
+@ThreadAnnotation
 public class InputHandlerThread extends Thread {
     private final Game game;
     private final Terminal terminal;
@@ -91,7 +94,7 @@ public class InputHandlerThread extends Thread {
                     }
                 }
             }
-        } catch (IOException e) {
+        } catch (IOException | BufferUnderflowException e) {
             e.printStackTrace();
         }
     }
diff --git a/src/main/java/cz/jzitnik/game/threads/list/NoHungerThread.java b/src/main/java/cz/jzitnik/game/threads/list/NoHungerThread.java
new file mode 100644
index 0000000..1e1028c
--- /dev/null
+++ b/src/main/java/cz/jzitnik/game/threads/list/NoHungerThread.java
@@ -0,0 +1,25 @@
+package cz.jzitnik.game.threads.list;
+
+import cz.jzitnik.game.annotations.ThreadAnnotation;
+import cz.jzitnik.game.entities.Player;
+import lombok.AllArgsConstructor;
+
+@AllArgsConstructor
+@ThreadAnnotation
+public class NoHungerThread extends Thread {
+    private final Player player;
+
+    @Override
+    public void run() {
+        while (true) {
+            try {
+                Thread.sleep(6000);
+                if (player.getHunger() == 0) {
+                    player.setHealth(player.getHunger() - 1);
+                }
+            } catch (InterruptedException e) {
+                break;
+            }
+        }
+    }
+}