Skip to content
Snippets Groups Projects
Commit 593de838 authored by Akka's avatar Akka Committed by Melledy
Browse files

optimized the lua serializer

parent 1925bf64
No related merge requests found
Showing
with 169 additions and 68 deletions
......@@ -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/')
......
......@@ -241,7 +241,7 @@ public class SceneScriptManager {
}
public void spawnGadgetsInGroup(SceneGroup group, SceneSuite suite) {
List<SceneGadget> gadgets = group.gadgets;
var gadgets = group.gadgets.values();
if (suite != null) {
gadgets = suite.sceneGadgets;
......
......@@ -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;
......
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;
......
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;
......
......@@ -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<Integer,SceneMonster> monsters;
public List<SceneGadget> gadgets;
/**
* ConfigId - Gadget
*/
public Map<Integer, SceneGadget> gadgets;
public List<SceneTrigger> triggers;
public List<SceneRegion> regions;
public List<SceneSuite> 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<Object> 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.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.sceneMonsters = new ArrayList<>(
suite.monsters.stream()
.filter(monsters::containsKey)
.map(monsters::get)
.toList()
);
suite.sceneGadgets = new ArrayList<>(
suite.gadgets.stream()
.filter(gadgets::containsKey)
.map(gadgets::get)
.toList()
);
}
} catch (ScriptException e) {
......
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;
......
......@@ -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;
......
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
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;
}
......@@ -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;
......
......@@ -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<Integer> monsters;
public List<Integer> gadgets;
......
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){
......
package emu.grasscutter.scripts.data;
import lombok.Setter;
import lombok.ToString;
@ToString
@Setter
public class SceneVar {
public String name;
public int value;
......
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<Class<?>, MethodAccess> methodAccessCache = new ConcurrentHashMap<>();
private final static Map<Class<?>, ConstructorAccess<?>> constructorCache = new ConcurrentHashMap<>();
private final static Map<Class<?>, Map<String, FieldMeta>> fieldMetaCache = new ConcurrentHashMap<>();
@Override
public <T> List<T> toList(Class<T> type, Object obj) {
return serializeList(type, (LuaTable) obj);
......@@ -20,7 +29,7 @@ public class LuaSerializer implements Serializer {
}
public <T> List<T> serializeList(Class<T> type, LuaTable table) {
List<T> list = new ArrayList();
List<T> list = new ArrayList<>();
try {
LuaValue[] keys = table.keys();
......@@ -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 <T> Field getField(Class<T> clazz, String name){
try{
return clazz.getField(name);
} catch (NoSuchFieldException ex) {
try {
return clazz.getDeclaredField(name);
} catch (NoSuchFieldException e) {
// ignore
public <T> Map<String, FieldMeta> cacheType(Class<T> 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<String, FieldMeta>();
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;
}
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment