Перейти к содержанию

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 (типы аргументов + возвращаемый тип).

Практика#

Создайте файл example_at.cfg в каталоге src/main/resources/META-INF. Имя example значения не имеет, можно выбрать любое, обычно это <modid>. Но важно, чтобы имя файла заканчивалось суффиксом _at.cfg

example_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) проекта. Задание будет завершаться ошибкой про заблокированный файл.

По этим причинам повсеместное использование трансформеров доступа в каждом проекте может оказаться неуместным.