Interface-Injektion
Die Interface-Injektion ist eine Art der Klassenoptimierung, mit der Implementierungen von Interfaces zu Minecraft-Klassen im dekompilierten Quellcode hinzugefügt werden.
Da die Implementierung im dekompilierten Quellcode der Klasse sichtbar ist, ist es nicht mehr erforderlich, zur Nutzung der Methoden der Schnittstelle eine Casting durchzuführen.
Darüber hinaus können Interface-Injektionen transitiv sein, wodurch Bibliotheken ihre hinzugefügten Methoden leichter für Mods verfügbar machen können, die von ihnen abhängen.
Um die Interface-Injektion zu veranschaulichen, verwenden die Codeausschnitte auf dieser Seite ein Beispiel, in dem wir eine neue Hilfsmethode zu FlowingFluid hinzufügen.
Das Interface erstellen
Erstelle in einem Paket, das nicht dein Mixin-Paket ist, das Interface, das du injizieren möchtest:
java
public interface BucketEmptySoundGetter {
default Optional<SoundEvent> example_mod$getBucketEmptySound() {
throw new AssertionError("Implemented in Mixin");
}
}1
2
3
4
5
2
3
4
5
In unserem Fall wird standardmäßig ein Ausnahmetyp ausgelöst, da wir die Methode über ein Mixin implementieren wollen.
WARNING
Die Methoden des injizierten Interface müssen alle auf default gesetzt sein, damit sie mit einem Klassenoptimierer injiziert werden können, selbst wenn du vorhast, die Methoden in der Zielklasse mithilfe eines Mixin zu implementieren.
Methoden sollten zusätzlich mit deiner Mod-ID und einem Trennzeichen wie $ oder _ beginnen, damit es nicht zu Konflikten mit Methoden anderer Mods kommt.
Das Interface implementieren
TIP
Wenn die Methoden des Interface vollständig mit den default-Werten des Interface implementiert sind, musst du kein Mixin verwenden, um das Interface einzubinden; der Klassenoptimierer-Eintrag reicht aus.
Um in der Zielklasse Überschreibungen der Methoden des Interface zu erstellen, solltest du ein Mixin verwenden, das das Interface implementiert und auf die Klasse abzielt, in die du das Interface einbinden möchtest.
java
@Mixin(FlowingFluid.class)
abstract class FlowingFluidMixin extends Fluid implements BucketEmptySoundGetter {
@Override
public Optional<SoundEvent> example_mod$getBucketEmptySound() {
return Optional.of(this.is(FluidTags.LAVA) ? SoundEvents.BUCKET_EMPTY_LAVA : SoundEvents.BUCKET_EMPTY);
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
Die Überschreibungen werden der Zielklasse zur Laufzeit hinzugefügt, erscheinen jedoch nicht im dekompilierten Quellcode, selbst wenn du den Klassenoptimierer verwendest, um die Implementierung des Interface sichtbar zu machen.
Den Eintrag für den Klassenoptimierer erstellen
Die Interface-Injektion verwendet den folgenden Syntax:
classtweaker
inject-interface <targetClassName> <injectedInterfaceName>Bei der Optimierung von Klassen verwenden Klassen und Interfaces ihre internen Namen.
Für unser Beispielinterface würde der Eintrag wie folgt lauten:
classtweaker
inject-interface net/minecraft/world/level/material/FlowingFluid com/example/docs/interface_injection/BucketEmptySoundGetter1
Generische Interfaces
Wenn dein Interface generische Typen enthält, kannst du diese im Eintrag angeben. Füge dazu am Ende des Interfacename spitze Klammern <> ein und setze die generischen Typen im Java-Bytecode-Signaturformat zwischen die Klammern.
Das Signaturformat ist:
| Beschreibung | Java Beispiel | Syntax | Beispiel für das Signaturformat |
|---|---|---|---|
| Klassentyp | java.lang.String | Bezeichnerformat | Ljava/lang/String; |
| Arraytyp | java.lang.String[] | Bezeichnerformat | [Ljava/lang/String; |
| Primitiver Typ | boolean | Bezeichnerzeichen | Z |
| Typvariable | T | T + Name + ; | TT; |
| Generischer Klassentyp | java.util.List<T> | L + interner Name + < + generischer Typ + >; | Ljava/util/List<TT;>; |
| Wildcard | ?, java.util.List<?> | *-Zeichen | *, java/util/List<*>; |
| Erweitert die Grenze des Wildcard | ? extends String | + + die Grenze | +Ljava/lang/String; |
| Super Grenze des Wildcard | ? super String | - + die Grenze | -Ljava/lang/String; |
Um ein Interface zu injizieren:
java
package com.example.docs.interface_injection;
public interface GenericInterface<T, U> {
}1
2
3
4
5
2
3
4
5
mite den generischen Typen <? extends String, Boolean[]>
Der Eintrag des Klassenoptimierer wäre:
classtweaker
inject-interface net/minecraft/world/level/material/FlowingFluid com/example/docs/interface_injection/GenericInterface<+Ljava/lang/String;[Ljava/lang/Boolean;>1
Änderungen anwenden
Damit deine Implementierung des Interface übernommen wird, musst du dein Gradle-Projekt aktualisieren, indem du die Quellen neu generierst. Wenn die Änderungen nicht angezeigt werden, kannst du versuchen, die Datei zu validieren und zu prüfen, ob Fehler auftreten.
Die hinzugefügten Methoden können nun auf Instanzen der Klasse angewendet werden, in die das Interface injiziert wurde:
java
void example(FlowingFluid flowingFluid) {
Optional<SoundEvent> sound = flowingFluid.example_mod$getBucketEmptySound();
/* ... */
}1
2
3
4
2
3
4
Bei Bedarf kannst du die Methoden in Unterklassen des Interface-Injektionsziels überschreiben.








