User Tools

Site Tools


tutorial:mixin_injects

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
Next revisionBoth sides next revision
tutorial:mixin_injects [2020/02/07 01:45] – clean up example draylartutorial:mixin_injects [2022/03/08 19:00] salvopelux
Line 2: Line 2:
  
 ===== Introduction ===== ===== Introduction =====
-Injects allows you to place custom code at a specified position inside an existing method. For a functional example, view the example section at the bottom of this page. The standard form of an inject is as shown:+Injects allows you to place custom code at a specified position inside an existing method. For a working example, view the [[tutorial:mixin_injects#practical_example|Practical Example]] category at the bottom of this page. The standard form of an inject is as shown:
  
 <code java> <code java>
Line 19: Line 19:
 | TAIL | Before the final return statement | | TAIL | Before the final return statement |
  
-In the case of injection points that reference statements or members, the target value can be set inside //@At//.+In the case of injection points that reference statements or members, the target value can be set inside //@At//. Target value is specified using JVM bytecode descriptors. 
 + 
 +Oracle defines the following [[https://docs.oracle.com/javase/specs/jvms/se14/html/jvms-4.html#jvms-4.3.2|field descriptors]]: 
 + 
 +^ Descriptor ^ Primitive ^ Description ^ 
 +| B | byte | signed byte | 
 +| C | char | Unicode character code point in the Basic Multilingual Plane, encoded with UTF-16 | 
 +| D | double | double-precision floating-point value | 
 +| F | float | single-precision floating-point value | 
 +| I | int | integer | 
 +| J | long | long integer | 
 +| L//ClassName//; | reference | an instance of //ClassName//
 +| S | short | signed short | 
 +| Z | boolean | ''true'' or ''false''
 +| [ | reference | one array dimension | 
 + 
 +A method descriptor is comprised of the method name, followed by a set of parentheses containing the input types, followed by the output type. A method defined in Java as ''Object m(int i, double[] d, Thread t)'' would have the method descriptor ''m(I[DLjava/lang/Thread;)Ljava/lang/Object;''.  
 + 
 +Generics' types are left out, as Generics don't exist on runtime. So ''Pair<Integer, ? extends Task<? super VillagerEntity>‍>'' would become ''Lcom/mojang/datafixers/util/Pair''.
  
 //@Inject// methods always have a void return type. The method name does not matter; using something that describes what the inject does is best. The target method's arguments are placed first in the method's header, followed by a ''CallbackInfo'' object. If the target method has a return type (T), ''CallbackInfoReturnable<T>'' is used instead of ''CallbackInfo''. //@Inject// methods always have a void return type. The method name does not matter; using something that describes what the inject does is best. The target method's arguments are placed first in the method's header, followed by a ''CallbackInfo'' object. If the target method has a return type (T), ''CallbackInfoReturnable<T>'' is used instead of ''CallbackInfo''.
  
 === Returning & Cancelling from Inject === === Returning & Cancelling from Inject ===
-To cancel or return early inside a method, use ''CallbackInfo#cancel'' or ''CallbackInfoReturnable<T>#setReturnValue(T)''. In both instances, ''cancellable' will have to be set to true in the inject annotation:+To cancel or return early inside a method, use ''CallbackInfo#cancel'' or ''CallbackInfoReturnable<T>#setReturnValue(T)''. Note that ''cancel'' does not have to be called after ''setReturnValue''. In both instances, ''cancellable'' will have to be set to true in the inject annotation:
 <code java> <code java>
 @Inject(method = "...", at = @At("..."), cancellable = true) @Inject(method = "...", at = @At("..."), cancellable = true)
 </code> </code>
 +
 +
 +=== Injecting into Constructors ===
 +To inject into a constructor, use ''<init>()V'' as the method target, with ''()'' containing the constructor argument descriptors. When injecting into constructors, ''@At'' must be set to either ''TAIL'' or ''RETURN''. No other forms of injection are officially supported. Note that some classes have methods named ''init'' which are different from ''<init>''. Don't get confused!
 +
 +To inject into a static constructor, use ''<clinit>'' as the method name.
  
 ===== Practical Example ===== ===== Practical Example =====
-The following example injects a print statement at the top of ''TitleScreen#init''.+The following example injects a print statement at the top of ''TitleScreen#init'' (note: the method ''init'' is a normal method and not a constructor).
  
 <code java [enable_line_numbers="false"]> <code java [enable_line_numbers="false"]>
Line 42: Line 66:
 </code> </code>
  
-For more information on this particular example, view its usage in the [[Fabric Example Mod repo]].+For more information on this particular example, view its usage in the [[https://github.com/FabricMC/fabric-example-mod/blob/master/src/main/java/net/fabricmc/example/mixin/ExampleMixin.java|Fabric Example Mod repo]]
 + 
 +====== Inject an interface ====== 
 + 
 +This is a new tecnique introduced by Loom 0.11 to add methods into a specific existing class. 
 +More specifically, you can create an Interface, and then inject this interface into the class. 
 +As result the target class will acquire all the methods of the interface, as if it always had them. 
 +Interface injection is a compile time only feature, this means that a Mixin should also be used to implement the interface into the target class. 
 + 
 +This is particulatly useful for libraries, with this you can add new methods to existing classes and use them without the need of casting or reimplementing the interface every time. 
 + 
 +Let's explain better with an example: 
 + 
 +The scope of this example is to add the following method into FlowableFluid to get the sound of the bucket when emptied. 
 +This, normally, is not possible because FlowableFluid does not has a similar method. 
 + 
 +<code java [enable_line_numbers="false"]> 
 +Optional<SoundEvent> getBucketEmptySound() 
 +</code> 
 + 
 +To add the method into the class, first of all you need to create an interface with it: 
 + 
 +<code java [enable_line_numbers="false"]> 
 +package net.fabricmc.example; 
 + 
 +public interface BucketEmptySoundGetter { 
 + Optional<SoundEvent> getBucketEmptySound(); 
 +
 +</code> 
 + 
 +Now you need to implement this interface into FlowableFluid with a mixin implementing the interface: 
 + 
 +<code java [enable_line_numbers="false"]> 
 +@Mixin(FlowableFluid.class) 
 +public class MixinFlowableFluid implements BucketEmptySoundGetter { 
 + @Override 
 + public Optional<SoundEvent> getBucketEmptySound() { 
 + //This is how to get the default sound, copied from BucketItem class. 
 + return Optional.of(((FlowableFluid) (Object) this).isIn(FluidTags.LAVA) ? SoundEvents.ITEM_BUCKET_EMPTY_LAVA : SoundEvents.ITEM_BUCKET_EMPTY); 
 +
 +
 +</code> 
 + 
 +Lastly you need to inject the interface into FabricFluid. 
 +The following snippet can be added to your fabric.mod.json file to add one or more interfaces to the net/minecraft/fluid/FlowableFluid class. 
 + 
 +<code json [enable_line_numbers="false"]> 
 +
 + "custom":
 + "loom:injected_interfaces":
 + "net/minecraft/class_3609": ["net/fabricmc/example/BucketEmptySoundGetter"
 +
 +
 +
 +</code> 
 + 
 +Now you can use the new method: 
 + 
 +<code java [enable_line_numbers="false"]> 
 +Optional<SoundEvent> sound = mytestfluid.getBucketEmptySound(); 
 +</code> 
 + 
 +You could also override this method in classes extending FlowableFluid to implement custom behaviours.
tutorial/mixin_injects.txt · Last modified: 2022/08/05 19:19 by clomclem