-
-
Notifications
You must be signed in to change notification settings - Fork 194
Introduction to Mixins The Mixin Environment
So now that we understand the basic features of mixins (hint: if you haven't read that, go back and read it first!), let's take a quick detour to understand how to leverage our newly-authored mixins into the game environment.
As I mentioned in the previous article, it's best to keep in mind that to most intents and purposes mixins are not classes in the strictest sense and are not classloaded at runtime, but rather the raw bytecode is parsed using ASM's Tree API. This approach generates a node-based view of the underlying bytecode which can then be merged into the target classes by the mixin transformer.
Mixins themselves are pumped through the transformer chain independently of regular classes, this is to allow any remapping transformers to do their work prior to the mixin being combined with its target class. It is important that the mixin transformer itself is downstream of any remapping transformers for this reason, this is handled internally by inserting proxy transfomers in the transformer chain.
Mixins require processing before class loading begins in order to identify their target classes, perform hierarchy validation, and resolve any updated static bindings for fields and methods. Mixins you wish to apply should be specified in a configuration file and your coremod, tweaker, jar metadata or litemod.json will specify the location of the resource inside your mod's jar.
The configuration path is loaded as a resource and thus can be placed any package within your jar. It is important that you do not prepend a leading slash onto the file path as resource paths must be relative.
For example, if your config file is in the package
com.somepackage
you should specify the path to your config file ascom/somepackage/myconfig.json
Each configuration defines a mixin set and you may have as many mixin sets as you require for your application. It is only required to separate mixins into sets when your mixins target different environments (see below). However it is sometimes desirable to also split mixins into sets for organisational purposes.
As well as defining the mixin set itself, mixin configuration files also define accompanying properties for the set.
Firstly, the set itself is defined with four keys:
-
package
defines the parent package for this group of mixins (this is important because the package and all subpackages will be excluded from theLaunchClassLoader
at run time. -
mixins
defines the list of mixin "classes" within the parent package to apply for this configuration, each mixin "class" is specified relative to the parent package, sub-packages are allowed. Entries in this list are applied to both client and dedicated server side. -
client
defines the list of mixins to apply only on the client side. -
server
defines the list of mixins to apply only on the dedicated server side.
Additional properties can then be defined:
-
refmap
defines the name of the reference map filename for this set (more on this in the discussion of how obfuscation is handled with mixins) -
priority
defines the priority of this mixin set relative to other configurations. -
plugin
is the name of an optional companion plugin class for the mixin configuration which can tweak the mixin configuration programmatically at run time. SeeMixin Companion Plugins(Not done yet). -
required
defines whether the mixin set is required or not. If a single mixin failing to apply should be considered a failure state for the entire game, then the required flag should be set totrue
. -
minVersion
should be set if this mixin set relies on some mixin functionality which was added in a particular version. It can be omitted for version-agnostic mixin sets. -
setSourceFile
causes the mixin processor to overwrite thesource file
property in target classes with the source file of the mixin class. This can be useful when debugging mixins. -
verbose
promotes allDEBUG
-level log messages toINFO
level for this mixin set. This can also be enabled globally via themixin.debug.verbose
system property
Most important to note is that mixin "classes" must exist within a package with no other classes in your project (this includes sub-packages of the main mixin package) because the entire package will be excluded from the transforming classloader on startup.
To understand how mixins interact with the underlying game, it is crucial to first understand the game's lifecycle when running within LaunchWrapper
.
LaunchWrapper usurps the game's normal startup process in order to allow Tweakers to modify the game. Each Tweaker can supply Class Transformers which are able to modify game classes at load time. Tweakers are specified on the command line and are loaded and initialised one by one by the Launch
class, which forms the core of LaunchWrapper's startup logic.
Tweakers such as FML and LiteLoader are also able to co-operatively load and inject other Tweakers that they discover, and so Launch
initialises Tweakers in a loop, continuing until no further additional Tweakers are offered. We can visualise the execution flow within Launch
like this:
It is absolutely vital to realise that this phase of initialisation must complete before the game begins to load, since otherwise a Transformer registered late in the Tweak initialisation cycle may not have the opportunity to process classes that it needs to, and the transformer chain may not be complete.
Things which happen at this phase of initialisation are:
- Tweakers such as FML and LiteLoader perform mod discovery, and inject their transformers and any additional Tweakers they find.
- FML "Core mods" (aka Loading Plugins) are initialised and can register their own transformers.
Since the mixin subsystem must be initialised at this early stage (before game classes load), it must be loaded by a coremod or Tweaker, see the sections on bootstrapping below.
Once Tweaker initialisation is complete, Launch
calls the game's original main()
method which starts the game loading process. At this point the transformer chain is complete and so game classes can be loaded and processed by the registered transformers. The game enters its main loop and classes are loaded on-demand and processed by the transformer chain (including Mixin).
It should be clear from the above that the game execution is thus split into two distinct phases, namely the pre-init phase where the Tweakers are initialised, and the default phase which is analogous to the normal game lifecycle when not running inside the wrapper. It is necessary to understand these phases and their relationship with the Mixin subsystem.
In the normal course of things, our Mixin setup would only deal with classes loaded in the default phase and life would be simple. During this phase we know that:
- The transformer chain is complete
- Game classes can be safely loaded
This means that it's safe to load and transform game-class bytecode and apply our mixins, it is also safe to load the class metadata that we require in order to apply those mixins (remember from above that validation and pre-transformation of mixins happens in a single pass at startup). If we perform metadata generation too early (during the pre-init phase for example) then there is a chance that critical transformers may not have been registered! This will result in the mixin bytecode being incomplete or invalid.
So why bother with pre-init at all?
The simple answer is: "so we can mix in to core classes provided by other tweakers, specifically FML"
One of the requirements of a platform like Sponge is that it is sometimes necessary to hook into the underlying platform in ways which simply wouldn't be possible if transformation occurred during the default phase when core classes have already been loaded (and thus are beyond the reach of transformers). However as we have already established, loading our game-relevant mixins during the pre-init phase will not work because transformers which we require will not be present.
In the Mixin processor then, we acknowledge this divide by splitting processing down into Environments, one for each phase, it is then possible to split up your mixin sets to target the desired Environment. Typical scenarios for mixins would thus resolve into
- A mixin set which only mixes game classes should be registered in the default environment
- Where mixins need to be applied in both phases, two mixin sets (configurations) should be specified with pre-init mixins in one set and default mixins in another.
One thing worth noting is that - as a Tweaker itself - FML co-operatively loads its coremods and re-injects them into the startup lifecycle using proxy Tweakers. Because our sensitivity to the startup order can be somewhat delicate, it is worth making note of a fine distinction between "first class" tweaker initialisation, and the indirection which occurs during coremod initialisation.
The Mixin library includes a first-class tweaker which is designed to register and begin processing at the earliest possible moment, this is marked by (1) in the diagram below. Normal coremod startup occurs during the first pass through the loop at (2) and game loading begins at (3).
If mixing in to classes during pre-init is required, then usage of the first-class tweaker is recommended.
For information on getting a Minecraft Forge project set up to use Mixin in conjunction with the MixinGradle plugin see the main chapter Mixins on Minecraft Forge.
For mixin use on other platforms please consult the platform documentation.