From 5154a1cc1d193c4a617d343bb3adfa8dbc0c9258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Mon, 17 Jul 2023 14:23:18 +0200 Subject: [PATCH 1/2] [REF] blockdom: micro optimizations --- src/runtime/blockdom/block_compiler.ts | 25 ++++++++++++++----------- src/runtime/blockdom/events.ts | 2 +- src/runtime/blockdom/list.ts | 10 +++++----- src/runtime/blockdom/multi.ts | 20 ++++++++++---------- src/runtime/template_helpers.ts | 2 +- src/runtime/utils.ts | 2 +- 6 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/runtime/blockdom/block_compiler.ts b/src/runtime/blockdom/block_compiler.ts index e67d71325..460caef43 100644 --- a/src/runtime/blockdom/block_compiler.ts +++ b/src/runtime/blockdom/block_compiler.ts @@ -306,7 +306,7 @@ interface IndexedLocation extends Location { interface Child { parentRefIdx: number; - afterRefIdx?: number; + afterRefIdx: number; isOnlyChild?: boolean; } @@ -374,6 +374,7 @@ function updateCtx(ctx: BlockCtx, tree: IntermediateTree) { // tree is the parentnode here ctx.children[info.idx] = { parentRefIdx: info.refIdx!, + afterRefIdx: 0, isOnlyChild: true, }; } else { @@ -568,7 +569,7 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass { } // applying data to all update points - if (locN) { + if (locN !== 0) { const data = this.data!; for (let i = 0; i < locN; i++) { const loc = locations[i]; @@ -579,13 +580,14 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass { nodeInsertBefore.call(parent, el, afterNode); // preparing all children - if (childN) { + if (childN !== 0) { const children = this.children; for (let i = 0; i < childN; i++) { const child = children![i]; - if (child) { + if (child !== undefined) { const loc = childrenLocs[i]; - const afterNode = loc.afterRefIdx ? refs[loc.afterRefIdx] : null; + const afterRefIdx = loc.afterRefIdx; + const afterNode = afterRefIdx !== 0 ? refs[afterRefIdx] : null; child.isOnlyChild = loc.isOnlyChild; child.mount(refs[loc.parentRefIdx] as any, afterNode); } @@ -601,7 +603,7 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass { } const refs = this.refs!; // update texts/attributes/ - if (locN) { + if (locN !== 0) { const data1 = this.data!; const data2 = other.data!; for (let i = 0; i < locN; i++) { @@ -616,14 +618,14 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass { } // update children - if (childN) { + if (childN !== 0) { let children1 = this.children; const children2 = other.children; for (let i = 0; i < childN; i++) { const child1 = children1![i]; const child2 = children2![i]; - if (child1) { - if (child2) { + if (child1 !== undefined) { + if (child2 !== undefined) { child1.patch(child2, withBeforeRemove); } else { if (withBeforeRemove) { @@ -632,9 +634,10 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass { child1.remove(); children1![i] = undefined; } - } else if (child2) { + } else if (child2 !== undefined) { const loc = childrenLocs[i]; - const afterNode = loc.afterRefIdx ? refs[loc.afterRefIdx] : null; + const afterRefIdx = loc.afterRefIdx; + const afterNode = afterRefIdx !== 0 ? refs[afterRefIdx] : null; child2.mount(refs[loc.parentRefIdx] as any, afterNode); children1![i] = child2; } diff --git a/src/runtime/blockdom/events.ts b/src/runtime/blockdom/events.ts index 9a6c73382..654060523 100644 --- a/src/runtime/blockdom/events.ts +++ b/src/runtime/blockdom/events.ts @@ -41,7 +41,7 @@ function createElementHandler(evName: string, capture: boolean = false): EventHa } function remove(this: HTMLElement) { - delete (this as any)[eventKey]; + (this as any)[eventKey] = false; this.removeEventListener(evName, listener, { capture }); } function update(this: HTMLElement, data: any) { diff --git a/src/runtime/blockdom/list.ts b/src/runtime/blockdom/list.ts index d3db67224..ed99a2b12 100644 --- a/src/runtime/blockdom/list.ts +++ b/src/runtime/blockdom/list.ts @@ -28,7 +28,7 @@ class VList { this.anchor = _anchor; nodeInsertBefore.call(parent, _anchor, afterNode); const l = children.length; - if (l) { + if (l !== 0) { const mount = children[0].mount; for (let i = 0; i < l; i++) { mount.call(children[i], parent, _anchor); @@ -186,7 +186,7 @@ class VList { } else { for (let i = startIdx1; i <= endIdx1; i++) { let ch = ch1[i]; - if (ch) { + if (ch !== null) { if (withBeforeRemove) { beforeRemove.call(ch); } @@ -200,7 +200,7 @@ class VList { beforeRemove() { const children = this.children; const l = children.length; - if (l) { + if (l !== 0) { const beforeRemove = children[0].beforeRemove; for (let i = 0; i < l; i++) { beforeRemove.call(children[i]); @@ -215,7 +215,7 @@ class VList { } else { const children = this.children; const l = children.length; - if (l) { + if (l !== 0) { const remove = children[0].remove; for (let i = 0; i < l; i++) { remove.call(children[i]); @@ -240,7 +240,7 @@ export function list(children: VNode[]): VNode { } function createMapping(ch1: any[], startIdx1: number, endIdx2: number): { [key: string]: any } { - let mapping: any = {}; + const mapping: any = {}; for (let i = startIdx1; i <= endIdx2; i++) { mapping[ch1[i].key] = i; } diff --git a/src/runtime/blockdom/multi.ts b/src/runtime/blockdom/multi.ts index 9fad5fff2..60fbddda7 100644 --- a/src/runtime/blockdom/multi.ts +++ b/src/runtime/blockdom/multi.ts @@ -26,7 +26,7 @@ export class VMulti { const anchors = new Array(l); for (let i = 0; i < l; i++) { let child = children[i]; - if (child) { + if (child !== undefined) { child.mount(parent, afterNode); } else { const childAnchor = document.createTextNode(""); @@ -44,7 +44,7 @@ export class VMulti { const anchors = this.anchors; for (let i = 0, l = children.length; i < l; i++) { let child = children[i]; - if (child) { + if (child !== undefined) { child.moveBeforeDOMNode(node, parent); } else { const anchor = anchors![i]; @@ -56,14 +56,14 @@ export class VMulti { moveBeforeVNode(other: VMulti | null, afterNode: Node | null) { if (other) { const next = other!.children[0]; - afterNode = (next ? next.firstNode() : other!.anchors![0]) || null; + afterNode = (next !== undefined ? next.firstNode() : other!.anchors![0]) || null; } const children = this.children; const parent = this.parentEl; const anchors = this.anchors; for (let i = 0, l = children.length; i < l; i++) { let child = children[i]; - if (child) { + if (child !== undefined) { child.moveBeforeVNode(null, afterNode); } else { const anchor = anchors![i]; @@ -83,8 +83,8 @@ export class VMulti { for (let i = 0, l = children1.length; i < l; i++) { const vn1 = children1[i]; const vn2 = children2[i]; - if (vn1) { - if (vn2) { + if (vn1 !== undefined) { + if (vn2 !== undefined) { vn1.patch(vn2, withBeforeRemove); } else { const afterNode = vn1.firstNode()!; @@ -97,7 +97,7 @@ export class VMulti { vn1.remove(); children1[i] = undefined; } - } else if (vn2) { + } else if (vn2 !== undefined) { children1[i] = vn2; const anchor = anchors[i]; vn2.mount(parentEl, anchor); @@ -110,7 +110,7 @@ export class VMulti { const children = this.children; for (let i = 0, l = children.length; i < l; i++) { const child = children[i]; - if (child) { + if (child !== undefined) { child.beforeRemove(); } } @@ -125,7 +125,7 @@ export class VMulti { const anchors = this.anchors; for (let i = 0, l = children.length; i < l; i++) { const child = children[i]; - if (child) { + if (child !== undefined) { child.remove(); } else { nodeRemoveChild.call(parentEl, anchors![i]); @@ -136,7 +136,7 @@ export class VMulti { firstNode(): Node | undefined { const child = this.children[0]; - return child ? child.firstNode() : this.anchors![0]; + return child !== undefined ? child.firstNode() : this.anchors![0]; } toString(): string { diff --git a/src/runtime/template_helpers.ts b/src/runtime/template_helpers.ts index 239e10291..5d93b1b9c 100644 --- a/src/runtime/template_helpers.ts +++ b/src/runtime/template_helpers.ts @@ -30,7 +30,7 @@ function callSlot( const slots = ctx.props.slots || {}; const { __render, __ctx, __scope } = slots[name] || {}; const slotScope = ObjectCreate(__ctx || {}); - if (__scope) { + if (__scope !== undefined) { slotScope[__scope] = extra; } const slotBDom = __render ? __render(slotScope, parent, key) : null; diff --git a/src/runtime/utils.ts b/src/runtime/utils.ts index 143e76d8e..640f41e0a 100644 --- a/src/runtime/utils.ts +++ b/src/runtime/utils.ts @@ -11,7 +11,7 @@ export type Callback = () => void; export function batched(callback: Callback): Callback { let scheduled = false; return async (...args) => { - if (!scheduled) { + if (scheduled === false) { scheduled = true; await Promise.resolve(); scheduled = false; From 4cffb23bd5fa61a0e10a93835728dd9d5c777a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Tue, 18 Jul 2023 10:36:48 +0200 Subject: [PATCH 2/2] [REF] blockdom: some small optimization --- src/runtime/blockdom/block_compiler.ts | 57 +++++++++++++++++++++----- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/src/runtime/blockdom/block_compiler.ts b/src/runtime/blockdom/block_compiler.ts index 460caef43..779e66405 100644 --- a/src/runtime/blockdom/block_compiler.ts +++ b/src/runtime/blockdom/block_compiler.ts @@ -93,6 +93,17 @@ function normalizeNode(node: HTMLElement | Text) { } } +/** + * Encode 2 numbers and 1 boolean in a number, using 31 bits: + * n1 => encoded in 16 most significant bits + * n2 => encoded in 15 next bits + * boolean => encoded in last significant bit. + * This code assumes that n1 and n2 are small enough to fit in that number of bits + */ +function encodeValue(n1: number, n2: number, b: boolean): number { + return (((n1 << 15) | n2) << 1) | (b ? 1 : 0); +} + // ----------------------------------------------------------------------------- // building a intermediate tree // ----------------------------------------------------------------------------- @@ -502,7 +513,6 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass { })); const locN = locations.length; const childN = children.length; - const childrenLocs = children; const isDynamic = refN > 0; // these values are defined here to make them faster to lookup in the class @@ -557,6 +567,19 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass { } if (isDynamic) { + const nextSibling = nodeGetNextSibling; + const firstChild = nodeGetFirstChild; + const bitPackedCollectors = new Uint32Array( + collectors.map((c) => { + return encodeValue(c.idx, c.prevIdx, c.getVal === nextSibling); + }) + ); + const childrenLocs = new Uint32Array( + children.map((c) => { + return encodeValue(c.afterRefIdx, c.parentRefIdx, Boolean(c.isOnlyChild)); + }) + ); + Block.prototype.mount = function mount(parent: HTMLElement, afterNode: Node | null) { const el = nodeCloneNode.call(template, true); // collecting references @@ -564,8 +587,13 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass { this.refs = refs; refs[0] = el; for (let i = 0; i < colN; i++) { - const w = collectors[i]; - refs[w.idx] = w.getVal.call(refs[w.prevIdx]); + let info = bitPackedCollectors[i]; + // decode info + const fn = (info & 1) === 1 ? nextSibling : firstChild; + info = info >> 1; + const prevIdx = info & 0b111111111111111; + const idx = info >> 15; + refs[idx] = fn.call(refs[prevIdx]); } // applying data to all update points @@ -585,11 +613,16 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass { for (let i = 0; i < childN; i++) { const child = children![i]; if (child !== undefined) { - const loc = childrenLocs[i]; - const afterRefIdx = loc.afterRefIdx; + let info = childrenLocs[i]; + // decode info + const isOnlyChild = info & 1; + info = info >> 1; + const parentRefIdx = info & 0b111111111111111; + const afterRefIdx = info >> 15; + const afterNode = afterRefIdx !== 0 ? refs[afterRefIdx] : null; - child.isOnlyChild = loc.isOnlyChild; - child.mount(refs[loc.parentRefIdx] as any, afterNode); + child.isOnlyChild = isOnlyChild as any; + child.mount(refs[parentRefIdx] as any, afterNode); } } } @@ -635,10 +668,14 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass { children1![i] = undefined; } } else if (child2 !== undefined) { - const loc = childrenLocs[i]; - const afterRefIdx = loc.afterRefIdx; + let info = childrenLocs[i]; + // decode info + info = info >> 1; + const parentRefIdx = info & 0b111111111111111; + const afterRefIdx = info >> 15; + const afterNode = afterRefIdx !== 0 ? refs[afterRefIdx] : null; - child2.mount(refs[loc.parentRefIdx] as any, afterNode); + child2.mount(refs[parentRefIdx] as any, afterNode); children1![i] = child2; } }