Нетерпеливые могут начать играть уже сейчас. Прошу отнестись с пониманием, если что-то пойдет не так. Эта версия только тестируется. Игра точно работает в 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. Я получил огромное удовлетворение от проделанной работы. В общем-то чего и всем желаю.