feat(native-export): scaffold local Expo module + native decode/encode bench probe (P0)
Local Kotlin module modules/recipescam-export (autolinked via expo ./modules) exposes decodeEncodeAsync — BitmapFactory decode -> JPEG q95 encode -> file, all on Dispatchers.IO off the JS/main threads. EXPO_PUBLIC_BENCH=1 builds run src/dev/nativeBenchProbe.ts: 3 timed decode+encode passes on the bundled wallframe artwork plus a JS-timer gap check proving the JS thread never blocks during native rendering. Legacy Skia engine untouched (reference).
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { Asset } from 'expo-asset';
|
||||
import * as FileSystem from 'expo-file-system/legacy';
|
||||
import RecipescamExport from '../../modules/recipescam-export';
|
||||
|
||||
const RUNS = 3;
|
||||
|
||||
/**
|
||||
* P0 spike harness (temporary — removed at P7). Runs native decode+encode on
|
||||
* the bundled wallframe artwork (3117x4000, representative of a 12MP export)
|
||||
* and logs per-stage ms plus a JS-timer gap check proving the JS thread is
|
||||
* not blocked while native work runs. Gated by EXPO_PUBLIC_BENCH=1.
|
||||
*/
|
||||
export async function nativeBenchProbe(): Promise<void> {
|
||||
console.log('[BENCH] start');
|
||||
try {
|
||||
const asset = Asset.fromModule(require('../../wallframe.png'));
|
||||
await asset.downloadAsync();
|
||||
if (!asset.localUri) {
|
||||
console.error('[BENCH] wallframe asset has no localUri');
|
||||
return;
|
||||
}
|
||||
const src = asset.localUri.replace(/^file:\/\//, '');
|
||||
console.log('[BENCH] src=' + src);
|
||||
|
||||
for (let i = 0; i < RUNS; i++) {
|
||||
const dst = `${FileSystem.cacheDirectory}bench-dst-${Date.now()}-${i}.jpg`;
|
||||
const gaps: number[] = [];
|
||||
let last = Date.now();
|
||||
// 5ms JS timer while native work runs: a blocked JS thread would stretch gaps.
|
||||
const iv = setInterval(() => {
|
||||
const now = Date.now();
|
||||
gaps.push(now - last);
|
||||
last = now;
|
||||
}, 5);
|
||||
|
||||
const t0 = Date.now();
|
||||
const timings = await RecipescamExport.decodeEncodeAsync(src, dst, 95);
|
||||
const jsElapsed = Date.now() - t0;
|
||||
clearInterval(iv);
|
||||
|
||||
const sorted = [...gaps].sort((a, b) => b - a);
|
||||
console.log(
|
||||
'[BENCH] run=' + i +
|
||||
' jsElapsed=' + jsElapsed + 'ms' +
|
||||
' decodeMs=' + timings.decodeMs +
|
||||
' encodeWriteMs=' + timings.encodeWriteMs +
|
||||
' totalMs=' + timings.totalMs +
|
||||
' jsGapMax=' + (sorted[0] ?? 0) + 'ms' +
|
||||
' jsGapP95=' + (sorted[Math.floor(sorted.length * 0.05)] ?? 0) + 'ms' +
|
||||
' dst=' + dst
|
||||
);
|
||||
}
|
||||
console.log('[BENCH] done');
|
||||
} catch (e) {
|
||||
console.error('[BENCH] failed', e);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user