Эта история началась, когда мой друг и соратник, Яп Чэ-шень, сказал мне следующее:
— Я больше не хочу никогда в своей жизни писать на Дельфи! Я поклялся: больше ни единой строчки! С сегодняшнего дня все свои проекты и библиотеки перевожу на JavaScript!
Яп — китаец, с классическим менталитетом, свойственным его народу. Я многие годы работаю с ним над гуманитарными проектами в области оцифровки древней литературы, в первую очередь, «Буддийской библии» — Типитаки. Познания Япа, как в области самых древних текстов, так и самого современного программирования, не перестают удивлять меня уже более десяти лет — с тех пор, как мы начали сотрудничать и общаться на самые разные темы. Для себя я давно понял, что, если Яп что-то говорит, а я не согласен или не понимаю, то это лишь значит, что надо продолжать обсуждение, и вся громада причин и следствий в размышлениях моего друга выйдет на поверхность, и как всегда окажется, что Яп прав. Кажущаяся эмоциональность китайцев, на самом деле, необычайно рациональна.
Так вышло и в тот раз. И, хотя я и сильно удивился заявлению друга, но не спешил с выводами и внимательно выслушал. Обычно Яп придумывал свои гениальные идеи, испытывая их на Дельфи, а моя работа была переписывать всё это на С++, оптимизируя скорость исполнения. Ведь древние тексты требуют множество программ, а это несчетные форматы файлов, множество кодировок для множества языков, работа с битмапами и векторами, системами ввода и отображения… Одних только уникальных rich text редакторов мы создали не один десяток.
Первое, что приходило в голову, это быстродействие. Яп — увлекающийся оптимизацией и всегда знающий, сколько микросекунд выполняется та или иная операция, говорит об интерпретируемом языке?! Второе — моя любовь к С++: ООП, перегрузка, выразительность и гибкость, ведь именно из-за мощи этого языка я переключился на него с Дельфи/Паскаля 10 лет назад. Можно ли всерьёз писать на JavaScript?! Позволяет ли он структурировать сложные проекты, красиво выражать свои идеи? Впрочем, я не знал этого наверняка: мои предыдущие опыты с JavaScript ограничивались лишь мелкими вкраплениями в сайты…
Оказалось — всё всерьёз! Вот, вкратце, что обнаружилось после изучения вопроса:
Существеный вклад в революцию внёс Ларс Бак, которого Google попросили применить его экспертные познания в области виртуальных машин и оптимизации языков. До этого он был известен в узких кругах, создав очень быстрые версии Java и Smalltalk. Перейдя в Google из Sun, Ларс переехал из Дании в США и возглавил работу над новым JavaScipt движком для Chromium. Just-in-time компиляция, сборщик мусора, глубоко оптимизированная объектная модель. Всего несколько мегабайт: скомпилированный бинарник движка включает в себя парсер, компилятор — всю виртуальную машину. Ларс назвал новый движок V8 что бы возникали ассоциации с мощным двигателем под капотом автомобиля, то есть браузера, на котором вы путешествуете по интернету.
Весьма скептически настроенный, я установил Хром и написал первые несколько тестов на скорость в index.html… Первый же цикл сложения чисел в массиве заставил меня приподнять брови. Надо сказать, что я и сам написал в свое время несколько интерпретаторов и компиляторов для мною же придуманных языков. Когда я вижу код на высокоуровневом языке, вроде a = b + c, передо мной сразу возникают картины превращения этого кода в последовательность бит и то, как ЦПУ их исполняет. (Так уж вышло, что Мама обьяснила мне, что такое биты и ЦПУ, по дороге в детский сад, в далеких семидесятых). Поэтому, чем больше я узнавал про V8, тем скорее мой скепсис превращался в уважение. Замечу, что при работе с одномерными или плоскими структурами, вроде массива integer — код скомипилированый С++ /O3 остается на порядок быстрее JavaScript, но чем сложнее иерархия оперируемых объектов, тем меньше разница в скорости исполнения.
Поняв, что делает с входным исходным кодом V8, я начал понимать и суть JavaScript как языка; стало видно, почему он именно такой, какой есть, почему нет пойнтеров, почему не нужны VMT (таблицы виртуальных методов), как использовать метапрограмирование без шаблонов (template), почему сборщик мусора позволяет closures… и процесс пошел!
JavaScript вовсе не обязательно часть браузера, есть множество интерпретаторов коммандной строки на основе разных движков: JScript, V8, SpiderMonkey, TinyJS, quad-wheels и т.п. Наиболее известный Node-js.
nodejs.org — «платформа для создания масштабируемых сетевых приложений», на основе движка V8. Node запускается из коммандной строки, первым параметром идет имя .js файла. Node даёт доступ к файлам, сети, процессам. Рабочий экран: Notepad++/SciTE и окошко Far Manager или xterm. Работает практически идентично на Windows, Linux, FreeBSD, MacOS, даже iphone (после джайлбрейка).
За работой и изучением новых возможностей прошло больше года. Наконец, я приехал в Китай (точнее Тайвань), чтобы общатся с волонтёрами-гуманитариями непосредственно. А это программисты, лингвисты и… монахи. Всё-таки наша основная цель это полная оцифровка «Буддийской библии». А когда я говорю «полная», это вовсе не означает OCR всех этих томов из монастырских шкафов. Это огромная работа по вычитке, сравнению версий и переводов, индексация, разные режимы поиска, инструменты для грамматического, синтаксического и семантического анализа. Достаточно сказать, что Типитака в 100 раз больше Библии и до сих пор даже не имеет четкой системы нумерации параграфов. Версия на тибетском языке только отсканирована и даже не распознана OCR. Что касается переводов, то Типитака переведена только на санскрит, китайский и тибетский. Некоторые небольшие части переведены на другие языки. Насколько я знаю, менее 10% переведено на английский, меньше 1% на русский.
Такой вот громадный объём работы предстоит! И весь этот немыслимый труд могут сделать только волонтёры, сотни и тысячи добровольцев. Но им нужны инструменты. Помимо открытых библиотек, которые мы делаем для индексации, поиска, конвертации форматов — нужны и визуальные инструменты. А ещё нужно, чтобы они работали на разных устройствах.
JavaScript на телефоне даёт возможность пользовательского интерфейса в компоненте WebView, обёртке над браузерной машиной. Браузер — это ограничения по доступу к локальным файлам и прочим ресурсам системы. Несколько созданых на основе WebView приложений показали, что скорость запуска и скорость реакции на действия пользователя оставляет желать лучшего, а объём памяти, который нужен для DOM, удивляет — даже мощные ПК до сих пор затрудняются открывать много веб-страниц одновременно. Вот бы здорово иметь нативный, то есть родной, интерфейс на JavaScript.
Стали искать, окалось, они есть. Сначала попробовали PhoneGap, вещь любопытная, но внутри тот же браузер, со всеми вытекающими. Помню, когда я только осваивал JavaScript, я пыталься пользоваться IDE под названием Aptana. Это форк Eclipse, специализированный для JavaScript. Однако позже Aptana пропала, оказалось она была приобретёна Titanuim и вошла в их проект. Appcelerator Titanuim — это фреймворк для создания нативных приложений для iOS и Android.
Стали мы делать первые проекты на Titanium. Но радость сменилась разочарованием. Более громоздкого инструмента не представить! Даже скачать и запустить Titanium Studio было проблемой. Казалось бы, что проще, чем JavaScript? Изменил исходник в текстовом редакторе — перезапустил программу, вот и всё? Но с «Титаником» это никак не выходило. К тому же, обещанная версия для ПК titanium-Desktop вообще так и не заработала. Потом ещё и плагины для нативных расширений оказались ограниченными по возможностям и быстродействию. Приходилось после каждой изменённой строчки пересобирать весь проект и переустанавливать его в телефон. Нет, это не наш метод, нам бы что нибудь быстрое и маленькое, и чтобы обязательно работало на десктопе, а не только на телефоне, и желательно работало быстро и без ifdef(platform), и прочих кросс-платформеных мучений.
Итак, после долгих разговоров, бесконечных поисков в интернете, испытания всевозможных фреймворков и библиотек стало ясно: оно — не существует. Кроссплатформенная библиотека или фреймворк для создания нативных GUI приложений на JavaScript, где она? Нет её! Однако уже убив несколько месяцев на поиски и испытания, я столь чётко представлял, что именно нам надо, а что не надо, понимал, какова сложность такой библиотеки, а так же, уже написав множество тестовых приложений для всех существующих платформ, я решил: мы можем это сделать сами. Тем более, что нам много не надо: кнопки, поля ввода, картинки, метки, доступ к файлам, выбор шрифтов, возможность группировать и скроллить. Все эти компоненты использует даже начинающий. Надо только это всё грамотно собрать и дать доступ из JavaScript.
После нескольких часов мозгового штурма было решено за это взяться. Яп тогда сказал мне:
— Всё сходится, компонентов нужно немного, они должны быть простыми, тогда мы сможем справиться и нашими скромными силами — без миллионов вложений и больших комманд разработчиков, а гибкость JavaScript даст нам возможность делать GUI любой сложности даже при ограниченном количестве доступных контролов.
Назвать пока решили просто: «Yet-another User Interface library» или «YaUI library» (по-английски «уай-эй-ю-ай»).
Итак, для начала было решено ограничиться тремя платформами: Windows на i386, Android и iOS*(iPhone/iPad).
* Для iOS использование V8 невозможно по дизайну Apple. Дело в том, что JIT компиляция требует перемаркировки страниц виртуальной памяти с типа «данные» на тип «исполняемый код», но именно эта операция заблокирована в iOS. Правда, если получить права root, то это ограничение можно устранить (поэтому можно запускать node.js в консоли jailbroken iphone), но пока что было решено использовать SpiderMonkey. SpiderMonkey — это, по их словам, самый первый в мире движок JavaScript, на нём работают все проекты Mozilla, включая браузер Firefox. Мне удалось найти проект iMonkey — особым образом урезаный порт SpiderMonkey для iOS и пересобрать его самому. iMonkey отличается вручную удаленными файлами и строками кода отвечающими за JIT. Надо сказать, что это дело крайне непростое, а уж чего стоило человеку сделать этот форк из основной ветки, (ведь там такой хаос! да простят меня разработчики Mozilla). Хотя по логике достаточно было бы ключика DISABLE_JIT перед запуском make, но увы, ему пришлось патчить ручками очень много. Отсюда и та проблема, что iMonkey это Spidermonkey 1.8.0., а последняя версия SpiderMonkey уже 1.8.7. Пересобрать V8 для любой платформы оказалось куда проще. Увы никто не хостит уже скомпилированные либы. (Может быть вы, уважаемый читатель?)
Основные принципы YaUI:
- 1) Иметь как можно меньше нативного кода. Если какойто функционал можно реализовать на JavaScript, то нативный код не пишется. Если есть какой-то нативный контрол, который можно реализовать используя только JavaScript (на основе уже реализованых) то такой контрол в low-API не попадает. (Например ComboBox, можно сделать из редактора и списка, а список из скроллера и меток — значит в low-API нужны только метки и сколлер). Это нужно чтобы упростить межплатформенную поддержку.
- 2) Программы должны работать на всех платформах либо вообще без изменений, либо там где это в принципе невозможно, с минимальными изменениями по шаблону: if (yaui_platform() == 'myplatform') path = '/platform/dependent/path'. Например, на iOS экранную клавиатуру надо прятать вручную вызывая yaui.hide_keyboard(), а на Android и Windows такой проблемы нет (на Android есть кнопка «Back», на Windows клавиатура не экранная, а на «Windows 8 touch» экранная клавиатура убирается системными средствами). Придётся вносить блок примерно такой: if (yaui_platform() == 'ios') yaui.hide_keyboard()
- 3) Поддержка всех языков Азии: Тибетский, Китайский, Японский, Санскрит и т.п.
- Кстати исходники JavaScript — UTF-8, и идентификаторы могут быть на любом языке, например «function привет();»
- 4) Возможность программировать, лишь редактируя .js файлы, без всяких компиляций, переустановок, скачивания SDK.
- 5) Полная открытость всего кода.
- 6) По возможности, использование только функций, предоставляемых ОС, без третье-сторонних библиотек.
На текущий момент (апрель 2013) Yaui умеет работать с файлами, манипулировать с кнопками, метками, строками ввода и plain text редакторами, картинками подгружаемыми из jpg или png, контролами типа pixmap которые предоставляют доступ к отдельным пикселям на JavaScript. Так же работают ScrollView, вертикальные и горизонтальные. В ScrollView можно поместить другие контролы, и один ScrollView можно поместить в другой (если они разнонаправленые, то есть вертикальный в горизонтальный или наоборот). На телефонах ScrollView скроллится касанием к экрану, на Windows зажатием Control или Alt (для горизонтальной и вертикальной прокрутки соответственно). Я потратил около недели пытаясь обойтись без использования Control/Alt но потерпел фиаско, видимо требуется использовать низкоуровневое WinAPI, системный hook или code injection.
Что такое Yaui в виде файлов на Windows? Так выглядит минимальный набор файлов для запуска «Hello World».
yaui/hello.yaui 88 bytes - Ознакомительное приложение
yaui/bootstrap.js 6,377 bytes - Основные определения, require(), setTimeout(), log()
yaui/yaui.js 10,008 bytes - Высокоуровневое API для работы с GUI-контролами
yaui/v8.dll 3,193,344 bytes - Движок JavaScript от Google
yaui/boot_win.js 3,989 bytes - Обёртка над нативными функиями
yaui/yaui.exe 189,440 bytes - Нативные функции для работы с WinAPI
Запускаем вот так:
>cd yaui
>yaui hello.yaui
На Android:
libdJS.so - 70,820 bytes
libv8.so - 6,640,280 bytes
classes.dex - 21,780 bytes
AndroidManifest.xml - 3,964 bytes
/js/bootstrap.js 6,377 bytes - Основные определения, require(), setTimeout(), log()
/js/boot_droid.js 4,135 bytes - Обёртка над нативными функиями
/js/main.js - Ознакомительное приложение, (поскольку запуск не из коммандной строки, файл программы всегда называется main.js)
/js/yaui.js 10,008 bytes - Высокоуровневое API для работы с GUI-контролами
(yaui.js, bootstrap.js — те же самые что и в Windows версии.)
iPhone не привожу, чтобы не перегружать статью.
Внутренее устройство. Условно исходный код YaUI можно разделить на три уровня. Нативный, связующий и высокоуровневый.
Уровень 0 — нативный код, набор функций для создания контролов и манипулирования ими. Разумеется, для каждой платформы код этого уровня свой, на своём языке и компилируется своими инструментами. Функции создания контролов возвращают хэндлы (некие значения неопределенного типа). Этот же уровень — функции для доступа к файлам: fopen() fread() и т.п. Уровень 0 это Java и C++(через JNI) на Android, С++ на Windows, Objective-C и C++ на iOS.
Вот пример одной и той же низкоуровневой функции создания метки на трёх платформах.
Windows — yaui.cpp
ADD_V8_FUNCTION(winapi_make_label) {
window * v = (window*) args[0].toNumber();
Label *L = new Label;
L->data["type"] = "label";
L->create(*v, "_undefined");
L->owner = v;
return Number::New((int)L);
}
iOS — main.m
if (code == ya_make_label) {
OBJECT_ARG(UIView, 0, parrent);
UILabel *L = [UILabel alloc];
[parent addSubview:L];
return convert_to_handle(L);
}
Android — Yaui.java
if (code == ya_make_label) {
int id = -1;
TextView t = new TextView(this);
if (t != null) {
t.setGravity(Gravity.TOP);
t.setMovementMethod(new ScrollingMovementMethod());
id = id_count++; t.setId(id); VG.addView(t);
}
return Integer.toString(id);
}
Всего таких функций около 30, так что портирование под новые платформы должно быть относительно малозатратным мероприятием. (И я буду биться за то чтобы их оставалось как можно меншье, см. пункт 1.)
Уровень 1 — это уже JavaScript, здесь мы напрямую привязываем нативный код к JavaScript, функция к функции, вот, например, как та же функция создания метки привязывается на Уровне 1.
Windows — boot_win.js
yaui.make_label = function(parent) {
return winapi_make_label(parent)
}
iOS — boot_ios.js
yaui.make_label = function(parent) {
return ijs_control_call(ya_make_label, parent)
}
Android — boot_droid.js
yaui.make_label = function(parent) {
return yadroid(ya_make_label, parent)
}
Целью этой привязки является получение одинаковой функции для всех платформ:
yaui.make_label = function(parent)
Уровень 2 — это собственно то API которым может пользоваться разработчик. Ему не обязательно знать о уровнях 0-1. На уровне 2, все низкоуровневые функции обёрнуты в объекты, функционал которых единообразен, например все они являют метод .set_xy(x, y) причём все методы возвращают this, то есть можно вызывать их цепочкой:
yaui.create('button').set_text('hello world').set_xy(0, 0).set_wh(200, 30).show().apply()
Это же «HelloWorld» на YaUI!
Можно конечно записать и «постаринке»:
var l = yaui.create('label')
l.set_text('hello world')
Основное средство повышения уровня абстракции в yaui — селекторы, (примерно как в jQuery.) Селектор, выборка — это возможность работать с массивами контролов, как с одним. Выбирать контролы можно по имени (id), по классу или указывая свою фильтрующую функцию. Вот простой пример, игра «гоночки», который показывает некоторые возможности селекторов, YaUI, и JavaScript в целом.
// race.yaui
var wh = yaui.get_app_size(), w = wh[0]*0.01, h = wh[1]*0.01, y = h, track = w*90
var team = ['ferrari', 'mclaren', 'williams'], car = [']=>', '}=]', '[=)']
function play() {
select({cls:['car']}).each(function(c) { with(c.race) {
accel += Math.random() * 0.01
pos += accel
c.set_xy(pos % track)
if (pos > track * 5) {
clearInterval(clock)
c.text += ' !WINNER!'
return true
}
}}).apply()
}
function row(cls, txt) {
var R = yaui.create('label').set_cls([cls, 'all']).set_text(txt).set_xy(0, y).set_wh(w*100, h*7)
y += h*7; return R
}
for (var i = 0; i < team.length; i++) {
row('team', team[i])
row('car', car[i])
row('car', car[i])
}
yaui.create('button').set_id('start').set_cls('all').set_text('start')
.set_xy(0, h*100-h*7).set_wh(w*100, h*7)
.bind('button', function() {
clock = setInterval(play, 10)
})
select({cls:['car']}).each(function(c) { c.race = {accel: 0, lap: 1, pos: 0} } )
select({cls:['all']}).show().apply()
Скриншоты, простите за качество фотографий, сделано с живых устройств. (Как видите в разных заездах победили разные «болиды».)
Чтобы запустить эту программу в Windows надо указать её как первый параметр для Yaui.exe.
C:yauiyaui.exe race.yaui
Расширение файла .yaui — условность полезная для ассоциации файлов, можно было бы назвать файл race.js. Где же взять yaui.exe? Два пути — либо скачать уже скомпилированый «екзешник» или собрать самому. И то и другое найдётся на Sourceforge.
Как инсталлировать и запускать yaui на мобильнике — это отдельная история, которая бы потребовала обширных разъяснений. И могу сказать, что не по моей вине это так сложно, (особенно на iOS). Хотелось бы предоставлять полностью бинарные решения, чтобы любой желающий мог программировать для всех поддерживаемых платформ.
Чего пока нет?
Нет хорошей документации Yaui для широкой аудитории разработчиков на JavaScript, документации для желающих развивать Yaui и портировать под новые платформы. Мало программ-примеров (и они ужасны).
Нет портов под X (Linux, FreeBSD), MacOS, Windows Phone, нет порта под браузер. Да, браузер вполне мог бы исполнять Yaui, и это дало бы возможность использовать отладчик браузера.
Не хватает богатства фреймворка: то есть предстоит написать новые контролы и функции на основе того что Yaui уже может. Например ComboBox, GridView, CalendarView на основе textview, scrollview которые уже есть…
Можно ещё усовершенствовать нативную часть с расширением функционала, производительности, совместимости и надежности, провести много тестирования, отладки, и создать автоматизированые средства для тестирования и отладки
Короче говоря, программисты и буддисты всех стран, соединяйтесь, или присоединяйтесь — если вас привлекают идеи Open Source, открытость практичность и прагматичность, кроссплатформенность, любовь к гаджетам, JavaScript, минимализм и наномализм в software, новый увлекательный уникальный фреймворк, желание помочь Будде, Сангхе и Дхарме или вам понравилась эта статья — пишите, скачивайте, делайте комиты в репо, форкайте, расказывайте друзьям.
Ссылка на репоситорий и бинарные дистрибутивы.
sourceforge.net/projects/yaui
Автор: 4p4