import { ref, shallowRef, computed } from 'vue' import type { Query } from '../lib/dump-store' import type { TreeChild } from '../lib/dump-store' export interface RowView { index: number address: number type: string typeId: number path: string name: string container: string outer: number offset: number } let worker: Worker | null = null let seq = 0 const pending = new Map void; reject: (e: any) => void }>() const loading = ref(false) const progress = ref(0) const error = ref('') const label = ref('') const dumpKey = ref('') const count = ref(0) const skipped = ref(0) const types = shallowRef([]) const typeCounts = shallowRef(new Int32Array(0)) const sharedAddressDigits = ref(0) const total = ref(0) const queryMs = ref(0) const parseMs = ref(0) function ensureWorker(): Worker { if (worker) return worker worker = new Worker(new URL('../workers/dump.worker.ts', import.meta.url), { type: 'module' }) worker.onmessage = (e) => { const { id, result, error: err, event, fraction } = e.data if (event === 'progress') { progress.value = fraction return } const p = pending.get(id) if (!p) return pending.delete(id) err ? p.reject(new Error(err)) : p.resolve(result) } return worker } function call(op: string, payload: Record = {}, transfer: Transferable[] = []): Promise { const w = ensureWorker() const id = ++seq return new Promise((resolve, reject) => { pending.set(id, { resolve, reject }) w.postMessage({ id, op, ...payload }, transfer) }) } export function useDump() { async function loadFile(file: File) { loading.value = true error.value = '' progress.value = 0 try { const buffer = await file.arrayBuffer() // Transferred, not copied: a 60 MB structured clone is a visible stall. const r = await call('parse', { buffer, name: file.name }, [buffer]) applyMeta(r) } catch (e) { error.value = (e as Error).message } finally { loading.value = false progress.value = 0 } } async function restore(key: string) { loading.value = true error.value = '' try { applyMeta(await call('restore', { key })) } catch (e) { error.value = (e as Error).message } finally { loading.value = false } } /** Unload the current dump without deleting it from the cache. */ async function reset() { await call('close').catch(() => {}) loading.value = false error.value = '' progress.value = 0 label.value = '' dumpKey.value = '' types.value = [] typeCounts.value = new Int32Array(0) skipped.value = 0 sharedAddressDigits.value = 0 total.value = 0 queryMs.value = 0 parseMs.value = 0 // Last: `ready` and the app's reset watcher both hang off this. count.value = 0 } function applyMeta(r: any) { count.value = r.count types.value = r.types skipped.value = r.skipped sharedAddressDigits.value = r.sharedAddressDigits label.value = r.label dumpKey.value = r.key parseMs.value = r.parseMs ?? 0 } async function runQuery(q: Query) { const plain: Query = { ...q, typeIds: q.typeIds ? Array.from(q.typeIds) : undefined, } const r = await call('query', { query: plain }) total.value = r.total typeCounts.value = r.typeCounts queryMs.value = r.ms return r } const fetchWindow = (start: number, end: number) => call<{ start: number; rows: RowView[] }>('window', { start, end }) const fetchChildren = (prefix: string) => call<{ prefix: string; children: TreeChild[] }>('children', { prefix }) const resolveAddress = (address: number) => call<{ row: RowView | null }>('resolve', { address }) return { loadFile, restore, reset, runQuery, fetchWindow, fetchChildren, resolveAddress, loading, progress, error, label, dumpKey, count, skipped, types, typeCounts, sharedAddressDigits, total, queryMs, parseMs, ready: computed(() => count.value > 0), } }