Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache compiled scripts #137

Merged
merged 13 commits into from
Mar 14, 2024
26 changes: 8 additions & 18 deletions src/main/java/com/cleanroommc/groovyscript/GroovyScript.java
Original file line number Diff line number Diff line change
@@ -1,29 +1,26 @@
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;
import com.cleanroommc.groovyscript.compat.mods.ModSupport;
import com.cleanroommc.groovyscript.compat.mods.tinkersconstruct.TinkersConstruct;
import com.cleanroommc.groovyscript.compat.vanilla.VanillaModule;
import com.cleanroommc.groovyscript.core.mixin.DefaultResourcePackAccessor;
import com.cleanroommc.groovyscript.documentation.linkgenerator.LinkGeneratorHooks;
import com.cleanroommc.groovyscript.documentation.Documentation;
import com.cleanroommc.groovyscript.documentation.linkgenerator.LinkGeneratorHooks;
import com.cleanroommc.groovyscript.event.EventHandler;
import com.cleanroommc.groovyscript.gameobjects.GameObjectHandlerManager;
import com.cleanroommc.groovyscript.helper.JsonHelper;
import com.cleanroommc.groovyscript.network.CReload;
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.google.common.base.Joiner;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import groovy.lang.GroovySystem;
Expand Down Expand Up @@ -53,7 +50,6 @@
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.InputEvent;
import net.minecraftforge.fml.relauncher.FMLLaunchHandler;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.ApiStatus;
Expand Down Expand Up @@ -97,8 +93,6 @@ 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
Expand All @@ -111,7 +105,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 Down Expand Up @@ -164,6 +158,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 @@ -274,14 +272,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
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ public GSCommand() {
.appendSibling(GSCommand.getTextForFile("Groovy Log", GroovyLog.get().getLogFilerPath().toString(), new TextComponentString("Click to open GroovyScript log"))));
}));

addSubcommand(new SimpleCommand("deleteClassCache", (server, sender, args) -> {
brachy84 marked this conversation as resolved.
Show resolved Hide resolved
GroovyScript.getSandbox().deleteClassCache();
brachy84 marked this conversation as resolved.
Show resolved Hide resolved
}));

if (ModSupport.MEKANISM.isLoaded()) {
addSubcommand(new GSMekanismCommand());
}
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
@@ -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
Loading