Deep dive on porting ActionScript to Haxe, Scott Pultz

В своем докладе Скотт Пултц рассказал об опыте портирования Vegas World с ActionScript 3 на Haxe.

Видео-версия доклада также доступна на официальном сайте haxe. Также в блоге Haxe появился краткий пересказ этого доклада на английском языке.

Scott Pultz

Кодовая база Vegas World довольно большая - более миллиона строк.
Проект с историей - развивается уже более 10 лет.
В нем использовались в основном векторные ассеты, так что были сильно завязаны на проприетарные технологии Adobe.
При этом ни у кого в компании FlowPlay до этого не было опыта работы с Haxe.

Project details

Чтобы упростить процесс перехода с чисто Flash на Haxe, было решено сначала выполнить следующие действия:

  • написать абстракции для реализации общих задач, например, загрузки файлов. Таким образом, переход на другую платформу реализуется через подмену реализации абстрактного API;
  • конвертировать векторную графику в растровую;
  • использовать иные инструменты для создания анимаций - DragonBones.

Project rewtite

После этого уже выполнялся процесс портирования AS3-кода на Haxe.

Project port

Сначала планировалось получивший Haxe-код компилировать в SWF.
Затем следовало создание html5-версии, т.к. веб - приносит компании FlowPlay бОльшую часть дохода. 
Ну а далее - создание мобильных портов игровой платформы.

Target Roadmap

При портировании кода в теории у вас есть 2 варианта:

  • сделать все хорошо, но тогда этот процесс займет дольше времени и потребует больше денег;
  • сделать по-быстрому, быстро запуститься и “причесать” код как-нибудь потом.

Но на практике обычно все обстоит иначе, и улучшить код как-нибудь потом будет значительно сложнее (т.к. проект постоянно развивается, то код будет усложняться, будут появляться все новые “костыли”, также всегда есть и другие задачи, которым может даваться более высокий приоритет выполнения). Поэтому было решено подойти к портированию обстоятельно и выполнить его качественно. 

Code Beauty?

 

Strategy

Так как Vegas World уже был запущен на Flash и постоянно происходило обновление AS3-кода, то при портировании было решено придерживаться следующих принципов:

  • сохранять одинаковую структуру как для AS3, так и для Haxe (в проекте должны использоваться классы и методы с одинаковыми именами). Если в AS3-коде что-то менялось, то соответствующие изменения должны вноситься в Haxe-код, и наоборот.
  • то же самое касается и логики - исправил логику в AS3-коде - будь любезен внести изменения в Haxe-код;
  • постоянно синхронизируй изменения между AS3 и Haxe-кодом. Делать это необходимо для того, что в какой-то момент времени все-таки придется переключиться на портированный код полностью, и тогда следование этому принципу упростит отладку ошибок, совершенных при портировании;
  • повышение типизации в коде;
  • фокусируйся на задаче, чтобы достичь результатов в кратчайшие сроки;
  • следи за мелочами. Если в процессе портирования ты действовал по какому-то шаблону, и в этом шаблоне обнаружилась ошибка, то необходимо вернуться и исправить ее везде, где она уже появилась;
  • если не уверен в каком-то фрагменте кода, то оставь его нерабочим и оставь комментарий, чтобы вернуться к нему позже (изначально в коде порта было около тысячи таких TODO, теперь же их порядка 30). 

Whatever you do...

В качестве подходов к процессу портирования рассматривались два варианта:
Первый - это структурное преобразование, при котором используются специализированные инструменты, наподобие AS3HX, которые могут преобразовывать конструкции языка ActionScript в соответствующие им конструкции Haxe. При данном подходе весь код прогоняется сначала через такую утилиту, а затем вносятся оставшиеся правки (т.к. инструмент может не справиться с преобразованием всех языковых конструкций).
Второй подход - использование автоматической замены текста. Процесс портирования в таком случае состоит из: переименования as-файлов в hx, поиске и замене текста согласно заданному списку, и дальнейшем ручном поиске и исправлении ошибок.

