feat(前端优化): 优化后台渲染与导出,修复快速滚动时加载状态残留
build.yaml / build (push) Successful in 46s

This commit is contained in:
xiaoqidun committed 2026-09-12 00:34:29 +08:00
1 parent 04e5cd7071
commit fb4ba04767
5 files changed
+162 -159

No files matched your search

+1 -1
View File
@@ -22,7 +22,7 @@ import (
// FS WebUI嵌入静态文件 // FS WebUI嵌入静态文件
// //
//go:embed index.html ofdgo.css ofdgo.js ofdgo.json ofdgo.svg ofdgo.sw.js ofdgo.wasm wasm_exec.js //go:embed index.html ofdgo.css ofdgo.js ofdgo.json ofdgo.svg ofdgo.sw.js ofdgo.wasm ofdgo.wasm.js wasm_exec.js
var FS embed.FS var FS embed.FS
// Checksum 计算WebUI嵌入资源校验值 // Checksum 计算WebUI嵌入资源校验值
-1
View File
@@ -152,7 +152,6 @@
</footer> </footer>
</div> </div>
<script src="./wasm_exec.js"></script>
<script type="module" src="./ofdgo.js"></script> <script type="module" src="./ofdgo.js"></script>
</body> </body>
</html> </html>
+125 -154
View File
@@ -13,19 +13,11 @@ const STATUS = {
exporting: "正在导出文档", exporting: "正在导出文档",
pageExporting: "正在导出单页", pageExporting: "正在导出单页",
}; };
const WASM_CALLBACKS = [ const wasmRequests = new Map();
"ofdgoOpen",
"ofdgoConfigure",
"ofdgoDocumentInfo",
"ofdgoRenderPage",
"ofdgoExportFormats",
"ofdgoExportPage",
"ofdgoExportPDF",
"ofdgoFontFileMatches",
];
let wasmPromise = null; let wasmPromise = null;
let wasmModule = null; let wasmWorker = null;
let wasmRequestID = 0;
let wasmRecoveryTimer = 0; let wasmRecoveryTimer = 0;
let fontDatabase = null; let fontDatabase = null;
@@ -35,9 +27,11 @@ const state = {
wasmSeq: 0, wasmSeq: 0,
wasmRecovering: false, wasmRecovering: false,
wasmRecoveries: 0, wasmRecoveries: 0,
exporting: false,
ofdBytes: null, ofdBytes: null,
fileName: "ofdgo.ofd", fileName: "ofdgo.ofd",
openSeq: 0, openSeq: 0,
pageSeq: 0,
fontSeq: 0, fontSeq: 0,
localFonts: [], localFonts: [],
userFonts: [], userFonts: [],
@@ -275,7 +269,7 @@ async function boot() {
fontsRestored = false; fontsRestored = false;
} }
setProgress("引擎准备完成", 70); setProgress("引擎准备完成", 70);
loadExportFormats(); await loadExportFormats();
setEmpty("选择 OFD 文件"); setEmpty("选择 OFD 文件");
updateFontSummary(); updateFontSummary();
renderFontList(); renderFontList();
@@ -419,8 +413,8 @@ async function refreshApplication() {
} }
} }
function loadExportFormats() { async function loadExportFormats() {
const formats = callWASM("ofdgoExportFormats") || []; const formats = await callWASM("ofdgoExportFormats") || [];
state.exportFormats = formats; state.exportFormats = formats;
el.pageExportFormat.replaceChildren(); el.pageExportFormat.replaceChildren();
for (const format of formats) { for (const format of formats) {
@@ -448,47 +442,41 @@ async function ensureWASM() {
} }
async function loadWASM() { async function loadWASM() {
if (!globalThis.Go) {
throw new Error("渲染引擎脚本缺失");
}
const wasmSeq = state.wasmSeq + 1; const wasmSeq = state.wasmSeq + 1;
state.wasmSeq = wasmSeq; state.wasmSeq = wasmSeq;
state.ready = false; state.ready = false;
state.wasmExited = false; state.wasmExited = false;
clearWASMCallbacks(); await new Promise((resolve, reject) => {
const go = new Go(); const worker = new Worker("./ofdgo.wasm.js");
if (!wasmModule) { wasmWorker = worker;
setProgress("正在下载引擎", 16); const fail = (err) => {
const response = await fetch("./ofdgo.wasm"); markWASMExited(wasmSeq, err);
try { reject(err);
setProgress("正在编译引擎", 35); };
wasmModule = await WebAssembly.compileStreaming(response); worker.onerror = (event) => fail(new Error(event.message || "渲染引擎加载失败"));
} catch { worker.onmessageerror = () => fail(new Error("渲染引擎通信失败"));
const fallback = await fetch("./ofdgo.wasm"); worker.onmessage = ({ data }) => {
const bytes = await fallback.arrayBuffer(); if (worker !== wasmWorker) {
setProgress("正在编译引擎", 45); return;
wasmModule = await WebAssembly.compile(bytes); }
} if (data.type === "progress") {
} setProgress(data.text, data.percent);
setProgress("正在启动引擎", 58); } else if (data.type === "ready") {
const instance = await WebAssembly.instantiate(wasmModule, go.importObject); state.ready = true;
go.run(instance).then(() => { resolve();
markWASMExited(wasmSeq); } else if (data.type === "exit") {
}).catch((err) => { fail(new Error(data.error));
markWASMExited(wasmSeq, err); } else {
const request = wasmRequests.get(data.id);
wasmRequests.delete(data.id);
if (data.ok) {
request.resolve(data.data);
} else {
request.reject(new Error(data.error));
}
}
};
}); });
await waitFor(() => WASM_CALLBACKS.every((name) => typeof globalThis[name] === "function"));
if (wasmSeq !== state.wasmSeq) {
return;
}
state.ready = true;
state.wasmExited = false;
}
function clearWASMCallbacks() {
for (const name of WASM_CALLBACKS) {
globalThis[name] = undefined;
}
} }
function markWASMExited(wasmSeq = state.wasmSeq, err) { function markWASMExited(wasmSeq = state.wasmSeq, err) {
@@ -497,6 +485,12 @@ function markWASMExited(wasmSeq = state.wasmSeq, err) {
} }
state.ready = false; state.ready = false;
state.wasmExited = true; state.wasmExited = true;
wasmWorker?.terminate();
wasmWorker = null;
for (const request of wasmRequests.values()) {
request.reject(err || new Error("渲染引擎已退出"));
}
wasmRequests.clear();
if (err) { if (err) {
setStatus("渲染引擎异常,正在恢复"); setStatus("渲染引擎异常,正在恢复");
} }
@@ -517,43 +511,28 @@ async function recoverWASM() {
} }
state.wasmRecovering = true; state.wasmRecovering = true;
state.wasmRecoveries += 1; state.wasmRecoveries += 1;
const pageIndex = state.pageIndex; const openSeq = ++state.openSeq;
const fitMode = state.fitMode || "width";
setBusy(true, "正在恢复引擎", 18, STATUS.recovering); setBusy(true, "正在恢复引擎", 18, STATUS.recovering);
try { try {
await ensureWASM();
await openDocument({ await openDocument({
pageIndex, pageIndex: state.pageIndex,
fitMode, fitMode: state.fitMode || "width",
scale: state.scale,
skipAutoFonts: true, skipAutoFonts: true,
openSeq,
}); });
} catch (err) { } catch (err) {
showError(err, !state.doc); if (openSeq === state.openSeq) {
showError(err, !state.doc);
}
} finally { } finally {
state.wasmRecovering = false; state.wasmRecovering = false;
if (!state.doc) { if (openSeq === state.openSeq) {
setBusy(false); setBusy(false);
} }
} }
} }
function waitFor(predicate) {
return new Promise((resolve, reject) => {
const started = performance.now();
const timer = window.setInterval(() => {
if (predicate()) {
window.clearInterval(timer);
resolve();
return;
}
if (performance.now() - started > 5000) {
window.clearInterval(timer);
reject(new Error("渲染引擎初始化超时"));
}
}, 20);
});
}
async function openOFD(file) { async function openOFD(file) {
if (!file) { if (!file) {
return; return;
@@ -564,14 +543,21 @@ async function openOFD(file) {
return; return;
} }
state.wasmRecoveries = 0; state.wasmRecoveries = 0;
const openSeq = ++state.openSeq;
setBusy(true, "正在读取 OFD", 10, STATUS.opening); setBusy(true, "正在读取 OFD", 10, STATUS.opening);
try { try {
state.ofdBytes = new Uint8Array(await file.arrayBuffer()); const bytes = new Uint8Array(await file.arrayBuffer());
if (openSeq !== state.openSeq) {
return;
}
state.ofdBytes = bytes;
state.fileName = file.name || "ofdgo.ofd"; state.fileName = file.name || "ofdgo.ofd";
await openDocument({ pageIndex: 0, resetScroll: true }); await openDocument({ pageIndex: 0, resetScroll: true, openSeq });
} catch (err) { } catch (err) {
showError(err, true); if (openSeq === state.openSeq) {
setBusy(false); showError(err, true);
setBusy(false);
}
} }
} }
@@ -818,9 +804,12 @@ async function loadDocumentLocalFonts(available, openSeq = state.openSeq) {
return false; return false;
} }
const docLoadLimit = Math.min(LOCAL_FONT_LOAD_LIMIT, Math.max(4, docNames.length * 3)); const docLoadLimit = Math.min(LOCAL_FONT_LOAD_LIMIT, Math.max(4, docNames.length * 3));
let selected = selectLocalFonts(available, docNames, docLoadLimit); let selected = await selectLocalFonts(available, docNames, docLoadLimit);
if (!selected.length) { if (!selected.length) {
selected = selectLocalFonts(available, [], 6); selected = await selectLocalFonts(available, [], 6);
}
if (openSeq !== state.openSeq) {
return false;
} }
const emptyStatus = available.length === 0 ? "未读取到系统字体" : "未匹配到所需字体"; const emptyStatus = available.length === 0 ? "未读取到系统字体" : "未匹配到所需字体";
const fonts = []; const fonts = [];
@@ -860,7 +849,7 @@ function uniqueLocalFonts(fonts) {
return selected; return selected;
} }
function selectLocalFonts(fonts, names, limit) { async function selectLocalFonts(fonts, names, limit) {
const available = new Map(); const available = new Map();
for (const font of uniqueLocalFonts(fonts)) { for (const font of uniqueLocalFonts(fonts)) {
available.set(localFontName(font), font); available.set(localFontName(font), font);
@@ -870,7 +859,7 @@ function selectLocalFonts(fonts, names, limit) {
} }
} }
} }
const matched = callWASM("ofdgoFontFileMatches", [...available.keys()], names); const matched = await callWASM("ofdgoFontFileMatches", [...available.keys()], names);
return uniqueLocalFonts(matched.map((name) => available.get(name))).slice(0, limit); return uniqueLocalFonts(matched.map((name) => available.get(name))).slice(0, limit);
} }
@@ -996,6 +985,9 @@ async function applyFontChange(options = {}) {
} }
async function toggleAnnotations() { async function toggleAnnotations() {
if (document.body.hasAttribute("aria-busy")) {
return;
}
state.renderAnnotations = !state.renderAnnotations; state.renderAnnotations = !state.renderAnnotations;
updateAnnotationButton(); updateAnnotationButton();
if (!state.ofdBytes || !state.doc) { if (!state.ofdBytes || !state.doc) {
@@ -1161,10 +1153,13 @@ async function openDocument(options = {}) {
if (!state.ofdBytes) { if (!state.ofdBytes) {
return; return;
} }
const openSeq = options.openSeq || (state.openSeq += 1);
if (!state.ready || state.wasmExited) { if (!state.ready || state.wasmExited) {
await ensureWASM(); await ensureWASM();
} }
const openSeq = options.openSeq || (state.openSeq += 1); if (openSeq !== state.openSeq) {
return;
}
const resetLocalFonts = !options.skipAutoFonts; const resetLocalFonts = !options.skipAutoFonts;
if (resetLocalFonts) { if (resetLocalFonts) {
state.localFonts = []; state.localFonts = [];
@@ -1184,8 +1179,8 @@ async function openDocument(options = {}) {
return; return;
} }
const doc = options.reuseSession const doc = options.reuseSession
? callWASM("ofdgoConfigure", fonts, state.renderAnnotations) ? await callWASM("ofdgoConfigure", fonts, state.renderAnnotations)
: callWASM("ofdgoOpen", state.ofdBytes, fonts, state.renderAnnotations); : await callWASM("ofdgoOpen", state.ofdBytes, fonts, state.renderAnnotations);
if (openSeq !== state.openSeq) { if (openSeq !== state.openSeq) {
return; return;
} }
@@ -1251,7 +1246,7 @@ async function loadDocumentDetails(openSeq) {
return; return;
} }
try { try {
const info = callWASM("ofdgoDocumentInfo"); const info = await callWASM("ofdgoDocumentInfo");
if (openSeq === state.openSeq) { if (openSeq === state.openSeq) {
Object.assign(state.doc, info, { detailsPending: false }); Object.assign(state.doc, info, { detailsPending: false });
renderMeta(); renderMeta();
@@ -1275,10 +1270,13 @@ async function renderPage(index, options = {}) {
if (index < 0 || index >= pageCount) { if (index < 0 || index >= pageCount) {
return; return;
} }
if (options.keepBusy) { const pageSeq = ++state.pageSeq;
setProgress("正在渲染页面", 76); if (!state.exporting) {
} else { if (options.keepBusy) {
setBusy(true, "正在渲染页面", 35, "正在渲染页面"); setProgress("正在渲染页面", 76);
} else {
setBusy(true, "正在渲染页面", 35, "正在渲染页面");
}
} }
try { try {
setCurrentPage(index); setCurrentPage(index);
@@ -1289,7 +1287,7 @@ async function renderPage(index, options = {}) {
scrollToPage(index); scrollToPage(index);
} }
await renderFlowPage(index, { throwError: true, openSeq, priority: 0 }); await renderFlowPage(index, { throwError: true, openSeq, priority: 0 });
if (openSeq !== state.openSeq) { if (openSeq !== state.openSeq || index !== state.pageIndex) {
return; return;
} }
if (options.scroll !== false) { if (options.scroll !== false) {
@@ -1301,30 +1299,22 @@ async function renderPage(index, options = {}) {
showError(err, false); showError(err, false);
} }
} finally { } finally {
if (!options.keepBusy && openSeq === state.openSeq) { if (!options.keepBusy && !state.exporting && openSeq === state.openSeq && pageSeq === state.pageSeq) {
setBusy(false); setBusy(false);
} }
} }
} }
async function exportPDF() { async function exportPDF() {
if (!state.doc) { if (!state.doc || document.body.hasAttribute("aria-busy")) {
return; return;
} }
const openSeq = state.openSeq; const openSeq = state.openSeq;
setExportControlsDisabled(true); state.exporting = true;
setBusy(true, "正在准备 PDF", 18, STATUS.exporting); updateControls();
setBusy(true, "正在生成 PDF", 45, STATUS.exporting);
try { try {
await waitForPaint(); const result = await callWASM("ofdgoExportPDF");
if (openSeq !== state.openSeq) {
return;
}
setProgress("正在生成 PDF", 45);
await waitForPaint();
if (openSeq !== state.openSeq) {
return;
}
const result = callWASM("ofdgoExportPDF");
if (openSeq !== state.openSeq) { if (openSeq !== state.openSeq) {
return; return;
} }
@@ -1337,6 +1327,7 @@ async function exportPDF() {
showError(err, false); showError(err, false);
} }
} finally { } finally {
state.exporting = false;
if (openSeq === state.openSeq) { if (openSeq === state.openSeq) {
setBusy(false); setBusy(false);
updateControls(); updateControls();
@@ -1345,7 +1336,7 @@ async function exportPDF() {
} }
async function exportCurrentPage() { async function exportCurrentPage() {
if (!state.doc) { if (!state.doc || document.body.hasAttribute("aria-busy")) {
return; return;
} }
const format = el.pageExportFormat.value; const format = el.pageExportFormat.value;
@@ -1355,32 +1346,26 @@ async function exportCurrentPage() {
const info = exportFormatInfo(format); const info = exportFormatInfo(format);
const label = info?.label || String(format || "").toUpperCase(); const label = info?.label || String(format || "").toUpperCase();
const openSeq = state.openSeq; const openSeq = state.openSeq;
setExportControlsDisabled(true); const fileName = pageFileName(info?.extension || format);
setBusy(true, `正在准备 ${label}`, 18, STATUS.pageExporting); state.exporting = true;
updateControls();
setBusy(true, `正在生成 ${label}`, 45, STATUS.pageExporting);
try { try {
await waitForPaint();
if (openSeq !== state.openSeq) {
return;
}
setProgress(`正在生成 ${label}`, 45);
await waitForPaint();
if (openSeq !== state.openSeq) {
return;
}
const dpi = exportFormatUsesDPI(format) ? currentImageDPI() : 0; const dpi = exportFormatUsesDPI(format) ? currentImageDPI() : 0;
const result = callWASM("ofdgoExportPage", state.pageIndex, format, dpi); const result = await callWASM("ofdgoExportPage", state.pageIndex, format, dpi);
if (openSeq !== state.openSeq) { if (openSeq !== state.openSeq) {
return; return;
} }
setProgress(`正在保存 ${result.label || label}`, 86); setProgress(`正在保存 ${result.label || label}`, 86);
const bytes = result.bytes; const bytes = result.bytes;
downloadBytes(bytes, result.mime || info?.mime || "application/octet-stream", pageFileName(result.extension || info?.extension || format)); downloadBytes(bytes, result.mime || info?.mime || "application/octet-stream", fileName);
setStatus(`${result.label || label} 已导出 ${formatBytes(result.size || bytes.length, result.label || label)}`); setStatus(`${result.label || label} 已导出 ${formatBytes(result.size || bytes.length, result.label || label)}`);
} catch (err) { } catch (err) {
if (openSeq === state.openSeq) { if (openSeq === state.openSeq) {
showError(err, false); showError(err, false);
} }
} finally { } finally {
state.exporting = false;
if (openSeq === state.openSeq) { if (openSeq === state.openSeq) {
setBusy(false); setBusy(false);
updateControls(); updateControls();
@@ -1571,7 +1556,7 @@ async function processPageRenderQueue() {
delayed = true; delayed = true;
continue; continue;
} }
const page = callWASM("ofdgoRenderPage", task.index); const page = await callWASM("ofdgoRenderPage", task.index);
if (task.openSeq === state.openSeq) { if (task.openSeq === state.openSeq) {
state.pageCache.set(task.index, page); state.pageCache.set(task.index, page);
cacheThumbnail(task.index, page.svg); cacheThumbnail(task.index, page.svg);
@@ -1714,7 +1699,7 @@ function setCurrentPage(index) {
} }
updatePageListCurrent(); updatePageListCurrent();
updateControls(); updateControls();
if (state.doc) { if (state.doc && !state.exporting) {
setStatus(pageStatus(index, state.doc.pageCount)); setStatus(pageStatus(index, state.doc.pageCount));
} }
} }
@@ -2461,7 +2446,7 @@ function setScale(nextScale, updateStatus = true, fitMode = "free") {
restoreScaleAnchor(anchor); restoreScaleAnchor(anchor);
} }
el.zoomLabel.textContent = `${Math.round(state.scale * 100)}%`; el.zoomLabel.textContent = `${Math.round(state.scale * 100)}%`;
if (updateStatus && state.doc) { if (updateStatus && state.doc && !state.exporting) {
setStatus(`第 ${state.pageIndex + 1} / ${state.doc.pageCount} 页`); setStatus(`第 ${state.pageIndex + 1} / ${state.doc.pageCount} 页`);
} }
updateControls(); updateControls();
@@ -2559,9 +2544,9 @@ function updateControls() {
el.fitButton.toggleAttribute("aria-pressed", hasDoc && state.fitMode === "width"); el.fitButton.toggleAttribute("aria-pressed", hasDoc && state.fitMode === "width");
el.fitHeightButton.toggleAttribute("aria-pressed", hasDoc && state.fitMode === "height"); el.fitHeightButton.toggleAttribute("aria-pressed", hasDoc && state.fitMode === "height");
updateAnnotationButton(); updateAnnotationButton();
el.pageExportFormat.disabled = !hasDoc || !state.exportFormats.length; el.pageExportFormat.disabled = !hasDoc || state.exporting || !state.exportFormats.length;
el.exportPageButton.disabled = !hasDoc || !state.exportFormats.length; el.exportPageButton.disabled = !hasDoc || state.exporting || !state.exportFormats.length;
el.exportButton.disabled = !hasDoc; el.exportButton.disabled = !hasDoc || state.exporting;
updateDPIControl(); updateDPIControl();
} }
@@ -2571,38 +2556,24 @@ function updateAnnotationButton() {
el.annotationButton.setAttribute("aria-label", el.annotationButton.title); el.annotationButton.setAttribute("aria-label", el.annotationButton.title);
} }
function setExportControlsDisabled(disabled) { async function callWASM(name, ...args) {
el.exportButton.disabled = disabled;
el.exportPageButton.disabled = disabled;
el.pageExportFormat.disabled = disabled;
updateDPIControl(disabled);
}
function callWASM(name, ...args) {
const fn = globalThis[name];
if (typeof fn !== "function") {
throw new Error("渲染引擎未初始化");
}
if (state.wasmExited) { if (state.wasmExited) {
scheduleWASMRecovery(); scheduleWASMRecovery();
throw new Error(state.wasmRecovering ? "渲染引擎正在恢复" : "渲染引擎已退出,正在恢复"); throw new Error(state.wasmRecovering ? "渲染引擎正在恢复" : "渲染引擎已退出,正在恢复");
} }
let payload; if (!state.ready) {
try { throw new Error("渲染引擎未初始化");
payload = fn(...args); }
} catch (err) { return new Promise((resolve, reject) => {
const message = String(err.message || err); const id = ++wasmRequestID;
if (message.includes("Go program has already exited")) { wasmRequests.set(id, { resolve, reject });
markWASMExited(state.wasmSeq); try {
throw new Error("渲染引擎已退出,正在恢复"); wasmWorker.postMessage({ id, name, args });
} catch (err) {
wasmRequests.delete(id);
reject(err);
} }
throw err; });
}
const result = typeof payload === "string" ? JSON.parse(payload) : payload;
if (!result.ok) {
throw new Error(result.error || "WASM 调用失败");
}
return result.data;
} }
function showError(err, empty = !state.doc) { function showError(err, empty = !state.doc) {
@@ -2694,8 +2665,8 @@ function exportFormatUsesDPI(value) {
return value === "png" || value === "jpg"; return value === "png" || value === "jpg";
} }
function updateDPIControl(disabled = false) { function updateDPIControl() {
el.imageDPI.disabled = disabled || !state.doc || !exportFormatUsesDPI(el.pageExportFormat.value); el.imageDPI.disabled = state.exporting || !state.doc || !exportFormatUsesDPI(el.pageExportFormat.value);
} }
function currentImageDPI() { function currentImageDPI() {
+5 -3
View File
@@ -1,6 +1,6 @@
const CACHE_PREFIX = `ofdgo:${self.registration.scope}:app:`; const CACHE_PREFIX = `ofdgo:${self.registration.scope}:app:`;
const META_CACHE = `${CACHE_PREFIX}meta`; const META_CACHE = `${CACHE_PREFIX}meta`;
const ASSETS = ["./", "ofdgo.css", "ofdgo.js", "ofdgo.json", "ofdgo.svg", "ofdgo.sw.js", "ofdgo.wasm", "wasm_exec.js"] const ASSETS = ["./", "ofdgo.css", "ofdgo.js", "ofdgo.json", "ofdgo.svg", "ofdgo.sw.js", "ofdgo.wasm", "ofdgo.wasm.js", "wasm_exec.js"]
.map((path) => new URL(path, self.registration.scope).href); .map((path) => new URL(path, self.registration.scope).href);
const CLIENT_PREFIX = new URL("__client/", self.registration.scope).href; const CLIENT_PREFIX = new URL("__client/", self.registration.scope).href;
@@ -14,7 +14,7 @@ self.addEventListener("fetch", (event) => {
if (event.request.method !== "GET" || !url.startsWith(self.registration.scope) || (navigation && url !== self.registration.scope)) { if (event.request.method !== "GET" || !url.startsWith(self.registration.scope) || (navigation && url !== self.registration.scope)) {
return; return;
} }
event.respondWith(navigator.locks.request(CACHE_PREFIX, { mode: navigation ? "exclusive" : "shared" }, () => readResource(event, navigation))); event.respondWith(navigator.locks.request(CACHE_PREFIX, { mode: event.resultingClientId ? "exclusive" : "shared" }, () => readResource(event, navigation)));
}); });
self.addEventListener("message", (event) => { self.addEventListener("message", (event) => {
@@ -85,13 +85,15 @@ async function readResource(event, navigation) {
let bundle; let bundle;
if (navigation) { if (navigation) {
bundle = await currentBundle(meta); bundle = await currentBundle(meta);
await meta.put(CLIENT_PREFIX + event.resultingClientId, Response.json(bundle));
} else { } else {
bundle = await readBundle(meta, CLIENT_PREFIX + event.clientId); bundle = await readBundle(meta, CLIENT_PREFIX + event.clientId);
} }
if (!bundle || !await caches.has(bundle.name)) { if (!bundle || !await caches.has(bundle.name)) {
return Response.error(); return Response.error();
} }
if (event.resultingClientId) {
await meta.put(CLIENT_PREFIX + event.resultingClientId, Response.json(bundle));
}
const url = resourceURL(event.request.url); const url = resourceURL(event.request.url);
if (!bundle.assets.includes(url)) { if (!bundle.assets.includes(url)) {
return fetch(event.request); return fetch(event.request);
+31
View File
@@ -0,0 +1,31 @@
importScripts("./wasm_exec.js");
self.onmessage = ({ data: { id, name, args } }) => {
try {
const payload = globalThis[name](...args);
const result = typeof payload === "string" ? JSON.parse(payload) : payload;
self.postMessage({ id, ...result }, result.data?.bytes ? [result.data.bytes.buffer] : []);
} catch (err) {
self.postMessage({ id, ok: false, error: err.message });
}
};
function exitWASM(err) {
self.postMessage({ type: "exit", error: err?.message || "渲染引擎已退出" });
self.close();
}
async function loadWASM() {
const go = new Go();
self.postMessage({ type: "progress", text: "正在下载引擎", percent: 16 });
const response = await fetch("./ofdgo.wasm");
self.postMessage({ type: "progress", text: "正在编译引擎", percent: 35 });
const { instance } = await WebAssembly.instantiateStreaming(response, go.importObject);
self.postMessage({ type: "progress", text: "正在启动引擎", percent: 58 });
go.run(instance).then(exitWASM, exitWASM);
if (!go.exited) {
self.postMessage({ type: "ready" });
}
}
loadWASM().catch(exitWASM);