User Tools

Site Tools


tutorial:mixin_tips

FIXME This page is currently being written, and may change at any time.

Mixin Tips (WIP)

This is a collection of different tips some might find useful. It's recommended to read the previous articles to understand the tips.

Make abstract classes

It's fair to say that you should never instantiate a mixin class, mainly because the class doesn't make sense to java if it isn't used in a mixin environment, and there are other ways to access methods declared in a mixin.

Declaring a mixin class abstract doesn't affect its function, and it becomes protected against accidental instantiation.

MixinClass foo = new MixinClass(); // can't do that with an abstract class

Make abstract shadow methods

If you want to access a unaccessible method or field from your target class into your mixin class, you need to use @Shadow to make that method/field visible.

You can perfectly do this in normal classes by using a dummy body:

@Shadow
protected void hiddenMethod() {/*dummy body*/}

But it's much more elegant to use an abstract method (and hence an abstract class):

@Shadow
protected abstract void hiddenMethod(); // no dummy body necessary

Note: this doesn't work with private methods, since you can't have private abstract methods, and hence you need to use the normal one.

Access the ''this'' instance more easily

In mixin, if you want to access the “this” instance, you have to cast it into Object first.

((TargetClass)(Object)this).field/method();

But that only works if your mixin class extends/implements everything that your target class does, which can be a pain because if one of those classes/interfaces has a method you need to implement, you'll be in for some trouble.

Luckily, this can all be avoided by using an abstract class, in which case you don't have to implement methods and all problems are avoided.

How to mixin inner classes

1. Normal inaccessible inner classes

Since this you can't directly access (and hence mention) these classes from outside, you need to use the “targets” field of the mixin annotation to specify the name.

You do this by using the complete name of the outer class, then a $ and finally the name of the inner class, as shown below:

Class:

package some.random.package;
 
public class Outer {
     private class Inner {
         public void someRandomMethod() {}
     }
}

Mixin with injection:

@Mixin(targets = "some.random.package.Outer$Inner")
public class MyMixin {
    @Inject(method = "someRandomMethod()V", at = @At("HEAD"))
    private void injected(CallbackInfo ci) {
        // your code here
    }
}

The only caveat is that if you want to mixin into the inner class constructor, the first parameter must be of the type of the outer class (this is implicitly added by the compiler to allow the inner class to access instance methods of the outer class):

@Inject(method = "<init>(Lsome/random/package/Outer;)V", at = @At("HEAD"))
private void injected(CallbackInfo ci) {
    // your code here
}

2. Static inaccessible inner classes

These are the same as above, the only difference is that the constructor doesn't have the outer class first parameter (because in static inner classes only private static methods can be accessed from the inner class, and hence that parameter isn't needed).

3. Anonymous inner classes

These are the same as the static inaccessible inner classes, the only difference is that since they don't have a name, they are declared by appearance order, for example: the anonymous inner class if declared in our previous example class first would be named Outer$1, a second one would be named Outer$2, a third one Outer$3 and so on (the declaration order is on source level).

How to mixin to lambdas

Sometimes you want to mixin to lambdas. However, lambda do not have visible names. In this case, remember that mixin is applied to bytecode instead of source. Therefore, you can view the bytecode to view lambdas.

For example, we want to inject the lambda in EntitySelectors. The target code is seen as follows:

public class EntitySelectorOptions {
  public static void register() {
    if (...) {
      putOption("name", reader -> {
        // Assume that you want to mixin to here
        int i = reader.getReader().getCursor();
        // ...
      }
      // ...
    }
  }
}

If you directly injects to register method, that does not work. Please view the bytecode, and you will find:

  private static synthetic method_9982(Lnet/minecraft/command/EntitySelectorReader;)V throws com/mojang/brigadier/exceptions/CommandSyntaxException 
   L0
   // ...

Therefore, the method name of the lambda you want to mixin should be method_9982.

Mixin to those not remapped

If you want to mixin to classes, methods or fields that are not remapped in yarn, such as com.mojang.brigadier.StringReader, you may add remap = false to avoid potential errors. For example:

@Mixin(value = StringReader.class, remap = false)
public abstract class StringReaderMixin { ... }

Another example is:

@Mixin(EntitySelectorReader.class)
public abstract class EntitySelectorReaderMixin {
  @Inject(method = "readTagCharacter", at = @At(value = "INVOKE", target = "Lcom/mojang/brigadier/StringReader;skipWhitespace()V", remap = false)
  // your injection method ...
}
tutorial/mixin_tips.txt · Last modified: 2023/12/18 02:06 by solidblock