Syntax Porting Options

Преимуществами первого подхода является автоматизация и получение чистого Haxe-кода.

AS3HX

AS3HX умеет преобразовывать часто встречающиеся конструкции типа циклов и описания свойств у классов.

AS3HX Pro

Однако у данного подхода имеются и недостатки:
1. Теряется форматирование кода. В компании FlowPlay часто пользуются форматированием кода таким образом, чтобы лучше видеть его структуру. На слайде приведен один из небольших фрагментов кода, но на практике можно встретить и гораздо более объемные конструкции, занимающие десятки строк кода. Но на выходе из AS3HX такой код будет преобразован в одну строку - все форматирование теряется;

AS3HX Cons

2. Теряются некоторые комментарии, которые помогают в понимании того или иного фрагмента кода;

AS3HX Cons 2

3. Не всегда на выходе получается верный код. И это наиболее серьезный недостаток структурного преобразования. Так у AS3HX выявлены такие проблемы как: 

  • он плохо работает с Dynamic/Object;
  • иногда теряются куски кода;
  • некорректный код на выходе, в котором сложно найти ошибки. В качестве примера был приведен цикл, в котором счетчик цикла должен увеличиваться на 2, но в коде, полученном на выходе из AS3HX, счетчик увеличивался на единицу. Да, такой код успешно скомпилируется и даже возможно не упадет, но исправить ошибку, связанную с таким неверным кодом, может оказаться непросто;
  • не всегда на выходе получается эффективный код;
  • иногда код может получиться “страшным” (плохо или странно выглядеть).

Для выявления и исправления таких ошибок может понадобиться сесть и сравнить AS3-код и Haxe-код строчку за строчкой, а для этого в таком большом проекте как Vegas World понадобится огромное количество времени.

AS3HX Cons - Wrong code

У второго подхода, связанного с автозаменой текста, также есть недостатки:

  • нельзя просто заменять одни языковые конструкции другими;
  • вследствие этого на выходе остается очень много кода, который необходимо править вручную.

Text Replace

В итоге было принято решение пойти по второму пути.
Для автозамены была составлена таблица соответствия:

Text Replace Mappings

А также утилита, которая копирует содержимое as-файлов, сохраняя их в hx-файлы, осуществляет автозамену текста в соответствии с таблицей.

Text Replace Tool

Для того, чтобы ускорить работу программистов, которые затем вручную правили код, использовались шаблоны в IntelliJ Idea.

IntelliJ Live Templates

Достоинствами подхода с автозаменой текста по мнению Скотта является то, что:

  • сохраняются все исходные комментарии к коду и его форматирование;
  • оригинальный код не удаляется, то есть ничего не может пропасть;
  • таким образом, отпадает необходимость сверки AS3 и Haxe-кода.

Text Replace Pros and Cons

Однако многое требуется портировать вручную.
FlowPlay изначально пошли по пути структурного преобразования кода, но когда столкнулись с его недостатками, то решили переключиться на автозамену.

Далее Скотт поделился личным опытом и чувствами, с которыми он столкнулся на этом пути.

Emotional Support

Ему пришлось пройти через пять стадий принятия неизбежного: отрицание, гнев, торг, депрессия и принятие :D
Чем быстрее вы смиритесь с изменениями, тем лучше для вас.
В конце концов код, который получится на выходе будет лучше, чем исходный, и это говорит в пользу Haxe.

Some things better, some things worse...

В начале процесса у вас может сложиться представление, что компилятор троллит вас: в начале у вас будет 1002 ошибки компиляции, через 6 часов работы - 997 ошибок, еще через полдня - 758 ошибок и так далее. В процессе исправления этих ошибок вы будете все лучше и лучше их понимать и привыкать к синтаксису Haxe.

Haxe Compiler Error Counts

И вот у вас осталась одна ошибка - не хватало какой-то точки с запятой в конце строки, вы ее ставите, пытаетесь скомпилировать, и...

