commit fb2890003202ba5f8ea7c3a2a3ef2d15151ec527 Author: FliegendeWurst <2012gdwu@web.de> Date: Wed May 13 10:57:08 2020 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cf21202 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.gradle +build +run diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/AsyncLighting.iml b/.idea/AsyncLighting.iml new file mode 100644 index 0000000..78b2cc5 --- /dev/null +++ b/.idea/AsyncLighting.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..61a9130 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c259d73 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# AsyncLighting + +This mod moves Minecraft's light engine processing to another thread. +It also fixes most issues with the Forge option "alwaysSetupTerrainOffThread" by simply moving all rendering off the main thread. It is recommended to enable this Forge option to avoid rendering issues. +Also included is a fix for opening screenshots using the chat link (vanilla behaviour: freezing the game until image viewer is closed). +Forge version this mod was tested to work with: 31.1.27. Version 31.1.87 is currently incompatible. + +## Known issues + +Block light in single player worlds is not rendered until the light source receives a block update. +Multiplayer worlds do not have this issue. diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..b37b2e5 --- /dev/null +++ b/build.gradle @@ -0,0 +1,90 @@ +buildscript { + repositories { + maven { url = 'https://files.minecraftforge.net/maven' } + jcenter() + mavenCentral() + } + dependencies { + classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '3.+', changing: true + } +} +apply plugin: 'net.minecraftforge.gradle' +// Only edit below this line, the above code adds and enables the necessary things for Forge to be setup. +apply plugin: 'eclipse' +apply plugin: 'maven-publish' + +ext.configFile = file('build.properties') +ext.config = parseConfig(configFile) + +version = config.version +group = "fliegendewurst.${config.mod_id}" +archivesBaseName = config.mod_name + +sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' + +minecraft { + mappings channel: "${config.mapping_channel}", version: "${config.mapping_version}" + // makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable. + accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') + + runs { + client { + workingDirectory project.file('run') + + property 'forge.logging.markers', '' + property 'forge.logging.console.level', 'debug' + } + + server { + workingDirectory project.file('run') + + property 'forge.logging.markers', 'REGISTRIES,REGISTRYDUMP' + property 'forge.logging.console.level', 'debug' + } + } +} + +dependencies { + minecraft "net.minecraftforge:forge:${config.mc_version}-${config.forge_version}" +} + +jar { + archiveName = "${baseName}-${config.mc_version}-${version}.${extension}" + + manifest { + attributes([ + "Specification-Title": "${config.mod_id}", + "Specification-Vendor": 'FliegendeWurst', + "Specification-Version": '1', + "Implementation-Title": "${config.mod_id}", + "Implementation-Version": "${version}", + "Implementation-Vendor" :'FliegendeWurst', + "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") + ]) + } +} + +// Example configuration to allow publishing using the maven-publish task +// This is the preferred method to reobfuscate your jar file +jar.finalizedBy('reobfJar') + +publishing { + publications { + mavenJava(MavenPublication) { + artifact jar + } + } + repositories { + maven { + url "file:///${project.projectDir}/mcmodsrepo" + } + } +} + +def parseConfig(File config) { + config.withReader { + def prop = new Properties() + prop.load(it) + return (new ConfigSlurper().parse(prop)) + } +} diff --git a/build.properties b/build.properties new file mode 100644 index 0000000..d7cce21 --- /dev/null +++ b/build.properties @@ -0,0 +1,7 @@ +mod_name=AsyncLighting +forge_version=31.1.27 +mapping_version=20200225-1.15.1 +mapping_channel=snapshot +mod_id=asynclighting +version=1.0 +mc_version=1.15.2 diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..784899a --- /dev/null +++ b/gradle.properties @@ -0,0 +1,4 @@ +# Sets default memory used for gradle commands. Can be overridden by user or command line properties. +# This is required to provide enough memory for the Minecraft decompilation process. +org.gradle.jvmargs=-Xmx3G +org.gradle.daemon=false diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7a3265e Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..949819d --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/fliegendewurst/asynclighting/AsyncLighting.java b/src/main/java/fliegendewurst/asynclighting/AsyncLighting.java new file mode 100644 index 0000000..397dd1e --- /dev/null +++ b/src/main/java/fliegendewurst/asynclighting/AsyncLighting.java @@ -0,0 +1,359 @@ +package fliegendewurst.asynclighting; + +import com.google.common.collect.Sets; +import net.minecraft.client.Minecraft; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.SectionPos; +import net.minecraft.world.LightType; +import net.minecraft.world.chunk.NibbleArray; +import net.minecraft.world.lighting.WorldLightManager; +import net.minecraftforge.client.event.RenderGameOverlayEvent; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.event.world.WorldEvent; +import net.minecraftforge.eventbus.api.EventPriority; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.annotation.Nullable; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.BlockingDeque; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.TimeUnit; + +@Mod("asynclighting") +public class AsyncLighting { + public static final String MODID = "asynclighting"; + public static final Logger LOGGER = LogManager.getLogger(MODID); + + public static Minecraft mc; + + private static final BlockingDeque LIGHT_QUEUE = new LinkedBlockingDeque<>(); + + private static boolean tickingLight = false; + + public AsyncLighting() { + AsyncLighting.mc = Minecraft.getInstance(); + + new Thread(AsyncLighting::processLightTicks, "Light thread").start(); + + MinecraftForge.EVENT_BUS.register(this); + } + + @SubscribeEvent + public void drawDebugText(RenderGameOverlayEvent.Text event) { + if (!Minecraft.getInstance().gameSettings.showDebugInfo) { + return; + } + if (!SECTION_LIGHT_UPDATES.isEmpty() || !BLOCK_LIGHT_UPDATES.isEmpty()) { + event.getLeft().add(""); + } + if (!SECTION_LIGHT_UPDATES.isEmpty()) + event.getLeft().add(String.format("%s section updates pending", SECTION_LIGHT_UPDATES.size())); + if (!BLOCK_LIGHT_UPDATES.isEmpty()) + event.getLeft().add(String.format("%s block updates pending", BLOCK_LIGHT_UPDATES.size())); + } + + @SubscribeEvent(priority = EventPriority.HIGHEST) + public void worldUnload(WorldEvent.Unload event) { + BLOCK_LIGHT_UPDATES.clear(); + SECTION_LIGHT_UPDATES.clear(); + } + + @SubscribeEvent + public void onRenderStart(TickEvent.RenderTickEvent event) { + if (event.phase == TickEvent.Phase.END && !tickingLight) { + // apply block light updates (queue for rendering) + Iterator iter = BLOCK_LIGHT_UPDATES.iterator(); + if (iter.hasNext()) { + BlockPos pos = iter.next(); + if (mc.worldRenderer == null) { + BLOCK_LIGHT_UPDATES.clear(); + SECTION_LIGHT_UPDATES.clear(); + return; + } + mc.worldRenderer.markBlockRangeForRenderUpdate(pos.getX(), pos.getY(), pos.getZ(), pos.getX(), pos.getY(), pos.getZ()); + BLOCK_LIGHT_UPDATES.remove(pos); + } + Iterator iter2 = SECTION_LIGHT_UPDATES.iterator(); + if (iter2.hasNext()) { + SectionPos pos = iter2.next(); + if (mc.worldRenderer == null) { + BLOCK_LIGHT_UPDATES.clear(); + SECTION_LIGHT_UPDATES.clear(); + return; + } + //LOGGER.debug("rendering section {} {} {}", pos.getX(), pos.getY(), pos.getZ()); + mc.worldRenderer.markForRerender(pos.getX(), pos.getY(), pos.getZ()); + //mc.worldRenderer.markSurroundingsForRerender(pos.getX(), pos.getY(), pos.getZ()); + SECTION_LIGHT_UPDATES.remove(pos); + } + } + } + + public static boolean uploadMore() { + if (mc.player != null) { + // lowest possible motion (standing still) is: 0.006146560239257815 + return mc.player.getMotion().lengthSquared() < 0.0073; + } else { + return false; + } + } + + public static void tickLightManager(WorldLightManager lightManager, int toUpdateCount, boolean updateSkyLight, boolean updateBlockLight) { + //LOGGER.debug("ticking light {} {} {} {}", lightManager, toUpdateCount, updateSkyLight, updateBlockLight); + try { + LIGHT_QUEUE.putLast(new LightTick(lightManager, toUpdateCount, updateSkyLight, updateBlockLight)); + } catch (InterruptedException e) { + LOGGER.warn("light ticking interrupted"); + } + } + + public static void enableLightSources(WorldLightManager lightManager, ChunkPos pos, boolean b) { + //LOGGER.debug("enabling light {} {}", pos, b); + try { + LIGHT_QUEUE.putLast(new LightEnable(lightManager, pos, b)); + } catch (InterruptedException e) { + LOGGER.warn("light enabling interrupted"); + } + } + + public static void updateSectionStatus(WorldLightManager lightManager, SectionPos pos, boolean b) { + //LOGGER.debug("updating section {} {}", pos, b); + try { + LIGHT_QUEUE.putLast(new LightUpdate(lightManager, pos, b)); + } catch (InterruptedException e) { + LOGGER.warn("light section update interrupted"); + } + } + + public static void setData(WorldLightManager lightManager, LightType type, SectionPos pos, @Nullable NibbleArray b) { + //LOGGER.debug("setting data {} {}", type, pos); + try { + LIGHT_QUEUE.putLast(new SetData(lightManager, type, pos, b)); + } catch (InterruptedException e) { + LOGGER.warn("light data interrupted"); + } + } + + public static void checkBlock(WorldLightManager lightManager, BlockPos pos) { + //LOGGER.debug("checking block {}", pos); + try { + LIGHT_QUEUE.putLast(new CheckBlock(lightManager, pos)); + } catch (InterruptedException e) { + LOGGER.warn("light check block interrupted"); + } + } + + // only called in the ServerWorldLightManager + public static void blockEmission(WorldLightManager lightManager, BlockPos pos, int i) { + try { + LIGHT_QUEUE.putLast(new BlockEmission(lightManager, pos, i)); + } catch (InterruptedException e) { + LOGGER.warn("block emission increase interrupted"); + } + } + + private static final Set BLOCK_LIGHT_UPDATES = Sets.newConcurrentHashSet(); + private static final Set SECTION_LIGHT_UPDATES = Sets.newConcurrentHashSet(); + + private static void processLightTicks() { + while (true) { + try { + Runnable tick = LIGHT_QUEUE.takeFirst(); + /* queue will never fill up (since thread never waits on render to finish) + if (LIGHT_QUEUE.remainingCapacity() < 100) { + LOGGER.warn("light tick queue almost full: {} slots remaining", LIGHT_QUEUE.remainingCapacity()); + } + */ + Minecraft mc = Minecraft.getInstance(); + //Set updated = new HashSet<>(); + long start = System.currentTimeMillis(); + int counter = 0; + if (mc.world == null) { + // world exited + LIGHT_QUEUE.clear(); + continue; + } + while (tick != null) { + tickingLight = true; + tick.run(); + counter++; + if (tick instanceof CheckBlock) { + BlockPos pos = ((CheckBlock) tick).pos; + //updated.add(pos); + BLOCK_LIGHT_UPDATES.add(pos); + } else if (tick instanceof LightUpdate) { + SECTION_LIGHT_UPDATES.add(((LightUpdate) tick).pos); + } else if (tick instanceof SetData) { + SECTION_LIGHT_UPDATES.add(((SetData) tick).pos); + } + tick = LIGHT_QUEUE.pollFirst(1, TimeUnit.MILLISECONDS); + } + /* + for (BlockPos pos : updated) { + mc.worldRenderer.markBlockRangeForRenderUpdate(pos.getX(), pos.getY(), pos.getZ(), pos.getX(), pos.getY(), pos.getZ()); + //mc.worldRenderer.notifyBlockUpdate(mc.world, pos, mc.world.getBlockState(pos), mc.world.getBlockState(pos), 8 | 1); + //mc.worldRenderer.markSurroundingsForRerender(pos.getX(), pos.getY(), pos.getZ()); + } + */ + tickingLight = false; + long time = System.currentTimeMillis() - start; + if (time > 100) { + LOGGER.warn("processing {} light updates: {} ms", counter, time); + } + } catch (InterruptedException e) { + LOGGER.warn("light ticking thread interrupted"); + } + } + } + + /* + private static void processBlockUpdates() { + try { + while (true) { + while (rendering) { + // do not update lights in current frame + Thread.sleep(1); + } + + } + } catch (InterruptedException e) { + LOGGER.warn("block light update thread interrupted"); + } + } + */ + + private static class LightTick implements Runnable { + WorldLightManager lightManager; + int toUpdateCount; + boolean updateSkyLight; + boolean updateBlockLight; + + LightTick(WorldLightManager lightManager, int toUpdateCount, boolean updateSkyLight, boolean updateBlockLight) { + this.lightManager = lightManager; + this.toUpdateCount = toUpdateCount; + this.updateSkyLight = updateSkyLight; + this.updateBlockLight = updateBlockLight; + } + + @Override + public void run() { + lightManager.tick(toUpdateCount, updateSkyLight, updateBlockLight); + } + } + + private static class LightEnable implements Runnable { + WorldLightManager lightManager; + ChunkPos pos; + boolean b; + + LightEnable(WorldLightManager lightManager, ChunkPos pos, boolean b) { + this.lightManager = lightManager; + this.pos = pos; + this.b = b; + } + + @Override + public void run() { + if (lightManager.blockLight != null) { + lightManager.blockLight.func_215620_a(pos, b); + } + if (lightManager.skyLight != null) { + lightManager.skyLight.func_215620_a(pos, b); + } + } + } + + private static class LightUpdate implements Runnable { + WorldLightManager lightManager; + SectionPos pos; + boolean b; + + LightUpdate(WorldLightManager lightManager, SectionPos pos, boolean b) { + this.lightManager = lightManager; + this.pos = pos; + this.b = b; + } + + @Override + public void run() { + if (lightManager.blockLight != null) { + lightManager.blockLight.updateSectionStatus(pos, b); + } + if (lightManager.skyLight != null) { + lightManager.skyLight.updateSectionStatus(pos, b); + } + } + } + + private static class SetData implements Runnable { + WorldLightManager lightManager; + LightType block; + SectionPos pos; + @Nullable + NibbleArray b; + + SetData(WorldLightManager lightManager, LightType type, SectionPos pos, @Nullable NibbleArray b) { + this.lightManager = lightManager; + this.block = type; + this.pos = pos; + this.b = b; + } + + @Override + public void run() { + if (block == LightType.BLOCK) { + if (lightManager.blockLight != null) { + lightManager.blockLight.setData(pos.asLong(), b); + } + } else if (lightManager.skyLight != null) { + lightManager.skyLight.setData(pos.asLong(), b); + } + } + } + + private static class CheckBlock implements Runnable { + WorldLightManager lightManager; + BlockPos pos; + + CheckBlock(WorldLightManager lightManager, BlockPos pos) { + this.lightManager = lightManager; + this.pos = pos; + } + + @Override + public void run() { + if (lightManager.blockLight != null) { + lightManager.blockLight.checkLight(pos); + } + + if (lightManager.skyLight != null) { + lightManager.skyLight.checkLight(pos); + } + } + } + + private static class BlockEmission implements Runnable { + WorldLightManager lightManager; + BlockPos pos; + int i; + + BlockEmission(WorldLightManager lightManager, BlockPos pos, int i) { + this.lightManager = lightManager; + this.pos = pos; + this.i = i; + } + + @Override + public void run() { + if (lightManager.blockLight != null) { + lightManager.blockLight.func_215623_a(pos, i); + } + } + } +} diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg new file mode 100644 index 0000000..53dec0a --- /dev/null +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -0,0 +1,3 @@ +public net.minecraft.world.lighting.WorldLightManager field_215576_a # blockLight +public net.minecraft.world.lighting.WorldLightManager field_215577_b # skyLight +public net.minecraft.world.lighting.LightEngine func_215621_a(JLnet/minecraft/world/chunk/NibbleArray;)V # setData \ No newline at end of file diff --git a/src/main/resources/META-INF/coremods.json b/src/main/resources/META-INF/coremods.json new file mode 100644 index 0000000..626af7b --- /dev/null +++ b/src/main/resources/META-INF/coremods.json @@ -0,0 +1,3 @@ +{ + "AsyncLighting Transformer": "asynclighting-transformer.js" +} \ No newline at end of file diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml new file mode 100644 index 0000000..d69b5ff --- /dev/null +++ b/src/main/resources/META-INF/mods.toml @@ -0,0 +1,28 @@ +modLoader="javafml" +loaderVersion="[31,)" +issueTrackerURL="https://github.com/FliegendeWurst/AsyncLighting/issues" + +[[mods]] +modId="asynclighting" +displayName="AsyncLighting" +version="0.1" +#updateJSONURL="" +displayURL="https://github.com/FliegendeWurst/AsyncLighting/" +#logoFile="" +authors="FliegendeWurst" +description=''' +Asynchronous light processing, rendering and more. +''' + +[[dependencies.asynclighting]] + modId="forge" + mandatory=true + versionRange="[31,)" #mandatory + ordering="NONE" + side="BOTH" +[[dependencies.asynclighting]] + modId="minecraft" + mandatory=true + versionRange="[1.15.2]" + ordering="NONE" + side="BOTH" diff --git a/src/main/resources/asynclighting-transformer.js b/src/main/resources/asynclighting-transformer.js new file mode 100644 index 0000000..b836638 --- /dev/null +++ b/src/main/resources/asynclighting-transformer.js @@ -0,0 +1,813 @@ +/** + * This function is called by Forge before any minecraft classes are loaded to + * setup the coremod. + * + * @return {object} All the transformers of this coremod. + */ +function initializeCoreMod() { + /*Class/Interface*/ Opcodes = Java.type("org.objectweb.asm.Opcodes"); + /*Class*/ ASMAPI = Java.type("net.minecraftforge.coremod.api.ASMAPI");; + + /*Class*/ InsnList = Java.type("org.objectweb.asm.tree.InsnList"); + /*Class*/ LabelNode = Java.type("org.objectweb.asm.tree.LabelNode"); + + /*Class*/ FieldNode = Java.type("org.objectweb.asm.tree.FieldNode"); + /*Class*/ MethodNode = Java.type("org.objectweb.asm.tree.MethodNode"); + + /*Class*/ AbstractInsnNode = Java.type("org.objectweb.asm.tree.AbstractInsnNode"); + /*Class*/ InsnNode = Java.type("org.objectweb.asm.tree.InsnNode"); + /*Class*/ VarInsnNode = Java.type("org.objectweb.asm.tree.VarInsnNode"); + /*Class*/ LdcInsnNode = Java.type("org.objectweb.asm.tree.LdcInsnNode"); + /*Class*/ FieldInsnNode = Java.type("org.objectweb.asm.tree.FieldInsnNode"); + /*Class*/ MethodInsnNode = Java.type("org.objectweb.asm.tree.MethodInsnNode"); + /*Class*/ JumpInsnNode = Java.type("org.objectweb.asm.tree.JumpInsnNode"); + /*Class*/ TypeInsnNode = Java.type("org.objectweb.asm.tree.TypeInsnNode"); + + ACC_PUBLIC = Opcodes.ACC_PUBLIC; + + INVOKESTATIC = Opcodes.INVOKESTATIC; + INVOKEVIRTUAL = Opcodes.INVOKEVIRTUAL; + + ALOAD = Opcodes.ALOAD; + ILOAD = Opcodes.ILOAD; + FLOAD = Opcodes.FLOAD; + DLOAD = Opcodes.DLOAD; + + ASTORE = Opcodes.ASTORE; + ISTORE = Opcodes.ISTORE; + + RETURN = Opcodes.RETURN; + ARETURN = Opcodes.ARETURN; + IRETURN = Opcodes.IRETURN; + DRETURN = Opcodes.DRETURN; + + NEW = Opcodes.NEW; + + MONITORENTER = Opcodes.MONITORENTER; + MONITOREXIT = Opcodes.MONITOREXIT; + + ACONST_NULL = Opcodes.ACONST_NULL; + ICONST_0 = Opcodes.ICONST_0; + + IFEQ = Opcodes.IFEQ; + IFNE = Opcodes.IFNE; + IF_ACMPEQ = Opcodes.IF_ACMPEQ; + IFNULL = Opcodes.IFNULL; + + GETFIELD = Opcodes.GETFIELD; + GETSTATIC = Opcodes.GETSTATIC; + + GOTO = Opcodes.GOTO; + FRAME = Opcodes.FRAME; + + LABEL = AbstractInsnNode.LABEL; + METHOD_INSN = AbstractInsnNode.METHOD_INSN; + VAR_INSN = AbstractInsnNode.VAR_INSN; + + return { + "WorldRenderer modify updateCameraAndRender": { + "target": { + "type": "METHOD", + "class": "net.minecraft.client.renderer.WorldRenderer", + "methodName": "func_228426_a_", // updateCameraAndRender func_228426_a_ + "methodDesc": "(Lcom/mojang/blaze3d/matrix/MatrixStack;FJZLnet/minecraft/client/renderer/ActiveRenderInfo;Lnet/minecraft/client/renderer/GameRenderer;Lnet/minecraft/client/renderer/LightTexture;Lnet/minecraft/client/renderer/Matrix4f;)V" + }, + "transformer": function (method) { + //printInstructions(method.instructions); + var toInject = new InsnList(); + var callback = new MethodInsnNode(INVOKESTATIC, "fliegendewurst/asynclighting/AsyncLighting", "tickLightManager", "(Lnet/minecraft/world/lighting/WorldLightManager;IZZ)V"); + // 40 to 47 need to go (method call has to be wrapped) + for (var i = 0; i < 48; i++) { + toInject.add(method.instructions.get(i)); + } + toInject.add(callback); + for (var i = 50; i < method.instructions.size(); i++) { + toInject.add(method.instructions.get(i)); + } + method.instructions.clear(); + method.instructions.add(toInject); + //printInstructions(method.instructions); + return method; + } + }, + "WorldLightManager#enableLightSources": { + "target": { + "type": "METHOD", + "class": "net.minecraft.world.lighting.WorldLightManager", + "methodName": "func_215571_a", // enableLightSources func_215571_a + "methodDesc": "(Lnet/minecraft/util/math/ChunkPos;Z)V" + }, + "transformer": function (method) { + //printInstructions(method.instructions); + var toInject = new InsnList(); + var callback = new MethodInsnNode(INVOKESTATIC, "fliegendewurst/asynclighting/AsyncLighting", "enableLightSources", "(Lnet/minecraft/world/lighting/WorldLightManager;Lnet/minecraft/util/math/ChunkPos;Z)V"); + // load parameters + toInject.add(new VarInsnNode(ALOAD, 0)); + for (var i = 9; i <= 10; i++) { + toInject.add(method.instructions.get(i)); + } + toInject.add(callback); + toInject.add(new InsnNode(RETURN)); + method.instructions.clear(); + method.instructions.add(toInject); + //printInstructions(method.instructions); + return method; + } + }, + "WorldLightManager#updateSectionStatus": { + "target": { + "type": "METHOD", + "class": "net.minecraft.world.lighting.WorldLightManager", + "methodName": "func_215566_a", // updateSectionStatus func_215566_a + "methodDesc": "(Lnet/minecraft/util/math/SectionPos;Z)V" + }, + "transformer": function (method) { + //printInstructions(method.instructions); + var toInject = new InsnList(); + var callback = new MethodInsnNode(INVOKESTATIC, "fliegendewurst/asynclighting/AsyncLighting", "updateSectionStatus", "(Lnet/minecraft/world/lighting/WorldLightManager;Lnet/minecraft/util/math/SectionPos;Z)V"); + // load parameters + toInject.add(new VarInsnNode(ALOAD, 0)); + for (var i = 9; i <= 10; i++) { + toInject.add(method.instructions.get(i)); + } + toInject.add(callback); + toInject.add(new InsnNode(RETURN)); + method.instructions.clear(); + method.instructions.add(toInject); + //printInstructions(method.instructions); + return method; + } + }, + "WorldLightManager#setData": { + "target": { + "type": "METHOD", + "class": "net.minecraft.world.lighting.WorldLightManager", + "methodName": "func_215574_a", // setData func_215574_a + "methodDesc": "(Lnet/minecraft/world/LightType;Lnet/minecraft/util/math/SectionPos;Lnet/minecraft/world/chunk/NibbleArray;)V" + }, + "transformer": function (method) { + //printInstructions(method.instructions); + var toInject = new InsnList(); + var callback = new MethodInsnNode(INVOKESTATIC, "fliegendewurst/asynclighting/AsyncLighting", "setData", "(Lnet/minecraft/world/lighting/WorldLightManager;Lnet/minecraft/world/LightType;Lnet/minecraft/util/math/SectionPos;Lnet/minecraft/world/chunk/NibbleArray;)V"); + // load parameters + toInject.add(new VarInsnNode(ALOAD, 0)); + toInject.add(new VarInsnNode(ALOAD, 1)); + toInject.add(new VarInsnNode(ALOAD, 2)); + toInject.add(new VarInsnNode(ALOAD, 3)); + toInject.add(callback); + toInject.add(new InsnNode(RETURN)); + method.instructions.clear(); + method.instructions.add(toInject); + //printInstructions(method.instructions); + return method; + } + }, + "less unnecessary rendering": { + "target": { + "type": "METHOD", + "class": "net.minecraft.client.network.play.ClientPlayNetHandler", + // setLightData func_217284_a (IILnet/minecraft/world/lighting/WorldLightManager;Lnet/minecraft/world/LightType;IILjava/util/Iterator;)V + "methodName": "func_217284_a", + "methodDesc": "(IILnet/minecraft/world/lighting/WorldLightManager;Lnet/minecraft/world/LightType;IILjava/util/Iterator;)V" + }, + "transformer": function (method) { + //printInstructions(method.instructions); + var toInject = new InsnList(); + for (var i = 0; i <= 81; i++) { + toInject.add(method.instructions.get(i)); + } + // ignore markForRerender + for (var i = 90; i < method.instructions.size(); i++) { + toInject.add(method.instructions.get(i)); + } + method.instructions.clear(); + method.instructions.add(toInject); + return method; + } + }, + "less unnecessary rendering #2": { + "target": { + "type": "METHOD", + "class": "net.minecraft.client.network.play.ClientPlayNetHandler", + // processChunkUnload func_184326_a (Lnet/minecraft/network/play/server/SUnloadChunkPacket;)V + "methodName": "func_184326_a", + "methodDesc": "(Lnet/minecraft/network/play/server/SUnloadChunkPacket;)V" + }, + "transformer": function (method) { + //printInstructions(method.instructions); + var toInject = new InsnList(); + for (var i = 0; i <= 42; i++) { + toInject.add(method.instructions.get(i)); + } + // ignore markForRerender + for (var i = 51; i < method.instructions.size(); i++) { + toInject.add(method.instructions.get(i)); + } + method.instructions.clear(); + method.instructions.add(toInject); + return method; + } + }, + "less unnecessary rendering #3": { + "target": { + "type": "METHOD", + "class": "net.minecraft.client.network.play.ClientPlayNetHandler", + // handleChunkData func_147263_a (Lnet/minecraft/network/play/server/SChunkDataPacket;)V + "methodName": "func_147263_a", + "methodDesc": "(Lnet/minecraft/network/play/server/SChunkDataPacket;)V" + }, + "transformer": function (method) { + //printInstructions(method.instructions); + var toInject = new InsnList(); + for (var i = 0; i <= 56; i++) { + toInject.add(method.instructions.get(i)); + } + // ignore markForRerender + for (var i = 65; i < method.instructions.size(); i++) { + toInject.add(method.instructions.get(i)); + } + method.instructions.clear(); + method.instructions.add(toInject); + return method; + } + }, + /* + "network debugging": { + "target": { + "type": "METHOD", + "class": "net.minecraft.network.NetworkManager", + // exceptionCaught (Lio/netty/channel/ChannelHandlerContext;Ljava/lang/Throwable;)V exceptionCaught + "methodName": "exceptionCaught", + "methodDesc": "(Lio/netty/channel/ChannelHandlerContext;Ljava/lang/Throwable;)V" + }, + "transformer": function (method) { + //printInstructions(method.instructions); + var toInject = new InsnList(); + toInject.add(new VarInsnNode(ALOAD, 2)); + toInject.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Throwable", "printStackTrace", "()V")); + method.instructions.insert(toInject); + return method; + } + }, + */ + "WorldLightManager#checkBlock": { + "target": { + "type": "METHOD", + "class": "net.minecraft.world.lighting.WorldLightManager", + "methodName": "func_215568_a", // checkBlock func_215568_a (Lnet/minecraft/util/math/BlockPos;)V + "methodDesc": "(Lnet/minecraft/util/math/BlockPos;)V" + }, + "transformer": function (method) { + //printInstructions(method.instructions); + var toInject = new InsnList(); + var callback = new MethodInsnNode(INVOKESTATIC, "fliegendewurst/asynclighting/AsyncLighting", "checkBlock", "(Lnet/minecraft/world/lighting/WorldLightManager;Lnet/minecraft/util/math/BlockPos;)V"); + // load parameters + toInject.add(new VarInsnNode(ALOAD, 0)); + toInject.add(new VarInsnNode(ALOAD, 1)); + toInject.add(callback); + toInject.add(new InsnNode(RETURN)); + method.instructions.clear(); + method.instructions.add(toInject); + //printInstructions(method.instructions); + return method; + } + }, + "queue call to onBlockEmissionIncrease": { + "target": { + "type": "METHOD", + "class": "net.minecraft.world.lighting.WorldLightManager", + // func_215573_a (Lnet/minecraft/util/math/BlockPos;I)V onBlockEmissionIncrease + "methodName": "func_215573_a", + "methodDesc": "(Lnet/minecraft/util/math/BlockPos;I)V" + }, + "transformer": function (method) { + //printInstructions(method.instructions); + var toInject = new InsnList(); + var callback = new MethodInsnNode(INVOKESTATIC, "fliegendewurst/asynclighting/AsyncLighting", "blockEmission", "(Lnet/minecraft/world/lighting/WorldLightManager;Lnet/minecraft/util/math/BlockPos;I)V"); + // load parameters + toInject.add(new VarInsnNode(ALOAD, 0)); + toInject.add(new VarInsnNode(ALOAD, 1)); + toInject.add(new VarInsnNode(ILOAD, 2)); + toInject.add(callback); + toInject.add(new InsnNode(RETURN)); + method.instructions.clear(); + method.instructions.add(toInject); + //printInstructions(method.instructions); + return method; + } + }, + "World: off-thread rendering": { + "target": { + "type": "METHOD", + "class": "net.minecraft.world.World", + // markAndNotifyBlock(BlockPos pos, @Nullable Chunk chunk, BlockState blockstate, BlockState newState, int flags) + "methodName": "markAndNotifyBlock", + "methodDesc": "(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/chunk/Chunk;Lnet/minecraft/block/BlockState;Lnet/minecraft/block/BlockState;I)V" + }, + "transformer": function (method) { + //printInstructions(method.instructions); + var toInject = new InsnList(); + toInject.add(new VarInsnNode(ILOAD, 5)); + toInject.add(new LdcInsnNode(~8)); + toInject.add(new InsnNode(Opcodes.IAND)); + toInject.add(new VarInsnNode(ISTORE, 5)); + method.instructions.insert(toInject); + return method; + } + }, + "Chunk: never immediately render": { + "target": { + "type": "METHOD", + "class": "net.minecraft.client.renderer.chunk.ChunkRenderDispatcher$ChunkRender", + // boolean needsImmediateUpdate() + "methodName": "func_188281_o", + "methodDesc": "()Z" + }, + "transformer": function (method) { + //printInstructions(method.instructions); + var toInject = new InsnList(); + toInject.add(new LdcInsnNode(false)); + toInject.add(new InsnNode(IRETURN)); + method.instructions.clear(); + method.instructions.insert(toInject); + return method; + } + }, + "ChunkRenderDispatcher: upload chunks slowly": { + "target": { + "type": "METHOD", + "class": "net.minecraft.client.renderer.chunk.ChunkRenderDispatcher", + // boolean runChunkUploads() + "methodName": "func_228908_d_", + "methodDesc": "()Z" + }, + "transformer": function (method) { + //printInstructions(method.instructions); + var toInject = new InsnList(); + toInject.add(new MethodInsnNode(INVOKESTATIC, "fliegendewurst/asynclighting/AsyncLighting", "uploadMore", "()Z")); + toInject.add(new JumpInsnNode(IFNE, method.instructions.get(18))); + toInject.add(new VarInsnNode(ILOAD, 1)); + toInject.add(new InsnNode(IRETURN)); + method.instructions.insert(method.instructions.get(17), toInject); + return method; + } + }, + "Open screenshots without blocking on viewer exit": { + "target": { + "type": "METHOD", + "class": "net.minecraft.util.Util$OS", + // void openURL(java.net.URL) + "methodName": "func_195639_a", + "methodDesc": "(Ljava/net/URL;)V" + }, + "transformer": function (method) { + //printInstructions(method.instructions); + var toInject = new InsnList(); + toInject.add(new InsnNode(RETURN)); + method.instructions.insert(method.instructions.get(5), toInject); + return method; + } + }, + /* + "SectionLightStorage synchronization": { + "target": { + "type": "CLASS", + "name": "net.minecraft.world.lighting.SectionLightStorage" + //"methodName": "func_215522_a", // updateSections func_215522_a + //"methodDesc": "(Lnet/minecraft/world/lighting/LightEngine;ZZ)V" + }, + "transformer": function (clazz) { + //method.instructions.insert(new InsnNode(MONITORENTER)); + //method.instructions.insert(new VarInsnNode(ALOAD, 0)); + //method.instructions.add(new InsnNode(MONITOREXIT)); + for (var i = 1; i < clazz.methods.size(); i++) { + var method = clazz.methods.get(i); + //if (method.name === "getLightOrDefault") { + if ((method.access & Opcodes.ACC_ABSTRACT) !== 0) { + continue; + } + method.access |= Opcodes.ACC_SYNCHRONIZED; + } + return clazz; + } + }, + "BlockLightStorage synchronization": { + "target": { + "type": "CLASS", + "name": "net.minecraft.world.lighting.BlockLightStorage" + }, + "transformer": function (clazz) { + for (var i = 1; i < clazz.methods.size(); i++) { + var method = clazz.methods.get(i); + if ((method.access & Opcodes.ACC_ABSTRACT) !== 0) { + continue; + } + method.access |= Opcodes.ACC_SYNCHRONIZED; + } + return clazz; + } + }, + "SkyLightStorage synchronization": { + "target": { + "type": "CLASS", + "name": "net.minecraft.world.lighting.SkyLightStorage" + }, + "transformer": function (clazz) { + for (var i = 1; i < clazz.methods.size(); i++) { + var method = clazz.methods.get(i); + if ((method.access & Opcodes.ACC_ABSTRACT) !== 0) { + continue; + } + method.access |= Opcodes.ACC_SYNCHRONIZED; + } + return clazz; + } + }, + "SectionDistanceGraph synchronization": { + "target": { + "type": "CLASS", + "name": "net.minecraft.util.SectionDistanceGraph" + }, + "transformer": function (clazz) { + for (var i = 1; i < clazz.methods.size(); i++) { + var method = clazz.methods.get(i); + if ((method.access & Opcodes.ACC_ABSTRACT) !== 0) { + continue; + } + method.access |= Opcodes.ACC_SYNCHRONIZED; + } + return clazz; + } + }, + "LevelBasedGraph synchronization": { + "target": { + "type": "CLASS", + "name": "net.minecraft.world.lighting.LevelBasedGraph" + }, + "transformer": function (clazz) { + for (var i = 1; i < clazz.methods.size(); i++) { + var method = clazz.methods.get(i); + if ((method.access & Opcodes.ACC_ABSTRACT) !== 0) { + continue; + } + method.access |= Opcodes.ACC_SYNCHRONIZED; + } + return clazz; + } + }, + */ + "WorldRenderer modify updateChunks": { + "target": { + "type": "METHOD", + "class": "net.minecraft.client.renderer.WorldRenderer", + "methodName": "func_174967_a", // updateChunks func_174967_a + "methodDesc": "(J)V" + }, + "transformer": function (method) { + return method; + printInstructions(method.instructions); + print(method.instructions.size()); + var toInject = new InsnList(); + for (var i = 0; i <= 97; i++) { + toInject.add(method.instructions.get(i)); + } + //toInject.add(new InsnNode(RETURN)); + toInject.add(new LdcInsnNode(0)); + toInject.add(new LdcInsnNode(1)); + for (var i = 100; i < method.instructions.size(); i++) { + toInject.add(method.instructions.get(i)); + } + print("done, clearing"); + method.instructions.clear(); + print("adding"); + method.instructions.add(toInject); + //print("printing " + method.instructions.size()); + //printInstructions(method.instructions); + print("done!"); + return method; + } + } + }; +} + +/** + * Util function to print a list of instructions for debugging + * + * @param {InsnList} instructions The list of instructions to print + */ +function printInstructions(instructions) { + var arrayLength = instructions.size(); + var labelNames = { + length: 0 + }; + for (var i = 0; i < arrayLength; ++i) { + var text = getInstructionText(instructions.get(i), labelNames); + if (text.length > 0) // Some instructions are ignored + print(text); + } +} + +/** + * Util function to get the text for an instruction + * + * @param {AbstractInsnNode} instruction The instruction to generate text for + * @param {Map} labelNames The names of the labels in the format Map + */ +function getInstructionText(instruction, labelNames) { + var out = ""; + if (instruction == null) { + return "null!"; + } + if (instruction.getType() != 8) // LABEL + out += " "; // Nice formatting + if (instruction.getOpcode() > 0) // Labels, Frames and LineNumbers don't have opcodes + out += OPCODES[instruction.getOpcode()] + " "; + switch (instruction.getType()) { + default: + case 0: // INSN + break; + case 1: // INT_INSN + out += instruction.operand; + break; + case 2: // VAR_INSN + out += instruction.var; + break; + case 3: // TYPE_INSN + out += instruction.desc; + break; + case 4: // FIELD_INSN + out += instruction.owner + "." + instruction.name + " " + instruction.desc; + break; + case 5: // METHOD_INSN + out += instruction.owner + "." + instruction.name + " " + instruction.desc + " (" + instruction.itf + ")"; + break; + case 6: // INVOKE_DYNAMIC_INSN + out += instruction.name + " " + instruction.desc; + break; + case 7: // JUMP_INSN + out += getLabelName(instruction.label, labelNames); + break; + case 8: // LABEL + out += getLabelName(instruction.getLabel(), labelNames); + break; + case 9: // LDC_INSN + out += instruction.cst; + break; + case 10: // IINC_INSN + out += instruction.var + " " + instruction.incr; + break; + case 11: // TABLESWITCH_INSN + out += instruction.min + " " + instruction.max; + out += "\n"; + for (var i = 0; i < instruction.labels.length; ++i) { + out += " " + (instruction.min + i) + ": "; + out += getLabelName(instruction.labels[i], labelNames); + out += "\n"; + } + out += " " + "default: " + getLabelName(instruction.dflt, labelNames); + break; + case 12: // LOOKUPSWITCH_INSN + for (var i = 0; i < instruction.labels.length; ++i) { + out += " " + instruction.keys[i] + ": "; + out += getLabelName(instruction.labels[i], labelNames); + out += "\n"; + } + out += " " + "default: " + getLabelName(instruction.dflt, labelNames); + break; + case 13: // MULTIANEWARRAY_INSN + out += instruction.desc + " " + instruction.dims; + break; + case 14: // FRAME + out += "FRAME"; + // Frames don't work because Nashhorn calls AbstractInsnNode#getType() + // instead of accessing FrameNode#type for the code "instruction.type" + // so there is no way to get the frame type of the FrameNode + break; + case 15: // LINENUMBER + out += "LINENUMBER "; + out += instruction.line + " " + getLabelName(instruction.start.getLabel(), labelNames); + break; + } + return out; +} + +/** + * Util function to get the name for a LabelNode "instruction" + * + * @param {LabelNode} label The label to generate a name for + * @param {Map} labelNames The names of other labels in the format Map + */ +function getLabelName(label, labelNames) { + var labelHashCode = label.hashCode(); + var labelName = labelNames[labelHashCode]; + if (labelName == undefined) { + labelName = "L" + labelNames.length; + labelNames[labelHashCode] = labelName; + ++labelNames.length; + } + return labelName; +} + +/** The names of the Java Virtual Machine opcodes. */ +OPCODES = [ + "NOP", // 0 (0x0) + "ACONST_NULL", // 1 (0x1) + "ICONST_M1", // 2 (0x2) + "ICONST_0", // 3 (0x3) + "ICONST_1", // 4 (0x4) + "ICONST_2", // 5 (0x5) + "ICONST_3", // 6 (0x6) + "ICONST_4", // 7 (0x7) + "ICONST_5", // 8 (0x8) + "LCONST_0", // 9 (0x9) + "LCONST_1", // 10 (0xa) + "FCONST_0", // 11 (0xb) + "FCONST_1", // 12 (0xc) + "FCONST_2", // 13 (0xd) + "DCONST_0", // 14 (0xe) + "DCONST_1", // 15 (0xf) + "BIPUSH", // 16 (0x10) + "SIPUSH", // 17 (0x11) + "LDC", // 18 (0x12) + "LDC_W", // 19 (0x13) + "LDC2_W", // 20 (0x14) + "ILOAD", // 21 (0x15) + "LLOAD", // 22 (0x16) + "FLOAD", // 23 (0x17) + "DLOAD", // 24 (0x18) + "ALOAD", // 25 (0x19) + "ILOAD_0", // 26 (0x1a) + "ILOAD_1", // 27 (0x1b) + "ILOAD_2", // 28 (0x1c) + "ILOAD_3", // 29 (0x1d) + "LLOAD_0", // 30 (0x1e) + "LLOAD_1", // 31 (0x1f) + "LLOAD_2", // 32 (0x20) + "LLOAD_3", // 33 (0x21) + "FLOAD_0", // 34 (0x22) + "FLOAD_1", // 35 (0x23) + "FLOAD_2", // 36 (0x24) + "FLOAD_3", // 37 (0x25) + "DLOAD_0", // 38 (0x26) + "DLOAD_1", // 39 (0x27) + "DLOAD_2", // 40 (0x28) + "DLOAD_3", // 41 (0x29) + "ALOAD_0", // 42 (0x2a) + "ALOAD_1", // 43 (0x2b) + "ALOAD_2", // 44 (0x2c) + "ALOAD_3", // 45 (0x2d) + "IALOAD", // 46 (0x2e) + "LALOAD", // 47 (0x2f) + "FALOAD", // 48 (0x30) + "DALOAD", // 49 (0x31) + "AALOAD", // 50 (0x32) + "BALOAD", // 51 (0x33) + "CALOAD", // 52 (0x34) + "SALOAD", // 53 (0x35) + "ISTORE", // 54 (0x36) + "LSTORE", // 55 (0x37) + "FSTORE", // 56 (0x38) + "DSTORE", // 57 (0x39) + "ASTORE", // 58 (0x3a) + "ISTORE_0", // 59 (0x3b) + "ISTORE_1", // 60 (0x3c) + "ISTORE_2", // 61 (0x3d) + "ISTORE_3", // 62 (0x3e) + "LSTORE_0", // 63 (0x3f) + "LSTORE_1", // 64 (0x40) + "LSTORE_2", // 65 (0x41) + "LSTORE_3", // 66 (0x42) + "FSTORE_0", // 67 (0x43) + "FSTORE_1", // 68 (0x44) + "FSTORE_2", // 69 (0x45) + "FSTORE_3", // 70 (0x46) + "DSTORE_0", // 71 (0x47) + "DSTORE_1", // 72 (0x48) + "DSTORE_2", // 73 (0x49) + "DSTORE_3", // 74 (0x4a) + "ASTORE_0", // 75 (0x4b) + "ASTORE_1", // 76 (0x4c) + "ASTORE_2", // 77 (0x4d) + "ASTORE_3", // 78 (0x4e) + "IASTORE", // 79 (0x4f) + "LASTORE", // 80 (0x50) + "FASTORE", // 81 (0x51) + "DASTORE", // 82 (0x52) + "AASTORE", // 83 (0x53) + "BASTORE", // 84 (0x54) + "CASTORE", // 85 (0x55) + "SASTORE", // 86 (0x56) + "POP", // 87 (0x57) + "POP2", // 88 (0x58) + "DUP", // 89 (0x59) + "DUP_X1", // 90 (0x5a) + "DUP_X2", // 91 (0x5b) + "DUP2", // 92 (0x5c) + "DUP2_X1", // 93 (0x5d) + "DUP2_X2", // 94 (0x5e) + "SWAP", // 95 (0x5f) + "IADD", // 96 (0x60) + "LADD", // 97 (0x61) + "FADD", // 98 (0x62) + "DADD", // 99 (0x63) + "ISUB", // 100 (0x64) + "LSUB", // 101 (0x65) + "FSUB", // 102 (0x66) + "DSUB", // 103 (0x67) + "IMUL", // 104 (0x68) + "LMUL", // 105 (0x69) + "FMUL", // 106 (0x6a) + "DMUL", // 107 (0x6b) + "IDIV", // 108 (0x6c) + "LDIV", // 109 (0x6d) + "FDIV", // 110 (0x6e) + "DDIV", // 111 (0x6f) + "IREM", // 112 (0x70) + "LREM", // 113 (0x71) + "FREM", // 114 (0x72) + "DREM", // 115 (0x73) + "INEG", // 116 (0x74) + "LNEG", // 117 (0x75) + "FNEG", // 118 (0x76) + "DNEG", // 119 (0x77) + "ISHL", // 120 (0x78) + "LSHL", // 121 (0x79) + "ISHR", // 122 (0x7a) + "LSHR", // 123 (0x7b) + "IUSHR", // 124 (0x7c) + "LUSHR", // 125 (0x7d) + "IAND", // 126 (0x7e) + "LAND", // 127 (0x7f) + "IOR", // 128 (0x80) + "LOR", // 129 (0x81) + "IXOR", // 130 (0x82) + "LXOR", // 131 (0x83) + "IINC", // 132 (0x84) + "I2L", // 133 (0x85) + "I2F", // 134 (0x86) + "I2D", // 135 (0x87) + "L2I", // 136 (0x88) + "L2F", // 137 (0x89) + "L2D", // 138 (0x8a) + "F2I", // 139 (0x8b) + "F2L", // 140 (0x8c) + "F2D", // 141 (0x8d) + "D2I", // 142 (0x8e) + "D2L", // 143 (0x8f) + "D2F", // 144 (0x90) + "I2B", // 145 (0x91) + "I2C", // 146 (0x92) + "I2S", // 147 (0x93) + "LCMP", // 148 (0x94) + "FCMPL", // 149 (0x95) + "FCMPG", // 150 (0x96) + "DCMPL", // 151 (0x97) + "DCMPG", // 152 (0x98) + "IFEQ", // 153 (0x99) + "IFNE", // 154 (0x9a) + "IFLT", // 155 (0x9b) + "IFGE", // 156 (0x9c) + "IFGT", // 157 (0x9d) + "IFLE", // 158 (0x9e) + "IF_ICMPEQ", // 159 (0x9f) + "IF_ICMPNE", // 160 (0xa0) + "IF_ICMPLT", // 161 (0xa1) + "IF_ICMPGE", // 162 (0xa2) + "IF_ICMPGT", // 163 (0xa3) + "IF_ICMPLE", // 164 (0xa4) + "IF_ACMPEQ", // 165 (0xa5) + "IF_ACMPNE", // 166 (0xa6) + "GOTO", // 167 (0xa7) + "JSR", // 168 (0xa8) + "RET", // 169 (0xa9) + "TABLESWITCH", // 170 (0xaa) + "LOOKUPSWITCH", // 171 (0xab) + "IRETURN", // 172 (0xac) + "LRETURN", // 173 (0xad) + "FRETURN", // 174 (0xae) + "DRETURN", // 175 (0xaf) + "ARETURN", // 176 (0xb0) + "RETURN", // 177 (0xb1) + "GETSTATIC", // 178 (0xb2) + "PUTSTATIC", // 179 (0xb3) + "GETFIELD", // 180 (0xb4) + "PUTFIELD", // 181 (0xb5) + "INVOKEVIRTUAL", // 182 (0xb6) + "INVOKESPECIAL", // 183 (0xb7) + "INVOKESTATIC", // 184 (0xb8) + "INVOKEINTERFACE", // 185 (0xb9) + "INVOKEDYNAMIC", // 186 (0xba) + "NEW", // 187 (0xbb) + "NEWARRAY", // 188 (0xbc) + "ANEWARRAY", // 189 (0xbd) + "ARRAYLENGTH", // 190 (0xbe) + "ATHROW", // 191 (0xbf) + "CHECKCAST", // 192 (0xc0) + "INSTANCEOF", // 193 (0xc1) + "MONITORENTER", // 194 (0xc2) + "MONITOREXIT", // 195 (0xc3) + "WIDE", // 196 (0xc4) + "MULTIANEWARRAY", // 197 (0xc5) + "IFNULL", // 198 (0xc6) + "IFNONNULL" // 199 (0xc7) +]; diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta new file mode 100644 index 0000000..1889cc2 --- /dev/null +++ b/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "asynclighting resources", + "pack_format": 5 + } +}