| Both sides previous revisionPrevious revisionNext revision | Previous revision |
| drafts:mixin_injectors [2026/01/07 16:50] – [Ordinal] Rephrase brittleness warning. gauntrecluse | drafts:mixin_injectors [2026/02/20 20:39] (current) – [Injection Point, the value attribute] Add CTOR_HEAD injection point gauntrecluse |
|---|
| In the source code of a Mixin class, an injector consists of two elements: the annotation(s), and the "handler" method, also simply called the handler, decorated by the annotation. The typical structure of an injector's annotation and method will look as follows: | In the source code of a Mixin class, an injector consists of two elements: the annotation(s), and the "handler" method, also simply called the handler, decorated by the annotation. The typical structure of an injector's annotation and method will look as follows: |
| <code java> | <code java> |
| @InjectorAnnotation(method = "<target method name or descriptor>", at = @At(value = "<INJECTION POINT>")) | @<InjectorAnnotation>(method = "<target method name or descriptor>", at = @At(value = "<INJECTION POINT>")) |
| private ReturnType handlerMethod(<Parameters>) { | private ReturnType handlerMethod(<Parameters>) { |
| |
| For overloads, the method's descriptor for its parameters must be appended to the name. This is detailed upon on [[https://docs.fabricmc.net/develop/mixins/bytecode#field-and-method-descriptors|the relevant Docs page]]'s method and field descriptor section. | For overloads, the method's descriptor for its parameters must be appended to the name. This is detailed upon on [[https://docs.fabricmc.net/develop/mixins/bytecode#field-and-method-descriptors|the relevant Docs page]]'s method and field descriptor section. |
| |
| [[https://mcdev.io|MCDev]]'s autocompletion also accounts for overloads, so just autocompleting the annotation's ''method ='' attribute with the proper name and parameters should automatically specify it. | [[https://mcdev.io|MCDev]]'s autocompletion also accounts for overloads, so just autocompleting the annotation's ''method ='' attribute with the proper name, and accounting for the autocompleted method's parameters should successfully add the correct method descriptor. |
| |
| === Constructor, Static Initializer === | === Constructor, Static Initializer === |
| If for whatever reason this is not suitable, you can open the target class's bytecode ([[https://docs.fabricmc.net/develop/getting-started/intellij-idea/tips-and-tricks#viewing-bytecode|IntelliJ]], [[https://docs.fabricmc.net/develop/getting-started/vscode/tips-and-tricks#viewing-bytecode|VSCode]]), and look for the lambda's bytecode by searching for the ''synthetic'' keyword. Lambdas in bytecode are either named ''lambda$enclosingMethod$indexNumber'', for example ''lambda$shear$2'' would be the second lambda in the class, declared in the method ''shear''; or they may be named in the intermediary ''method_XXXXX'' format. | If for whatever reason this is not suitable, you can open the target class's bytecode ([[https://docs.fabricmc.net/develop/getting-started/intellij-idea/tips-and-tricks#viewing-bytecode|IntelliJ]], [[https://docs.fabricmc.net/develop/getting-started/vscode/tips-and-tricks#viewing-bytecode|VSCode]]), and look for the lambda's bytecode by searching for the ''synthetic'' keyword. Lambdas in bytecode are either named ''lambda$enclosingMethod$indexNumber'', for example ''lambda$shear$2'' would be the second lambda in the class, declared in the method ''shear''; or they may be named in the intermediary ''method_XXXXX'' format. |
| |
| It's possible to either leverage [[https://mcdev.io|MCDev]]'s autocompletion by first typing ''method_'' in the ''method = '' attribute and pressing ''CTRL + SPACE'' and seeing, if the lambdas show up in intermediary, which one has the parameter and return type you're looking for, or doing the same but by starting to type ''lambda$'' in the attribute rather than ''method_''. | It's possible to leverage [[https://mcdev.io|MCDev]]'s autocompletion by first typing ''method_'' in the ''method = '' attribute and pressing ''CTRL + SPACE'' and seeing, if the lambdas show up in intermediary, which one has the parameter and return type you're looking for, or doing the same but by starting to type ''lambda$'' in the attribute rather than ''method_''. |
| |
| If you must however look at the bytecode, it is possible to quickly find synthetic methods by searching for the ''synthetic'' keyword. You may search for ''synthetic method_'' to test if they are in intermediary format. You can then look at the lambda method body in source code to identify key elements such as parameters and certain stand-out method calls or constants, and use that to figure out which method's bytecode corresponds to the lambda you wish to mix into. | If you must look at the bytecode, it is possible to quickly find synthetic methods by searching for the ''synthetic'' keyword. You may search for ''synthetic method_'' to test if they are in intermediary format. You can then look at the lambda method body in source code to identify key elements such as parameters and certain stand-out method calls or constants, and use that to figure out which method's bytecode corresponds to the lambda you wish to mix into. |
| |
| Once you have specified the lambda's bytecode name successfully, you can now modify it as you would normally. Be aware that lambdas are not passed all of the variables from the outer scope, and only capture their parameters' values. | Once you have specified the lambda's bytecode name successfully, you can now modify it as you would normally. Be aware that lambdas are not passed all of the variables from the outer scope, and only capture their parameters' values. |
| The ''@At'' annotation takes one necessary ''value'' attribute, followed by optional ones: the "injection point". Injection Points serve as a "base" indicating what type of instruction in the target method to select for. They are referenced via a String, and are tied to an ''InjectionPoint'' subclass. Some common injection points include: | The ''@At'' annotation takes one necessary ''value'' attribute, followed by optional ones: the "injection point". Injection Points serve as a "base" indicating what type of instruction in the target method to select for. They are referenced via a String, and are tied to an ''InjectionPoint'' subclass. Some common injection points include: |
| |
| ^ String Form in Annotation ^ Function ^ | ^ String Form in Annotation ^ Function ^ |
| | ''HEAD'' | Inject at the earliest possible point in the method | | | ''HEAD'' | Inject at the earliest possible point in the method | |
| | ''RETURN'' | Inject before every ''RETURN'' instruction in the target method | | | ''CTOR_HEAD'' | For constructors, inject at the earliest possible point after the super call. | |
| | ''TAIL'' | Inject before the very last ''RETURN'' instruction in the target method | | | ''RETURN'' | Inject before every ''RETURN'' instruction in the target method | |
| | ''INVOKE'' | Inject before a method call/invoke instruction | | | ''TAIL'' | Inject before the very last ''RETURN'' instruction in the target method | |
| | ''FIELD'' | Inject before a field access instruction | | | ''INVOKE'' | Inject before a method call/invoke instruction | |
| | | ''FIELD'' | Inject before a field access instruction | |
| | ''MIXINEXTRAS:EXPRESSION'' | Target via an [[https://github.com/LlamaLad7/MixinExtras/wiki/Expressions|Expression]]. | | | ''MIXINEXTRAS:EXPRESSION'' | Target via an [[https://github.com/LlamaLad7/MixinExtras/wiki/Expressions|Expression]]. | |
| |
| It is very important to note that for injectors which rely on modifying or wrapping their targets (''@WrapOperation'', ''@ModifyArg'', ''@ModifyExpressionValue''...) rather than injecting new instructions relative to the target (''@Inject'', ''@ModifyVariable''...) the injection point will not be used to inject "before" the target, but to specify the target to modify/wrap directly. | It is very important to note that for injectors which rely on modifying or wrapping their targets (''@WrapOperation'', ''@ModifyArg'', ''@ModifyExpressionValue''...) rather than injecting new instructions relative to the target (''@Inject'', ''@ModifyVariable''...) the injection point will not be used to inject "before" the target, but to specify the target to modify/wrap directly. |
| |
| However, the injection points themselves are at times insufficient, such as with ''INVOKE'', which on its own would target //every// method call. | However, the injection points themselves are at times insufficient, such as with ''INVOKE'', which on its own would target //every// method call. In the next section, we will cover how to further specify target instructions beyond the injection point. |
| |
| ===== Target Discriminators ===== | ===== Target Discriminators ===== |
| On top of specifying an injection point it is possible to use and combine different discriminators to filter the potential targets further. Three are consistent across all injectors and injection points: ''[[#ordinal|ordinal]]'', ''[[#slice|slice]]'' and [[#injection_point_specifiers|injection point specifiers]]. | The term of discriminators here refers to a set of features provided by Mixin and MixinExtras to narrow down which instruction to target. There are some which are only supported by specific injection points, but if two discriminators are supported by the injection point used, then they can be combined to further refine which instruction to target. |
| |
| Ordinals are used to pick one of the potential candidate targets based on other discriminators similarly as using an array index. | However, not all discriminators are necessarily good to use, as some are more "brittle" than others. Brittleness here means likely to break from changes made to the target method which shouldn't concern the functionality you're interacting with. This is particularly the case for [[#ordinal|ordinals]], which should only be used when other discriminators are unavailable or not applicable. |
| | |
| Slicing is used to define a range within which to search for candidates, based on two additional ''@At''s. | |
| | |
| Injection point specifiers can be used to additionally specify the intended targeting for the injection point, taking the form of a suffix appended to the injection point. | |
| | |
| Certain injection points also take a ''[[#target_attribute|target]]'' attribute in coordination with the injection point. This can be used, typically, to specify the specific field being accessed with ''FIELD'', or to specify the method being called with ''INVOKE''. | |
| | |
| The ''FIELD'' injection point uses a special ''[[#opcode|opcode]]'' discriminator to specify the specific field interaction. | |
| | |
| ''MIXINEXTRAS:EXPRESSION'' uses [[#expressions|Expressions]], and does not support ''target''. | |
| | |
| Whilst some discriminators may be exclusive to certain injection points, they are not fundamentally incompatible with one another. It is possible to combine an Expression with a slice, an opcode with an ordinal etc., as long as the injection point supports both of the discriminators. | |
| | |
| Some discriminators are however more "brittle" than others, meaning that they are likelier to break between updates, as their approach to selecting the target(s) is susceptible to break from unrelated modifications. | |
| |
| ==== Injection Point Specifiers ==== | ==== Injection Point Specifiers ==== |
| Injection points can be supplied an additional specifier from the following list to modify how they behave: | Injection points can be supplied an additional specifier from the following list to modify how they behave: |
| ^ Specifier Name ^ Function ^ | ^ Specifier Name ^ Function ^ |
| | ''ALL'' | Default when not slicing. Takes in all instructions that match discriminators | | | ''ALL'' | Default when not slicing. Takes in all instructions that match discriminators | |
| | ''FIRST'' | Default when slicing. Takes in the first matching instruction | | | ''FIRST'' | Default when slicing. Takes in the first matching instruction | |
| | ''LAST'' | Takes in the last matching instruction | | | ''LAST'' | Takes in the last matching instruction | |
| | ''ONE'' | There must be only one matching instruction, otherwise fail and throw an exception. | | | ''ONE'' | There must be only one matching instruction, otherwise fail and throw an exception. | |
| | ''DEFAULT'' | Use the default specifier behavior for the consumer | | | ''DEFAULT'' | Explicitly use the default specifier behavior for the consumer | |
| | JavaDocs for specifiers can be found [[https://jenkins.liteloader.com/job/Mixin/javadoc/org/spongepowered/asm/mixin/injection/InjectionPoint.Specifier.html|here]]. |
| |
| Specifiers ''FIRST'' and ''LAST'' should be used instead of their ordinal equivalents when they are relevant, and ''ONE'' can be used to enforce there being only one matching target when using the Mixin, thus making it impossible to silently target multiple instructions between updates, for example. | Specifiers ''FIRST'' and ''LAST'' should be used instead of their [[#ordinal|ordinal]] equivalents when they are relevant, and ''ONE'' can be used to enforce there being only one matching target when using the Mixin, thus making it impossible to silently target multiple instructions between updates, for example. |
| |
| The default behavior being ''FIRST'' for slices should be kept in mind if you intend to inject into every matching instruction when slicing, which would necessitate using ''ALL''. | The default behavior being ''FIRST'' for [[#slice|slices]] should be kept in mind if you intend to inject into every matching instruction when slicing, which would necessitate explicitly specifying ''ALL''. |
| |
| Specifiers are compatible with every injection point, including ''MIXINEXTRAS:EXPRESSION'', and are appended as a suffix which separates the injection point from the specifier with a colon '':''. For instance: | Specifiers are compatible with every injection point, including ''MIXINEXTRAS:EXPRESSION'', and are appended as a suffix which separates the injection point from the specifier with a colon '':''. For instance: |
| The target is specified by using the target's [[https://docs.fabricmc.net/develop/mixins/bytecode#field-and-method-descriptors|descriptor]], based on [[https://docs.fabricmc.net/develop/mixins/bytecode#field-and-method-descriptors|type descriptors]]. This can be entirely autocompleted using [[https://mcdev.io|the MCDev plugin]]. | The target is specified by using the target's [[https://docs.fabricmc.net/develop/mixins/bytecode#field-and-method-descriptors|descriptor]], based on [[https://docs.fabricmc.net/develop/mixins/bytecode#field-and-method-descriptors|type descriptors]]. This can be entirely autocompleted using [[https://mcdev.io|the MCDev plugin]]. |
| |
| The ''target'' attribute is specified as with any other annotation attribute: | The ''target'' attribute is specified as with any other annotation attribute. For example: |
| <code java> | <code java> |
| @At(value = "<INJECTION POINT>", target = "<TARGET REFERENCE>") | @At(value = "INVOKE", target = "<method descriptor>") |
| </code> | </code> |
| | Would narrow targeting down from "all method calls" to "all calls to that specific method". |
| |
| This discriminator is not particularly brittle in any particular way, and should mostly always be used on its relevant injection points in combination with other discriminators for the target. | This discriminator is not particularly brittle in any particular way, and should mostly always be used on its relevant injection points in combination with other discriminators for the target. If there is only one instance of a method being called, using it alone would work, but it may be good to pair it with the '':ONE'' [[#injection_point_specifiers|specifier]] to make the injector throw an error if our assumption of there only being one call to that method is broken in an update. |
| |
| |
| ==== Ordinal ==== | ==== Ordinal ==== |
| Ordinals allow selecting one instruction returned by other discriminators and the injection point similarly as a zero-indexed array index. This means that an ordinal of 0 corresponds to the first instruction in the available list, whilst an ordinal of 3 would correspond to the fourth. Ordinals are processed after a slice. The ordinal attribute, if unspecified, will not be used, and all instructions in the list will be targeted by the injector. | Ordinals allow selecting one instruction among the ones returned by the other discriminators and injection point used, similarly to zero-indexed array indices. This means that an ordinal of 0 corresponds to the first instruction in the available list, whilst an ordinal of 3 would correspond to the fourth. Ordinals can be used for any injection point. |
| |
| It is specified as an integer attribute in the ''@At'' annotation: | It is specified as an integer attribute in the ''@At'' annotation: |
| </code> | </code> |
| |
| :!: Ordinals are generally the most brittle discriminator, as it may break from a new matching instruction being added prior to the one selected by the ordinal. A [[#injection_point_specifiers|specifier]] of ''LAST'' should generally be used in place of an ordinal of the last ordinal available; in most cases it is recommendable to use an [[#expressions|expression]] and/or a [[#slice|slice]] instead of an ordinal, as those are much more expressive and less likely to break between updates. | :!: Ordinals are generally the most brittle discriminator, as it may break from a new matching instruction being added prior to the one selected by the ordinal. A [[#injection_point_specifiers|specifier]] of '':LAST'' should generally be used in place of an ordinal of the last ordinal available; in most cases it is recommendable to use an [[#expressions|expression]] and/or a [[#slice|slice]] instead of an ordinal, as those are much more expressive and less likely to break between updates. |
| |
| |
| ==== Slice ==== | ==== Slice ==== |
| A slice uses one or two additional ''@At''s to specify a range to include when searching for instructions matching the main ''@At''. Slicing is specified via the ''slice'' attribute, which takes a ''@Slice'' annotation (code differently formatted to distinguish between the different ''@At''s more clearly): | A slice uses one or two additional ''@At''s to specify a range to include when searching for instructions matching the main ''@At'', it is usable for any injection point. Slicing is specified via the ''slice'' attribute, which takes a ''@Slice'' annotation (code differently formatted to distinguish between the different ''@At''s more clearly), for example: |
| <code java> | <code java> |
| @At( | @InjectorAnnotation( |
| value = "INJECTION POINT" | method = "...", |
| | at = @At(...), |
| slice = @Slice( | slice = @Slice( |
| from = @At(...) | from = @At(...), |
| to = @At(...) | to = @At(...) |
| ) | ) |
| ) | ) |
| </code> | </code> |
| | ''from'' defaults to ''@At("HEAD")'', and ''to'' defaults to ''@At("TAIL")'', you may only specify one's value if needed. They otherwise can be specified as normal ''@At''s. |
| |
| ''from'' defaults to ''@At("HEAD")'', and ''to'' defaults to ''@At("TAIL")'', you may only specify one's value if needed. | :!: When slicing, the default [[#injection_point_specifiers|specifier]] behavior of the injection point shifts from '':ALL'' to '':FIRST''. This means you must explicitly use '':ALL'' for the injector to be able to target multiple instructions within a slice. |
| |
| Slicing is a lot less brittle than ordinals assuming the ''from'' and ''to'' can be specified without being brittle themselves. Similarly to other discriminators, slicing can be used to complement other targeting tools and be combined with them. | Slicing is a lot less brittle than ordinals assuming the ''from'' and ''to'' can be specified without being brittle themselves. Similarly to other discriminators, slicing can be used to complement other targeting tools and be combined with them. |
| |
| ==== Opcode ==== | ==== Opcode ==== |
| ''opcode'' is a discriminator used for ''FIELD'' injection point. The ''opcode'' attribute takes an integer value corresponding to the Bytecode opcode. This should be filled using [[https://asm.ow2.io|ASM]]'s ''Opcodes'' interface's presets, which are named to match the Opcode corresponding to their values: | ''opcode'' is a discriminator used specifically for the ''FIELD'' injection point, and is specified within the ''@At'' annotation. The ''opcode'' attribute takes an integer value corresponding to the bytecode opcode. This should be filled using [[https://asm.ow2.io|ASM]]'s ''Opcodes'' interface's presets, which are named to match the Opcode corresponding to their values: |
| <code java> | <code java> |
| @At(value = "FIELD", target = "...", opcode = Opcodes.<...>) | @At(value = "FIELD", target = "...", opcode = Opcodes.<...>) |
| </code> | </code> |
| Opcodes for ''FIELD'' would include ''GETSTATIC'', ''PUTSTATIC'' for accessing a static field, and ''GETFIELD'', ''PUTFIELD'' for a non-static field. This can be autocompleted by [[https://mcdev.io|MCDev]], which will give a warning to remind you to use an opcode in the event your ''FIELD'' injector does not use one already, with a quick-fix to add it. | Opcodes for ''FIELD'' would include ''GETSTATIC'', ''PUTSTATIC'' for accessing a static field, and ''GETFIELD'', ''PUTFIELD'' for a non-static field. This can be auto-generated by [[https://mcdev.io|MCDev]], which will give a warning to remind you to use an opcode in the event your ''FIELD'' injector does not use one already, with a quick-fix action to add it. |
| |
| ''opcode'' can be combined with a ''target'' and other discriminators allowed by the ''FIELD'' injection point. It should generally always be used when using ''FIELD''. | ''opcode'' should always complement a proper [[#target_attribute|target]], and may be used along with any other discriminator that is not incompatible with the ''FIELD'' injection point. |
| |
| |
| ==== Expressions ==== | ==== Expressions ==== |
| MixinExtras 0.5.0 (bundled in Fabric Loader version 0.17.0+) adds a new injection point: ''MIXINEXTRAS:EXPRESSION'', which does not take a ''target'', but instead uses an ''@Expression'' annotation to define a Java-like String that will correspond to the instructions to target. This allows, importantly, to define the surrounding context of a target, allowing for very high precision without having to resort to use ordinals or slices. The possibility of adding context rather than targeting preset instructions or groups of instructions also allows targeting much more complex expressions in the target class. It is sometimes the //only way// to target a specific set of instructions: | MixinExtras 0.5.0 (bundled in Fabric Loader version 0.17.0+) adds a new injection point: ''MIXINEXTRAS:EXPRESSION'', which does not take a ''target'', but instead uses an ''@Expression'' annotation to store a Java-like String that will correspond to the instructions to target. |
| |
| | This allows, importantly, to define the surrounding context of a target, allowing for high precision without having to resort to use less expressive or more brittle discriminators such as [[#ordinal|ordinals]]. The possibility of adding context rather than targeting preset instructions or groups of instructions also allows targeting much more complex expressions in the target class. |
| | |
| | Using expressions is sometimes the //only way// to directly target a specific set of instructions, this is the case for: |
| | * comparisons |
| | * array operations |
| | * array literals |
| | * binary expressions |
| | * method references |
| | * unary expressions |
| | * string concatenation |
| | * loading and storing of local variables outside of ''@ModifyVariable'''s ''STORE'' and ''LOAD'' injection points. |
| | |
| | The ''@Expression'' annotation uses "identifiers" to stand in for the non-constant elements of an expression. Those identifiers are then defined and mapped to their bytecode counterparts using a ''@Definition'' annotation each: |
| <code java> | <code java> |
| | @Definition(...) |
| @Expression("...") | @Expression("...") |
| @InjectorAnnotation(method = "...", at = @At(value = "MIXINEXTRAS:EXPRESSION")) | @InjectorAnnotation(method = "...", at = @At(value = "MIXINEXTRAS:EXPRESSION")) |
| </code> | </code> |
| |
| The concept is much more precisely elaborated upon in its [[https://github.com/LlamaLad7/MixinExtras/wiki/Expressions|official Wiki pages]], which contain many examples for its syntax and use-cases. | Additionally, identifiers or entire parts of an expression can be substituted by "wildcards", represented by a ''?'' operator, which are a way to explicitly leave a part of an expression ambiguous. Wildcards are mainly useful to make a less cluttered injectors when the identifiers are unnecessary, or to avoid referencing elements that would be problematic or brittle to define. They can also be used to make an expression match multiple targets by intentionally leaving select parts ambiguous. |
| | |
| | This is mainly the case for local variables on obfuscated versions' MC code (Minecraft code is obfuscated up to and including 1.21.11) where defining locals is in general very brittle due to being unable to define them by name, instead needing to rely on type, ordinals and whether the local variable is part of the method arguments. This is no longer an issue when targeting unobfuscated code, however, as defining locals by name is very reliable. |
| | |
| | [[https://mcdev.io|MCDev]] is very useful for writing expressions, as autocompleting the different identifiers will automatically generate ''@Definition''s for them. Additionally, the plugin allows you to open a flow diagram for expressions debugging which corresponds to the bytecode of different expressions in the decompiled source code by right clicking on either an expression string, or on the targeted source code, and then pressing "MixinExtras Flow Diagram".\\ |
| | Doing so on an expression string will also highlight the plugin's best attempt at matching your expression to the target class's bytecode, which can help highlight which parts of an expression are failing to match your target. Hovering over individual "blocks" in the diagram also allows you to see the corresponding bytecode instruction. |
| | |
| | The concept, language and limitations of Expressions are much more precisely elaborated upon in its official Wiki pages, which contain many examples for its syntax and use-cases. It is very important to read both the main page and the setup page to be able to use Expressions properly: |
| | * [[https://github.com/LlamaLad7/MixinExtras/wiki/Expressions|Main Tutorial]] |
| | * [[https://github.com/LlamaLad7/MixinExtras/wiki/Expressions-Setup|Setup]] |
| | * [[https://github.com/LlamaLad7/MixinExtras/wiki/Expression-Language|Full language specification]] |
| |
| |
| |
| ==== Whether an Injector is Needed ==== | ==== Whether an Injector is Needed ==== |
| It should first be mentioned that there are many situations where using an Injector or even a Mixin is unnecessary. Mixins should be resorted to if the given modding framework does not provide other, more convenient, ways to achieve the specified goal. | It should first be mentioned that there are many situations where using an Injector or even a Mixin is unnecessary. |
| |
| === Existing Events === | === Existing Events === |
| Whilst using Mixins can be functionally equivalent to events, since Fabric uses Mixins to back its events already, some developers may prefer going through the Fabric-defined event to avoid needing to create a Mixin class and writing an injector when an event already exists. Events are also ported between versions by the Fabric API's team of maintainers, whilst you will need to maintain your own Mixins manually. On the other hand, there is not necessarily a guarantee that the events you use will be uniformly ported through different MC versions. | Whilst using Mixins can be functionally equivalent to events, since Fabric uses Mixins to back its events already, some developers may prefer going through the Fabric-defined event to avoid needing to create a Mixin class and writing an injector unnecessarily. Events are also ported forward through updates when possible by the Fabric API's team of maintainers, whilst you will need to maintain your own Mixins manually. On the other hand, there is not necessarily a guarantee that the events you use will be uniformly ported through different MC versions, which is especially relevant if you plan to backport your mod. |
| |
| Mixins are however a fully valid option if the existing events either do not fill your use-case or dissatisfy you in any way. If you find that a useful event does not exist, you may create the Mixin implementation for it and contribute it into Fabric API yourself. | Mixins are however a fully valid option if the existing events either do not fill your use-case or dissatisfy you in any way.\\ |
| | You may also [[https://docs.fabricmc.net/develop/events#custom-events|create your own events]] if you are developing a library for example, where other mods would want to use your event. |
| |
| === Attaching Data === | === Attaching Data === |
| Mixins are able to merge new members into target classes and modify existing methods related to data management, which can theoretically be used to effectively add data to existing classes for your own use-cases. This, however, is not advised as it takes a great amount of work, and Fabric already provides a Data Attachment API for this purpose, along with other means to save data persistently. Using Mixins to do it manually will often result in unnecessary maintenance and a higher likelihood for bugs. | Mixins are able to merge new members into target classes and modify existing methods related to data management, which can theoretically be used to effectively add data to existing classes for your own use-cases. This, however, is not advised as it takes a great amount of work, and Fabric already provides a [[https://docs.fabricmc.net/develop/data-attachments|Data Attachment API]] for this purpose, along with other means to save data persistently such as [[https://docs.fabricmc.net/develop/saved-data|Saved Data]]. Using Mixins to do it manually can often result in unnecessary maintenance and a higher likelihood for bugs. |
| |
| | There are cases where Fabric may not provide a sufficient API for adding data for your goals. In those cases, using Mixin to merge methods and fields to store data for certain classes is a valid approach. In those cases, it is however wise to first ask for advice if you are not experienced with it yourself, as this requires you to be very careful. |
| |
| ==== Modifying Vs Adding Operations or Values ==== | === Merging static blocks and Members === |
| | Sometimes, a Mixin is needed, but an injector may be unnecessary. As mentioned in the previous section, Mixin is able to merge new members, but this also extends to static blocks. If you for example want to reassign a static field's value during class initialization, you may use a static block which shadows the field to reassign its value. This can be a much simpler and less brittle option than trying to do certain injections into the field's initial value. |
| |
| === Adding === | As with injectors, though, conserving the original values in your calculations is very important for compatibility and chaining with other mods who may want to do the same thing. Injectors should be used if that proves to be a challenge or an impossibility when reassigning the value via a static block. |
| The first question to ask yourself when trying to choose the correct injector for a given situation is whether you wish to modify existing values and operations, or whether you want to add new operations altogether. ''@Inject'' for example is only ever rarely suitable for other purposes than adding new operations, as it is limited to adding a call to your injector's handler method, and possibly injecting an early return. This is not suited for modifying values, method calls, etc. | |
| |
| There are however contexts where ''@Inject'' may also not be the injector to choose for adding new operations, such as when needing to get more context relative to the operation you are injecting next to. It can be wise to use ''@WrapOperation'' or ''@ModifyExpressionValue'' to be able to capture one or more values relevant to an operation, and then adding one's own operations before or after it. Those injectors are also more relevant for wrapping the operation itself with something, such as a try/catch. | ==== Adding Vs Modifying ==== |
| | The first question to ask yourself when trying to choose the correct injector for a given situation is whether you wish to modify existing values and operations, or whether you want to add new operations altogether. |
| |
| === Modifying === | === Adding new Operations === |
| In order to modify existing operations and values, the three main tools are ''@WrapOperation'', which is ideal for modifying things such as comparisons, method calls, and a variety of other operations, whilst also giving context, ''@ModifyExpressionValue'', which is exceptionally flexible in the amount of expressions and operations it can target, as long as the given operation results in a value; and ''@ModifyArg'', which is preferable for modifying method arguments, comparatively to using a ''@ModifyExpressionValue'' on a given method's passed argument. | ''@Inject'' for example is only ever rarely suitable for other purposes than adding new operations, as it is limited to adding a call to your injector's handler method, and possibly injecting an early return. This is not suited for modifying values, method calls, etc. |
| |
| | There are however contexts where ''@Inject'' may also not be the injector to choose for adding new operations, such as when needing to get more context relative to the operation you are injecting next to. It can be wise to use ''@WrapOperation'' or ''@ModifyExpressionValue'' to be able to capture one or more values relevant to an operation, and then adding one's own operations before or after it. Those injectors are also more relevant for //wrapping// the operation itself with something, such as a try/catch. |
| | |
| | ''@WrapMethod'' can also be used for wrapping a method in a try/catch, or when trying to more consistently do something before or after a method is called. This can be used if you are particularly afraid of an injector cancelling your added operations, but is unable to target a specific return statement and overall does not have access to a lot of context about the target method's flow. |
| | |
| | === Modifying Operations & Values === |
| | In order to modify specific operations such as method calls, comparisons, field gets/puts, instantiations, and [[https://github.com/LlamaLad7/MixinExtras/wiki/WrapOperation|more]], ''@WrapOperation'' is often preferred as it both can chain with other injections and often gives the most context for the target. |
| | |
| | When the context from ''@WrapOperation'' is unnecessary, or when wanting to target an expression which is not covered by it, ''@ModifyExpressionValue'' can be used as a more flexible alternative with less context. This can typically include modifying individual boolean expressions within an ''if'' condition((boolean logic between multiple values in the form of ''&&'' or ''||'' is not currently targetable directly, requiring individual boolean expressions be targeted instead.)). |
| | |
| | === Modifying Method Arguments === |
| | In order to modify method call arguments' values, ''@ModifyArg'' should be used for modifying a single argument's value, or ''@WrapOperation'' on the method call itself to modify multiple arguments, and on individual arguments to benefit from the context it provides for the original operation. ''@WrapOperation'' generally replaces ''@ModifyArgs'' because the latter provides less type safety. |
| |
| |
| * @ModifyArg | * @ModifyArg |
| * @ModifyArgs | * @ModifyArgs |
| | |
| | ==== Fabric Pages on Mixin ==== |
| | The following pages are generally trusted pages of the Fabric Wiki or Docs: |
| | * [[tutorial:mixin_introduction|Introduction to Mixins]] |
| | * [[https://docs.fabricmc.net/develop/mixins/bytecode|Java Bytecode]] (Docs) |
| | * [[tutorial:mixin_your_first_mixin|Tutorial: Making your first Mixin]] |
| | * [[tutorial:mixin_accessors|Mixin Accessors and Invokers]] |
| | * [[tutorial:mixin_export|Exporting and Dumping Mixin Targets]] |
| | * [[tutorial:mixin_override|Overriding a Mixin Target's Parent]] |
| | |
| | ==== External Resources ==== |
| | The following are official resources for Mixin and MixinExtras: |
| | * [[https://github.com/SpongePowered/Mixin/wiki/|Mixin Wiki]] |
| | * [[https://jenkins.liteloader.com/job/Mixin/javadoc/|Mixin JavaDoc]] |
| | * [[https://github.com/LlamaLad7/MixinExtras/wiki/|MixinExtras Wiki]] |
| |
| |
| === Review feedback to likely implement === | === Review feedback to likely implement === |
| * Expanding upon when not to use a Mixin, particularly in the data attachment section, for which looking into cases where Fabric doesn't provide a helpful API to note would be helpful. | * Expanding upon when not to use a Mixin, particularly in the data attachment section, for which looking into cases where Fabric doesn't provide a helpful API to note would be helpful. |
| * Add injection point specifiers to [[#target_discriminators]] segment | |
| |
| ==== Plans after getting out of Drafting ==== | ==== Plans after getting out of Drafting ==== |