1343 Errors Remain

и у вас уже 1343 ошибки. Problems?

Ничего страшного, продолжайте работу. И вот, в какой-то момент программа успешно скомпилируется. И это значит, что вы близки к тому, что игра скоро снова заработает.
У Скотта заняло примерно 2 месяца, чтобы портировать первые 100 тыс. строк кода, и потребовался всего день, чтобы этот код заработал.

Don't worry, you will get there!

И правильным ответом на вопрос: “Сколько времени займет портирование?”, Скотт считает - “Столько, сколько потребуется”.

How long is the port going to take?

Далее Скотт сравнил синтаксис и возможности ActionScript и Haxe, перечислил моменты, с которыми он столкнулся в новом для себя языке.

Language Details

В Haxe описание функций типизировано, что является плюсом по сравнению с AS3.

Type safe function pointers

Также в Haxe есть приватные конструкторы, которых не хватает в AS3.
Но в Haxe поля, помеченные как private, на самом деле ведут себя как protected. Кроме того, в Haxe есть мета @:access, которая позволяет получить доступ к любым полям. Таким образом, в Haxe нельзя полностью сокрыть какие-либо свойства или методы, что, по мнению автора, является недостатком, провоцирующим плохие практики программирования.
В Haxe нет ключевого слова internal, но в качестве более мощной альтернативы предлагается использовать мету @:allow, с помощью которой можно явно указать какие методы, классы, или даже пакеты будут иметь доступ к помеченному метой полю.

Visibility and Access

Синтаксис циклов также сильно отличается, из-за чего потребовалось много времени на их портирование. 
Плюсами циклов в Haxe по сравнению с AS3 являются:

  • более краткая форма записи;
  • ограничение области видимости счетчиков циклов;
  • возможность реализации собственных итераторов для обхода структур данных.

Главным минусом является то, что в for-циклах счетчик может только увеличиваться на единицу, и если нужен другой шаг итерации, то приходится использовать цикл while. 

For loops

Try/catch в принципе такой же, как и в AS3, только отлавливать нужно не Error, а любые типы.

Try/Catch

В Haxe нет ключевого слова finally, поэтому необходимо перерабатывать такой код.

Try/Finally

Массивы в Haxe типизированы, что также является плюсом по сравнению с AS3.

Array and Vector

Мапы типизированы, плюс для проверки существования записи по ключу есть метод exists(), однако в качестве ключей нельзя использовать Dynamic или Float, что также потребовало от программистов внести соответствующие изменения в портируемый код.

Maps

Синтаксис явного приведения типов очень сильно отличается, поэтому с ним плохо справляется AS3HX, который может спутать попытку приведения с вызовом метода.
Также в Haxe нет ключевого слова “as”.

A cast of thousands

В начале процесса портирования везде использовалось небезопасное приведение (unsafe cast), но т.к. его поведение отличается от безопасного, и использовать его следует только тогда, когда точно знаешь, что тип подходит, то от него отказались.

Unsafe cast

В Haxe нет автоматического приведения типов как в AS3, что в принципе можно считать плюсом, т.к. повышает типизацию кода и нужно явно приводить типы.

No auto cast

Properties - Source of confusion

Большие трудности вызвали отличия в описании свойств объектов, поэтому для программистов была составлена шпаргалка с описанием ситуаций, для которых нужно применять ту или иную форму. 

Properties - Access

В Haxe в switch/case не используется ключевое слово break, поэтому при портировании кода, в котором switch/case находится внутри цикла, нужно быть внимательным, т.к. компилятор пропустит такой код с break, но работать он будет неверно - break будет прерывать цикл тогда, когда вы этого не ожидаете.

Case Statements

Также в switch/case выражениях не поддерживаются объекты, поэтому для них используется if/else.

Switch Statements

Разбор XML-документов имеет совершенно другой синтаксис:

XML Parsing

Также как и JSON:

JSON Parsing

