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.
This commit is contained in:
Jakub Žitník 2025-03-16 18:20:18 +01:00
parent c558b756de
commit ae5d34b41a
Signed by: jzitnik
GPG Key ID: C577A802A6AF4EF3
9 changed files with 136 additions and 29 deletions

View File

@ -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<Block>[][] world = (List<Block>[][]) 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);
}

View File

@ -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<? extends AutoTransientInitializer<?>> initializer() default DefaultInitializer.class;
}

View File

@ -0,0 +1,5 @@
package cz.jzitnik.game.core.autotransient;
public interface AutoTransientInitializer<T> {
T initialize(Object parent);
}

View File

@ -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<? extends AutoTransientInitializer<?>> 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;
}
}
}

View File

@ -0,0 +1,8 @@
package cz.jzitnik.game.core.autotransient;
public class DefaultInitializer implements AutoTransientInitializer<Object> {
@Override
public Object initialize(Object parent) {
return null;
}
}

View File

@ -0,0 +1,10 @@
package cz.jzitnik.game.core.autotransient.initilizers;
import cz.jzitnik.game.core.autotransient.AutoTransientInitializer;
public class GameMiningInitializer implements AutoTransientInitializer<Boolean> {
@Override
public Boolean initialize(Object parent) {
return false;
}
}

View File

@ -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<Window> {
@Override
public Window initialize(Object parent) {
return Window.WORLD;
}
}

View File

@ -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 -> {
}

View File

@ -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