feat: Room Task Scheduler for mob logic

This commit is contained in:
2026-01-01 13:36:31 +01:00
parent d747c4d248
commit d9f7f5a2ac
16 changed files with 177 additions and 10 deletions

View File

@@ -0,0 +1,11 @@
package cz.jzitnik.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.METHOD)
public @interface PostInit {
}

View File

@@ -5,6 +5,6 @@ import lombok.Getter;
@Getter
@Config
public class EventThreadPoolConfig {
private final int threadCount = 8;
public class Logging {
private final boolean showPlayerCordsLogs = false;
}

View File

@@ -0,0 +1,11 @@
package cz.jzitnik.config;
import cz.jzitnik.annotations.Config;
import lombok.Getter;
@Getter
@Config
public class ThreadPoolConfig {
private final int eventThreadCount = 8;
private final int taskThreadCount = 6;
}

View File

@@ -9,6 +9,7 @@ import cz.jzitnik.utils.DependencyManager;
import cz.jzitnik.utils.StateManager;
import cz.jzitnik.utils.ThreadManager;
import cz.jzitnik.utils.events.AbstractEventHandler;
import cz.jzitnik.utils.roomtasks.RoomTaskScheduler;
@EventHandler(ExitEvent.class)
public class ExitEventHandler extends AbstractEventHandler<ExitEvent> {
@@ -18,6 +19,9 @@ public class ExitEventHandler extends AbstractEventHandler<ExitEvent> {
@InjectState
private RunningState runningState;
@InjectDependency
private RoomTaskScheduler roomTaskScheduler;
public ExitEventHandler(DependencyManager dm) {
super(dm);
}
@@ -25,6 +29,7 @@ public class ExitEventHandler extends AbstractEventHandler<ExitEvent> {
@Override
public void handle(ExitEvent event) {
threadManager.shutdownAll();
roomTaskScheduler.finalShutdown();
runningState.setRunning(false);
System.exit(0); // Pls don't blame me
}

View File

@@ -22,6 +22,7 @@ import cz.jzitnik.utils.DependencyManager;
import cz.jzitnik.utils.RerenderUtils;
import cz.jzitnik.utils.events.AbstractEventHandler;
import cz.jzitnik.utils.events.EventManager;
import cz.jzitnik.utils.roomtasks.RoomTaskScheduler;
import lombok.extern.slf4j.Slf4j;
import java.awt.image.BufferedImage;
@@ -52,6 +53,9 @@ public class FullRoomDrawHandler extends AbstractEventHandler<FullRoomDraw> {
@InjectConfig
private Debugging debugging;
@InjectDependency
private RoomTaskScheduler roomTaskScheduler;
public FullRoomDrawHandler(DependencyManager dm) {
super(dm);
}
@@ -86,6 +90,7 @@ public class FullRoomDrawHandler extends AbstractEventHandler<FullRoomDraw> {
if (renderState.isFirstRender() || event.isFullRerender()) {
eventManager.emitEvent(RerenderScreen.full(terminalSize));
renderState.setFirstRender(false);
roomTaskScheduler.setupNewSchedulers(currentRoom);
} else {
eventManager.emitEvent(new RerenderScreen(partsToRerender.toArray(RerenderScreen.ScreenPart[]::new)));
}

View File

@@ -7,6 +7,7 @@ import cz.jzitnik.annotations.injectors.InjectConfig;
import cz.jzitnik.annotations.injectors.InjectDependency;
import cz.jzitnik.annotations.injectors.InjectState;
import cz.jzitnik.config.Debugging;
import cz.jzitnik.config.Logging;
import cz.jzitnik.config.PlayerConfig;
import cz.jzitnik.events.PlayerMoveEvent;
import cz.jzitnik.events.RerenderScreen;
@@ -58,6 +59,9 @@ public class PlayerMoveEventHandler extends AbstractEventHandler<PlayerMoveEvent
@InjectState
private RenderState renderState;
@InjectConfig
private Logging logging;
@Override
public void handle(PlayerMoveEvent event) {
if (renderState.isTerminalTooSmall()) {
@@ -121,7 +125,9 @@ public class PlayerMoveEventHandler extends AbstractEventHandler<PlayerMoveEvent
}
int newPlayerX = playerCords.getX();
int newPlayerY = playerCords.getY();
log.debug("x: {}, y: {}", newPlayerX, newPlayerY);
if (logging.isShowPlayerCordsLogs()) {
log.debug("x: {}, y: {}", newPlayerX, newPlayerY);
}
BufferedImage playerTexture = RerenderUtils.getPlayer(resourceManager, player);
int forStartX = Math.min(originalPlayerX, newPlayerX);
int forStartY = Math.min(originalPlayerY, newPlayerY);

View File

@@ -11,6 +11,7 @@ import cz.jzitnik.game.utils.RoomCords;
import cz.jzitnik.utils.DependencyManager;
import cz.jzitnik.utils.events.AbstractEventHandler;
import cz.jzitnik.utils.events.EventManager;
import cz.jzitnik.utils.roomtasks.RoomTaskScheduler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@@ -26,6 +27,9 @@ public class RoomChangeEventHandler extends AbstractEventHandler<RoomChangeEvent
@InjectDependency
private EventManager eventManager;
@InjectDependency
private RoomTaskScheduler roomTaskScheduler;
@Override
public void handle(RoomChangeEvent event) {
RoomCords playerCords = gameState.getPlayer().getPlayerCords();
@@ -49,6 +53,7 @@ public class RoomChangeEventHandler extends AbstractEventHandler<RoomChangeEvent
}
gameState.setCurrentRoom(newRoom);
roomTaskScheduler.setupNewSchedulers(newRoom);
eventManager.emitEvent(new FullRoomDraw());
}
}

View File

@@ -1,6 +1,7 @@
package cz.jzitnik.game;
import cz.jzitnik.game.objects.GameObject;
import cz.jzitnik.game.objects.Mob;
import cz.jzitnik.ui.pixels.Empty;
import cz.jzitnik.ui.pixels.Pixel;
import lombok.Getter;
@@ -17,6 +18,7 @@ public class GameRoom {
private final Pixel[][] overrideBuffer;
private final ResourceManager.Resource texture;
private final List<GameObject> objects = new ArrayList<>();
private final List<Mob> mobs = new ArrayList<>();
private final List<GameRoomPart> colliders = new ArrayList<>();
public GameRoom(ResourceManager.Resource texture) {
@@ -41,6 +43,10 @@ public class GameRoom {
objects.add(gameObject);
}
public void addMob(Mob mob) {
mobs.add(mob);
}
public void addCollider(GameRoomPart collider) {
colliders.add(collider);
}

View File

@@ -0,0 +1,11 @@
package cz.jzitnik.game.objects;
import cz.jzitnik.utils.roomtasks.RoomTask;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public abstract class Mob {
private final RoomTask task;
}

View File

@@ -6,9 +6,17 @@ import cz.jzitnik.game.ResourceManager;
import cz.jzitnik.game.items.GameItem;
import cz.jzitnik.game.items.WoodenSword;
import cz.jzitnik.game.objects.Chest;
import cz.jzitnik.game.objects.Mob;
import cz.jzitnik.game.utils.RoomCords;
import cz.jzitnik.states.SoundState;
import cz.jzitnik.utils.DependencyManager;
import cz.jzitnik.utils.StateManager;
import cz.jzitnik.utils.roomtasks.RoomTask;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
@Slf4j
public class MainRoom extends GameRoom {
public MainRoom(DependencyManager dependencyManager, ResourceManager resourceManager) {
super(ResourceManager.Resource.ROOM1);
@@ -23,5 +31,14 @@ public class MainRoom extends GameRoom {
new RoomCords(135, 15)
));
addObject(chest);
SoundState soundState = dependencyManager.getDependencyOrThrow(StateManager.class).getOrThrow(SoundState.class);
Mob testMob = new Mob(new RoomTask(
() -> log.debug("Sound: {}", soundState.getSoundVolume()),
1,
TimeUnit.SECONDS
)) {};
addMob(testMob);
}
}

View File

@@ -137,7 +137,7 @@ public abstract class VideoPlayScreen extends Screen {
} finally {
if (tempVideo != null && tempVideo.exists()) {
if (!tempVideo.delete()) {
System.err.println("Warning: failed to delete temp video file " + tempVideo.getAbsolutePath());
log.error("Warning: failed to delete temp video file {}", tempVideo.getAbsolutePath());
}
}
}

View File

@@ -4,9 +4,11 @@ import cz.jzitnik.annotations.ThreadRegistry;
import cz.jzitnik.annotations.injectors.InjectState;
import cz.jzitnik.states.SoundState;
import cz.jzitnik.utils.ShutdownableThread;
import lombok.extern.slf4j.Slf4j;
import javax.sound.sampled.*;
@Slf4j
@ThreadRegistry
public class MicrophoneThread extends ShutdownableThread {
@InjectState
@@ -20,7 +22,7 @@ public class MicrophoneThread extends ShutdownableThread {
DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
if (!AudioSystem.isLineSupported(info)) {
System.err.println("Line not supported: " + info);
log.error("Line not supported: {}", info);
return;
}
@@ -42,7 +44,7 @@ public class MicrophoneThread extends ShutdownableThread {
}
}
} catch (LineUnavailableException e) {
System.err.println("Microphone line unavailable: " + e.getMessage());
log.error("Microphone line unavailable: {}", e.getMessage());
} finally {
if (line != null) {
line.stop();

View File

@@ -7,6 +7,7 @@ import com.google.common.collect.ClassToInstanceMap;
import com.google.common.collect.MutableClassToInstanceMap;
import cz.jzitnik.annotations.Config;
import cz.jzitnik.annotations.Dependency;
import cz.jzitnik.annotations.PostInit;
import cz.jzitnik.annotations.injectors.InjectConfig;
import cz.jzitnik.annotations.injectors.InjectDependency;
import cz.jzitnik.annotations.injectors.InjectState;
@@ -16,6 +17,7 @@ import org.reflections.Reflections;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
@Slf4j
@@ -153,5 +155,24 @@ public class DependencyManager {
}
}
}
for (Method method : instance.getClass().getDeclaredMethods()) {
if (!method.isAnnotationPresent(PostInit.class)) {
continue;
}
if (method.getParameterCount() != 0) {
throw new IllegalStateException("@PostInit method must have no parameters: " + method);
}
try {
method.setAccessible(true);
method.invoke(instance);
} catch (Exception e) {
throw new RuntimeException(
"Failed to invoke @PostInit method: " + method, e
);
}
}
}
}

View File

@@ -4,12 +4,11 @@ import cz.jzitnik.annotations.Dependency;
import cz.jzitnik.annotations.EventHandler;
import cz.jzitnik.annotations.injectors.InjectConfig;
import cz.jzitnik.annotations.injectors.InjectState;
import cz.jzitnik.config.EventThreadPoolConfig;
import cz.jzitnik.config.ThreadPoolConfig;
import cz.jzitnik.states.RunningState;
import cz.jzitnik.utils.DependencyManager;
import lombok.extern.slf4j.Slf4j;
import org.reflections.Reflections;
import org.w3c.dom.events.EventException;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
@@ -21,7 +20,7 @@ public class EventManager extends Thread {
@InjectState
private RunningState runningState;
@InjectConfig
private EventThreadPoolConfig eventThreadPoolConfig;
private ThreadPoolConfig threadPoolConfig;
private ExecutorService eventExecutor;
private final HashMap<Class<? extends Event>, AbstractEventHandler<? extends Event>> handlers = new HashMap<>();
@@ -62,7 +61,7 @@ public class EventManager extends Thread {
dependencyManager.inject(instance);
}
eventExecutor = Executors.newFixedThreadPool(eventThreadPoolConfig.getThreadCount());
eventExecutor = Executors.newFixedThreadPool(threadPoolConfig.getEventThreadCount());
while (runningState.isRunning()) {
try {
Event event = eventQueue.take();

View File

@@ -0,0 +1,6 @@
package cz.jzitnik.utils.roomtasks;
import java.util.concurrent.TimeUnit;
public record RoomTask(Runnable task, long rate, TimeUnit rateUnit) {
}

View File

@@ -0,0 +1,62 @@
package cz.jzitnik.utils.roomtasks;
import cz.jzitnik.annotations.Dependency;
import cz.jzitnik.annotations.PostInit;
import cz.jzitnik.annotations.injectors.InjectConfig;
import cz.jzitnik.config.ThreadPoolConfig;
import cz.jzitnik.game.GameRoom;
import cz.jzitnik.game.objects.Mob;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Slf4j
@Dependency
public class RoomTaskScheduler {
ScheduledExecutorService scheduler;
@InjectConfig
private ThreadPoolConfig threadPoolConfig;
private boolean firstRun = true;
@PostInit
public void initExecutor() {
scheduler = Executors.newScheduledThreadPool(threadPoolConfig.getTaskThreadCount());
}
private void shutdownAll() {
scheduler.shutdown();
try {
if (!scheduler.awaitTermination(1, TimeUnit.SECONDS)) {
scheduler.shutdownNow();
if (!scheduler.awaitTermination(1, TimeUnit.SECONDS)) {
log.error("Pool did not terminate");
}
}
} catch (InterruptedException e) {
scheduler.shutdownNow();
Thread.currentThread().interrupt();
}
}
public void finalShutdown() {
shutdownAll();
}
public void setupNewSchedulers(GameRoom currentRoom) {
if (!firstRun) {
shutdownAll();
initExecutor();
}
for (Mob mob : currentRoom.getMobs()) {
RoomTask task = mob.getTask();
scheduler.scheduleAtFixedRate(task.task(), 0, task.rate(), task.rateUnit());
}
firstRun = false;
}
}