Compile scripts to replace require with script content

This commit is contained in:
KingRainbow44 2023-08-31 01:08:30 -04:00
parent 67c0e82dfb
commit d43e5ca608
No known key found for this signature in database
GPG Key ID: FC2CB64B00D257BE
5 changed files with 127 additions and 29 deletions

View File

@ -111,6 +111,12 @@ public class ConfigContainer {
public ServerRunMode runMode = ServerRunMode.HYBRID;
public boolean logCommands = false;
/**
* If enabled, the 'require' Lua function will load the script's compiled varient into the context. (faster; doesn't work as well)
* If disabled, all 'require' calls will be replaced with the referenced script's source. (slower; works better)
*/
public boolean fastRequire = true;
public HTTP http = new HTTP();
public Game game = new Game();

View File

@ -1,11 +1,12 @@
package emu.grasscutter.config;
import static emu.grasscutter.Grasscutter.config;
import emu.grasscutter.utils.FileUtils;
import java.nio.file.Path;
import java.util.Locale;
import static emu.grasscutter.Grasscutter.config;
/**
* A data container for the server's configuration.
*
@ -38,6 +39,7 @@ public final class Configuration extends ConfigContainer {
config.server.game.gameOptions.inventoryLimits;
public static final GameOptions.HandbookOptions HANDBOOK =
config.server.game.gameOptions.handbook;
public static final boolean FAST_REQUIRE = config.server.fastRequire;
private static final String DATA_FOLDER = config.folderStructure.data;
private static final String PLUGINS_FOLDER = config.folderStructure.plugins;
private static final String SCRIPTS_FOLDER = config.folderStructure.scripts;

View File

@ -6,9 +6,10 @@ import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction;
import emu.grasscutter.game.ability.Ability;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.scripts.ScriptLoader;
import javax.script.Bindings;
import org.luaj.vm2.LuaFunction;
import javax.script.Bindings;
@AbilityAction(AbilityModifierAction.Type.ServerLuaCall)
public final class ActionServerLuaCall extends AbilityActionHandler {
@Override

View File

@ -1,6 +1,7 @@
package emu.grasscutter.scripts;
import emu.grasscutter.*;
import emu.grasscutter.config.Configuration;
import emu.grasscutter.game.dungeons.challenge.enums.*;
import emu.grasscutter.game.props.*;
import emu.grasscutter.game.quest.enums.QuestState;
@ -8,46 +9,54 @@ import emu.grasscutter.scripts.constants.*;
import emu.grasscutter.scripts.data.SceneMeta;
import emu.grasscutter.scripts.serializer.*;
import emu.grasscutter.utils.FileUtils;
import lombok.Getter;
import org.luaj.vm2.*;
import org.luaj.vm2.lib.*;
import org.luaj.vm2.lib.jse.CoerceJavaToLua;
import org.luaj.vm2.script.*;
import javax.script.*;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import javax.script.*;
import lombok.Getter;
import org.luaj.vm2.*;
import org.luaj.vm2.lib.OneArgFunction;
import org.luaj.vm2.lib.jse.CoerceJavaToLua;
import org.luaj.vm2.script.LuajContext;
public class ScriptLoader {
private static ScriptEngineManager sm;
@Getter private static ScriptEngine engine;
private static ScriptEngineFactory factory;
@Getter private static LuaScriptEngine engine;
@Getter private static Serializer serializer;
@Getter private static ScriptLib scriptLib;
@Getter private static LuaValue scriptLibLua;
/** suggest GC to remove it if the memory is less */
private static Map<String, SoftReference<String>> scriptSources =
new ConcurrentHashMap<>();
private static Map<String, SoftReference<CompiledScript>> scriptsCache =
new ConcurrentHashMap<>();
/** sceneId - SceneMeta */
private static Map<Integer, SoftReference<SceneMeta>> sceneMetaCache = new ConcurrentHashMap<>();
private static final AtomicReference<Bindings> currentBindings = new AtomicReference<>(null);
private static final AtomicReference<ScriptContext> currentContext = new AtomicReference<>(null);
/**
* Initializes the script engine.
*/
public static synchronized void init() throws Exception {
if (sm != null) {
throw new Exception("Script loader already initialized");
}
// Create script engine
sm = new ScriptEngineManager();
engine = sm.getEngineByName("luaj");
factory = getEngine().getFactory();
ScriptLoader.sm = new ScriptEngineManager();
var engine = ScriptLoader.engine = (LuaScriptEngine) sm.getEngineByName("luaj");
ScriptLoader.serializer = new LuaSerializer();
// Lua stuff
serializer = new LuaSerializer();
var ctx = (LuajContext) engine.getContext();
// Set the Lua context.
var ctx = new LuajContext(true, false);
ctx.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);
engine.setContext(ctx);
// Set the 'require' function handler.
ctx.globals.set("require", new RequireFunction());
@ -141,6 +150,7 @@ public class ScriptLoader {
// Append the script to the context.
try {
var bindings = currentBindings.get();
if (bindings != null) {
ScriptLoader.eval(script, bindings);
} else {
@ -158,19 +168,96 @@ public class ScriptLoader {
}
}
/**
* Loads the sources of a script.
*
* @param path The path of the script.
* @return The sources of the script.
*/
public static String readScript(String path) {
// Check if the path is cached.
var cached = ScriptLoader.tryGet(
ScriptLoader.scriptSources.get(path));
if (cached.isPresent()) {
return cached.get();
}
// Attempt to load the script.
var scriptPath = FileUtils.getScriptPath(path);
if (!Files.exists(scriptPath)) return null;
try {
var source = Files.readString(scriptPath);
ScriptLoader.scriptSources.put(
path, new SoftReference<>(source));
return source;
} catch (IOException exception) {
Grasscutter.getLogger()
.error("Loading script {} failed! - {}", path, exception.getLocalizedMessage());
return null;
}
}
/**
* Fetches a script and compiles it, or uses the cached varient.
*
* @param path The path of the script.
* @return The compiled script.
*/
public static CompiledScript getScript(String path) {
var sc = tryGet(scriptsCache.get(path));
// Check if the script is cached.
var sc = ScriptLoader.tryGet(
ScriptLoader.scriptsCache.get(path));
if (sc.isPresent()) {
return sc.get();
}
// Grasscutter.getLogger().debug("Loading script " + path);
final Path scriptPath = FileUtils.getScriptPath(path);
if (!Files.exists(scriptPath)) return null;
try {
var script = ((Compilable) getEngine()).compile(Files.newBufferedReader(scriptPath));
scriptsCache.put(path, new SoftReference<>(script));
CompiledScript script;
if (Configuration.FAST_REQUIRE) {
// Attempt to load the script.
var scriptPath = FileUtils.getScriptPath(path);
if (!Files.exists(scriptPath)) return null;
// Compile the script from the file.
var source = Files.newBufferedReader(scriptPath);
script = ScriptLoader.getEngine().compile(source);
} else {
// Load the script sources.
var sources = ScriptLoader.readScript(path);
if (sources == null) return null;
// Check to see if the script references other scripts.
if (sources.contains("require")) {
var lines = sources.split("\n");
var output = new StringBuilder();
for (var line : lines) {
// Skip non-require lines.
if (!line.startsWith("require")) {
output.append(line).append("\n");
continue;
}
// Extract the script name.
var scriptName = line.substring(9, line.length() - 1);
// Resolve the script path.
var scriptPath = "Common/" + scriptName + ".lua";
var scriptSource = ScriptLoader.readScript(scriptPath);
if (scriptSource == null) continue;
// Append the script source.
output.append(scriptSource).append("\n");
}
sources = output.toString();
}
// Compile the script & cache it in memory.
script = ScriptLoader.getEngine().compile(sources);
}
// Cache the script.
ScriptLoader.scriptsCache.put(path, new SoftReference<>(script));
return script;
} catch (Exception e) {
Grasscutter.getLogger()

View File

@ -3,12 +3,13 @@ package emu.grasscutter.scripts.data;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.world.Position;
import emu.grasscutter.scripts.ScriptLoader;
import java.util.*;
import java.util.stream.Collectors;
import javax.script.*;
import lombok.*;
import org.luaj.vm2.*;
import javax.script.*;
import java.util.*;
import java.util.stream.Collectors;
@ToString
@Setter
public final class SceneGroup {
@ -35,7 +36,8 @@ public final class SceneGroup {
public SceneReplaceable is_replaceable;
private transient boolean loaded; // Not an actual variable in the scripts either
/* These are not script variables. */
private transient boolean loaded;
private transient CompiledScript script;
private transient Bindings bindings;
@ -82,7 +84,7 @@ public final class SceneGroup {
}
// Set flag here so if there is no script, we don't call this function over and over again.
this.setLoaded(true);
// Create the bindings.
this.bindings = ScriptLoader.getEngine().createBindings();
var cs =