GMCheck — анонс

Немножко предыстории. Я ооочень давно познакомилась с замечательным игроконструктором Game Maker. Каждый апдейт, с 4.0 в 2001 году и до 6.1 в 2005, я встречала с нетерпением — чего же нового привнёс нам Марк Овермарс. Пусть за всё это время из-под моих лапок ни одной мало-мальски законченной игры не вышло, GM надолго стал одним из моих основных компьютерных развлечений, а скриптовый Game Maker Language — пожалуй, первым языком программирования.

Впоследствии по ряду причин — учёба, работа, другие увлечения — новые версии, созданные новыми разработчиками, прошли мимо. И когда меня на старости лет снова потянуло в геймдев, оказалось, за это время среда разработки очень сильно развилась в профессиональную сторону, похорошела, обросла множеством удобных фишечек. А вот язык остался практически на том же уровне, что и в начале нулевых. По сути, единственное крупное обновление произошло совсем недавно, в версии GMS 2.3. И даже оно, привнося несколько новых и действительно крутых возможностей, не исправляет имманентных проблем, лежащих в корне дизайна языка и его стандартной библиотеки.

Вообще говоря, даже немножко шаря в дизайне языков программирования, к Game Maker Language уже можно предъявить много объективных претензий, но это тема отдельной длинной статьи; а эта целенаправленно посвящена одному из самых больных мест — и чем проект крупнее, тем оно больнее. GML предоставляет множество способов наделать ошибок, но неохотно помогает их находить:

  • Типизация в языке динамическая, что подразумевает проверки только в ходе выполнения, а выполнение за неимением юнит-тестов — это много-много пробных запусков игры, что долго и утомительно. И не факт, что эти запуски дойдут до ошибки, которая затесалась в ветке кода, срабатывающей только при очень особых условиях.
  • При этом типичная реакция на ошибку — грохнуться с концами, сказав игроку «сорян» механизм исключений появился только в 2.3, а в остальном для отладки предлагается пошагово выполнять и останавливаться на брейкпойнтах, как в старых добрых 80-х.
  • Разных типов, а соответственно, разных проверок — кот наплакал; даже такие вроде бы разные сущности, как булевые и целые значения, цвета, идентификаторы объектов, дескрипторы ресурсов и структур данных — это всё прикрытые фиговым листиком числа с плавающей точкой.
  • Неявное объявление переменной по месту её использования массово приводит к ошибкам доступа к неинициализированной переменной, а флажок «treat as 0» — лишь уход от проблемы, лечение пульпита парацетамолом.

