Updated perf
This commit is contained in:
233
spikes/card-harness/scripts/pack-performance.test.mjs
Normal file
233
spikes/card-harness/scripts/pack-performance.test.mjs
Normal file
@@ -0,0 +1,233 @@
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import * as THREE from 'three'
|
||||
import { installDOM, TestCanvas, geometryHash, wrapperCases } from './test-support.mjs'
|
||||
|
||||
const { createFoilWrapper } = await import('../src/foilWrapper.ts')
|
||||
const { PackOpening } = await import('../src/packOpening.ts')
|
||||
|
||||
// Captured from the pre-optimization implementation, including normals, UVs and bounds.
|
||||
const originalHashes = [
|
||||
'f20984ff506a2b6ac557e30592b4d3b990efdd5e9f335a2655fe78567e240d1a',
|
||||
'2ae0cf1d2ba9fcc29dde5cd6c5cca0862e22466ed5a8c4257a3d0d0075b9d500',
|
||||
'f8c912718e192139b6d33843c2c5527e765372f339897033c864909929b59bbe',
|
||||
'1f70db18f24f8a629a0765e21cb1d73f377ffb816e2e7893edaabe9616053441',
|
||||
'64e019c59fa6fe120ac044a01b2ba852ce09e86cd4abe6d286bb18d456bd8700',
|
||||
'140e2ad4fdc339d671a8b19da32c89b74c98e785b05250f4a4c491edf23a460a',
|
||||
'cacbbd787ff9f47acddf0227d31fe8051adf06d6e401a0f53a69a7d81c3c12fc',
|
||||
'7a76c0eeea7cf22a3f6d888c43df76a2dcdeaad418d91c9cee5e5102a09ebf49',
|
||||
'e5625218a4d89f58bb8a935d30fa6aee80613b41868bad334079fca6da33e397',
|
||||
'f20984ff506a2b6ac557e30592b4d3b990efdd5e9f335a2655fe78567e240d1a',
|
||||
'a6f746d8d729cf77068a05a24269024cd30c1a807828b907c559c140645a3b86',
|
||||
'91fd70b90a7a40976ccfe9ad6727af5f12db6e8f20ac414503f5fe10aaee4d8b',
|
||||
]
|
||||
|
||||
test('wrapper geometry exactly matches the original across tear, dent, mouth and restart poses', () => {
|
||||
installDOM()
|
||||
const wrapper = createFoilWrapper()
|
||||
wrapperCases.forEach(([tear, detach, mouth, touch, exit], index) => {
|
||||
wrapper.deform(tear, detach, mouth, new THREE.Vector3(...touch), exit)
|
||||
assert.equal(geometryHash(wrapper), originalHashes[index], `pose ${index}`)
|
||||
})
|
||||
})
|
||||
|
||||
test('only changed surfaces recompute normals, bounds and upload buffers', (t) => {
|
||||
installDOM()
|
||||
const wrapper = createFoilWrapper()
|
||||
const meshes = wrapper.root.children
|
||||
const normalSpies = meshes.map((mesh) => t.mock.method(mesh.geometry, 'computeVertexNormals'))
|
||||
const boundsSpies = meshes.map((mesh) => t.mock.method(mesh.geometry, 'computeBoundingSphere'))
|
||||
const versions = () => meshes.map((mesh) => [
|
||||
mesh.geometry.getAttribute('position').version, mesh.geometry.getAttribute('normal').version,
|
||||
])
|
||||
const before = versions()
|
||||
const touch = new THREE.Vector3()
|
||||
wrapper.deform(0, 0, 0, touch)
|
||||
assert.deepEqual(versions(), before)
|
||||
assert.deepEqual(normalSpies.map((spy) => spy.mock.callCount()), [0, 0, 0])
|
||||
|
||||
wrapper.deform(0.5, 0, 0, touch)
|
||||
assert.deepEqual(normalSpies.map((spy) => spy.mock.callCount()), [0, 0, 1])
|
||||
assert.deepEqual(versions().slice(0, 2), before.slice(0, 2))
|
||||
wrapper.deform(0.5, 0, 0, touch.set(0.4, 1.7, 0.5))
|
||||
assert.deepEqual(normalSpies.map((spy) => spy.mock.callCount()), [1, 0, 1])
|
||||
wrapper.deform(0.5, 0, 0.5, touch)
|
||||
assert.deepEqual(normalSpies.map((spy) => spy.mock.callCount()), [2, 1, 1])
|
||||
assert.deepEqual(boundsSpies.map((spy) => spy.mock.callCount()), [2, 1, 1])
|
||||
})
|
||||
|
||||
function setupPack(t, reducedMotion = false) {
|
||||
installDOM(reducedMotion)
|
||||
let now = 1000
|
||||
t.mock.method(performance, 'now', () => now)
|
||||
const canvas = new TestCanvas()
|
||||
const pack = new PackOpening({
|
||||
canvas,
|
||||
textures: {
|
||||
David: { artwork: new THREE.Texture(), mask: new THREE.Texture() },
|
||||
Timothy: { artwork: new THREE.Texture(), mask: new THREE.Texture() },
|
||||
},
|
||||
backMaterial: new THREE.MeshPhysicalMaterial(),
|
||||
normalMap: new THREE.Texture(),
|
||||
environment: new THREE.CubeTexture(),
|
||||
lightPosition: new THREE.Vector3(2.2, 2.5, 4.4),
|
||||
onChange() {},
|
||||
})
|
||||
pack.resize(canvas.width, canvas.height)
|
||||
pack.setActive(true)
|
||||
pack.tick(now)
|
||||
const deform = t.mock.method(pack.wrapper, 'deform')
|
||||
return {
|
||||
pack, canvas, deform,
|
||||
step(ms = 16) { now += ms; pack.tick(now) },
|
||||
}
|
||||
}
|
||||
|
||||
function pointer(id, x, y) {
|
||||
return { pointerId: id, clientX: x, clientY: y, button: 0 }
|
||||
}
|
||||
|
||||
test('a burst of tear events and the touch dent deform at most once per rendered frame', (t) => {
|
||||
const { pack, deform, step } = setupPack(t)
|
||||
const seam = pack.seamScreenBounds()
|
||||
const start = pointer(1, seam.left + 10, seam.y)
|
||||
pack.pointerDown(start)
|
||||
for (let offset = 10; offset <= 100; offset += 10) {
|
||||
pack.pointerMove(pointer(1, start.clientX + offset, start.clientY))
|
||||
}
|
||||
const progress = pack.tearProgress
|
||||
assert.ok(progress > 0 && progress < 1)
|
||||
assert.equal(deform.mock.callCount(), 0)
|
||||
step()
|
||||
assert.equal(deform.mock.callCount(), 1)
|
||||
assert.equal(deform.mock.calls[0].arguments[0], progress)
|
||||
assert.ok(deform.mock.calls[0].arguments[3].z > 0)
|
||||
pack.pointerMove(pointer(1, start.clientX + 20, start.clientY))
|
||||
assert.equal(pack.tearProgress, progress, 'pulling backward must not reseal')
|
||||
pack.pointerUp(pointer(1, start.clientX + 20, start.clientY), true)
|
||||
step()
|
||||
assert.equal(pack.tearProgress, progress)
|
||||
assert.equal(pack.paused, true)
|
||||
pack.primary()
|
||||
step(3200)
|
||||
assert.equal(pack.state, 'stackReady')
|
||||
})
|
||||
|
||||
test('body dragging batches dent changes without updating the back or ribbon', (t) => {
|
||||
const { pack, deform, step } = setupPack(t)
|
||||
pack.pointerDown(pointer(1, 195, 368))
|
||||
for (let x = 200; x <= 220; x += 5) pack.pointerMove(pointer(1, x, 368))
|
||||
assert.equal(deform.mock.callCount(), 0)
|
||||
const [front, back, strip] = pack.wrapper.root.children
|
||||
const backVersion = back.geometry.getAttribute('position').version
|
||||
const stripVersion = strip.geometry.getAttribute('position').version
|
||||
const frontVersion = front.geometry.getAttribute('position').version
|
||||
step()
|
||||
assert.equal(deform.mock.callCount(), 1)
|
||||
assert.ok(front.geometry.getAttribute('position').version > frontVersion)
|
||||
assert.equal(back.geometry.getAttribute('position').version, backVersion)
|
||||
assert.equal(strip.geometry.getAttribute('position').version, stripVersion)
|
||||
})
|
||||
|
||||
test('interruption and mode changes preserve progress without deforming in pointer handlers', (t) => {
|
||||
const { pack, deform, step } = setupPack(t)
|
||||
pack.primary()
|
||||
step(600)
|
||||
const calls = deform.mock.callCount()
|
||||
const versions = pack.wrapper.root.children.map((mesh) => mesh.geometry.getAttribute('position').version)
|
||||
pack.pointerDown(pointer(1, 0, 0))
|
||||
const progress = pack.tearProgress
|
||||
assert.equal(deform.mock.callCount(), calls)
|
||||
assert.equal(pack.paused, true)
|
||||
pack.pointerUp(pointer(1, 0, 0))
|
||||
step(600)
|
||||
assert.equal(pack.tearProgress, progress)
|
||||
assert.deepEqual(
|
||||
pack.wrapper.root.children.map((mesh) => mesh.geometry.getAttribute('position').version), versions,
|
||||
)
|
||||
const pausedCalls = deform.mock.callCount()
|
||||
step()
|
||||
assert.equal(deform.mock.callCount(), pausedCalls)
|
||||
pack.setActive(false)
|
||||
step(500)
|
||||
pack.setActive(true)
|
||||
assert.equal(pack.tearProgress, progress)
|
||||
assert.equal(pack.view.action, 'Continue')
|
||||
pack.primary()
|
||||
step(3200)
|
||||
assert.equal(pack.state, 'stackReady')
|
||||
})
|
||||
|
||||
test('a second finger cancels seam dragging for pinch without losing the partial tear', (t) => {
|
||||
const { pack, canvas, step } = setupPack(t)
|
||||
const seam = pack.seamScreenBounds()
|
||||
const start = pointer(1, seam.left + 10, seam.y)
|
||||
pack.pointerDown(start)
|
||||
pack.pointerMove(pointer(1, start.clientX + 70, seam.y))
|
||||
step()
|
||||
const progress = pack.tearProgress
|
||||
pack.pointerDown(pointer(2, start.clientX + 170, seam.y))
|
||||
pack.pointerMove(pointer(2, start.clientX + 200, seam.y))
|
||||
assert.equal(pack.tearProgress, progress)
|
||||
assert.ok(pack.zoom < 1)
|
||||
pack.restart()
|
||||
step()
|
||||
assert.equal(canvas.captures.size, 0)
|
||||
assert.equal(pack.tearProgress, 0)
|
||||
assert.equal(geometryHash(pack.wrapper), originalHashes[0])
|
||||
})
|
||||
|
||||
test('full-pull release continues opening and hidden wrappers do no geometry work', (t) => {
|
||||
const { pack, deform, step } = setupPack(t)
|
||||
const seam = pack.seamScreenBounds()
|
||||
pack.pointerDown(pointer(1, seam.left + 10, seam.y))
|
||||
pack.pointerMove(pointer(1, seam.right + 10, seam.y))
|
||||
pack.pointerUp(pointer(1, seam.right + 10, seam.y))
|
||||
assert.equal(pack.tearProgress, 1)
|
||||
step(3200)
|
||||
assert.equal(pack.state, 'stackReady')
|
||||
assert.equal(pack.wrapper.root.visible, false)
|
||||
assert.equal(deform.mock.callCount(), 0)
|
||||
pack.restart()
|
||||
step()
|
||||
assert.equal(pack.wrapper.root.visible, true)
|
||||
assert.equal(geometryHash(pack.wrapper), originalHashes[0])
|
||||
})
|
||||
|
||||
test('skip retains ordered lift/reveal states and emits each reveal and completion once', (t) => {
|
||||
const { pack, canvas, step } = setupPack(t)
|
||||
const reveals = []
|
||||
let completions = 0
|
||||
canvas.addEventListener('packreveal', (event) => reveals.push(event.detail.index))
|
||||
canvas.addEventListener('packcomplete', () => completions++)
|
||||
pack.skip()
|
||||
step()
|
||||
for (let index = 1; index <= 3; index++) {
|
||||
assert.equal(pack.state, 'stackReady')
|
||||
assert.equal(pack.cards.some((card) => card.front.visible), false)
|
||||
pack.skip()
|
||||
assert.equal(pack.state, 'lifted')
|
||||
assert.equal(pack.cards.some((card) => card.front.visible), false)
|
||||
pack.skip()
|
||||
assert.equal(pack.state, 'inspecting')
|
||||
step()
|
||||
pack.skip()
|
||||
}
|
||||
assert.equal(pack.state, 'complete')
|
||||
pack.skip()
|
||||
assert.deepEqual(reveals, [1, 2, 3])
|
||||
assert.equal(completions, 1)
|
||||
pack.restart()
|
||||
step()
|
||||
assert.equal(pack.state, 'sealed')
|
||||
assert.equal(geometryHash(pack.wrapper), originalHashes[0])
|
||||
})
|
||||
|
||||
test('reduced-motion opening keeps the existing 70 ms transition duration', (t) => {
|
||||
const { pack, step } = setupPack(t, true)
|
||||
pack.primary()
|
||||
step(69)
|
||||
assert.equal(pack.state, 'opening')
|
||||
step(1)
|
||||
assert.equal(pack.state, 'stackReady')
|
||||
})
|
||||
Reference in New Issue
Block a user