В наше время никого не удивишь, когда программа, написанная на скриптовом языке, вызывает нативный код, например, когда необходима максимальная производительность, обращение к каким-то внешним библиотекам или специфические системные вызовы. Точно так же, никого не удивишь, когда в программу на компилируемом языке встраивают интепретатор скриптового языка, например, для расширения функционала или возможности автоматизации действий пользователя. Но сегодня речь пойдет не о том, сегодня все будет немного более упорото.
Мы занимались разработкой... скажем так, системы отображения интерактивного контента для рынка одной азиатской страны. Пользователь имел "умное устройство", например, ТВ-приставку или смарт-телевизор, а "интерактивный контент" представлял собой по сути дела html/js/css-приложение, которое прилетало на устройство с трансляции или из интернета и отображалось в прозрачном окне поверх видео. В качестве веб-движка использовался модифицированный Blink из гугловского Chrome.
И вот, в один прекрасный день после какого-то из обновлений, один наш партнер (читай "поставщик контента") обратился к нам с проблемой: что-то не работает. "Что-то не работает" означало, что в приложении не отображается большая часть текста. Начали разбираться. Как я уже сказал, страна была азиатская и пользовалась, скажем так, непривычной нам письменностью. То ли из-за каких-то проблем, то ли просто желая поиграться со шрифтами, разработчики контента использовали так называемый SVG font: когда символы в шрифте представляют собой по сути дела SVG-изображения.
Такой веб-стандарт реально существует, но есть одно НО: он уже давно объявлен как deprecated и выпилен из всех современных веб-движков. Ожидать, что поставщик контента изменит свой контент, было бесполезно: контент есть, в старых продуктах раньше работал, да и вообще, у конкурентов все тоже работает. Проверили — у конкурентов действительно работает, но разгадка оказалась простая: они просто использовали очень древние браузерные движки, из которых поддержку этого добра еще не выкинули, а мы, как самые прогрессивные и идущие в ногу со временем, на эту проблему наткнулись первые.
Стали думать, что делать дальше.
Вариант "а давайте возьмем поддержку формата из старой версии Blink'а и портируем ее в новую" отмели сразу. Blink — проект огромный, код поддержки SVG fonts был тоже немаленький, и имел довольно большое количество точек соприкосновения с другими компонентами движка рендеринга. Даже если бы один раз удалось "перетащить" его в новую версию и ничего не сломать, что заняло бы немало времени и сил, то потом при каждом обновлении пришлось бы каждый тратить еще больше времени, особенно учитывая, что сам движок постоянно рефакторится, внутренности переписываются, изменяются API.
Тут достаточно вспомнить старую историю с Яндексом. Когда Google наконец-то выкинул NPAPI (старый интерфейс плагинов) из своего Chrome, Яндекс объявил, что они будут продолжать его поддерживать в своем Я.Браузере. Хватило их не то чтобы очень надолго... Это реально сизифов труд, и даже Яндекс с их экспертизой и ресурсами подобное не осилил.
Мозговой штурм продолжился. Следущая мысль: конвертнуть SVG-шрифт во что-то более привычное, типа TTF. Взяли первый попавшийся онлайн-ковертер, конвертнули, подсунули в контент — отлично, работает, и выглядит точно так, как должно. Найти внутри браузера место между тем, когда шрифт уже скачан из сети и тем, когда он начинал использоваться для рендеринга, особой сложности тоже не было, как и определить по MIME или сигнатуре тип файла и при необходимости вызвать функцию-конвертер, которая подменит шрифт в массиве байт. Библиотека-конвертер, написанная на Си, тоже нашлась легко, еще немного времени было потрачено на избавление ее от внешних зависимостей (от чего-то удалось отвязаться выставлением нужных define'ов, какие-то функции удалось быстро и просто переписать самим), и уже буквально к следующему вечеру у нас был патч, с которым контент показывался как надо.
Но тут всплыло еще одно но: лицензия. Изначальный проект, из которого нам подошла библиотека, был опубликован автором под BSD-лицензией, потом передан сообществу, которое его частично перелицензировало под GPL, и в итоге с лицензиями в проекте творился полный бардак: часть файлов под BSD, часть под GPL, часть вообще без указания лицензии (!). После обмозговывания ситуации совместно с руководством и legal-командой, стало ясно, что использовать это нельзя.
А других подходящих библиотек на C или C++ не было. Писать самим конвертер с нуля было бы безумной затеей — на разбирательство с рендерингом SVG, со спецификациями форматов TTF или OpenFont, на вникание в специфику шрифтовой магии, реализацию всего этого дела и тестирование ушло бы ну просто очень много времени. Зато нашлась еще одна библиотека, написанная на JS. Хорошо работающая. Подходящая по лицензии. "А почему бы и нет?" — решили мы. Браузер, сам по себе, изначально имеет высокопроизводительный JS-интерпретатор внутри, и к его API можно обращаться почти откуда угодно. Сама библиотека была написана для NodeJS, ее легко удалось конвертнуть в браузеропонятный вид с browserifyjs, а дальше дело техники и чуть-чуть магии: движок V8 предоставляет методы Compile и Run, позволяющие запустить произвольный скрипт и получить результат его работы — в нашем случае объект-функцию, конвертирующую шрифты. Объект-функция сохраняется для дальнейшего использования, и когда наступает время, мы создаем C++-реинкарнацию джаваскриптового ArrayBuffer с содержимым файла шрифта, вызываем функцию, передавая его как аргумент, и получаем на выходе тот же самый ArrayBuffer, обращением к публичному полю которого можно получить массив байт с уже преобразованным файлом. И всё работало. Вот так внутри сурового и плотно сбитого C++ кода появился javascript-блоб.
Пытливые читатели, наверное, сразу же спросят про производительность всего этого дела. Отвечу: бенчмарков не делали, но каких-либо явно видимых лагов при отображении страницы на используемом железе не наблюдалось. Кто-то, может быть, начнет бухтеть про быдлокодинг и js-хипстерство, а я скажу наоборот: как по мне, это был именно инженерный подход к решению проблемы. Сам патч для движка уместился в пару десятков строчек и использовал только стабильные API, что обеспечило его беспроблемную переносимость при дальнейших апгрейдах — раз. Задача была решена в кратчашие сроки, что особенно хорошо, учитывая, что она была не high-priority и у нас было много других гораздо более важных дел — два. И вполне приемлемая производительность у клиента — три.
Такие дела.
Комментарии (21)
BarakAdama
15.09.2021 08:31+3Историю с поддержкой NPAPI в Яндекс.Браузере вы не совсем точно пересказали :) Тот анонс (а точнее, даже два) как раз я писал в блоге, поэтому хорошо помню.
Там изначально речь шла о временной поддержке, чтобы дать разработчикам, в особенности игр на Unity Web Player, время на переход с NPAPI. Примерно год так выиграли им (с апреля 2015 по июль 2016). За этот год последний оплот этого API в лице FF успел объявить о прекращении поддержки. Unity закрыл свой плагин. Java объявил о прекращении поддержки. Сами понимаете, когда весь мир прекратил поддержку, а разработчики разбежались по другим технологиям, то поддерживать его дальше в гордом одиночестве не имело никакого практического смысла.
F0iL Автор
15.09.2021 09:35+2А, ну окей, значит не "не смогли", а "не было смысла", понятно :)
Мы просто тоже очень долгое время таскали поддержку NPAPI у себя, с теми же целями, что и в статье -- для совместимости со старым контентом, который никто не собирался переписывать. Правда, в итоге остановились на поддержке определенной ограниченной части фнукционала NPAPI, иначе бы портировать из версии в версию за разумное время и не ломая ничего было просто невозможно.
tyomitch
15.09.2021 09:35+3Отличная история, но по два мемасика на экран — это перебор.
F0iL Автор
15.09.2021 09:41+2Серьезно? Ну вот, а я старался :( Убрал половину, теперь только один мемасик на экран.
andreyegor
15.09.2021 13:44Теперь интересно как было :( Мемасиков много не бывает.
F0iL Автор
15.09.2021 14:26Был мемасик про азиатские языки, про бенчарки и еще один про опенсорс-лицензии :)
Dmitriy_Volkov
15.09.2021 11:06+2JS используется даже в embedded, например, QML. В т.ч. HMI в ряде автомобилей написан на нём. По хорошему на нём предполагается реализовывать сугубо вьюшку, но по факту иногда оказывается намного больше
F0iL Автор
15.09.2021 14:03+1а можно еще вспомнить Espruino... правда, в продакшн-изделиях его все-таки не используют, мне, по крайней мере, ни разу такое не встречалось :)
event1
15.09.2021 17:02Те времена когда встроенные системы были сильно ограниченными по ресурсам давно канули в Лету. Сегодня можно запросто встроить процессор на несколько ядер и гигагерц и гигобайт оперативы. А NAND флеша меньше 256 Мб и не бывает уже, по-моему. Особенно если нет ограничений по электричеству (как в машине). По-этому, ничего удивительного что в машине пишут на джаваскрипте. К нам тоже регулярно приходят потенциальные клиенты и просят питон на маршрутизаторе
Alexey2005
15.09.2021 13:21Похожие проблемы возникают при попытке построить приложение на основе натренированной нейронки, например для сегментации изображений. Вдруг оказывается, что реально кроссплатформенное решение — это только JS, потому что разработчики нативных фреймворков намертво присосались к сиське NVIDIA, да так, что и не оторвёшь, а потому никакой кроссплатформой там не пахнет.
avdosev
15.09.2021 16:36Вроде бы все фреймворки для нейронок способны считать на cpu, да и поддержка amd видеокарт начинает появляться
Alexey2005
16.09.2021 01:36Способны, только вот скорость работы на CPU уровня Celeron N2830 примерно в 100 раз ниже, чем на Tensorflow.js, который проталкивает расчёты на интегрированный GPU через кроссплатформенный GL ES.
поддержка amd видеокарт начинает появляться
Что AMD, что NVIDIA — это главным образом десктоп и не самые бюджетные ноуты. Поддержка этих карточек конечно нужна, но для массового сегмента требуется в первую очередь фреймворк с поддержкой интегрированных Intel GPU, причём таких, где даже DirectX 12 не поддерживается.
Arbane
16.09.2021 00:32+1Лонгстори, но что-то кода совсем нет. Даже если код тривиален, без этих строчек больше похоже на историю "представьте, мы взяли и смогли".
F0iL Автор
16.09.2021 10:02Код во-первых тривиален, во-вторых тащить код из коммерческого проекта в паблик не ок, а в-третьих его у меня и не осталось, давно было дело, и я там уже не работаю. Так что да, это больше из разряда "представьте, мы упоролись и сделали".
napa3um
16.09.2021 01:34Я почему-то предположил, что вы JS превратите в Си с помощью чего-то типа https://github.com/andrei-markeev/ts2c или https://github.com/timruffles/js-to-c . Но я зашёл покритиковать такое решение и сказать, что полифилы на JS для поддержки нужных веб-стандартов - это норма :).
F0iL Автор
16.09.2021 10:02Мы рассматривали подобные конверторы, не знаю как сейчас, а тогда они ES5-то не полностью поддерживали, не говоря уж о ES6, и даже попытки заbabel'ить библиотеку в итоге ни к чему рабочему не привели.
datacompboy
Не хватает тега "нормальное программирование"