Для разбора JSON-файлов можно использовать как Dynamic, так и typedef с описание структуры JSON, тогда у вас появляется типизация, позволяющая избежать ошибок, связанных с ее отсутствием.

JSON Parsing 2

В Haxe нет ключевого слова const, взамен ему рекомендуется использовать или inline переменные (для простых типов), или свойства (default, never).
Также ожидается, что в Haxe 4 появится ключевое слово final, которое также можно будет использовать для замены const.

Const

В if’ах нет автоматического приведения чисел, строк и объектов к булевым значениям, что требует от программиста явно указать условие, которое он имеет в виду. Это, по мнению автора, также является плюсом.

If statements

Далее Скотт остановился на особенностях сред исполнения.

Runtime Implications

Хорошей практикой является инициализация переменных при их объявлении, т.к. на одних платформах им могут автоматически присваиваться значения по-умолчанию (на flash int присваивается нулевое значение), а на других - нет (на html5 у int’а значением будет undefined), что может явиться причиной множества ошибок.

Members

Порт AS3-кода на Haxe для компиляции в swf - наиболее простая задача, т.к. портированный код скорее всего (если при портировании не было ошибок) будет работать также. При этом и в IntelliJ Idea и в FlashDevelop доступен отладчик.

Flash target

Но при портирования кода для работы на других платформах у вас могут появиться проблемы связанные с особенностями реализации API для них, при этом flash/html5 менее строги к типизации вашего кода, и программа может относительно легко запуститься на этих платформах, однако на статических платформах (C++) та же программа может просто сразу же упасть.
Поэтому в случае, когда у вас уже есть рабочая кодовая база на flash, то лучше начинать портирование именно на Haxe именно под flash. Но если вы начинаете проект с нуля, то, по мнению Скотта, лучше начинать со статических платформ, которые предъявляют более строгие требования, таким образом у вас получится более чистый и стабильный код, который будет проще портировать под html5.

Different rules

Поддержка шрифтов в OpenFL работает вполне достойно, однако отрисовка текста все же разнится от платформы к платформе.

OpenFL Fonts

Работа со звуками также имеет свои особенности:

  • разные платформы поддерживают разные форматы файлов (flash - mp3, мобильные платформы - ogg, web - в зависимости от браузера могут поддерживаться или mp3 или ogg), поэтому пришлось хранить по две копии одних и тех же звуков, но в разных форматах;
  • т.к. в браузере могут поддерживаться разные форматы, то API для работы со звуком на html5 немного отличается от остальных - необходимо передавать массив ссылок на звуковые ассеты, а внутренняя логика OpenFL / Lime сама определит, какой файл воспроизводить;
  • также на html5 работа со звуком может иногда приводить к неожиданным падениям программы, поэтому весь код работы со звуком обернут в try/catch.

OpenFL Sound

В случае с Vegas World реализация Display List’а в OpenFL была не так важна, т.к. игра использует Starling, для которого также есть порт, поддерживаемый в актуальном состоянии.
Однако возник ряд проблем при работе с текстурами - разные платформы поддерживают разные форматы сжатых текстур, поэтому нельзя просто взять ATF-текстуру из flash/AIR проекта и использовать ее везде.

OpenFL Display

Формат хранения пользовательских данных отличается, поэтому при переходе с Flash на html5 был написан конвертер этих данных, иначе бы пользователи, игравшие на flash-клиенте потеряли бы все свои данные при переходе на html5.

OpenFL Storage

Для работы на разных платформах также использовались и специфические для них средства:

  • для мобильных платформ использовался ряд нативных расширений;
  • для html5 - дописан код для вызова внешнего javascript-кода.

Environment

Пройдя все стадии портирования, FlowPlay наконец запустили html5-клиент для Vegas World. При этом пользователи практически не заметили этого изменения, главным плюсом для пользователей стало, что теперь браузер не блокирует его.

HTML5

В ближайшее время планируется запуск мобильного приложения, которое хорошо оптимизировано и показывает стабильные 60 fps.

Mobile