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

[RFC] Convert Forge's CoreMods to Mixins, deprecate JS CoreMods #10177

Draft
wants to merge 3 commits into
base: 1.21.x
Choose a base branch
from

Conversation

Jonathing
Copy link
Member

It's time to kill CoreMods.

Background

Since the FML rewrite in 1.13, JS CoreMods have existed as a replacement to the previous Java-based CoreMods transformers that has existed in versions 1.12.2 and older. This was done to keep transformers within a sandbox where they couldn't cause unhinged early classloading issues and other nonsense. However, since 1.16 (and eventually 1.15), Forge has been shipping SpongePowered's Mixin as an alternative solution to transforming classes at runtime. It has since become the defacto "coremodding tool" and effectively supersedes CoreMods. Besides, if people really wanted to write ASM transformers in Java, they already can with ModLauncher services.

All that said, I have made this preliminary PR for a handful of reasons:

  1. Begin the process of converting Forge's existing CoreMods into Mixins.
  2. Get help implementing and testing Mixins in ForgeDev.
  3. Garner discussion and feedback on the topic of the deprecation of CoreMods and the potential enhancements of Mixin in ForgeDev and UserDev.

Migration to Mixins

With regards to migrating to Mixins, there are a few key points that need to be addressed.

Forge's Mixins

As of the creation of this PR, I have converted the existing CoreMods into Mixins.

  • These Mixins are very verbose, but offer the same functionality that these CoreMods did.
  • Additionally, since these Mixins are written in Java, it can prevent bugs such as [1.21.3] MobSpawnEvent.FinalizeSpawn is never fired #10157 from occurring at all.
  • In the future, similar Mixins could be made that move binary Forge patches into Mixins.
    • Of course, this would only apply to patches that would be similar to field or method redirects: repetitive patches that would be better to have effectively matched by mixin targets.

Mixin Libraries

  1. Are we going to continue to use SpongePowered Mixin? As in, do we have any need to fork it to include patches of our own?
  2. Do we want to include LlamaLad's MixinExtras in ForgeDev and UserDev?
    • MixinExtras is an add-on companion library to Mixin that includes several safer injection types that keep mod compatibility (i.e., it is better to use @WrapOperation than @Redirect since MixinExtras can chain injectors of the former properly).
    • Fabric and NeoForge already bundle MixinExtras in production and their toolchains, which gives modders better defaults when it comes to creating safer injection points. The same could be done for Forge's UserDev.
    • Bonus points from me personally because I love MixinExtras and can't live without it (this is why).

Note

MixinExtras can already be, and is already commonly, shadowed in with mods by using the JarJar system. It is not imperative that this is added to ForgeDev or UserDev, but would add to the convenience of modders.

Deprecation of CoreMods

As mentioned above, an additional purpose of moving Forge's CoreMods to Mixins is to incentivize the deprecation, and eventual removal, of the CoreMods framework. CoreMods's only purpose was to provide a method of creating ASM transformers within a sandbox, and it has ultimately failed at that goal considering the status of Mixin and the availability of ModLauncher transformer services.

So, we need to decide the following:

  1. When are we going to officially deprecate CoreMods? When this PR is merged? The following Minecraft version after this PR is merged?
  2. When are we going to remove CoreMods from Forge? The following major Minecraft version after this PR is merged?
    • For example, if this PR was merged during 1.21.3's lifecycle, then CoreMods could be set for removal in 1.22.

Note

Raw ASM transformers can still be created, with Mixin, by using a Mixin config plugin and overriding postApply().

Merge Process

Before this PR can be merged, the following must happen:

  • The core premise must be approved by the Core team.
  • The PR must be fully complete and removed from draft status.
  • ForgeDev, UserDev, and Installer must be thoroughly tested to make sure that Forge's own Mixins do not cause problems with the game environment.
    • In addition, the IDE support of Mixins must be identical to that of UserDev. If this isn't done automatically, it may be necessary to look into bringing in some hacks from MixinGradle.
  • Some sort of plan needs to be discussed, or implemented, regarding the deprecation of JS CoreMods in favor of Mixin.
  • The PR must be approved by the Triage team.

