Парочку месяцев назад на Гиктаймсе проскакивала новость о запущенной Half-Life на Android Wear. В статье ни слова не сказано было о разработчиках и тогда один из хабровчан спросил в комментариях, почему никто из них не напишет здесь?
И вот уже годовщина с того момента, как я начал порт свободного движка Xash3D на Linux, а также, поскольку несколько моих знакомых всё же интересовались подробностями, я решил написать этот пост. Кому интересна история проекта, обходы разных проблем и мои личные советы, прошу под кат.
Начну с истории проекта. Движок изначально был написан программистом с ником Дядя Миша или g-cont. Именно ему мы обязаны тому, что сейчас есть. Лишь в 2011 году он выложил исходники движка под GPLv3. Но у него был фатальный недостаток, Дядя Миша не хотел портировать движок на что-то кроме Windows. За Android порт он хотел некоторую сумму, и, естественно, идея провалилась из-за нашего коммьюнити.
Я лично начал следить за движком с 2012 года, год спустя после открытия исходников, но лишь осенью 2014, с небольшим опытом программирования на С++ я скачал исходный код движка. Загоревшись мыслью о том, что свободный игровой движок и кроссплатформенность — понятия ещё с кармаковских времён тесно связанные, я начал портировать его на Linux.
Поначалу порт планировался с использованием winelib, но со временем, вникнув в архитектуру движка, я остановился на SDL2, как самой вездесущей мультимедийной библиотеке. Да и Valve для своих портов использовали его.
И вот сразу мой первый совет тем, кто хочет переносить любое неизвестное ему приложение на любую ОС:
1) Создавайте проектные файлы сразу и, не делая ни единого изменения в проекте, компилируйте его.
Да, от моего совета ваша IDE может даже подвиснуть, так как компилятор выдаст не одну тысячу ошибок и предупреждений. У меня компилятор выдавал около 4000 ошибок.
Переходя от одной ошибки к другой, я постепенно избавлялся от них. Не оставалось и виндового кода, -Wl,--no-undefined очень сильно спасает при портировании.
И вот свершилось — загрузился движок. Показывает собственную консоль, а за окном, тем временем, уже было примерно начало декабря прошлого года. Должен подчеркнуть, что до меня попытки предпринимались и примерно на консоли и заканчивались, если не раньше. Весь декабрь ушёл на загрузку библиотек в рантайме, SDL ввод, звук — и вот я выкладываю на Linux.org.ru скриншот работы.
Несколько человек заинтересовываются проделанной работой. Я же оглядываюсь на исходники и понимаю, что с этим уже работать нельзя: во многих местах код из-за макросов становится нечитабелен, появляются странные баги, сборка за пределами моего локалхоста очень сложна. Да и версия движка сильно старая. В комментариях к треду указывают, что новые версии распространяются на каком-то другом форуме. В итоге я начинаю всё с нуля, а мой знакомый помогает приводить всё в серьёзный вид — переводит сборку на CMake, гонит на меня плохо пахнущими тряпками за слишком большие коммиты и говнокод. Также на GitHub создаётся организация SDLash3D, названная в честь старого порта. На момент написания статьи в ней аж пять человек, но активны только двое.
Внезапносовет номер 2, который я для себя уяснил в этом время:
2) Сделайте заголовочный файл, содержащий одни макросы, которые будут вставлять нужный для целевой платформы код.
Также тут совет для тех, кто, читая эту статью, пишет код в своей студии.
3) На счёт всякого подозрительного и странного кода обращайтесь к Google. Он вам расскажет, что ваш код компилируется только под Visual Studio, у которого много нестандартных расширений. И поменьше завязывайтесь на WinAPI.
Тем временем, nicknekit/Unc0nnected молча начинает портировать старый порт на Android.
С февраля мы с Никитой начинаем работать вместе над портом. Он занимается старым портом, а я допиливаю новый и переношу в него функционал, связанный с Android.
В этот момент устанавливается базовая схема работы порта на Android. Сейчас оно выглядит так:
В марте мы создаём тред на 4PDA, о котором сообщаем об успехах переноса, новые видео и скрины. Первого же апреля выходит версия 0.1, но первое апреля не может быть без шутки, поэтому в движке сделано специально условие, при котором он закрывается, если не найден pakandroid.pak, в котором был «зашит» очень весёлый и достаточно качественный мод G-Man Invasion.
С тех пор, среди значимых событий для порта оказались:
- Поддержка сенсорного управления, сделанная совместно с Beloko Games;
- Релиз версии 0.14, который действительно принёс успех. Около 20 тыс. посетителей за два дня, судя по статистике на GitHub;
- Интерфейс Android-версии для модификаций, сделанный нашим mittorn. Мододелы могут самостоятельно выпустить свою игру ещё и на Android.
На этом можно и закончить, ниже бонус в виде отдельных коммитов и ссылки на нас на GitHub и ModDB.
Бонус №1. Несколько коммитов, связанных с разным пониманием кода в Visual Studio и GCC. После исправления их всех я точно уверен, что поддержка Си в студии — это худшее, что случалось в сфере программирования:
- Баг, при котором звуковые файлы слов записывались на один элемент массива вперёд. Под MSVC не проявляется, в GCC проявляется;
- Баг, при котором GCC ругался на выход за границы массива. Под MSVC так же не проявляется;
- Студия умудряется накосячить и с С++;
- Ещё один баг. И это в Си, в котором уж точно одинаковые имена запрещены. Прикиньте, в студии оно компилируется!
- Исправление бага, при котором останавливались вентляторы в игре. Не совсем уверен, кто здесь виноват, но мы этот баг полгода искали.
Бонус №2. Также некоторые коммиты, связанные с ARM, Android и OpenGL ES:
- Баг, со странным умножением и делением на 1.0 на ARM;
- Ещё проблемы с float. Вызывало рандомные краши с SIGBUS;
- А здесь ссылки нет. Просто внезапно для себя открыл, что все char на ARM — беззнаковые. Оттого навигация NPC сходила с ума, а в движке не работали спрайты. Можно или ключ -fsigned-char вставить, или явно указывать, что char должен быть знаковым для этой переменной;
- Баг рендера. Не совсем проблемы ARM, зато проблема OpenGL ES. До сих пор не пофикшена полностью;
- Своя собственная реализация dlsym() на Android, а всё потому что в Android до Lollipop просуществовал баг, с которым не резолвились некоторые символы из библиотек;
Ссылки:
Организация на GitHub.
Страница на ModDB.
Спасибо за внимание, ваш a1batross.
Комментарии (17)
ruikarikun
14.10.2015 14:38+3>>> pchan->words[i] = pwords[i++];
Тут undefined behaviour, оно может в будущем сломаться и от другой версии Студии, например.
>>>Просто внезапно для себя открыл, что все char на ARM — беззнаковые.
А стандарт языка не специфицирует знаковость char. Компилятор вправе реализовать этот момент как ему удобнее. Странно, что где-то он знаковый, в большинстве компиляторов он как раз unsigned по умолчанию.
libpony
14.10.2015 16:01Согласен. Но ты не представляешь как много в этом коде UB.
Таков алгоритм из SDK. И в его реализации ошибка, поскольку он полагается на знаковость char.
mezastel
14.10.2015 15:08А расскажите про баг с умножением на 1
libpony
14.10.2015 16:06pandoralive.info/?p=5043
Вот тут точно описано. Баг исправил ptitSeb, который сильно помог с багами на ARM. Он занимается движоком на Pandora.
Sap_ru
15.10.2015 12:46Это не бага ARM, это вполне себе бага разработчиков (оригинальной версии). У них там плавающая арифметика считается с точностью single float. Но на x86 на некоторых компиляторах плавающие числа всё равно считались с точностью 80 бит (внутреннее представление FPU), а потом конвертировались (округлялись) к нужно точности.
А на ARM всё считается с указанной точностью (более того, никто не обещает даже double float на ARM) и повылазили ошибки округления. Ровно с тем же успехом они бы повылазили и на x86 с другим компилятором или с другими ключами сборки, т.к. компилятор по факту отклонялся от стандарта в данном случае.
Более того, кажется, что там алгоритмическая/архитектурная ошибка и патч не исправляет её полностью — пропадают лишь самые явные проявления, но, возможно, что это не критично.
iSage
14.10.2015 15:36+2>char на ARM — беззнаковые
Используйте int8_t (или аналоги из SDL) и ваши волосы будут мягкие и шелковистыеlibpony
14.10.2015 16:02+1Это существующий код. Я все же стараюсь делать так, чтобы работало оно везде.
iSage
14.10.2015 16:16Не очень понятно, почему вдруг fixed-width типы должны где-то не работать.
libpony
14.10.2015 16:24Вы не совсем меня поняли. Код уже был написан и использование какого-то char программистом из Valve(ошибка была в Half-Life SDK) стоило недели поиска бага. А я всё же стараюсь писать так, чтобы работало везде. В возможностях собственого опыта, конечно.
slonopotamus
14.10.2015 21:31Пока не посмотришь скриншоты, из текста совершенно невозможно понять при чем здесь собственно Half-Life :)
Zlobober
14.10.2015 22:23Про баг с одинаковыми именами:
max = max( max( r, g ), b );
Насколько я помню, в Visual Studio при правильном подборе заголовочных файлов и дефайнов max(a, b) является макросом с содержанием в духе (((a) > (b))? (a): (b)). Это объясняет, почему данная строка компилируется (в правой части скобки есть, и поэтому max развернулся в тело соответствующего макроса, в левой остался, как был, переменной max).
Особо приятной плюшкой такого макроса является черт знает какое поведение, когда выражения a и b имеют сайд-эффекты при вычислении, или, что еще хуже, когда макрос используется в рекурсии (легкой подстановкой макроса время исполнения функции из логарифмического становится экспоненциальным!)libpony
14.10.2015 23:05Так и есть. Но тем не менее, что GCC, что шланг на этом месте фейлелись. И там тоже max() — макрос.
libpony
Часа два вчера думал о том, что же я забыл. Добавил абзац про дорогого Дядю Мишу.