import { performance } from 'node:perf_hooks' import { evaluateLogic, registerDefaultPredicates, } from 'imhotep-solver' import { adaptCanonicalWorldToSolver } from 'imhotep-core' registerDefaultPredicates() function buildWorld(n) { const canonical = { sceneId: 's', snapshotId: 'sn', env: { viewportWidth: 1280, viewportHeight: 800, deviceScaleFactor: 1, colorScheme: 'light', pointer: 'fine', hover: false, reducedMotion: false, locale: 'en', writingMode: 'horizontal-tb', }, strings: { values: [] }, subjects: { ids: new Uint32Array(Array.from({length: n}, (_, i) => i)), domNodeId: new Uint32Array(Array.from({length: n}, (_, i) => i + 10)), subjectKind: new Uint8Array(Array.from({length: n}, () => 1)), primaryBoxId: new Uint32Array(Array.from({length: n}, (_, i) => i)), firstFragmentId: new Uint32Array(Array.from({length: n}, () => 0)), fragmentCount: new Uint8Array(Array.from({length: n}, () => 1)), }, boxes: { boxId: new Uint32Array(Array.from({length: n}, (_, i) => i)), subjectId: new Uint32Array(Array.from({length: n}, (_, i) => i)), frameId: new Uint32Array(Array.from({length: n}, () => 0)), borderLeft: new Float64Array(Array.from({length: n}, (_, i) => (i % 10) * 60)), borderTop: new Float64Array(Array.from({length: n}, (_, i) => Math.floor(i / 10) * 60)), borderRight: new Float64Array(Array.from({length: n}, (_, i) => (i % 10) * 60 + 50)), borderBottom: new Float64Array(Array.from({length: n}, (_, i) => Math.floor(i / 10) * 60 + 50)), paddingLeft: new Float64Array(Array.from({length: n}, () => 0)), paddingTop: new Float64Array(Array.from({length: n}, () => 0)), paddingRight: new Float64Array(Array.from({length: n}, () => 0)), paddingBottom: new Float64Array(Array.from({length: n}, () => 0)), contentLeft: new Float64Array(Array.from({length: n}, (_, i) => (i % 10) * 60)), contentTop: new Float64Array(Array.from({length: n}, (_, i) => Math.floor(i / 10) * 60)), contentRight: new Float64Array(Array.from({length: n}, (_, i) => (i % 10) * 60 + 50)), contentBottom: new Float64Array(Array.from({length: n}, (_, i) => Math.floor(i / 10) * 60 + 50)), }, visualBoxes: { boxId: new Uint32Array(Array.from({length: n}, (_, i) => i)), subjectId: new Uint32Array(Array.from({length: n}, (_, i) => i)), frameId: new Uint32Array(Array.from({length: n}, () => 0)), borderLeft: new Float64Array(Array.from({length: n}, (_, i) => (i % 10) * 60)), borderTop: new Float64Array(Array.from({length: n}, (_, i) => Math.floor(i / 10) * 60)), borderRight: new Float64Array(Array.from({length: n}, (_, i) => (i % 10) * 60 + 50)), borderBottom: new Float64Array(Array.from({length: n}, (_, i) => Math.floor(i / 10) * 60 + 50)), paddingLeft: new Float64Array(Array.from({length: n}, () => 0)), paddingTop: new Float64Array(Array.from({length: n}, () => 0)), paddingRight: new Float64Array(Array.from({length: n}, () => 0)), paddingBottom: new Float64Array(Array.from({length: n}, () => 0)), contentLeft: new Float64Array(Array.from({length: n}, (_, i) => (i % 10) * 60)), contentTop: new Float64Array(Array.from({length: n}, (_, i) => Math.floor(i / 10) * 60)), contentRight: new Float64Array(Array.from({length: n}, (_, i) => (i % 10) * 60 + 50)), contentBottom: new Float64Array(Array.from({length: n}, (_, i) => Math.floor(i / 10) * 60 + 50)), }, dom: { nodeId: new Uint32Array(Array.from({length: n}, (_, i) => i + 10)), parentNodeId: new Uint32Array(Array.from({length: n}, () => 1)), childCount: new Uint8Array(Array.from({length: n}, () => 0)), tagNameStringId: new Uint16Array(Array.from({length: n}, () => 0)), }, transforms: { transformId: new Uint32Array(0), subjectId: new Uint32Array(0), matrixStart: new Uint32Array(0), matrixLength: new Uint32Array(0), originX: new Float64Array(0), originY: new Float64Array(0) }, matrices: { values: new Float64Array(0) }, rects: { rectId: new Uint32Array(0), left: new Float64Array(0), top: new Float64Array(0), right: new Float64Array(0), bottom: new Float64Array(0) }, topology: { containingBlockOf: new Uint32Array(Array.from({length: n}, () => 0)), nearestPositionedAncestorOf: new Uint32Array(Array.from({length: n}, () => 0)), scrollContainerOf: new Uint32Array(Array.from({length: n}, () => 0)), stackingContextOf: new Uint32Array(Array.from({length: n}, () => 0)), formattingContextOf: new Uint32Array(Array.from({length: n}, () => 0)), clippingRootOf: new Uint32Array(Array.from({length: n}, () => 0)), paintOrderBucket: new Uint8Array(Array.from({length: n}, () => 0)), paintOrderIndex: new Uint32Array(Array.from({length: n}, (_, i) => i)), }, scroll: { containerId: new Uint32Array(0), scrollLeft: new Float64Array(0), scrollTop: new Float64Array(0), scrollWidth: new Float64Array(0), scrollHeight: new Float64Array(0), clientWidth: new Float64Array(0), clientHeight: new Float64Array(0) }, clipping: { clipNodeId: new Uint32Array(0), subjectId: new Uint32Array(0), clipKind: new Uint16Array(0), clipLeft: new Float64Array(0), clipTop: new Float64Array(0), clipRight: new Float64Array(0), clipBottom: new Float64Array(0), parentClipNodeId: new Uint32Array(0) }, visibility: { subjectId: new Uint32Array(0), isRendered: new Uint8Array(0), isVisible: new Uint8Array(0), visibleArea: new Float64Array(0), clippedArea: new Float64Array(0) }, } return adaptCanonicalWorldToSolver(canonical) } class SimpleResolver { constructor() { this.domains = new Map() } register(selector, ids) { this.domains.set(selector, { domainId: `dom_${selector}`, subjectIds: new Uint32Array(ids), provenance: `elements(${selector})`, closed: true, }) } resolve(domain) { return this.domains.get(domain.selector ?? domain.domain) } } console.log('=== Evaluation With/Without Trace ===\n') for (const n of [10, 50, 100]) { const world = buildWorld(n) const resolver = new SimpleResolver() resolver.register('.a', Array.from({length: n}, (_, i) => i)) resolver.register('.b', Array.from({length: n}, (_, i) => i)) const formula = { type: 'FormulaNode', kind: 'forall', bindings: [{ type: 'TupleBinding', variables: ['$subject'], domain: { type: 'DomainRef', domain: 'elements', selector: '.a' } }], body: { type: 'FormulaNode', kind: 'forall', bindings: [{ type: 'TupleBinding', variables: ['$reference'], domain: { type: 'DomainRef', domain: 'elements', selector: '.b' } }], body: { type: 'FormulaNode', kind: 'predicate', predicate: 'leftOf', args: [ { type: 'VariableRef', name: '$subject' }, { type: 'VariableRef', name: '$reference' } ] } } } const timesWithTrace = [] const timesWithoutTrace = [] for (let i = 0; i < 100; i++) { const start1 = performance.now() evaluateLogic({ formula, world, resolver, options: { trace: true } }) timesWithTrace.push(performance.now() - start1) const start2 = performance.now() evaluateLogic({ formula, world, resolver, options: { trace: false } }) timesWithoutTrace.push(performance.now() - start2) } const mean = (arr) => arr.reduce((a,b) => a+b, 0) / arr.length const withTrace = mean(timesWithTrace) const withoutTrace = mean(timesWithoutTrace) console.log(`${n}×${n} pairs:`) console.log(` With trace: ${withTrace.toFixed(2)}ms`) console.log(` Without trace: ${withoutTrace.toFixed(2)}ms`) console.log(` Savings: ${((1 - withoutTrace/withTrace) * 100).toFixed(0)}%`) }