diff --git a/build.gradle b/build.gradle index b4f66aa5c..8801b3da0 100644 --- a/build.gradle +++ b/build.gradle @@ -87,6 +87,7 @@ dependencies { implementation group: 'org.luaj', name: 'luaj-jse', version: '3.0.1' implementation group: 'ch.ethz.globis.phtree', name : 'phtree', version: '2.5.0' + implementation group: 'com.esotericsoftware', name : 'reflectasm', version: '1.11.9' protobuf files('proto/') diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java index 0b4b0c001..d57cb9ab5 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -241,7 +241,7 @@ public class SceneScriptManager { } public void spawnGadgetsInGroup(SceneGroup group, SceneSuite suite) { - List gadgets = group.gadgets; + var gadgets = group.gadgets.values(); if (suite != null) { gadgets = suite.sceneGadgets; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java b/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java index 37d03e88b..0f4400a50 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java @@ -6,6 +6,8 @@ import emu.grasscutter.Grasscutter; import emu.grasscutter.scripts.SceneIndexManager; import emu.grasscutter.scripts.ScriptLoader; import emu.grasscutter.utils.Position; +import lombok.Setter; +import lombok.ToString; import javax.script.Bindings; import javax.script.CompiledScript; @@ -14,6 +16,8 @@ import java.util.List; import static emu.grasscutter.Configuration.SCRIPT; +@ToString +@Setter public class SceneBlock { public int id; public Position max; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneConfig.java b/src/main/java/emu/grasscutter/scripts/data/SceneConfig.java index 3a1ca60ea..cba2be8bd 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneConfig.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneConfig.java @@ -1,7 +1,11 @@ package emu.grasscutter.scripts.data; import emu.grasscutter.utils.Position; +import lombok.Setter; +import lombok.ToString; +@ToString +@Setter public class SceneConfig { public Position vision_anchor; public Position born_pos; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java b/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java index e93f93c3c..8b3e452fd 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java @@ -1,5 +1,10 @@ package emu.grasscutter.scripts.data; +import lombok.Setter; +import lombok.ToString; + +@ToString +@Setter public class SceneGadget extends SceneObject{ public int gadget_id; public int state; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java index 3ec446465..82efb1969 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java @@ -3,19 +3,22 @@ package emu.grasscutter.scripts.data; import emu.grasscutter.Grasscutter; import emu.grasscutter.scripts.ScriptLoader; import emu.grasscutter.utils.Position; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import lombok.Setter; +import lombok.ToString; import javax.script.Bindings; import javax.script.CompiledScript; import javax.script.ScriptException; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import static emu.grasscutter.Configuration.SCRIPTS_FOLDER; +@ToString +@Setter public class SceneGroup { public transient int block_id; // Not an actual variable in the scripts but we will keep it here for reference @@ -27,7 +30,10 @@ public class SceneGroup { * ConfigId - Monster */ public Map monsters; - public List gadgets; + /** + * ConfigId - Gadget + */ + public Map gadgets; public List triggers; public List regions; public List suites; @@ -76,8 +82,15 @@ public class SceneGroup { // Set monsters = ScriptLoader.getSerializer().toList(SceneMonster.class, bindings.get("monsters")).stream() .collect(Collectors.toMap(x -> x.config_id, y -> y)); - gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, bindings.get("gadgets")); + monsters.values().forEach(m -> m.groupId = id); + + gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, bindings.get("gadgets")).stream() + .collect(Collectors.toMap(x -> x.config_id, y -> y)); + gadgets.values().forEach(m -> m.groupId = id); + triggers = ScriptLoader.getSerializer().toList(SceneTrigger.class, bindings.get("triggers")); + triggers.forEach(t -> t.currentGroup = this); + suites = ScriptLoader.getSerializer().toList(SceneSuite.class, bindings.get("suites")); regions = ScriptLoader.getSerializer().toList(SceneRegion.class, bindings.get("regions")); init_config = ScriptLoader.getSerializer().toObject(SceneInitConfig.class, bindings.get("init_config")); @@ -85,35 +98,21 @@ public class SceneGroup { // Add variables to suite variables = ScriptLoader.getSerializer().toList(SceneVar.class, bindings.get("variables")); - - // Add monsters to suite TODO optimize - Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); - monsters.entrySet().forEach(m -> map.put(m.getValue().config_id, m)); - monsters.values().forEach(m -> m.groupId = id); - gadgets.forEach(m -> map.put(m.config_id, m)); - gadgets.forEach(m -> m.groupId = id); - + // Add monsters to suite for (SceneSuite suite : suites) { - suite.sceneMonsters = new ArrayList<>(suite.monsters.size()); - suite.monsters.forEach(id -> { - Object objEntry = map.get(id.intValue()); - if (objEntry instanceof Map.Entry monsterEntry) { - Object monster = monsterEntry.getValue(); - if(monster instanceof SceneMonster sceneMonster){ - suite.sceneMonsters.add(sceneMonster); - } - } - }); + suite.sceneMonsters = new ArrayList<>( + suite.monsters.stream() + .filter(monsters::containsKey) + .map(monsters::get) + .toList() + ); - suite.sceneGadgets = new ArrayList<>(suite.gadgets.size()); - for (int id : suite.gadgets) { - try { - SceneGadget gadget = (SceneGadget) map.get(id); - if (gadget != null) { - suite.sceneGadgets.add(gadget); - } - } catch (Exception ignored) { } - } + suite.sceneGadgets = new ArrayList<>( + suite.gadgets.stream() + .filter(gadgets::containsKey) + .map(gadgets::get) + .toList() + ); } } catch (ScriptException e) { diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneInitConfig.java b/src/main/java/emu/grasscutter/scripts/data/SceneInitConfig.java index fcdc92c87..a9bc586c9 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneInitConfig.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneInitConfig.java @@ -1,7 +1,10 @@ package emu.grasscutter.scripts.data; -import emu.grasscutter.utils.Position; +import lombok.Setter; +import lombok.ToString; +@ToString +@Setter public class SceneInitConfig { public int suite; public int end_suite; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java b/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java index 0a2cf9582..4014b2e1e 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java @@ -5,6 +5,9 @@ import ch.ethz.globis.phtree.v16.PhTree16; import emu.grasscutter.Grasscutter; import emu.grasscutter.scripts.SceneIndexManager; import emu.grasscutter.scripts.ScriptLoader; +import lombok.Data; +import lombok.Setter; +import lombok.ToString; import javax.script.Bindings; import javax.script.CompiledScript; @@ -15,6 +18,8 @@ import java.util.stream.Collectors; import static emu.grasscutter.Configuration.SCRIPT; +@ToString +@Setter public class SceneMeta { public SceneConfig config; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java b/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java index 9c508d147..02435d800 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java @@ -1,5 +1,10 @@ package emu.grasscutter.scripts.data; +import lombok.Setter; +import lombok.ToString; + +@ToString +@Setter public class SceneMonster extends SceneObject{ public int monster_id; } \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneObject.java b/src/main/java/emu/grasscutter/scripts/data/SceneObject.java index 7f9f1f709..ecf089a5e 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneObject.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneObject.java @@ -1,7 +1,11 @@ package emu.grasscutter.scripts.data; import emu.grasscutter.utils.Position; +import lombok.Setter; +import lombok.ToString; +@ToString +@Setter public class SceneObject { public int level; public int config_id; @@ -11,5 +15,5 @@ public class SceneObject { /** * not set by lua */ - public int groupId; + public transient int groupId; } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java b/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java index dac164d0e..edc04be15 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java @@ -5,7 +5,12 @@ import emu.grasscutter.scripts.constants.ScriptRegionShape; import emu.grasscutter.utils.Position; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; +import lombok.Data; +import lombok.Setter; +import lombok.ToString; +@ToString +@Setter public class SceneRegion { public int config_id; public int shape; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java b/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java index 90433b3d4..2afa9cf72 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java @@ -2,8 +2,11 @@ package emu.grasscutter.scripts.data; import java.util.List; -import emu.grasscutter.utils.Position; +import lombok.Setter; +import lombok.ToString; +@ToString +@Setter public class SceneSuite { public List monsters; public List gadgets; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java b/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java index 301fdb8e0..6f5d761ef 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java @@ -1,5 +1,8 @@ package emu.grasscutter.scripts.data; +import lombok.Setter; + +@Setter public class SceneTrigger { public String name; public int config_id; @@ -8,6 +11,7 @@ public class SceneTrigger { public String condition; public String action; + public SceneGroup currentGroup; @Override public boolean equals(Object obj) { if(obj instanceof SceneTrigger sceneTrigger){ diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneVar.java b/src/main/java/emu/grasscutter/scripts/data/SceneVar.java index 9ead6f474..41ebda1b1 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneVar.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneVar.java @@ -1,5 +1,10 @@ package emu.grasscutter.scripts.data; +import lombok.Setter; +import lombok.ToString; + +@ToString +@Setter public class SceneVar { public String name; public int value; diff --git a/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java b/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java index c31c13b81..b262079ee 100644 --- a/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java +++ b/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java @@ -1,14 +1,23 @@ package emu.grasscutter.scripts.serializer; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; - + import com.esotericsoftware.reflectasm.ConstructorAccess; +import com.esotericsoftware.reflectasm.MethodAccess; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.experimental.FieldDefaults; import org.luaj.vm2.LuaTable; import org.luaj.vm2.LuaValue; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + public class LuaSerializer implements Serializer { - + + private final static Map, MethodAccess> methodAccessCache = new ConcurrentHashMap<>(); + private final static Map, ConstructorAccess> constructorCache = new ConcurrentHashMap<>(); + private final static Map, Map> fieldMetaCache = new ConcurrentHashMap<>(); + @Override public List toList(Class type, Object obj) { return serializeList(type, (LuaTable) obj); @@ -20,7 +29,7 @@ public class LuaSerializer implements Serializer { } public List serializeList(Class type, LuaTable table) { - List list = new ArrayList(); + List list = new ArrayList<>(); try { LuaValue[] keys = table.keys(); @@ -55,7 +64,7 @@ public class LuaSerializer implements Serializer { return list; } - + public T serialize(Class type, LuaTable table) { T object = null; @@ -70,30 +79,34 @@ public class LuaSerializer implements Serializer { } try { - //noinspection ConfusingArgumentToVarargsMethod - object = type.getDeclaredConstructor().newInstance(null); - + if(!methodAccessCache.containsKey(type)){ + cacheType(type); + } + var methodAccess = methodAccessCache.get(type); + var fieldMetaMap = fieldMetaCache.get(type); + + object = (T) constructorCache.get(type).newInstance(); + LuaValue[] keys = table.keys(); for (LuaValue k : keys) { try { - Field field = getField(object.getClass(), k.checkjstring()); - if (field == null) { + var keyName = k.checkjstring(); + if(!fieldMetaMap.containsKey(keyName)){ continue; } - - field.setAccessible(true); + var fieldMeta = fieldMetaMap.get(keyName); LuaValue keyValue = table.get(k); if (keyValue.istable()) { - field.set(object, serialize(field.getType(), keyValue.checktable())); - } else if (field.getType().equals(float.class)) { - field.setFloat(object, keyValue.tofloat()); - } else if (field.getType().equals(int.class)) { - field.setInt(object, keyValue.toint()); - } else if (field.getType().equals(String.class)) { - field.set(object, keyValue.tojstring()); + methodAccess.invoke(object, fieldMeta.index, serialize(fieldMeta.getType(), keyValue.checktable())); + } else if (fieldMeta.getType().equals(float.class)) { + methodAccess.invoke(object, fieldMeta.index, keyValue.tofloat()); + } else if (fieldMeta.getType().equals(int.class)) { + methodAccess.invoke(object, fieldMeta.index, keyValue.toint()); + } else if (fieldMeta.getType().equals(String.class)) { + methodAccess.invoke(object, fieldMeta.index, keyValue.tojstring()); } else { - field.set(object, keyValue); + methodAccess.invoke(object, fieldMeta.index, keyValue.tojstring()); } } catch (Exception ex) { //ex.printStackTrace(); @@ -107,16 +120,57 @@ public class LuaSerializer implements Serializer { return object; } - public Field getField(Class clazz, String name){ - try{ - return clazz.getField(name); - } catch (NoSuchFieldException ex) { - try { - return clazz.getDeclaredField(name); - } catch (NoSuchFieldException e) { - // ignore - return null; - } + public Map cacheType(Class type){ + if(fieldMetaCache.containsKey(type)) { + return fieldMetaCache.get(type); } + if(!constructorCache.containsKey(type)){ + constructorCache.putIfAbsent(type, ConstructorAccess.get(type)); + } + var methodAccess = Optional.ofNullable(methodAccessCache.get(type)).orElse(MethodAccess.get(type)); + methodAccessCache.putIfAbsent(type, methodAccess); + + var fieldMetaMap = new HashMap(); + var methodNameSet = new HashSet<>(Arrays.stream(methodAccess.getMethodNames()).toList()); + + Arrays.stream(type.getDeclaredFields()) + .filter(field -> methodNameSet.contains(getSetterName(field.getName()))) + .forEach(field -> { + var setter = getSetterName(field.getName()); + var index = methodAccess.getIndex(setter); + fieldMetaMap.put(field.getName(), new FieldMeta(field.getName(), setter, index, field.getType())); + }); + + Arrays.stream(type.getFields()) + .filter(field -> !fieldMetaMap.containsKey(field.getName())) + .filter(field -> methodNameSet.contains(getSetterName(field.getName()))) + .forEach(field -> { + var setter = getSetterName(field.getName()); + var index = methodAccess.getIndex(setter); + fieldMetaMap.put(field.getName(), new FieldMeta(field.getName(), setter, index, field.getType())); + }); + + fieldMetaCache.put(type, fieldMetaMap); + return fieldMetaMap; + } + + public String getSetterName(String fieldName){ + if(fieldName == null || fieldName.length() == 0){ + return null; + } + if(fieldName.length() == 1){ + return "set" + fieldName.toUpperCase(); + } + return "set" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); + } + + @Data + @AllArgsConstructor + @FieldDefaults(level = AccessLevel.PRIVATE) + static class FieldMeta{ + String name; + String setter; + int index; + Class type; } }