mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 13:56:32 +08:00
perf(性能提升): 优化字体并发与复杂页渲染
This commit is contained in:
1 parent
15a532a1ca
commit
04d7d2d672
3 files changed
+53
-21
No files matched your search
+44
-19
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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" });
|
||||
|
||||
Reference in new issue
Block a user