Disclaimers

Please note that, as of writing, this PR is still a work-in-progress and things about it may change, including the information in this PR description and the discussion surrounding it. Major testing still needs to done and discussions need to happen in order for this to go anywhere. There is no time limit, but I hope that this RFC PR is enough to start preparing for this change.

None of the changes in this PR will be backported to any versions before the merged version unless there are specific changes that can be which don't break compatibility, binary or otherwise.

Important

If you are a modder, you should not be using CoreMods or Mixins before attempting to use existing Forge APIs. You are more than welcome to write PRs that add your desired functionality to the mod loader.

- Minecraft Forge can now ship its own Mixins.
- `ForgeMixinPlugin` is used as both the connector and plugin.
  - The plugin can be very useful if we need to do manual ASM for any
unhinged reason.
- `minecraftforge.mixins.json` acts as the Mixin counterpart to
`coremods.json`, giving all the necessary info for Forge's mixins.
@autoforge autoforge bot added Triage This request requires the active attention of the Triage Team. Requires labelling or reviews. RFC This request aims to gather opinions on something that may or may not be planned for the future. labels Nov 17, 2024
@Jonathing Jonathing added Regression This request demonstrates something missing in a newer version that was present in a prior version. Feature This request implements a new feature. Cleanup This request reorganizes messy code, or fixing it would require doing so. Work In Progress This request has lots of changes that need attention. labels Nov 17, 2024
@PaintNinja PaintNinja self-requested a review November 17, 2024 23:39
@Redirect(
method = {
"<init>(Lnet/minecraft/world/effect/MobEffectInstance;)V",
"getParticleOptions()Lnet/minecraft/core/particles/ParticleOptions;",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any way that these strings can be encoded better to provide better error when things change?
@MethodRef("<init>", args = { MobEffectInstance.class }, ret = Void.class) or something?
Perhaps a build script task that verifies that we have all the expected targets?
We already have the code to generated the json that feeds the existing coremod, can something be expanded to mixins?

This comment was marked as outdated.

@Jonathing
Copy link
Member Author

Jonathing commented Nov 18, 2024

As discussed on Discord, I will be taking a different approach to these larger-scale Mixins that require several classes in order to have the same functionality that the original CoreMods did. This will likely mean writing a tool that generates Mixin classes on Gradle build. Smaller-scale mixins, like for patches we make already through bin patch, are more suited to the way Mixins are currently written.

@ByThePowerOfScience
Copy link

ByThePowerOfScience commented Nov 20, 2024

Issue with this proposal:

Mixins are incredibly non-dynamic.

If you need to target an arbitrary number of classes - for example, trying to allow pack devs to add blocks as Thaumcraft "Infusion Stabilizers", meaning you have to monkey-patch an interface onto them because they for some reason don't use capabilities - you physically cannot do this with Mixin. Believe me, I've tried.

If you need to do any sort of dynamic targeting, you have to either use a class transformer or a janked-out Mixin plugin. Obviously, I'd much rather use a class transformer to reduce the amount of jank present rather than increase it.

Mixin is an amazing framework for ASM, but access to the raw ASM is required for anything that isn't known at compile-time.

I would strongly suggest that you keep some way of accessing the raw ASM layer in Forge, even if it's just "put the names of your old-fashioned IClassTransformers in your mods.toml".

@Jonathing
Copy link
Member Author

Jonathing commented Nov 20, 2024

Issue with this proposal: Mixins are incredibly non-dynamic.

Yes, we've had this discussion in the Discord server already. I forgot to give updates here, so my bad. However, ModLauncher transformers effectively do what CoreMods already does (it itself is a ModLauncher transformer service) without the difficulties that come with maintaining CoreMods in JS (see #10157 for an example).

Although it is probably true that a simpler solution could be created that uses ModLauncher transformation service without being to cumbersome. Right now, ModLauncher transformers are expected to add their own plugins as well, and it's kind of an esoteric process. (Disclaimer: I have never actually written any ModLauncher transformer services myself, but this is how I've seen it done in the wild.)

In fact, Mixin has already turned out to be a pretty bad solution for Forge's existing CoreMods. I plan on rewriting them in raw ASM transformers instead, probably through a ModLauncher plugin.

@ByThePowerOfScience
Copy link

ByThePowerOfScience commented Nov 20, 2024

Although it is probably true that a simpler solution could be created that uses ModLauncher transformation service without being to cumbersome.

I'll be honest, a hybrid approach might be best. Instead of requiring people to write in JS to avoid classloading loops, just use the old coremod system but require all coremod classes to be in META-INF instead of the main jar. That way you can load the mods' classes as late as you want while still having access to their coremods.

All you need from there is a way to export the data from the coremod phase to the mod phase, like a global data cache that persists between phases that mods can retrieve from when they launch. A singleton hashmap that mods request data from by name.

Adding that second part would pretty much fix all of the issues with the old coremod system. Hell, I'd even be able to actually use a segmented jar if that were a thing.

@Jonathing
Copy link
Member Author

I'll be honest, a hybrid approach might be best. Instead of requiring people to write in JS to avoid classloading loops, just use the old coremod system but require all coremod classes to be in META-INF instead of the main jar.

That might be the play, although I'm not entirely sure how I would be able to access compiled classes from META-INF (I have never done that before!!!), let alone make a way for modders to write CoreMods so that they can end up in there. But yeah, repurposing the current CoreMods framework is a decent solution since it already complies with the ModLauncher transformation implementation.

All you need from there is a way to export the data from the coremod phase to the mod phase, like a global data cache that persists between phases that mods can retrieve from when they launch. A singleton hashmap that mods request data from by name.

I don't know why you'd need to export data from the coremod phase, when the only thing that should be done during the coremod phase (or even what the CoreMods should be doing at all) is running transformers on ASM trees. However, since these CoreMods would be running in Java and can access ModLauncher, you can easily write a key to ModLauncher's blackbord (here is a write example in MinecraftForge and a read example in CoreMods).

@ByThePowerOfScience
Copy link

ByThePowerOfScience commented Nov 20, 2024

I don't know why you'd need to export data from the coremod phase, when the only thing that should be done during the coremod phase (or even what the CoreMods should be doing at all) is running transformers on ASM trees.

Well, besides the fact that visitors are a nice option for a lot of one-time transformations, the biggest thing is that anything sufficiently high-level requires some amount of knowledge of what's on the classpath and what transformations were or weren't applied. Considering it's just "let a map exist during the ASM phase", I hope it's not too much to ask.

The thing I ended up needing it for was checking if TickProfiler was installed, and disabling my Mixin and adding warning tooltips to a block if it was. For some reason, TP caused nm.World to become amu when my Mixin got to it and everything broke when I tried to fix a bug in it. I still don't know why that was, actually.

There's one more thing: as someone who tried his darnedest to work with the 1.12-era coremod system, the place where the split jar failed was trying to load Mixins in the Init phase.

Since Mixins are compiled classes, they have to reference things that exist on the classpath. The problem is, when those things are APIs that only exist during the Mod phase, you can only compile them in your mod jar. But then you can only apply the Mixins early enough during the coremod phase... and it all comes apart. You can see this happen in real time between this commit and this commit.

I can't think of how to fix this, but I figured you should know about it before continuing down that path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
1.21 Cleanup This request reorganizes messy code, or fixing it would require doing so. Feature This request implements a new feature. Regression This request demonstrates something missing in a newer version that was present in a prior version. RFC This request aims to gather opinions on something that may or may not be planned for the future. Triage This request requires the active attention of the Triage Team. Requires labelling or reviews. Work In Progress This request has lots of changes that need attention.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants