Access Transformers#
Иногда во время написания мода возникают ситуации, когда требуется обращение к приватным полям и методам классов игры, но сделать это просто так не получается.
Первое, что приходит в голову, это использовать возможности рефлексии (преимущественно ReflectionHelper
).
Это неплохое решение проблемы, но оно немного снижает производительность игры и портит внешний вид кода, если использовать рефлексию слишком часто.
И тут на помощь нам приходят трансформеры доступа (Access Transformers - AT). Смысл в том, что они изменяют атрибуты классов, полей и методов «на лету» – это позволяет нам обращаться к ним напрямую, без использования рефлексии.
Reflection или AT?
- Рефлексия куда проще в использовании - достаточно написать небольшой фрагмент кода (использование
ReflectionHelper
делает это ещё проще); не требуется ничего настраивать. Этот вариант хорошо подходит когда случаев обращения к приватным атрибутам в вашем коде не так много и обращение к ним происходит не слишком часто. - Access Transformers идеально подходит когда нужно наследовать класс с приватными методами, получить возможность переопределения final-методов; когда обращение к атрибутам происходит слишком часто (в рендере/тике), где низкая производительность рефлексии становится ощутима.
Если вы решились использовать Access Transformers, то используйте его повсюду в проекте для улучшения читаемости кода. Совмещать оба подхода в рамках одного проекта не стоит.
Описание файла конфигурации AT#
Правила записываются в специальном *_at.cfg файле. Данный файл конфигурации описывает, какие классы, методы и поля должны быть преобразованы. Каждое правило записывается отдельной стройкой в следующем формате:
# Изменение модификатора доступа КЛАССА
<AccessModifier> <ClassName>
# Изменение модификатора доступа ПОЛЯ
<AccessModifier> <ClassName> <FieldName>
# Изменение модификатора доступа МЕТОДА
<AccessModifier> <ClassName> <MethodName><MethodSignature>
<AccessModifier>
– Модификатор доступа который требуется установить: public, protected или private. В большинстве случаев это будет public. В дополнение к модификатору доступа можно добавить суффикс +f или -f чтобы установить или снять флаг final.<ClassName>
– Полное имя класса.<FieldName>
– Имя поля.<MethodName>
– Имя метода.<MethodSignature>
– Сигнатура метода во внутреннем формате JVM (типы аргументов + возвращаемый тип).
См. спецификация формата файла FML Access Transformer.
Практика#
Создайте файл example_at.cfg в каталоге src/main/resources/META-INF
. Имя example значения не имеет, можно выбрать любое, обычно это <modid>
.
Но важно, чтобы имя файла заканчивалось суффиксом _at.cfg
# Снимает модификатор final чтобы метод dropBlockAsItem(World, int, int, int, int, int)
# можно было переопределять.
public-f net.minecraft.block.Block func_149697_b(Lnet/minecraft/world/World;IIIII)V
# Для обращения к внутренним классам используется разделитель '$'.
# Делает вложенный класс LongHashMap.Entry публичным.
public net.minecraft.util.LongHashMap$Entry
# Делает поле isBadEffect публичным (полезно для сервера, где геттер недоступен)
public net.minecraft.potion.Potion field_76418_K
# Делаем конструктор класса публичным (конструктор - это метод с названием <init>)
public net.minecraft.potion.Potion#Potion <init>(IZI)V
# Делает поля в классе ItemStack станут публичными и изменяемыми
public-f net.minecraft.item.ItemStack *
# Делает все методы в классе ItemStack станут публичными.
public net.minecraft.item.ItemStack *()
Обратите внимание что здесь используются SRG-имена полей и методов. В поиске таких имён и сигнатур вам поможет сервис Minecraft Names. В некоторых случаях может потребоваться продублировать MCP-имена.
Не злоупотребляйте!
В последних двух примерах показана возможность использования символа *
вместо названий полей/методов. Так лучше никогда не делать,
всегда указывая имена нужных элементов класса. Это нарушает весь смысл инкапсуляции. В некоторых случаях это может приводить к ошибкам.
Финальным штрихом будет указание имени конфигурационного в манифесте JAR файла, чтобы Forge мог найти в релизной версии мода. Добавьте в файл build.gradle следующие строки (не забудьте изменить имя файла, если меняли):
jar {
manifest {
attributes 'FMLAT': 'example_at.cfg'
}
}
Теперь запустите сборку проекта (gradlew build
). ForgeGradle должен увидеть файл конфигурации (вы увидите сообщение в логе) и
сгенерировать специальный minecraft.jar с применёнными трансформерами. Затем переимпортируйте проект в IDE, чтобы та переключилась на новый файл.
При внесении изменений в файл AT нужно будет запускать сборку проекта снова. Сообщение в логе «Applying SpecialSource...» сигнализирует о том что был применён файл AT.
Если ForgeGradle игнорирует ваши изменения...
Иногда случается так что FG не хочет перегенерировать minecraft.jar ни в какую. Завершите работу Gradle Daemon командой gradlew --stop
и удалите папку build в директории вашего проекта. Затем запустите сборку проекта снова.
На заметку
Хоть трансформеры доступа рассчитаны в основном для кода Minecraft, ничто не запрещает их применять в том числе к другим модификациям. Это может быть полезно, например, при разработке дополнения к моду. Однако среда ForgeGradle не сможет сгенерировать вам специальный JAR файл мода с применением трансформера. Возможно потребуется добавить в проект изменённые файлы исходного кода исключительно для возможности сборки и запуска проекта в dev-среде.
Теперь вы можете приступать к написанию кода!
Дополнительно#
Баги ForgeGradle
К большому сожалению, как только вы начинаете использовать в своём проекте трансформеры доступа появляются следующие баги:
- Сгенерированный minecraft.jar (forgeBin.jar) подключается к среде без исходного кода. Становится недоступен просмотр исходного кода игры и JavaDoc. Потребуется вручную подключить архив с исходным кодом в настройках проекта IDE.
- ForgeGradle забывает закрыть сгенерированный minecraft.jar как ресурс из-за чего становится невозможным выполнять чистую сборку (clean) проекта. Задание будет завершаться ошибкой про заблокированный файл.
По этим причинам повсеместное использование трансформеров доступа в каждом проекте может оказаться неуместным.