import { html } from 'dhtml' import { invalidate } from 'dhtml/client' import { bench } from '../../../scripts/test/test.ts' import { setup } from './setup.ts' // ============================== // Data Structures // ============================== class TableState { items: TableItemState[] constructor(rows: number, cols: number) { this.items = [] for (let i = 0; i < rows; i++) { const props: string[] = [] for (let j = 0; j < cols; j++) { props.push(`${i}:${j}`) } this.items.push(new TableItemState(i, false, props)) } } remove_all() { this.items = [] } sort_by_column(col: number) { this.items.sort((a, b) => a.props[col].localeCompare(b.props[col])) } filter(nth: number) { this.items = this.items.filter((_, i) => (i + 1) % nth !== 0) } async activate(nth: number) { for (let i = 0; i < this.items.length; i++) { this.items[i].active = (i + 1) % nth === 0 } await invalidate(...this.items) } render() { return html` ${this.items}
` } } class TableItemState { id: number active: boolean props: string[] constructor(id: number, active: boolean, props: string[]) { this.id = id this.active = active this.props = props } render() { function cell(text: string) { return html` { console.log('Clicked' + text) e.stopPropagation() }} > ${text} ` } return html` ${cell('#' + this.id)} ${this.props.map(prop => cell(prop))} ` } } // ============================== // Animation Data Structures // ============================== class AnimState { items: AnimBoxState[] constructor(count: number) { this.items = [] for (let i = 0; i < count; i++) { this.items.push(new AnimBoxState(i, 0)) } } async advance_each(nth: number) { const renderables: AnimBoxState[] = [] for (let i = 0; i < this.items.length; i++) { if ((i + 1) % nth === 0) { this.items[i].time++ renderables.push(this.items[i]) } } await invalidate(...renderables) } render() { return html`
${this.items}
` } } class AnimBoxState { id: number time: number constructor(id: number, time: number) { this.id = id this.time = time } render() { return html`
` } } // ============================== // Tree Data Structures // ============================== class TreeState { root: TreeNodeState constructor(hierarchy: number[]) { let id_counter = 0 function create_node(depth: number, max_depth: number): TreeNodeState { const id = id_counter++ if (depth === max_depth) { return new TreeNodeState(id, false, null) } const children: TreeNodeState[] = [] const child_count = hierarchy[depth] for (let i = 0; i < child_count; i++) { children.push(create_node(depth + 1, max_depth)) } return new TreeNodeState(id, true, children) } this.root = create_node(0, hierarchy.length - 1) } remove_all() { this.root.children = [] } async reverse() { const renderables: TreeNodeState[] = [] const reverse_children = (node: TreeNodeState) => { if (node.container && node.children) { node.children.reverse() for (const child of node.children) { if (child.container) { reverse_children(child) } } renderables.push(node) } } reverse_children(this.root) await invalidate(...renderables) } async insert_first(n: number) { const renderables: TreeNodeState[] = [] function insert_at_containers(node: TreeNodeState, id_counter: { value: number }) { if (node.container && node.children) { const new_nodes: TreeNodeState[] = [] for (let i = 0; i < n; i++) { new_nodes.push(new TreeNodeState(id_counter.value++, false, null)) } node.children.unshift(...new_nodes) for (const child of node.children) { if (child.container) { insert_at_containers(child, id_counter) } } renderables.push(node) } } // Find the highest ID to start creating new IDs from let max_id = 0 const find_max_id = (node: TreeNodeState) => { max_id = Math.max(max_id, node.id) if (node.container && node.children) { for (const child of node.children) { find_max_id(child) } } } find_max_id(this.root) const id_counter = { value: max_id + 1 } insert_at_containers(this.root, id_counter) await invalidate(...renderables) } async insert_last(n: number) { const renderables: TreeNodeState[] = [] function insert_at_containers(node: TreeNodeState, id_counter: { value: number }) { if (node.container && node.children) { const new_nodes: TreeNodeState[] = [] for (let i = 0; i < n; i++) { new_nodes.push(new TreeNodeState(id_counter.value++, false, null)) } node.children.push(...new_nodes) for (const child of node.children) { if (child.container) { insert_at_containers(child, id_counter) } } renderables.push(node) } } // Find the highest ID to start creating new IDs from let max_id = 0 const find_max_id = (node: TreeNodeState) => { max_id = Math.max(max_id, node.id) if (node.container && node.children) { for (const child of node.children) { find_max_id(child) } } } find_max_id(this.root) const id_counter = { value: max_id + 1 } insert_at_containers(this.root, id_counter) await invalidate(...renderables) } async remove_first(n: number) { const renderables: TreeNodeState[] = [] const remove_from_containers = (node: TreeNodeState) => { if (node.container && node.children) { node.children.splice(0, Math.min(n, node.children.length)) for (const child of node.children) { if (child.container) { remove_from_containers(child) } } renderables.push(node) } } remove_from_containers(this.root) await invalidate(...renderables) } async remove_last(n: number) { const renderables: TreeNodeState[] = [] const remove_from_containers = (node: TreeNodeState) => { if (node.container && node.children) { const length = node.children.length node.children.splice(Math.max(0, length - n), Math.min(n, length)) for (const child of node.children) { if (child.container) { remove_from_containers(child) } } renderables.push(node) } } remove_from_containers(this.root) await invalidate(...renderables) } async move_from_end_to_start(n: number) { const renderables: TreeNodeState[] = [] const move_in_containers = (node: TreeNodeState) => { if (node.container && node.children && node.children.length > n) { const length = node.children.length const moved = node.children.splice(length - n, n) node.children.unshift(...moved) for (const child of node.children) { if (child.container) { move_in_containers(child) } } renderables.push(node) } } move_in_containers(this.root) await invalidate(...renderables) } async move_from_start_to_end(n: number) { const renderables: TreeNodeState[] = [] const move_in_containers = (node: TreeNodeState) => { if (node.container && node.children && node.children.length > n) { const moved = node.children.splice(0, n) node.children.push(...moved) for (const child of node.children) { if (child.container) { move_in_containers(child) } } renderables.push(node) } } move_in_containers(this.root) await invalidate(...renderables) } // Worst case scenarios async kivi_worst_case() { await this.remove_first(1) await this.remove_last(1) await this.reverse() } async snabbdom_worst_case() { const renderables: TreeNodeState[] = [] const transform = (node: TreeNodeState) => { if (node.container && node.children && node.children.length > 2) { const first = node.children.shift() if (first) { const secondToLast = node.children.splice(node.children.length - 2, 1)[0] node.children.push(first, secondToLast) } for (const child of node.children) { if (child.container) { transform(child) } } renderables.push(node) } } transform(this.root) await invalidate(...renderables) } async react_worst_case() { await this.remove_first(1) await this.remove_last(1) await this.move_from_end_to_start(1) } async virtual_dom_worst_case() { await this.move_from_start_to_end(2) } render() { return html`
${this.root}
` } } class TreeNodeState { id: number container: boolean children: TreeNodeState[] | null constructor(id: number, container: boolean, children: TreeNodeState[] | null) { this.id = id this.container = container this.children = children } render() { if (!this.container) { return html`
  • ${this.id}
  • ` } return html` ` } } // ============================== // Benchmark Cases // ============================== function bench_setup(name: string, fn: (root: ReturnType['root']) => void | Promise): void { bench(name, async () => { const { root, el } = setup() try { await fn(root) } finally { root.render(null) el.remove() } }) } // Table Benchmark Cases bench_setup('table/small/render', async root => { const state = new TableState(15, 4) root.render(state) }) bench_setup('table/small/removeAll', async root => { const state = new TableState(15, 4) root.render(state) state.remove_all() await invalidate(state) }) bench_setup('table/small/sort', async root => { const state = new TableState(15, 4) root.render(state) state.sort_by_column(1) await invalidate(state) }) bench_setup('table/small/filter', async root => { const state = new TableState(15, 4) root.render(state) state.filter(4) await invalidate(state) }) bench_setup('table/small/activate', async root => { const state = new TableState(15, 4) root.render(state) await state.activate(4) }) bench_setup('table/large/render', async root => { const state = new TableState(100, 4) root.render(state) }) bench_setup('table/large/removeAll', async root => { const state = new TableState(100, 4) root.render(state) state.remove_all() await invalidate(state) }) bench_setup('table/large/sort', async root => { const state = new TableState(100, 4) root.render(state) state.sort_by_column(1) await invalidate(state) }) bench_setup('table/large/filter', async root => { const state = new TableState(100, 4) root.render(state) state.filter(16) await invalidate(state) }) bench_setup('table/large/activate', async root => { const state = new TableState(100, 4) root.render(state) await state.activate(16) }) // Animation Benchmark Cases bench_setup('anim/small/advance', async root => { const state = new AnimState(30) root.render(state) await state.advance_each(4) }) bench_setup('anim/large/advance', async root => { const state = new AnimState(100) root.render(state) await state.advance_each(16) }) // Tree Benchmark Cases - Small bench_setup('tree/small/render', async root => { const state = new TreeState([5, 10]) root.render(state) }) bench_setup('tree/small/removeAll', async root => { const state = new TreeState([5, 10]) root.render(state) state.remove_all() await invalidate(state) }) bench_setup('tree/small/reverse', async root => { const state = new TreeState([5, 10]) root.render(state) await state.reverse() }) bench_setup('tree/small/insertFirst', async root => { const state = new TreeState([5, 10]) root.render(state) await state.insert_first(2) }) bench_setup('tree/small/insertLast', async root => { const state = new TreeState([5, 10]) root.render(state) await state.insert_last(2) }) bench_setup('tree/small/removeFirst', async root => { const state = new TreeState([5, 10]) root.render(state) await state.remove_first(2) }) bench_setup('tree/small/removeLast', async root => { const state = new TreeState([5, 10]) root.render(state) await state.remove_last(2) }) bench_setup('tree/small/moveFromEndToStart', async root => { const state = new TreeState([5, 10]) root.render(state) await state.move_from_end_to_start(2) }) bench_setup('tree/small/moveFromStartToEnd', async root => { const state = new TreeState([5, 10]) root.render(state) await state.move_from_start_to_end(2) }) bench_setup('tree/small/no_change', async root => { const state = new TreeState([5, 10]) root.render(state) await invalidate(state) }) // Tree Benchmark Cases - Large bench_setup('tree/large/render', async root => { const state = new TreeState([50, 10]) root.render(state) }) bench_setup('tree/large/removeAll', async root => { const state = new TreeState([50, 10]) root.render(state) state.remove_all() await invalidate(state) }) bench_setup('tree/large/reverse', async root => { const state = new TreeState([50, 10]) root.render(state) await state.reverse() }) // Worst Case Scenarios bench_setup('tree/worst_case/kivi', async root => { const state = new TreeState([10, 10]) root.render(state) await state.kivi_worst_case() }) bench_setup('tree/worst_case/snabbdom', async root => { const state = new TreeState([10, 10]) root.render(state) await state.snabbdom_worst_case() }) bench_setup('tree/worst_case/react', async root => { const state = new TreeState([10, 10]) root.render(state) await state.react_worst_case() }) bench_setup('tree/worst_case/virtual_dom', async root => { const state = new TreeState([10, 10]) root.render(state) await state.virtual_dom_worst_case() })