From 04d7d2d672a4791d648018da2b80bd5061fea002 Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Mon, 26 Oct 2026 14:42:35 +0800 Subject: [PATCH] =?UTF-8?q?perf(=E6=80=A7=E8=83=BD=E6=8F=90=E5=8D=87):=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AD=97=E4=BD=93=E5=B9=B6=E5=8F=91=E4=B8=8E?= =?UTF-8?q?=E5=A4=8D=E6=9D=82=E9=A1=B5=E6=B8=B2=E6=9F=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/webui/ofdgo_host.js | 63 ++++++++++++++++++++++++++------------ assets/webui/ofdgo_view.js | 9 ++++-- assets/webui/ofdgo_wasm.js | 2 ++ 3 files changed, 53 insertions(+), 21 deletions(-) diff --git a/assets/webui/ofdgo_host.js b/assets/webui/ofdgo_host.js index f184fcf..afadee3 100644 --- a/assets/webui/ofdgo_host.js +++ b/assets/webui/ofdgo_host.js @@ -64,16 +64,16 @@ class BrowserScheduler { if (data.id == null || data.type) return; const key = requestKey(worker, data); const started = worker.requests.get(key); + const elapsed = data.elapsed ?? (started === undefined ? 0 : performance.now() - started); const operation = worker.operations.get(key), owner = this.owner(worker, data); - if (started !== undefined && data.ok && owner) { - const elapsed = performance.now() - started; + if (worker.primary && started !== undefined && data.ok && owner) { if (operation?.name === "ofdgoOpen") owner.openTime = elapsed; if (operation?.name === "ofdgoRenderPage") owner.pageTime = owner.pageTime ? owner.pageTime * 0.75 + elapsed * 0.25 : elapsed; } worker.requests.delete(key); worker.operations.delete(key); if (this.memory > this.budget) this.limit = Math.max(1, this.limit - 1); - else if (started !== undefined && data.ok && performance.now() - started > 100 && this.memory + Math.max(128 << 20, worker.memory) < this.budget) this.limit = Math.min(this.capacity, this.limit + 1); + else if (started !== undefined && data.ok && elapsed > 100 && this.memory + Math.max(128 << 20, worker.memory) < this.budget) this.limit = Math.min(this.capacity, this.limit + 1); } drain() { @@ -300,8 +300,9 @@ class BrowserEngine { const data = event.data; if (data.memory) this.memory = data.memory; if (this.source) this.parallelism = engineScheduler.limit; - if (data.id != null && !data.type) this.requests.delete(data.id); + const rendered = data.id != null && !data.type && this.requests.delete(data.id); this.onmessage?.(event); + if (rendered && data.ok) this.dispatchPages(); }; this.primary.onerror = event => this.onerror?.(event); this.primary.onmessageerror = event => this.onmessageerror?.(event); @@ -407,6 +408,36 @@ class BrowserEngine { }); } + pageSlot(data) { + const preferred = [...this.slots].find(slot => !slot.busy && slot.pages.has(data.args[0])); + const pending = [...this.requests.values()].filter(slot => slot === null).length - Number(this.requests.has(data.id)); + if (!preferred && pending <= 0) return null; + let slot = preferred || [...this.slots].filter(slot => !slot.busy).sort((a,b) => Math.abs(a.lastPage-data.args[0])-Math.abs(b.lastPage-data.args[0]))[0]; + const worthwhile = (this.primary.pageTime || 0) * pending >= Math.max(50, this.startupTime || this.primary.openTime || 0); + if (!slot && worthwhile && this.foreground && !this.pagesPaused && this.slots.size < this.parallelism - 1 + && engineScheduler.memory + Math.max(128 << 20, this.memory) <= this.budget) slot = this.createSlot(this.source); + return slot; + } + + dispatchPages() { + if (this.closed || !this.source || !this.foreground || this.pagesPaused) return; + const draining = engineScheduler.draining; + engineScheduler.draining = true; + try { + const queued = engineScheduler.queue.filter(task => task.worker === this.primary.runtime && task.data.engineID === this.engineID).sort((a,b) => a.order-b.order); + for (const task of queued) { + if (task.data.name !== "ofdgoRenderPage") break; + if (this.requests.get(task.data.id) !== null) continue; + let slot; + try { slot = this.pageSlot(task.data); } catch { break; } + if (!slot) break; + engineScheduler.queue.splice(engineScheduler.queue.indexOf(task), 1); + this.primary.requests.delete(task.data.id); + this.renderSlot(slot, task.data, task.transfer); + } + } finally { engineScheduler.draining = draining; } + } + postMessage(data, transfer) { if (this.closed) return; if (!data.type && data.preload && this.pagesPaused) { @@ -415,7 +446,7 @@ class BrowserEngine { } if (data.type === "cancel") { const slot = this.requests.get(data.id); - if (slot) this.closeSlot(slot); + if (slot && slot !== this.primary) this.closeSlot(slot); else this.primary.postMessage(data); return; } @@ -430,23 +461,16 @@ class BrowserEngine { "ofdgoPreparePrint", "ofdgoLayoutPrint", "ofdgoPrintSide", "ofdgoPrintPDF", "ofdgoDiscardPrint", "ofdgoExportAttachment", "ofdgoExportResource", "ofdgoExportMedia", "ofdgoExportVersion" ].includes(data.name)) this.invalidatePages(); - const preferred = data.name === "ofdgoRenderPage" ? [...this.slots].find(slot => !slot.busy && slot.pages.has(data.args[0])) : null; - if (data.name !== "ofdgoRenderPage" || !this.source || !preferred && ![...this.requests.values()].includes(null)) { + const slot = data.name === "ofdgoRenderPage" && this.source ? this.pageSlot(data) : null; + if (!slot) { if (data.name === "ofdgoRenderPage") this.requests.set(data.id, null); this.primary.postMessage(data, transfer); return; } - let slot = preferred || [...this.slots].filter(slot => !slot.busy).sort((a,b) => Math.abs(a.lastPage-data.args[0])-Math.abs(b.lastPage-data.args[0]))[0]; - const memory = engineScheduler.memory; - const pending = [...this.requests.values()].filter(slot => slot === null).length; - const worthwhile = (this.primary.pageTime || 0) * pending >= Math.max(50, this.startupTime || this.primary.openTime || 0); - if (!slot && worthwhile && this.foreground && !this.pagesPaused - && this.slots.size < this.parallelism - 1 && memory + Math.max(128 << 20, this.memory) <= this.budget) slot = this.createSlot(this.source); - if (!slot) { - this.requests.set(data.id, null); - this.primary.postMessage(data, transfer); - return; - } + this.renderSlot(slot, data, transfer); + } + + renderSlot(slot, data, transfer) { slot.busy = true; this.requests.set(data.id, slot); const revision = this.revision; @@ -462,7 +486,7 @@ class BrowserEngine { if (!this.closed && revision === this.revision && error.name !== "AbortError") { this.closeSlot(slot, error); this.parallelism = Math.max(1, this.slots.size + 1); - this.requests.set(data.id, null); + this.requests.set(data.id, this.primary); fallback = true; this.primary.postMessage(data, transfer); return; @@ -474,6 +498,7 @@ class BrowserEngine { slot.worker.reservation = 0; const memory = engineScheduler.memory; if (memory > this.budget) { this.closeSlot(slot); this.parallelism = Math.max(1, this.slots.size + 1); } + this.dispatchPages(); engineScheduler.drain(); }); } diff --git a/assets/webui/ofdgo_view.js b/assets/webui/ofdgo_view.js index e6e0514..bf159d9 100644 --- a/assets/webui/ofdgo_view.js +++ b/assets/webui/ofdgo_view.js @@ -535,6 +535,10 @@ export function createPageView(context) { const results = await Promise.allSettled(fonts.slice(offset, offset + 4).map(async font => { if (state.svgFonts.has(font.name)) return; const key = `${openSeq}:${loadSeq}:${font.name}`; + while (current() && !state.svgFonts.has(font.name) && !state.svgFontLoads.has(key) && state.svgFontLoads.size >= 4) { + await Promise.race([...state.svgFontLoads.values()].map(loading => loading.catch(() => {}))); + } + if (!current() || state.svgFonts.has(font.name)) return; let loading = state.svgFontLoads.get(key); if (!loading) { loading = pageView.loadSVGFont(font, openSeq, loadSeq); @@ -615,8 +619,9 @@ export function createPageView(context) { if (task.shell && pageView.pageShell(task.index) === task.shell && !task.shell.classList.contains("rendered") && (!state.pageObserver || state.visiblePages.has(task.index) || task.index === state.pageIndex)) { task.text ||= pageView.createTextLayerParts(task.page.text); - const text = task.text.next(); - if (!text.done) continue; + let text; + do { text = task.text.next(); } while (!text.done && performance.now() - started < 6); + if (!text.done) break; pageView.mountPageSVG(task.index, task.page, task.openSeq, text.value); } if (task.thumb && context.el.pageList.querySelector(`[data-page-index="${task.index}"] .thumb-paper`) === task.thumb) pageView.updateThumbnail(task.index, task.openSeq); diff --git a/assets/webui/ofdgo_wasm.js b/assets/webui/ofdgo_wasm.js index 0c261db..7d8d1eb 100644 --- a/assets/webui/ofdgo_wasm.js +++ b/assets/webui/ofdgo_wasm.js @@ -337,9 +337,11 @@ async function handleMessage(engine, { id, name, args }) { if (!saving && args.some(value => value?.mode !== undefined)) args.push(done => finish(done)); } } + const started = name === "ofdgoOpen" || name === "ofdgoRenderPage" ? performance.now() : null; const payload = await globalThis.ofdgoEngineCall(engine.id, name, ...args); signal?.throwIfAborted(); const result = typeof payload === "string" ? JSON.parse(payload) : payload; + if (started !== null && result.ok) result.elapsed = performance.now() - started; if (cancellable && !importing && !converting && !resources && result.ok) { result.data.size = size; if (name !== "ofdgoExportMedia") postMessage({ id, type: "export", stage: "save" });