Я люблю игры, но не умею их делать. Зато люблю языки, и умею программировать, и хочу своими навыками помочь тем, кто тоже считает, что геймдев — это в том числе программирование. А также считаю, что самый быстрый и лучший способ исправлять ошибки — это заранее их не допускать. Поэтому с июня веду неспешную разработку GMCheck — статического анализатора кода в проектах на Game Maker Studio 2 (как более актуальной версии, но поддержку первой добавить несложно). Те, кто знаком с миром промышленного программирования, наверняка слышали о таких продуктах, как Сppcheck для C++, PVS Studio для C++ и Java, ReSharper для C#. А также о том, что старые динамические языки (PHP, Python, JavaScript) движутся в сторону опциональной типизации или ответвляются в статические диалекты (Dart, TypeScript). А если вы от всего этого далеки, то статический анализатор, вкратце — инструмент, позволяющий до запуска программы вылавливать в ней ряд потенциальных семантических (в т. ч. некоторых логических) ошибок, которые не заметил компилятор:

  • Для динамического языка, коим является GML, это такой внешний тайпчекер, отслеживающий типы переменных и их возможные изменения, чтобы заранее не дать сложить строку с числом, использовать неинициализированную переменную, обратиться не тем аксессором к структуре данных или перепутать местами аргументы в развесистых функциях наподобие draw_sprite_general.
  • Причём динамические возможности — например, в рантайме поменять тип переменной, или передавать аргументом функции значения разных типов, в зависимости от чего она может вести себя по-разному — в языке-то и не используются. Все функции стандартной библиотеки имеют однозначные сигнатуры, без перегрузок. Свой скрипт можно написать каким угодно, нашпиговав внутри проверками типа if is_real (argument0), но юз-кейсы такого рода сомнительны. Поэтому если одна переменная может иметь разные типы в разных участках кода или вдруг поменять его в ходе выполнения — это, возможно, и не ошибка, но как минимум предупреждение.
  • Поскольку типы переменных явно не указываются, их нужно выводить по контексту использования, и я не уверена, насколько полно это будет получаться. Однако несложно добавить опциональную типизацию, как в вышеупомянутых динамических языках — скажем, комментариями специального вида, которые сам GM просто игнорирует, а чекеру это будет помогать. label /*: string*/ = <…>
  • Для некоторых переменных имеет смысл отслеживать вхождение в возможный диапазон — например, alpha должен быть от 0 до 1, даже после всех возможных математических преобразований.
  • Скрипты в Game Maker — эдакие глобальные методы, которые можно вызвать из любого объекта, невзирая на его внутренности. Чекер может проверять, всё ли окружение, нужное скрипту, доступно в объекте на момент вызова, и не перетирает ли он чего-то важного.
  • Не все, особенно новички, умеют грамотно пользоваться локальными переменными (var), из-за чего они могут висеть в объекте до скончания времён, или конфликтовать с другими (особенно при вызове скриптов).
  • Множество вещей, при которых программа вроде бы успешно работает, но что-то явно не так — неиспользование забытых переменных, вызов рисовательных функций вне события Draw, вызов или наследование пустых событий, обращение к other там, где его нет…
  • Структуры данных требуют ручного вызова _create и _destroy, иначе можно словить утечку памяти.
  • Ошибки, порождаемые опечатками или копипастой — например, когда логическое выражение всегда получается истинным или ложным, в разных ветках условного выражения одинаковые конструкции, или параметрами передали x, y, y вместо x, y, z.
  • …и много других.

Понятное дело, задача вот это всё обнаружить в общем случае неразрешима, что доказано ещё стариной Тьюрингом. Тем не менее, разнообразными эвристическими алгоритмами можно достичь очень неплохих результатов, пусть и с некоторым количеством ложноположительных срабатываний. Использование предполагается максимально простым — натравливаете программку на папочку с вашим проектом, и получаете HTML-отчёт обо всяких подозрительных местах. Интегрироваться в саму среду GM, увы, нельзя, но можно завести помощника-демона, который будет висеть в фоне и обновлять отчёт каждый раз, когда что-то добавляется в проект.

Разработка ведётся на Haskell — наилучшем языке для такого рода задач (компиляторов, трансляторов и прочих анализаторов сложноструктурированных данных). Репозиторий — здесь. На данный момент готов парсер практически всей грамматики GML в синтаксическое дерево, и растут зайчатки вывода и проверки типов. Stand-alone запускалки пока нет, разрабатываю, экспериментирую и проверяю юнит-тестами и в REPL.

Честно говоря, зная своё хроническое неумение доводить дела до конца, я совершенно не могу гарантировать, что проект дорастёт до хоть какого-то юзабельного состояния. И уж тем более не смогу  своими скромными силами реализовать весь спектр возможностей, перечисленный выше. Но мне очень поможет, если буду знать, что он правда может кому-то пригодиться — любителей (и профессионалов!) Game Maker тут немало, я знаю. И кроме того, вы можете поддержать и другими способами:

  • Триалка GMS2 у меня закончилась, а на лицензию раскошеливаться пока не хочу, поэтому очень полезными будут исходники маленьких проектов — например, с джемов, или просто синтетических примеров. Особенно здорово, если там действительно будут какие-нибудь загадочные ошибки, которые вы так и не смогли отловить.
  • Рассказывать про самые-самые типичные и надоевшие баги, возникавшие у вас при разработке на GM.
  • Проаннотировать сигнатуры всех этих бесчисленных функций стандартной библиотеки GML. Они лежат в отдельном текстовом файле с готовыми примерами, и знания хаскеля для этого не нужно.
  • Просто предлагать всякие идеи и хотелки.

Stay tuned!

  • Yuuri
  • 29 сентября 2020, 22:44