Skip to content

Commit

Permalink
Cache compiled scripts (#137)
Browse files Browse the repository at this point in the history
* start

* fix closures

* fix issues with inner classes and script deps

* fix dependent scripts force compiling

* fix not detecting some inner classes

* cache preprocessors, delete cache of deleted scripts

* comments

* clear index on deleteClassCache

* file util class & save compiled class in proper dir

* fix issues

* reviews
  • Loading branch information
brachy84 authored Mar 14, 2024
1 parent e06f7ec commit 61cba0b
Show file tree
Hide file tree
Showing 19 changed files with 598 additions and 96 deletions.
33 changes: 12 additions & 21 deletions src/main/java/com/cleanroommc/groovyscript/GroovyScript.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.cleanroommc.groovyscript;

import com.cleanroommc.groovyscript.api.GroovyBlacklist;
import com.cleanroommc.groovyscript.api.GroovyLog;
import com.cleanroommc.groovyscript.command.CustomClickAction;
import com.cleanroommc.groovyscript.command.GSCommand;
import com.cleanroommc.groovyscript.compat.content.GroovyResourcePack;
Expand All @@ -17,14 +18,10 @@
import com.cleanroommc.groovyscript.network.NetworkHandler;
import com.cleanroommc.groovyscript.network.NetworkUtils;
import com.cleanroommc.groovyscript.registry.ReloadableRegistryManager;
import com.cleanroommc.groovyscript.sandbox.GroovyLogImpl;
import com.cleanroommc.groovyscript.sandbox.GroovyScriptSandbox;
import com.cleanroommc.groovyscript.sandbox.LoadStage;
import com.cleanroommc.groovyscript.sandbox.RunConfig;
import com.cleanroommc.groovyscript.sandbox.*;
import com.cleanroommc.groovyscript.sandbox.mapper.GroovyDeobfMapper;
import com.cleanroommc.groovyscript.sandbox.security.GrSMetaClassCreationHandle;
import com.cleanroommc.groovyscript.server.GroovyScriptLanguageServer;
import com.google.common.base.Joiner;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import groovy.lang.GroovySystem;
Expand Down Expand Up @@ -98,16 +95,10 @@ public class GroovyScript {
private static KeyBinding reloadKey;
private static long timeSinceLastUse = 0;

private static final Joiner fileJoiner = Joiner.on(File.separator);

public static final Random RND = new Random();

@Mod.EventHandler
public void onConstruction(FMLConstructionEvent event) {
if (Boolean.parseBoolean(System.getProperty("groovyscript.run_ls"))) {
runLanguageServer();
}

MinecraftForge.EVENT_BUS.register(this);
MinecraftForge.EVENT_BUS.register(EventHandler.class);
NetworkHandler.init();
Expand All @@ -116,7 +107,7 @@ public void onConstruction(FMLConstructionEvent event) {
LinkGeneratorHooks.init();
ReloadableRegistryManager.init();
try {
sandbox = new GroovyScriptSandbox(scriptPath.toURI().toURL());
sandbox = new GroovyScriptSandbox(scriptPath, FileUtil.makeFile(FileUtil.getMinecraftHome(), "cache", "groovy"));
} catch (MalformedURLException e) {
throw new IllegalStateException("Error initializing sandbox!");
}
Expand All @@ -131,6 +122,10 @@ public void onConstruction(FMLConstructionEvent event) {

FluidRegistry.enableUniversalBucket();
getRunConfig().initPackmode();

if (Boolean.parseBoolean(System.getProperty("groovyscript.run_ls"))) {
runLanguageServer();
}
}

@Mod.EventHandler
Expand Down Expand Up @@ -169,6 +164,10 @@ public static void initializeGroovyPreInit() {
@ApiStatus.Internal
public static long runGroovyScriptsInLoader(LoadStage loadStage) {
// called via mixin between fml post init and load complete
if (!getRunConfig().isLoaderConfigured(loadStage.getName())) {
GroovyLog.get().infoMC("Skipping load stage {}, since no scripts are configured!", loadStage.getName());
return -1;
}
if (scriptMod == null) scriptMod = Loader.instance().getIndexedModList().get(getRunConfig().getPackId());
ModContainer current = Loader.instance().activeModContainer();
Loader.instance().setActiveModContainer(scriptMod);
Expand Down Expand Up @@ -279,14 +278,6 @@ private static RunConfig createRunConfig(JsonObject json) {
return new RunConfig(json);
}

public static File makeFile(String... pieces) {
return new File(fileJoiner.join(pieces));
}

public static File makeFile(File parent, String... pieces) {
return new File(parent, fileJoiner.join(pieces));
}

public static void postScriptRunResult(ICommandSender sender, boolean onlyLogFails, boolean running, boolean packmode, long time) {
List<String> errors = GroovyLogImpl.LOG.collectErrors();
if (errors.isEmpty()) {
Expand Down Expand Up @@ -315,7 +306,7 @@ public static void postScriptRunResult(ICommandSender sender, boolean onlyLogFai

public static boolean runLanguageServer() {
if (languageServerThread != null) return false;
languageServerThread = new Thread(GroovyScriptLanguageServer::listen);
languageServerThread = new Thread(() -> GroovyScriptLanguageServer.listen(getSandbox().getScriptRoot()));
languageServerThread.start();
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,14 @@ public GSCommand() {
.appendSibling(GSCommand.getTextForFile("Groovy Log", GroovyLog.get().getLogFilerPath().toString(), new TextComponentString("Click to open GroovyScript log"))));
}));

addSubcommand(new SimpleCommand("deleteScriptCache", (server, sender, args) -> {
if (GroovyScript.getSandbox().deleteScriptCache()) {
sender.sendMessage(new TextComponentString(TextFormatting.GREEN + "Deleted groovy script cache"));
} else {
sender.sendMessage(new TextComponentString(TextFormatting.RED + "An error occurred while deleting groovy script cache"));
}
}));

addSubcommand(new SimpleCommand("runLS", (server, sender, args) -> {
if (GroovyScript.runLanguageServer()) {
sender.sendMessage(new TextComponentString("Starting language server"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.cleanroommc.groovyscript.api.GroovyLog;
import com.cleanroommc.groovyscript.compat.vanilla.VanillaModule;
import com.cleanroommc.groovyscript.helper.JsonHelper;
import com.cleanroommc.groovyscript.sandbox.FileUtil;
import com.google.gson.JsonObject;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import net.minecraft.block.Block;
Expand Down Expand Up @@ -108,7 +109,7 @@ public GroovyBlock register() {
}

private static void checkBlockModel(ResourceLocation loc) {
File file = GroovyScript.makeFile(GroovyScript.getResourcesFile(), loc.getNamespace(), "blockstates", loc.getPath() + ".json");
File file = FileUtil.makeFile(GroovyScript.getResourcesFile().getPath(), loc.getNamespace(), "blockstates", loc.getPath() + ".json");
if (!file.exists()) {
JsonObject stateJson = new JsonObject();
JsonObject variantsJson = new JsonObject();
Expand All @@ -119,7 +120,7 @@ private static void checkBlockModel(ResourceLocation loc) {
JsonHelper.saveJson(file, stateJson);
}

file = GroovyScript.makeFile(GroovyScript.getResourcesFile(), loc.getNamespace(), "models", "block", loc.getPath() + ".json");
file = FileUtil.makeFile(GroovyScript.getResourcesFile().getPath(), loc.getNamespace(), "models", "block", loc.getPath() + ".json");
if (!file.exists()) {
JsonObject modelJson = new JsonObject();
modelJson.addProperty("parent", "block/cube_all");
Expand All @@ -131,7 +132,7 @@ private static void checkBlockModel(ResourceLocation loc) {
}

private static void checkItemModel(ResourceLocation loc) {
File file = GroovyScript.makeFile(GroovyScript.getResourcesFile(), loc.getNamespace(), "models", "item", loc.getPath() + ".json");
File file = FileUtil.makeFile(GroovyScript.getResourcesFile().getPath(), loc.getNamespace(), "models", "item", loc.getPath() + ".json");
if (!file.exists()) {
JsonObject modelJson = new JsonObject();
modelJson.addProperty("parent", loc.getNamespace() + ":block/" + loc.getPath());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.cleanroommc.groovyscript.GroovyScript;
import com.cleanroommc.groovyscript.api.GroovyBlacklist;
import com.cleanroommc.groovyscript.helper.JsonHelper;
import com.cleanroommc.groovyscript.sandbox.FileUtil;
import com.google.gson.JsonObject;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
Expand Down Expand Up @@ -69,7 +70,7 @@ public GroovyFluid(String fluidName, ResourceLocation still, ResourceLocation fl
}

private static void checkBlockState(ResourceLocation loc) {
File file = GroovyScript.makeFile(GroovyScript.getResourcesFile(), loc.getNamespace(), "blockstates", loc.getPath() + ".json");
File file = FileUtil.makeFile(GroovyScript.getResourcesFile().getPath(), loc.getNamespace(), "blockstates", loc.getPath() + ".json");
if (!file.exists()) {
JsonObject stateJson = new JsonObject();
stateJson.addProperty("forge_marker", 1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.cleanroommc.groovyscript.api.GroovyLog;
import com.cleanroommc.groovyscript.compat.vanilla.VanillaModule;
import com.cleanroommc.groovyscript.helper.JsonHelper;
import com.cleanroommc.groovyscript.sandbox.FileUtil;
import com.google.gson.JsonObject;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import net.minecraft.client.renderer.block.model.ModelBakery;
Expand Down Expand Up @@ -117,7 +118,7 @@ public boolean hasEffect(@NotNull ItemStack stack) {
}

private static void checkModelFile(ResourceLocation loc) {
File modelFile = GroovyScript.makeFile(GroovyScript.getResourcesFile(), loc.getNamespace(), "models", "item", loc.getPath() + ".json");
File modelFile = FileUtil.makeFile(GroovyScript.getResourcesFile().getPath(), loc.getNamespace(), "models", "item", loc.getPath() + ".json");
if (!modelFile.exists()) {
JsonObject modelJson = new JsonObject();
modelJson.addProperty("parent", "item/generated");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,27 @@


import com.cleanroommc.groovyscript.GroovyScript;
import com.cleanroommc.groovyscript.sandbox.FileUtil;
import net.minecraft.client.resources.FolderResourcePack;
import net.minecraft.client.resources.data.IMetadataSection;
import net.minecraft.client.resources.data.MetadataSerializer;
import org.jetbrains.annotations.NotNull;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

public class GroovyResourcePack extends FolderResourcePack {

private static void makePath(File root, String... pieces) {
File file = GroovyScript.makeFile(root, pieces);
if (!file.isDirectory()) {
file.mkdirs();
}
}

private static void makeFile(File root, String... pieces) {
File file = GroovyScript.makeFile(root, pieces);
if (!file.isFile()) {
try {
file.getParentFile().mkdirs();
Files.createFile(file.toPath());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

public GroovyResourcePack() {
super(GroovyScript.getScriptFile());
if (GroovyScript.getRunConfig().isValidPackId()) {
File root = GroovyScript.makeFile(GroovyScript.getResourcesFile(), GroovyScript.getRunConfig().getPackId());
makeFile(root, "lang", "en_us.lang");
makePath(root, "models", "item");
makePath(root, "models", "block");
makePath(root, "textures", "items");
makePath(root, "textures", "blocks");
makePath(root, "blockstates");
String root = FileUtil.makePath(GroovyScript.getResourcesFile().getPath(), GroovyScript.getRunConfig().getPackId());
FileUtil.mkdirsAndFile(FileUtil.makeFile(root, "lang", "en_us.lang"));
FileUtil.mkdirsAndFile(FileUtil.makeFile(root, "models", "item"));
FileUtil.mkdirsAndFile(FileUtil.makeFile(root, "models", "block"));
FileUtil.mkdirsAndFile(FileUtil.makeFile(root, "textures", "items"));
FileUtil.mkdirsAndFile(FileUtil.makeFile(root, "textures", "blocks"));
FileUtil.mkdirsAndFile(FileUtil.makeFile(root, "blockstates"));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public class ModSupport implements IDynamicGroovyProperty {
public static final GroovyContainer<JustEnoughItems> JEI = new InternalModContainer<>("jei", "Just Enough Items", JustEnoughItems::new, "hei");
public static final GroovyContainer<Mekanism> MEKANISM = new InternalModContainer<>("mekanism", "Mekanism", Mekanism::new);
public static final GroovyContainer<PyroTech> PYROTECH = new InternalModContainer<>("pyrotech", "Pyrotech", PyroTech::new);
public static final GroovyContainer<Roots> ROOTS = new InternalModContainer<>("roots", "Roots 3", Roots::new);
public static final GroovyContainer<Roots> ROOTS = new InternalModContainer<>("roots", "Roots 3", () -> new Roots());
public static final GroovyContainer<Thaumcraft> THAUMCRAFT = new InternalModContainer<>("thaumcraft", "Thaumcraft", Thaumcraft::new, "tc", "thaum");
public static final GroovyContainer<ThermalExpansion> THERMAL_EXPANSION = new InternalModContainer<>("thermalexpansion", "Thermal Expansion", ThermalExpansion::new, "te", "thermal");
public static final GroovyContainer<TinkersComplement> TINKERS_COMPLEMENT = new InternalModContainer<>("tcomplement", "Tinkers Complement", TinkersComplement::new, "tcomp", "tinkerscomplement");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.cleanroommc.groovyscript.core.mixin.groovy;

import com.cleanroommc.groovyscript.GroovyScript;
import groovy.lang.GroovyClassLoader;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.control.SourceUnit;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

@Mixin(value = GroovyClassLoader.ClassCollector.class, remap = false)
public class ClassCollectorMixin {

@Shadow
@Final
private SourceUnit su;

@Inject(method = "createClass", at = @At("RETURN"))
public void onCreateClass(byte[] code, ClassNode classNode, CallbackInfoReturnable<Class<?>> cir) {
GroovyScript.getSandbox().onCompileClass(su, su.getName(), cir.getReturnValue(), code, classNode.getName().contains("$"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.cleanroommc.groovyscript.core.mixin.groovy;

import com.cleanroommc.groovyscript.GroovyScript;
import groovy.lang.GroovyClassLoader;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import java.net.URL;

/**
* If a script depends on another script and the there is a compiled cache for the script, it needs to be loaded manually.
*/
@Mixin(value = GroovyClassLoader.class, remap = false)
public class GroovyClassLoaderMixin {

@Inject(method = "recompile", at = @At("HEAD"), cancellable = true)
public void onRecompile(URL source, String className, Class<?> oldClass, CallbackInfoReturnable<Class<?>> cir) {
if (source != null && oldClass == null) {
Class<?> c = GroovyScript.getSandbox().onRecompileClass((GroovyClassLoader) (Object) this, source, className);
if (c != null) {
cir.setReturnValue(c);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.cleanroommc.groovyscript.sandbox;

import com.cleanroommc.groovyscript.api.GroovyLog;
import org.apache.commons.lang3.builder.ToStringBuilder;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;

class CompiledClass {

public static final String CLASS_SUFFIX = ".clz";

final String path;
String name;
byte[] data;
Class<?> clazz;

public CompiledClass(String path, String name) {
this.path = path;
this.name = name;
}

public void onCompile(byte[] data, Class<?> clazz, String basePath) {
this.data = data;
onCompile(clazz, basePath);
}

public void onCompile(Class<?> clazz, String basePath) {
this.clazz = clazz;
this.name = clazz.getName();
if (this.data == null) {
GroovyLog.get().errorMC("The class doesnt seem to be compiled yet. (" + name + ")");
return;
}
if (!GroovyScriptSandbox.WRITE_CACHE) return;
try {
File file = getDataFile(basePath);
file.getParentFile().mkdirs();
try (FileOutputStream stream = new FileOutputStream(file)) {
stream.write(this.data);
stream.flush();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public boolean readData(String basePath) {
if (this.data != null) return true;
File file = getDataFile(basePath);
if (!file.exists()) return false;
try {
this.data = Files.readAllBytes(file.toPath());
return true;
} catch (IOException e) {
return false;
}
}

public void deleteCache(String cachePath) {
try {
Files.deleteIfExists(getDataFile(cachePath).toPath());
} catch (IOException e) {
throw new RuntimeException(e);
}
}

protected File getDataFile(String basePath) {
return FileUtil.makeFile(basePath, FileUtil.getParent(this.path), this.name + CLASS_SUFFIX);
}

public String getName() {
return name;
}

public String getPath() {
return path;
}

@Override
public String toString() {
return new ToStringBuilder(this)
.append("name", name)
.toString();
}
}
Loading

0 comments on commit 61cba0b

Please sign in to comment.