Transport Tycoon Deluxe / портировали на JavaScript

:

  

Transport Tycoon (Транспортный магнат) — изрядно древняя, но до сих пор доставляющая, особенно маньякам, игра в жанре экономической RTS. /.../

Также существует OpenTTD, открытый продукт маньяццтва unixоидов, вследствие чего от игры можно не отвлекаться даже в сортире и метро, установив её на коммуникатор или КПК. © lurkmore

Могу честно признаться: я убил огромное количество времени, играя в Transport Tycoon Deluxe. Да я просто фанат этой игры. Поэтому, когда на хабре появилась статья про то, как ребята из mozilla портировали Doom, я загорелся этой идей – перенести одну из моих любимейших игр сюда, в интернет.


Интернет версия: play-ttd.com
Версии под все платформы: www.openttd.org/en/download-stable


К слову, статья была скудная, но я узнал главное – портирование можно осуществить с помощью emscripten. С момента зарождения самой мысли о возможности реализации такого проекта прошло не меньше года (первая моя попытка разбилась о скалы непонимания). И вот сегодня я спешу обрадовать вас всех: я сделал это! Теперь просиживать попу за игрой в TTD можно из любой точки, подключенной к интернету.

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

OpenTTD


Хочу сказать пару слов о проекте OpenTTD, который стал донором исходного кода для JavaScript-версии. Проект включает в себя по общим оценкам порядка 300 000 строк C++ кода. Причем, возможности проекта потрясают. Судите сами:
  • Четыре графических подсистемы (драйвера)
  • Пять звуковых драйверов
  • little/big-endian
  • На уровне исходных кодов поддерживается возможность однопоточного и многопоточного исполнения
  • Работа в сетевом окружении (загрузка графических и музыкальных сетов из сети) и без него
  • Многопользовательская игра
  • Сжатие файлов сохранения с помощью lzma
  • Кэширующий рендеринг
  • Анимация тайлов через вращение палитры цветов
  • Встроенный скриптовый язык squirel

Большая часть кода написана с огоньком, творчески. Было очень интересно копаться во внутренностях этого проекта. К сожалению, за такую всеядность приходится платить и в данном случае я столкнулся с монструозной системой сборки. Пришлось написать perl-скрипт, автоматизирующий патчинг make-файлов OpenTTD, для нужд портирования.

Портировать OpenTTD удалось в следующей конфигурации:

  • однопоточный режим
  • little-endian
  • полностью отключенные сетевые возможности
  • видео драйвер – патченый SDL
  • самописный звуковой драйвер
  • патченый механизм для сохранения игр на сервере
  • патченый локатор ресурсов игры

Остальное по умолчанию.

Emscipten


Над портированием кода колдовал emscripten, а я колдовал над ним, дабы он мог переварить эту глыбу. Развивая свою предыдущую статью, опишу, как эта эзотерическая технология работает.

Идея проста: C++ код компилируется в LLVM байт-код с помощью clang. Байт-код же в свою очередь компилируется в JavaScript с помощью emscripten. LLVM байт-код это промежуточный код, который может выполняться на низкоуровневой виртуальной машине. Спецификации LLVM байт-кода отлично документированы и это одна из причин, почему он был выбран авторами проекта в качестве промежуточного представления кода. Компилятор emscripten пытается преобразовывать инструкции байт-кода в JavaScript-код один к одному, как на рисунке ниже (слева байт-код, справа соответствующий JavaScript).

Для большинства инструкций эта схема действует прекрасно. Однако нельзя сказать, что разработка такого компилятора простая задача. LLVM байт-код и JavaScript в значительной степени различаются. Для реализации более сложных вещей (арифметика указателей, циклы, обработка ошибок) приходится выполнять дополнительную работу. Вот простой пример различного поведения C++ и JavaScript.

Выполнение деления в C++ приведет к отбрасыванию дробной части числа. Для получения такой же функциональности в JavaScript приходится прибегать к ухищрениям вроде Math.floor (приведенный код не корректен для всех случаев, он верен для большинства).

Более сложный пример – реализация цикла в LLVM байт-коде и JavaScript:

Реализовать схожий функционал в JavaScript можно многочисленными способами, но многие из них будут слабо производительными, и здесь задача emscripten сгенерировать максимально производительный код.

Помимо этих низкоуровневых вещей emscripten предоставляет реализации для: libc, libcxx, POSIX, SDL, GL, GLES и других библиотек. Полностью реализована файловая подсистема C (fopen, fclose, fread, ...). Большинство программ могут компилироваться с помощью emscripten без особых проблем. Вся прелесть идеологии emscripten заключается в том, что нет необходимости править исходный код – он и так будет работать. При этом в случае успешного портирования какого-либо проекта на JavaScript, переход с версии на версию осуществляется легко и непринужденно. Например, пока я пилил emscripten, сообщество успело выпустить следующую минорную версию OpenTTD, я просто синхронизировал исходники и получил свежий порт последней версии, удобно.

