neoforged / NeoForge

Neo Modding API for Minecraft, based on Forge
https://projects.neoforged.net/neoforged/neoforge
Other
1.18k stars 172 forks source link

add option to generate jars with baked in mixins #1583

Open noah1510 opened 1 day ago

noah1510 commented 1 day ago

graalvm has support for compiling java programs to the native platform in a feature called native image. There have been several people that got this working for vanilla minecraft but so far these projects don't work for modded minecraft mainly because of mixins. Mixins don't work in native code because most of the class information is gone as it got turned into native byte code. This proposal is about adding a flag that generates binaries that have the mixins baked into them, so that they don't have to be reapplied on every launch again. This might improve the game load time especially when a lot of mods are installed and it allows generating a native image using graalvm, as the runtime mixins are already applied.

It is already possible to output the patched classes using the -Dmixin.debug.export=true flag. This generates a .mixin.out dir in the .minecraft dir with all of the mixed in java byte code classes.

My idea would be to add a flag to first dump all the bytecode in that dir (excluding all mixins and mixin plugins) and then override the classes as the mixins get applied. The mod loader should then make sure to run all the mixin plugins and apply all the mixins early, to spit out the patched bytecode. All the assets and data dirs should be extracted as well and be turned into a giant resource and asset pack that can be loaded in one go as well.

In a second step the user can turn that output dir into a really big jar, that can be ran directly and has all the mod code in it. This second step might need some mod loader changes to work correctly as this patched code must not try to load code from the mods dir, nor from the minecraft library dirs.

Since I am not a java dev some feedback on this is very appreciated. In theory many parts of this can be done easily by just extracting the jars as zip into the .mixin.out (doing it recursively for the jar in jar files) and then ignoring the META-INF dir and removing the top level files (mixin configs and pack config). While this process would result in a way slower first start time, following game launches could be significantly faster.

noah1510 commented 1 day ago

I looked a bit more into the tools that exist for the neoforge build process. JavaSourceTransformer and MergeTool should be able to do most of the things I suggested here. All that is missing is a way to run those tools with all ATs and Mixins from all installed mods during the runtime.

heipiao233 commented 1 day ago

In (Neo)Forge, all the transformations (including Mixin) of class files must go through ModLauncher. Therefore, ModLauncher can be modified to export the transformed class files and load them directly if the mod list is not changed. I think we can add a method in ILaunchPluginService, such as generateCacheSign. If the signature returned by one of the services is different from the last launch, rebuild the cache. For example, we can return the mod file hash values for generateCacheSign in FML.

noah1510 commented 1 day ago

That architecture makes this a lot easier.

Mods can mixin/AT other mods right? In that case once any mod gets updated the whole cache has to be rebuilt, unless all transformers get indexed and hashed).

IThundxr commented 21 hours ago

If i am understanding this correctly, you are saying that modified classes should be built into mods so they can be used instead of mixins being applied (basically mixins being done at compile time)

However this would not work in the event where multiple mods are mixing into the same classes as who's bundled class should be applied? Mod A's or Mod B's?

noah1510 commented 19 hours ago

No not at all.

The idea is that on the first launch of the game all mods apply their transformations and all of the modified classes get cached and saved to the disk. (maybe even add a launch flag to just do this step then close the game)

On the following game starts these cached class files then get used and all source transformation steps can be skipped.

Every time a mod gets updated or added the whole class cache needs to be rebuilt but that is still way less often than doing it on every launch.

The goal is to speed up the game load times on slower CPUs and to potentially allow graalvm native image generation for even better performance gains. This doesn't actually do something the mod loader isn't doing at the moment, this is just about reusing the intermediate output.

HenryLoenwind commented 13 hours ago

There's one caveat I don't see a solution for: One of the core concepts of how modding is set up is that classes are loaded conditionally and on demand. This means that in a typical installations there may be hundreds of classes that won't classload. Trying to convert everything that's in a modded installation into class file that can be processed would run into countless classloading error---with no way of knowing which ones are real errors and which ones are classes that would never be loaded in the current setup.