mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-28 07:49:59 +00:00
ifcviewer-web: complete the OPFS cache in the background
Filling only from the viewer's reads meant the cache converged on the bytes the camera had needed — a user had to orbit every model into view (unloading others to get there) before an entry could finish. Now a filling entry fetches its uncovered spans in order, 8 MB at a time, whenever the viewer has been quiet for 1.5 s, yielding the moment real reads resume so interactive streaming always wins. A 42 MB model that levelled off at 85% viewed now completes seconds after load with no interaction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -233,6 +233,7 @@
|
||||
let broken = false; // a write failed (quota?): serve network, stop filling
|
||||
let complete = spansCover(spans, 0, meta.size);
|
||||
let writeChain = Promise.resolve();
|
||||
let lastForegroundRead = 0; // performance.now() of the viewer's last read
|
||||
|
||||
const persistLedger = () => {
|
||||
dirtySince = 0;
|
||||
@@ -249,12 +250,66 @@
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const storeBytes = (start, stop, buf) => {
|
||||
if (broken || complete || buf.byteLength !== stop - start) return;
|
||||
const copy = buf.slice(0);
|
||||
writeChain = writeChain
|
||||
.then(() => cacheCall('write', name, { pos: start, data: copy }, [copy]))
|
||||
.then(() => {
|
||||
spans = spansAdd(spans, start, stop);
|
||||
dirtySince += stop - start;
|
||||
// Persist the ledger periodically — bytes on disk that the
|
||||
// ledger does not record are merely re-fetched next visit.
|
||||
if (dirtySince >= (4 << 20)) return persistLedger();
|
||||
})
|
||||
.then(finishIfComplete)
|
||||
.catch(() => { broken = true; });
|
||||
};
|
||||
|
||||
// Background completion: streaming only reads what the camera needs, so
|
||||
// left alone the cache converges on the *viewed* bytes, not the file —
|
||||
// and a user cannot be expected to orbit every model into view to
|
||||
// finish it. Once the viewer has been quiet for a moment, fetch the
|
||||
// uncovered spans in order, one modest range at a time, yielding
|
||||
// whenever real reads resume so interactive streaming always wins.
|
||||
const IDLE_MS = 1500, STEP_BYTES = 8 << 20;
|
||||
let backgroundDone = false;
|
||||
async function backgroundFill() {
|
||||
while (!complete && !broken && !backgroundDone) {
|
||||
if (performance.now() - lastForegroundRead < IDLE_MS) {
|
||||
await new Promise((r) => setTimeout(r, IDLE_MS));
|
||||
continue;
|
||||
}
|
||||
// First gap not yet covered.
|
||||
let at = 0;
|
||||
for (const [a, b] of spans) { if (a > at) break; at = Math.max(at, b); }
|
||||
if (at >= meta.size) { finishIfComplete(); return; }
|
||||
let stop = Math.min(at + STEP_BYTES, meta.size);
|
||||
for (const [a] of spans) { if (a > at) { stop = Math.min(stop, a); break; } }
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
headers: { Range: 'bytes=' + at + '-' + (stop - 1) },
|
||||
});
|
||||
if (res.status !== 206 && res.status !== 200) return; // server changed its mind
|
||||
let buf = await res.arrayBuffer();
|
||||
if (res.status === 200 && buf.byteLength > stop - at) buf = buf.slice(at, stop);
|
||||
storeBytes(at, stop, buf);
|
||||
await writeChain;
|
||||
} catch (err) {
|
||||
return; // offline etc: the foreground path is affected too, stop quietly
|
||||
}
|
||||
}
|
||||
}
|
||||
setTimeout(backgroundFill, IDLE_MS);
|
||||
|
||||
return {
|
||||
size: meta.size,
|
||||
stopBackgroundFill() { backgroundDone = true; },
|
||||
slice(start, end) {
|
||||
const stop = Math.min(end, meta.size);
|
||||
return {
|
||||
arrayBuffer: async () => {
|
||||
lastForegroundRead = performance.now();
|
||||
if (spansCover(spans, start, stop)) {
|
||||
if (complete) {
|
||||
const fh = await dir.getFileHandle(name);
|
||||
@@ -277,20 +332,7 @@
|
||||
if (res.status === 200 && buf.byteLength > stop - start) {
|
||||
buf = buf.slice(start, stop);
|
||||
}
|
||||
if (!broken && !complete && buf.byteLength === stop - start) {
|
||||
const copy = buf.slice(0);
|
||||
writeChain = writeChain
|
||||
.then(() => cacheCall('write', name, { pos: start, data: copy }, [copy]))
|
||||
.then(() => {
|
||||
spans = spansAdd(spans, start, stop);
|
||||
dirtySince += stop - start;
|
||||
// Persist the ledger periodically — bytes on disk that the
|
||||
// ledger does not record are merely re-fetched next visit.
|
||||
if (dirtySince >= (4 << 20)) return persistLedger();
|
||||
})
|
||||
.then(finishIfComplete)
|
||||
.catch(() => { broken = true; });
|
||||
}
|
||||
storeBytes(start, stop, buf);
|
||||
return buf;
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user