
Сегодня, наконец, хочу увидеть что-нибудь в браузере.
Moй первый JavaScript
Для начала, могу сказать что я вообще не знаю как работают scopes в JavaScript (и моя предыдущая проблема с переменной цикла без var тому пример), я решил для начала вывести DOM в консоль, и для этого погуглил функцию вывода n пробелов (я уже веду себя как настоящий JS-разработчик, не начав программировать). Реализация вылезла следующая:
function space(num) {
return new Array(num + 1).join(" ");
}
Тут я полностью охуел и понял, что JS-программистом мне не быть никогда. Я просто не смогу такое придумать - в прошлом я не протоптал ни одной тропинки в мозгу, которая могла бы привести к такому решению задачи. И, честно говоря, протаптывать такие тропинки (учиться) мне пока не хочется. Поэтому в пизду - будем сразу вываливать DOM.
mergeInto(LibraryManager.library, {
$renderElement: function (p) {
p = p | 0;
const tags = [
"DOCUMENT_ROOT", "TEXT", "a", "button", "div", "h1", "header", "hr", "input",
"label", "li", "p", "section", "span", "table", "tbody", "td", "tr", "ul"
];
const A = ["TEXT", "class", "for", "placeholder", "type", "value", "width", "title"];
var t = HEAP32[((p)>>2)];
p += 4;
var kids = HEAP32[((p)>>2)];
p += 4;
var attrs = HEAP32[((p)>>2)];
p += 4;
var e;
switch(t) {
case 0:
console.log("render DOCUMENT_ROOT");
e = createTextNode("error: DOCUMENT ROOT HERE");
break;
case 1:
p += 4; //attrid == text
e = document.createTextNode(Pointer_stringify(HEAP32[((p)>>2)]));
p += 4;
break;
default:
e = document.createElement(tags[t]);
for (var i=0; i<attrs; i++) {
var a = HEAP32[((p)>>2)];
p += 4;
var v = HEAP32[((p)>>2)];
p += 4;
e.setAttribute(A[a], Pointer_stringify(v));
}
for (var i=0; i<kids; i++) {
var kid = HEAP32[((p)>>2)];
p += 4;
e.appendChild( renderElement(kid) );
}
}
return e;
},
JSrender: function(p) {
p = p | 0;
var root = document.getElementById("app");
console.log("Hello from emscripten: ", root.childElementCount);
var app = renderElement(p, 0);
if (root.childElementCount == 0) {
root.appendChild( app );
}
},
JSrender__deps: ["$renderElement"],
})
Я намеренно вываливаю DOM на экран лишь один раз - хочу замерить генерацию JS DOM off-screen. Ну что-ж: 18.5 ms на итерацию в Safari (это притом что asm.js часть справилась за 4.2ms). Chrome: хром вообще охуел от того что я ему сунул, пришлось считать на 250 итерациях - 75ms на итерацию. Это вообще ни в какие ворота, но как бы радует что asm.js часть все делает за 7.5ms.
Из “тяжелого” у меня в цикле только Pointer_stringify
, который работает с UTF-8, пробую заменить на более быстрый AsciiToString
, что, впрочем не совсем верно, но надо посмотреть. Safari - 17.1ms. Выйграл миллисекунду, и понятно, что дело не в “математике”, а в большом количестве создаваемых и собираемых сборщиком мусора нод. Вот он корень зла, и подтверждение того, что вроде бы Virtual DOM - нужная вещь. Для проверки, отключил все операции с JS DOM - 6.9ms на итерацию.
Ну что-ж остался основной ингридиент - diff/patch, и наступит момент истины.
Момент истины
Итак, примитивный (не значит что медленный) diff реализован, запускаю без UI. На, нахуй! 1.3ms на итерацию нативный код, 7.8ms на итерацию в V8 (node.js). Это генерация модели + посторение DOM + простроение Diff. Какой-то запас еще есть. Осталось патчить DOM в браузере.
21:54 центрально-европейского времени
Готово применение патча в браузере - 9.8ms
на итерацию! в Safari. Рано радоваться, поскольку итерация не отрисовывается (я не передаю управление в main loop), отрисовывается только результат последней. Но время обнадеживает: думаю что до 8ms эта история оптимизируется, 8ms оставим на отрисовку, и здравствуй 60 FPS. Правда Chrome дает 14ms
, но будем смотреть - сейчас все довольно схематично.
Выглядит примерно так:
JSrender: function(p) {
p = p | 0;
var element = document.getElementById("app");
//console.log("RENDER: ", p);
// p is PATCH address
while(true) {
var cmd = HEAP32[((p)>>2)] | 0;
//console.log("cmd: ", cmd);
p += 4;
switch(cmd) {
case 0: // HALT
return;
case 1: // NAV_UP
var times = HEAP32[((p)>>2)] | 0;
p += 4;
for(var i=0;i<times;i++)
element = element.parentNode;
break;
case 2: // NAV_KID
var nkid = HEAP32[((p)>>2)] | 0;
p += 4;
element = element.children[nkid];
break;
case 3: // NAV_PARENT
element = element.parentNode;
break;
case 4: // NAV_GRAND_PARENT
element = element.parentNode.parentNode;
break;
case 5: // NAV_FIRST_CHILD
element = element.firstChild;
break;
case 6: // NAV_SECOND_CHILD
element = element.firstChild.nextSibling;
break;
case 7: // NAV_NEXT_SIBLING
element = element.nextSibling;
break;
case 8: // APPEND
var e = HEAP32[((p)>>2)] | 0;
p += 4;
element.appendChild(renderElement(e));
break;
case 9: // REMOVE_LAST
element.removeChild(element.lastChild);
break;
case 10: // REMOVE_LAST_MANY
var times = HEAP32[((p)>>2)] | 0;
p += 4;
for(var i=0;i<times;i++)
element.removeChild(element.lastChild);
break;
case 11: // ATTR_SET
var attr = HEAP32[((p)>>2)] | 0;
p += 4;
var value = AsciiToString(HEAP32[((p)>>2)]);
p += 4;
if(attr == 0) {
//console.log("current value: ", element.nodeValue);
//console.log("text: ", value);
element.nodeValue = value;
} else {
element.setAttribute(attributeNames[attr], value);
}
break;
case 12: // ATTR_REMOVE
var attr = HEAP32[((p)>>2)] | 0;
p += 4;
element.removeAttribute(attributeNames[attr]);
break;
default:
console.log("SHIT HAPPENS");
return;
}
}
}
Завтра лечу в Прагу, а в дороге пока не получается работать. А пока результаты под тегом day-6
https://github.com/platoff/faxma/tree/day-6.