initial commit
This commit is contained in:
164
app/composables/useDump.ts
Normal file
164
app/composables/useDump.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
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<number, { resolve: (v: any) => 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<string[]>([])
|
||||
const typeCounts = shallowRef<Int32Array>(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<T = any>(op: string, payload: Record<string, unknown> = {}, transfer: Transferable[] = []): Promise<T> {
|
||||
const w = ensureWorker()
|
||||
const id = ++seq
|
||||
return new Promise<T>((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),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user