User Tools

Site Tools


drafts:mixin_access_added_members

Accessing Added Members

Introduction

Mixin allows developers to add new fields and methods to a target class, this can be useful for operations internal to the Mixin class, but you may want to expose your added members to the rest of your project. In order to do this, we can leverage interfaces. By implementing an interface into the Mixin class, and using the implemented methods to expose our added members, we're able to call the interface methods outside of the Mixin class by casting instances of the target class to the interface.

This process is often called using a “duck” interface, but for the purposes of exposing members added via Mixin, this is arguably a rather unintuitive name. It may be more fitting to use a term such as “extension” interfaces, for example.

Tutorial

Hypothetical Scenario

For our example situation, we'll use 1.21.10 with Mojang Mappings. We will add and expose fields to the Zombie class for a hypothetical combo mechanic we'd want to be able to interact with outside of the Mixin.

Adding the Field

@Mixin(Zombie.class)
abstract class ZombieExampleMixin {
 
    @Unique
    private int modid$comboCount;
 
    @Inject(method = "<init>*", at = @At("TAIL"))
    private void populateField() {
        modid$comboCount = 0;
    }
 
    @Inject(method = "tick", at = @At("TAIL"))
    private void doSomethingWithCombos(CallbackInfo ci) {
        /* ... */
    }
 
}

This field is normally entirely inaccessible, and we can't just make our Mixin class public and get the field that way, because our Mixin class itself won't exist at runtime. However, if we use an interface that will exist at runtime, and implement it into the target class via our Mixin, we'll be able to use the fields via the interface.

:?: We use @Unique to ensure that we do not clash with any other mods' added fields in the same class, and prefix the field with our mod id in order to also make it easier to find which mod is involved in issues pertaining to added fields in logs.

Creating the Interface

A somewhat common naming convention for interfaces with this purpose is TargetClassAccess, which is what we'll use for our example:

public interface ZombieAccess {
 
    int modid$getComboHits();
 
    void modid$setComboHits(int newCount);
 
}

This effectively acts as an exposed getter and setter for our example's sake, but any number of methods can be added this way. Since the methods will be added to the target class by our implementation, we should also add modid$ here to avoid clashing with other mods.

Implementing the Interface

@Mixin(Zombie.class)
abstract class ZombieExampleMixin implements ZombieAccess {
 
    @Unique
    private int modid$comboCount;
 
    @Override
    public int modid$getComboHits() {
        return modid$comboCount;
    }
 
    @Override
    public void modid$setComboHits(int newCount) {
        modid$comboCount = newCount;
    }
 
    @Inject(method = "<init>*", at = @At("TAIL"))
    private void populateField() {
        modid$comboCount = 0;
    }
 
 
    @Inject(method = "tick", at = @At("TAIL"))
    private void doSomethingWithCombos(CallbackInfo ci) {
        /* ... */
    }
 
}

Using the Interface

The compiler will allow us to cast instances of the Zombie class to our interface without causing issues, and since the interface will be implemented by Mixin at runtime, it won't cause issues at runtime either:

public class ExampleClass {
 
    public static void holder(Zombie zombie) {
        int currentCombo = ((ZombieAccess) zombie).modid$getComboHits();
        ((ZombieAccess) zombie).modid$setComboHits(currentCombo + 1);
    }
 
}
drafts/mixin_access_added_members.txt · Last modified: 2026/01/03 14:50 by gauntrecluse