Transport Tycoon Deluxe


С помощью emscripten в JavaScript скомпилировано уже достаточное большое число серьезных проектов: Doom, zlib, Poppler, OpenJPEG, FreeType, Bullet, SQLite, Python, Ruby, Lua и прочие. Сейчас в группе обсуждения emscripten идет активная дискуссия компиляции Fortran.

Тем не менее, для того, чтобы успешно скомпилировать сложный проект, нужны знания C++. Вот, например, какой результат я получил при первой успешной компиляции OpenTTD.

В этой версии не работала ни мышь, ни клавиатура. Только вот такое изображение и все.

Авторы emscripten заявляют о поддержке большого числа библиотек, но поддержка эта реализована не полностью. Например, в emscripten реализовано лишь небольшое подмножество SDL 2.0 с примесью SDL 1.2. Соответственно я столкнулся с отсутствием нужных функций в emscripten, и мне пришлось их реализовать самому. Зато я стал одним из коммиттеров проекта ( ЧСВ!!!).

Проблема, запечатленная на скриншоте выше, была вызвана тем, что реализация SDL 1.2 в emscripten поддерживала лишь 32-битные поверхности и ничего не знала про флаг SDL_HWPALLETE. Изображение в режиме SDL_HWPALLETE кодируется восемью битами (256 цветов). Именно с этим связана сжатость изображения. Почему же оно отражено по горизонтали мне не понятно до сих пор. Помимо этого пришлось реализовать следующие функции: SDL_VideoModeOK, SDL_VideoDriverName, SDL_QuitSubSystem и strndump. Мною была выявлена серьезная проблема в производительности, о которой было сообщено авторам и они ее исправили. К сожалению, мои правки по звуковой подсистеме не вошли в кодовую базу emscripten на данный момент.

Было выявлено множество проблем, связанных с оптимизационными флагами emscripten. Розовые тайлы, которые вы видите на видео, это как раз проблемы оптимизации – без флагов они нужного цвета. Очень сложно разбираться с проблемами такого рода. Проблема сама по себе невменяемая (розовые лишь тайлы воды с наклоном в 45 градусов по двум направлениям) и портирование с включенными оптимизациями происходит очень долго (более 2 часов), так что часто не потестируешь. Зато доставляет то, что проект достаточно быстро развивается, баги закрываются в течение дня-двух (мои, по крайней мере).

В общем и целом, портировать сложный проект с помощью emscripten можно, если очень захотеть. В любом случае придется столкнуться с необходимостью внесения мелких правок в emscripten или в сам проект. Зато, справившись один раз, можно повторять портирование снова и снова без особых проблем.

Это ужасно медленно не так ли?


Alon Zakai (автор проекта), выступая на конференции с докладом о emscripten, высказал мнение, что JavaScript сам по себе достаточно быстр и узким местом является компилятор и его не всегда оптимальные преобразования кода. Он привел следующую табличку:

SpiderMonker (SM) – это JavaScript-движок Firefox; Typed Arrays (TA) – самый быстрый на данный момент способ эмуляции кучи в JavaScript, который предоставляет emscripten. В ячейках таблицы числа указывают, во сколько раз выполнение скомпилированного в JavaScript кода оказалось медленнее, чем нативный код. Разброс, как мы видим, от одного до восьми раз (хотя среднее где-то возле 3-5).

Цифры на рисунке ниже показывают, во сколько раз тот или иной язык медленнее C++ в наборе тестов http://shootout.alioth.debian.org/. (прим.: Данная информация уже достаточно устаревшая и преследует цель показать, что JavaScript – быстрый язык. Если кто не согласен с этим, прошу высказываться, а другие языки давайте не трогать – наверняка они уже имеют другие цифры в этих тестах.)

В целом, я согласен с этими цифрами и скажу, что JavaScript-версия OpenTTD работает примерно в 5-7 раз медленнее, чем нативная. Но только в браузере FireFox. И вот это самый удивительный момент, который я для себя открыл. Я вел разработку c nodejs + chromium. Сам-то я фанат FF, но, к сожалению, дебажные версии JavaScript OpenTTD были размером с полусотню мегабайт и вешали FF намертво, в то время как хромиум работал очень шустро. Я думал хромиум на волне, но внезапно оказалось, что релизная версия работает неиллюзорно быстрее в FF (в 2-3 раза), нежели в хромиум. Поэтому я всем рекомендую играть, используя FireFox.

Я очень рад, что занялся проектом и довел его до завершения. Я узнал много нового о clang, llvm, python и javascript. Пообщался с интересными людьми. Сообществу очень понравился мой проект, совсем недавно авторы внесли его на страницу демонстрационных проектов emscripten. Я получил огромное удовлетворение от проделанной работы. В общем-то чего и всем желаю.

Еще раз ссылочка: play-ttd.com