From ae5d34b41ab1d3d61c144e2a38399301e1364542 Mon Sep 17 00:00:00 2001 From: jzitnik-dev Date: Sun, 16 Mar 2025 18:20:18 +0100 Subject: [PATCH] refactor(transient): Rewritten transient handling Rewritten transient handling using @AutoTransient annotation. Now we don't have to override readObject method to automatically initilize transient properties in serilizable classes. --- src/main/java/cz/jzitnik/game/Game.java | 25 +++---- .../game/annotations/AutoTransient.java | 16 +++++ .../AutoTransientInitializer.java | 5 ++ .../autotransient/AutoTransientSupport.java | 67 +++++++++++++++++++ .../autotransient/DefaultInitializer.java | 8 +++ .../initilizers/GameMiningInitializer.java | 10 +++ .../initilizers/GameWindowInitializer.java | 11 +++ .../game/threads/list/InputHandlerThread.java | 8 +-- .../java/cz/jzitnik/game/ui/Inventory.java | 15 ++--- 9 files changed, 136 insertions(+), 29 deletions(-) create mode 100644 src/main/java/cz/jzitnik/game/annotations/AutoTransient.java create mode 100644 src/main/java/cz/jzitnik/game/core/autotransient/AutoTransientInitializer.java create mode 100644 src/main/java/cz/jzitnik/game/core/autotransient/AutoTransientSupport.java create mode 100644 src/main/java/cz/jzitnik/game/core/autotransient/DefaultInitializer.java create mode 100644 src/main/java/cz/jzitnik/game/core/autotransient/initilizers/GameMiningInitializer.java create mode 100644 src/main/java/cz/jzitnik/game/core/autotransient/initilizers/GameWindowInitializer.java diff --git a/src/main/java/cz/jzitnik/game/Game.java b/src/main/java/cz/jzitnik/game/Game.java index ca2232d..18f08ed 100644 --- a/src/main/java/cz/jzitnik/game/Game.java +++ b/src/main/java/cz/jzitnik/game/Game.java @@ -10,9 +10,13 @@ import cz.jzitnik.game.handlers.place.CustomPlaceHandler; import cz.jzitnik.game.mobs.EntitySpawnProvider; import cz.jzitnik.game.sprites.Breaking; import cz.jzitnik.game.sprites.Steve; +import cz.jzitnik.game.annotations.AutoTransient; import cz.jzitnik.game.annotations.BreaksByPlace; import cz.jzitnik.game.blocks.Chest; import cz.jzitnik.game.blocks.Furnace; +import cz.jzitnik.game.core.autotransient.AutoTransientSupport; +import cz.jzitnik.game.core.autotransient.initilizers.GameMiningInitializer; +import cz.jzitnik.game.core.autotransient.initilizers.GameWindowInitializer; import cz.jzitnik.game.ui.Window; import cz.jzitnik.game.ui.Inventory; import cz.jzitnik.game.handlers.rightclick.RightClickHandlerProvider; @@ -22,35 +26,26 @@ import lombok.Getter; import lombok.Setter; import org.jline.terminal.Terminal; -import java.io.Serial; -import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @Getter -public class Game implements Serializable { +public class Game extends AutoTransientSupport { @SuppressWarnings("unchecked") private final List[][] world = (List[][]) new CopyOnWriteArrayList[256][512]; private final Player player = new Player(); + @AutoTransient(initializer = GameMiningInitializer.class) private transient boolean mining = false; @Setter - private Window window = Window.WORLD; + @AutoTransient(initializer = GameWindowInitializer.class) + private transient Window window = Window.WORLD; private final Inventory inventory = new Inventory(); - + @AutoTransient private transient EntitySpawnProvider entitySpawnProvider = new EntitySpawnProvider(); - + @AutoTransient private transient GameStates gameStates = new GameStates(this); - @Serial - private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException { - in.defaultReadObject(); - - entitySpawnProvider = new EntitySpawnProvider(); - gameStates = new GameStates(this); - mining = false; - } - public Game() { Generation.generateWorld(this); } diff --git a/src/main/java/cz/jzitnik/game/annotations/AutoTransient.java b/src/main/java/cz/jzitnik/game/annotations/AutoTransient.java new file mode 100644 index 0000000..a8012c2 --- /dev/null +++ b/src/main/java/cz/jzitnik/game/annotations/AutoTransient.java @@ -0,0 +1,16 @@ +package cz.jzitnik.game.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import cz.jzitnik.game.core.autotransient.AutoTransientInitializer; +import cz.jzitnik.game.core.autotransient.DefaultInitializer; + + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface AutoTransient { + Class> initializer() default DefaultInitializer.class; +} diff --git a/src/main/java/cz/jzitnik/game/core/autotransient/AutoTransientInitializer.java b/src/main/java/cz/jzitnik/game/core/autotransient/AutoTransientInitializer.java new file mode 100644 index 0000000..5a66737 --- /dev/null +++ b/src/main/java/cz/jzitnik/game/core/autotransient/AutoTransientInitializer.java @@ -0,0 +1,5 @@ +package cz.jzitnik.game.core.autotransient; + +public interface AutoTransientInitializer { + T initialize(Object parent); +} diff --git a/src/main/java/cz/jzitnik/game/core/autotransient/AutoTransientSupport.java b/src/main/java/cz/jzitnik/game/core/autotransient/AutoTransientSupport.java new file mode 100644 index 0000000..79cbb45 --- /dev/null +++ b/src/main/java/cz/jzitnik/game/core/autotransient/AutoTransientSupport.java @@ -0,0 +1,67 @@ +package cz.jzitnik.game.core.autotransient; + +import cz.jzitnik.game.annotations.AutoTransient; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serial; +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; + +public abstract class AutoTransientSupport implements Serializable { + + @Serial + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + reinitializeAutoTransients(); + } + + protected void reinitializeAutoTransients() { + for (Field field : this.getClass().getDeclaredFields()) { + if (field.isAnnotationPresent(AutoTransient.class)) { + field.setAccessible(true); + AutoTransient annotation = field.getAnnotation(AutoTransient.class); + try { + Object value; + + // Use initializer if provided + Class> initializerClass = annotation.initializer(); + if (!initializerClass.equals(DefaultInitializer.class)) { + AutoTransientInitializer initializer = initializerClass.getDeclaredConstructor().newInstance(); + value = initializer.initialize(this); + } else { + // Fallback to default instantiation + value = instantiateField(field.getType()); + } + + if (value != null) { + field.set(this, value); + } + + } catch (Exception e) { + throw new RuntimeException("Failed to reinitialize @AutoTransient field: " + field.getName(), e); + } + } + } + } + + protected Object instantiateField(Class clazz) { + try { + // Try constructor with (this) reference first + Constructor constructor = clazz.getDeclaredConstructor(this.getClass()); + return constructor.newInstance(this); + } catch (NoSuchMethodException e) { + // Fallback to default constructor + try { + return clazz.getDeclaredConstructor().newInstance(); + } catch (Exception ex) { + ex.printStackTrace(); + return null; + } + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/src/main/java/cz/jzitnik/game/core/autotransient/DefaultInitializer.java b/src/main/java/cz/jzitnik/game/core/autotransient/DefaultInitializer.java new file mode 100644 index 0000000..cf43534 --- /dev/null +++ b/src/main/java/cz/jzitnik/game/core/autotransient/DefaultInitializer.java @@ -0,0 +1,8 @@ +package cz.jzitnik.game.core.autotransient; + +public class DefaultInitializer implements AutoTransientInitializer { + @Override + public Object initialize(Object parent) { + return null; + } +} diff --git a/src/main/java/cz/jzitnik/game/core/autotransient/initilizers/GameMiningInitializer.java b/src/main/java/cz/jzitnik/game/core/autotransient/initilizers/GameMiningInitializer.java new file mode 100644 index 0000000..a9991de --- /dev/null +++ b/src/main/java/cz/jzitnik/game/core/autotransient/initilizers/GameMiningInitializer.java @@ -0,0 +1,10 @@ +package cz.jzitnik.game.core.autotransient.initilizers; + +import cz.jzitnik.game.core.autotransient.AutoTransientInitializer; + +public class GameMiningInitializer implements AutoTransientInitializer { + @Override + public Boolean initialize(Object parent) { + return false; + } +} diff --git a/src/main/java/cz/jzitnik/game/core/autotransient/initilizers/GameWindowInitializer.java b/src/main/java/cz/jzitnik/game/core/autotransient/initilizers/GameWindowInitializer.java new file mode 100644 index 0000000..2e4af04 --- /dev/null +++ b/src/main/java/cz/jzitnik/game/core/autotransient/initilizers/GameWindowInitializer.java @@ -0,0 +1,11 @@ +package cz.jzitnik.game.core.autotransient.initilizers; + +import cz.jzitnik.game.core.autotransient.AutoTransientInitializer; +import cz.jzitnik.game.ui.Window; + +public class GameWindowInitializer implements AutoTransientInitializer { + @Override + public Window initialize(Object parent) { + return Window.WORLD; + } +} diff --git a/src/main/java/cz/jzitnik/game/threads/list/InputHandlerThread.java b/src/main/java/cz/jzitnik/game/threads/list/InputHandlerThread.java index 6efcdfd..6b862b6 100644 --- a/src/main/java/cz/jzitnik/game/threads/list/InputHandlerThread.java +++ b/src/main/java/cz/jzitnik/game/threads/list/InputHandlerThread.java @@ -91,10 +91,10 @@ public class InputHandlerThread extends Thread { game.setWindow(Window.ESC); } screenRenderer.render(game); - // System.out.println("Exiting game..."); - // isRunning[0] = false; - // game.getGameStates().dependencies.gameSaver.save(game); - // System.exit(0); + System.out.println("Exiting game..."); + isRunning[0] = false; + game.getGameStates().dependencies.gameSaver.save(game); + System.exit(0); } default -> { } diff --git a/src/main/java/cz/jzitnik/game/ui/Inventory.java b/src/main/java/cz/jzitnik/game/ui/Inventory.java index d1ccf4d..bc53962 100644 --- a/src/main/java/cz/jzitnik/game/ui/Inventory.java +++ b/src/main/java/cz/jzitnik/game/ui/Inventory.java @@ -1,5 +1,7 @@ package cz.jzitnik.game.ui; +import cz.jzitnik.game.annotations.AutoTransient; +import cz.jzitnik.game.core.autotransient.AutoTransientSupport; import cz.jzitnik.game.entities.items.InventoryItem; import cz.jzitnik.game.entities.items.Item; import cz.jzitnik.tui.utils.SpriteCombiner; @@ -9,29 +11,22 @@ import lombok.Getter; import lombok.Setter; import org.jline.terminal.Terminal; -import java.io.Serial; -import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Optional; @Getter -public class Inventory implements Serializable { +public class Inventory extends AutoTransientSupport { public static final int INVENTORY_SIZE_PX = 470; public static final int COLUMN_AMOUNT = 5; public static final int ROW_AMOUNT = 4; private final InventoryItem[] items = new InventoryItem[20]; private final InventoryItem[] hotbar = new InventoryItem[9]; + + @AutoTransient private transient SmallCraftingTable smallCraftingTable = new SmallCraftingTable(this); - @Serial - private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException { - in.defaultReadObject(); - - smallCraftingTable = new SmallCraftingTable(this); - } - @Setter private int itemInhHandIndex = 0; @Setter