mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 01:27:26 +08:00
This commit is contained in:
1 parent
04e5cd7071
commit
fb4ba04767
5 files changed
+162
-159
No files matched your search
@@ -22,7 +22,7 @@ import (
|
||||
|
||||
// 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
|
||||
|
||||
// Checksum 计算WebUI嵌入资源校验值
|
||||
|
||||
@@ -152,7 +152,6 @@
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="./wasm_exec.js"></script>
|
||||
<script type="module" src="./ofdgo.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+125
-154
@@ -13,19 +13,11 @@ const STATUS = {
|
||||
exporting: "正在导出文档",
|
||||
pageExporting: "正在导出单页",
|
||||
};
|
||||
const WASM_CALLBACKS = [
|
||||
"ofdgoOpen",
|
||||
"ofdgoConfigure",
|
||||
"ofdgoDocumentInfo",
|
||||
"ofdgoRenderPage",
|
||||
"ofdgoExportFormats",
|
||||
"ofdgoExportPage",
|
||||
"ofdgoExportPDF",
|
||||
"ofdgoFontFileMatches",
|
||||
];
|
||||
const wasmRequests = new Map();
|
||||
|
||||
let wasmPromise = null;
|
||||
let wasmModule = null;
|
||||
let wasmWorker = null;
|
||||
let wasmRequestID = 0;
|
||||
let wasmRecoveryTimer = 0;
|
||||
let fontDatabase = null;
|
||||
|
||||
@@ -35,9 +27,11 @@ const state = {
|
||||
wasmSeq: 0,
|
||||
wasmRecovering: false,
|
||||
wasmRecoveries: 0,
|
||||
exporting: false,
|
||||
ofdBytes: null,
|
||||
fileName: "ofdgo.ofd",
|
||||
openSeq: 0,
|
||||
pageSeq: 0,
|
||||
fontSeq: 0,
|
||||
localFonts: [],
|
||||
userFonts: [],
|
||||
@@ -275,7 +269,7 @@ async function boot() {
|
||||
fontsRestored = false;
|
||||
}
|
||||
setProgress("引擎准备完成", 70);
|
||||
loadExportFormats();
|
||||
await loadExportFormats();
|
||||
setEmpty("选择 OFD 文件");
|
||||
updateFontSummary();
|
||||
renderFontList();
|
||||
@@ -419,8 +413,8 @@ async function refreshApplication() {
|
||||
}
|
||||
}
|
||||
|
||||
function loadExportFormats() {
|
||||
const formats = callWASM("ofdgoExportFormats") || [];
|
||||
async function loadExportFormats() {
|
||||
const formats = await callWASM("ofdgoExportFormats") || [];
|
||||
state.exportFormats = formats;
|
||||
el.pageExportFormat.replaceChildren();
|
||||
for (const format of formats) {
|
||||
@@ -448,47 +442,41 @@ async function ensureWASM() {
|
||||
}
|
||||
|
||||
async function loadWASM() {
|
||||
if (!globalThis.Go) {
|
||||
throw new Error("渲染引擎脚本缺失");
|
||||
}
|
||||
const wasmSeq = state.wasmSeq + 1;
|
||||
state.wasmSeq = wasmSeq;
|
||||
state.ready = false;
|
||||
state.wasmExited = false;
|
||||
clearWASMCallbacks();
|
||||
const go = new Go();
|
||||
if (!wasmModule) {
|
||||
setProgress("正在下载引擎", 16);
|
||||
const response = await fetch("./ofdgo.wasm");
|
||||
try {
|
||||
setProgress("正在编译引擎", 35);
|
||||
wasmModule = await WebAssembly.compileStreaming(response);
|
||||
} catch {
|
||||
const fallback = await fetch("./ofdgo.wasm");
|
||||
const bytes = await fallback.arrayBuffer();
|
||||
setProgress("正在编译引擎", 45);
|
||||
wasmModule = await WebAssembly.compile(bytes);
|
||||
}
|
||||
}
|
||||
setProgress("正在启动引擎", 58);
|
||||
const instance = await WebAssembly.instantiate(wasmModule, go.importObject);
|
||||
go.run(instance).then(() => {
|
||||
markWASMExited(wasmSeq);
|
||||
}).catch((err) => {
|
||||
markWASMExited(wasmSeq, err);
|
||||
await new Promise((resolve, reject) => {
|
||||
const worker = new Worker("./ofdgo.wasm.js");
|
||||
wasmWorker = worker;
|
||||
const fail = (err) => {
|
||||
markWASMExited(wasmSeq, err);
|
||||
reject(err);
|
||||
};
|
||||
worker.onerror = (event) => fail(new Error(event.message || "渲染引擎加载失败"));
|
||||
worker.onmessageerror = () => fail(new Error("渲染引擎通信失败"));
|
||||
worker.onmessage = ({ data }) => {
|
||||
if (worker !== wasmWorker) {
|
||||
return;
|
||||
}
|
||||
if (data.type === "progress") {
|
||||
setProgress(data.text, data.percent);
|
||||
} else if (data.type === "ready") {
|
||||
state.ready = true;
|
||||
resolve();
|
||||
} else if (data.type === "exit") {
|
||||
fail(new Error(data.error));
|
||||
} 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) {
|
||||
@@ -497,6 +485,12 @@ function markWASMExited(wasmSeq = state.wasmSeq, err) {
|
||||
}
|
||||
state.ready = false;
|
||||
state.wasmExited = true;
|
||||
wasmWorker?.terminate();
|
||||
wasmWorker = null;
|
||||
for (const request of wasmRequests.values()) {
|
||||
request.reject(err || new Error("渲染引擎已退出"));
|
||||
}
|
||||
wasmRequests.clear();
|
||||
if (err) {
|
||||
setStatus("渲染引擎异常,正在恢复");
|
||||
}
|
||||
@@ -517,43 +511,28 @@ async function recoverWASM() {
|
||||
}
|
||||
state.wasmRecovering = true;
|
||||
state.wasmRecoveries += 1;
|
||||
const pageIndex = state.pageIndex;
|
||||
const fitMode = state.fitMode || "width";
|
||||
const openSeq = ++state.openSeq;
|
||||
setBusy(true, "正在恢复引擎", 18, STATUS.recovering);
|
||||
try {
|
||||
await ensureWASM();
|
||||
await openDocument({
|
||||
pageIndex,
|
||||
fitMode,
|
||||
pageIndex: state.pageIndex,
|
||||
fitMode: state.fitMode || "width",
|
||||
scale: state.scale,
|
||||
skipAutoFonts: true,
|
||||
openSeq,
|
||||
});
|
||||
} catch (err) {
|
||||
showError(err, !state.doc);
|
||||
if (openSeq === state.openSeq) {
|
||||
showError(err, !state.doc);
|
||||
}
|
||||
} finally {
|
||||
state.wasmRecovering = false;
|
||||
if (!state.doc) {
|
||||
if (openSeq === state.openSeq) {
|
||||
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) {
|
||||
if (!file) {
|
||||
return;
|
||||
@@ -564,14 +543,21 @@ async function openOFD(file) {
|
||||
return;
|
||||
}
|
||||
state.wasmRecoveries = 0;
|
||||
const openSeq = ++state.openSeq;
|
||||
setBusy(true, "正在读取 OFD", 10, STATUS.opening);
|
||||
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";
|
||||
await openDocument({ pageIndex: 0, resetScroll: true });
|
||||
await openDocument({ pageIndex: 0, resetScroll: true, openSeq });
|
||||
} catch (err) {
|
||||
showError(err, true);
|
||||
setBusy(false);
|
||||
if (openSeq === state.openSeq) {
|
||||
showError(err, true);
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -818,9 +804,12 @@ async function loadDocumentLocalFonts(available, openSeq = state.openSeq) {
|
||||
return false;
|
||||
}
|
||||
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) {
|
||||
selected = selectLocalFonts(available, [], 6);
|
||||
selected = await selectLocalFonts(available, [], 6);
|
||||
}
|
||||
if (openSeq !== state.openSeq) {
|
||||
return false;
|
||||
}
|
||||
const emptyStatus = available.length === 0 ? "未读取到系统字体" : "未匹配到所需字体";
|
||||
const fonts = [];
|
||||
@@ -860,7 +849,7 @@ function uniqueLocalFonts(fonts) {
|
||||
return selected;
|
||||
}
|
||||
|
||||
function selectLocalFonts(fonts, names, limit) {
|
||||
async function selectLocalFonts(fonts, names, limit) {
|
||||
const available = new Map();
|
||||
for (const font of uniqueLocalFonts(fonts)) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -996,6 +985,9 @@ async function applyFontChange(options = {}) {
|
||||
}
|
||||
|
||||
async function toggleAnnotations() {
|
||||
if (document.body.hasAttribute("aria-busy")) {
|
||||
return;
|
||||
}
|
||||
state.renderAnnotations = !state.renderAnnotations;
|
||||
updateAnnotationButton();
|
||||
if (!state.ofdBytes || !state.doc) {
|
||||
@@ -1161,10 +1153,13 @@ async function openDocument(options = {}) {
|
||||
if (!state.ofdBytes) {
|
||||
return;
|
||||
}
|
||||
const openSeq = options.openSeq || (state.openSeq += 1);
|
||||
if (!state.ready || state.wasmExited) {
|
||||
await ensureWASM();
|
||||
}
|
||||
const openSeq = options.openSeq || (state.openSeq += 1);
|
||||
if (openSeq !== state.openSeq) {
|
||||
return;
|
||||
}
|
||||
const resetLocalFonts = !options.skipAutoFonts;
|
||||
if (resetLocalFonts) {
|
||||
state.localFonts = [];
|
||||
@@ -1184,8 +1179,8 @@ async function openDocument(options = {}) {
|
||||
return;
|
||||
}
|
||||
const doc = options.reuseSession
|
||||
? callWASM("ofdgoConfigure", fonts, state.renderAnnotations)
|
||||
: callWASM("ofdgoOpen", state.ofdBytes, fonts, state.renderAnnotations);
|
||||
? await callWASM("ofdgoConfigure", fonts, state.renderAnnotations)
|
||||
: await callWASM("ofdgoOpen", state.ofdBytes, fonts, state.renderAnnotations);
|
||||
if (openSeq !== state.openSeq) {
|
||||
return;
|
||||
}
|
||||
@@ -1251,7 +1246,7 @@ async function loadDocumentDetails(openSeq) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const info = callWASM("ofdgoDocumentInfo");
|
||||
const info = await callWASM("ofdgoDocumentInfo");
|
||||
if (openSeq === state.openSeq) {
|
||||
Object.assign(state.doc, info, { detailsPending: false });
|
||||
renderMeta();
|
||||
@@ -1275,10 +1270,13 @@ async function renderPage(index, options = {}) {
|
||||
if (index < 0 || index >= pageCount) {
|
||||
return;
|
||||
}
|
||||
if (options.keepBusy) {
|
||||
setProgress("正在渲染页面", 76);
|
||||
} else {
|
||||
setBusy(true, "正在渲染页面", 35, "正在渲染页面");
|
||||
const pageSeq = ++state.pageSeq;
|
||||
if (!state.exporting) {
|
||||
if (options.keepBusy) {
|
||||
setProgress("正在渲染页面", 76);
|
||||
} else {
|
||||
setBusy(true, "正在渲染页面", 35, "正在渲染页面");
|
||||
}
|
||||
}
|
||||
try {
|
||||
setCurrentPage(index);
|
||||
@@ -1289,7 +1287,7 @@ async function renderPage(index, options = {}) {
|
||||
scrollToPage(index);
|
||||
}
|
||||
await renderFlowPage(index, { throwError: true, openSeq, priority: 0 });
|
||||
if (openSeq !== state.openSeq) {
|
||||
if (openSeq !== state.openSeq || index !== state.pageIndex) {
|
||||
return;
|
||||
}
|
||||
if (options.scroll !== false) {
|
||||
@@ -1301,30 +1299,22 @@ async function renderPage(index, options = {}) {
|
||||
showError(err, false);
|
||||
}
|
||||
} finally {
|
||||
if (!options.keepBusy && openSeq === state.openSeq) {
|
||||
if (!options.keepBusy && !state.exporting && openSeq === state.openSeq && pageSeq === state.pageSeq) {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function exportPDF() {
|
||||
if (!state.doc) {
|
||||
if (!state.doc || document.body.hasAttribute("aria-busy")) {
|
||||
return;
|
||||
}
|
||||
const openSeq = state.openSeq;
|
||||
setExportControlsDisabled(true);
|
||||
setBusy(true, "正在准备 PDF", 18, STATUS.exporting);
|
||||
state.exporting = true;
|
||||
updateControls();
|
||||
setBusy(true, "正在生成 PDF", 45, STATUS.exporting);
|
||||
try {
|
||||
await waitForPaint();
|
||||
if (openSeq !== state.openSeq) {
|
||||
return;
|
||||
}
|
||||
setProgress("正在生成 PDF", 45);
|
||||
await waitForPaint();
|
||||
if (openSeq !== state.openSeq) {
|
||||
return;
|
||||
}
|
||||
const result = callWASM("ofdgoExportPDF");
|
||||
const result = await callWASM("ofdgoExportPDF");
|
||||
if (openSeq !== state.openSeq) {
|
||||
return;
|
||||
}
|
||||
@@ -1337,6 +1327,7 @@ async function exportPDF() {
|
||||
showError(err, false);
|
||||
}
|
||||
} finally {
|
||||
state.exporting = false;
|
||||
if (openSeq === state.openSeq) {
|
||||
setBusy(false);
|
||||
updateControls();
|
||||
@@ -1345,7 +1336,7 @@ async function exportPDF() {
|
||||
}
|
||||
|
||||
async function exportCurrentPage() {
|
||||
if (!state.doc) {
|
||||
if (!state.doc || document.body.hasAttribute("aria-busy")) {
|
||||
return;
|
||||
}
|
||||
const format = el.pageExportFormat.value;
|
||||
@@ -1355,32 +1346,26 @@ async function exportCurrentPage() {
|
||||
const info = exportFormatInfo(format);
|
||||
const label = info?.label || String(format || "").toUpperCase();
|
||||
const openSeq = state.openSeq;
|
||||
setExportControlsDisabled(true);
|
||||
setBusy(true, `正在准备 ${label}`, 18, STATUS.pageExporting);
|
||||
const fileName = pageFileName(info?.extension || format);
|
||||
state.exporting = true;
|
||||
updateControls();
|
||||
setBusy(true, `正在生成 ${label}`, 45, STATUS.pageExporting);
|
||||
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 result = callWASM("ofdgoExportPage", state.pageIndex, format, dpi);
|
||||
const result = await callWASM("ofdgoExportPage", state.pageIndex, format, dpi);
|
||||
if (openSeq !== state.openSeq) {
|
||||
return;
|
||||
}
|
||||
setProgress(`正在保存 ${result.label || label}`, 86);
|
||||
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)}`);
|
||||
} catch (err) {
|
||||
if (openSeq === state.openSeq) {
|
||||
showError(err, false);
|
||||
}
|
||||
} finally {
|
||||
state.exporting = false;
|
||||
if (openSeq === state.openSeq) {
|
||||
setBusy(false);
|
||||
updateControls();
|
||||
@@ -1571,7 +1556,7 @@ async function processPageRenderQueue() {
|
||||
delayed = true;
|
||||
continue;
|
||||
}
|
||||
const page = callWASM("ofdgoRenderPage", task.index);
|
||||
const page = await callWASM("ofdgoRenderPage", task.index);
|
||||
if (task.openSeq === state.openSeq) {
|
||||
state.pageCache.set(task.index, page);
|
||||
cacheThumbnail(task.index, page.svg);
|
||||
@@ -1714,7 +1699,7 @@ function setCurrentPage(index) {
|
||||
}
|
||||
updatePageListCurrent();
|
||||
updateControls();
|
||||
if (state.doc) {
|
||||
if (state.doc && !state.exporting) {
|
||||
setStatus(pageStatus(index, state.doc.pageCount));
|
||||
}
|
||||
}
|
||||
@@ -2461,7 +2446,7 @@ function setScale(nextScale, updateStatus = true, fitMode = "free") {
|
||||
restoreScaleAnchor(anchor);
|
||||
}
|
||||
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} 页`);
|
||||
}
|
||||
updateControls();
|
||||
@@ -2559,9 +2544,9 @@ function updateControls() {
|
||||
el.fitButton.toggleAttribute("aria-pressed", hasDoc && state.fitMode === "width");
|
||||
el.fitHeightButton.toggleAttribute("aria-pressed", hasDoc && state.fitMode === "height");
|
||||
updateAnnotationButton();
|
||||
el.pageExportFormat.disabled = !hasDoc || !state.exportFormats.length;
|
||||
el.exportPageButton.disabled = !hasDoc || !state.exportFormats.length;
|
||||
el.exportButton.disabled = !hasDoc;
|
||||
el.pageExportFormat.disabled = !hasDoc || state.exporting || !state.exportFormats.length;
|
||||
el.exportPageButton.disabled = !hasDoc || state.exporting || !state.exportFormats.length;
|
||||
el.exportButton.disabled = !hasDoc || state.exporting;
|
||||
updateDPIControl();
|
||||
}
|
||||
|
||||
@@ -2571,38 +2556,24 @@ function updateAnnotationButton() {
|
||||
el.annotationButton.setAttribute("aria-label", el.annotationButton.title);
|
||||
}
|
||||
|
||||
function setExportControlsDisabled(disabled) {
|
||||
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("渲染引擎未初始化");
|
||||
}
|
||||
async function callWASM(name, ...args) {
|
||||
if (state.wasmExited) {
|
||||
scheduleWASMRecovery();
|
||||
throw new Error(state.wasmRecovering ? "渲染引擎正在恢复" : "渲染引擎已退出,正在恢复");
|
||||
}
|
||||
let payload;
|
||||
try {
|
||||
payload = fn(...args);
|
||||
} catch (err) {
|
||||
const message = String(err.message || err);
|
||||
if (message.includes("Go program has already exited")) {
|
||||
markWASMExited(state.wasmSeq);
|
||||
throw new Error("渲染引擎已退出,正在恢复");
|
||||
if (!state.ready) {
|
||||
throw new Error("渲染引擎未初始化");
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = ++wasmRequestID;
|
||||
wasmRequests.set(id, { resolve, reject });
|
||||
try {
|
||||
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) {
|
||||
@@ -2694,8 +2665,8 @@ function exportFormatUsesDPI(value) {
|
||||
return value === "png" || value === "jpg";
|
||||
}
|
||||
|
||||
function updateDPIControl(disabled = false) {
|
||||
el.imageDPI.disabled = disabled || !state.doc || !exportFormatUsesDPI(el.pageExportFormat.value);
|
||||
function updateDPIControl() {
|
||||
el.imageDPI.disabled = state.exporting || !state.doc || !exportFormatUsesDPI(el.pageExportFormat.value);
|
||||
}
|
||||
|
||||
function currentImageDPI() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const CACHE_PREFIX = `ofdgo:${self.registration.scope}:app:`;
|
||||
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);
|
||||
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)) {
|
||||
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) => {
|
||||
@@ -85,13 +85,15 @@ async function readResource(event, navigation) {
|
||||
let bundle;
|
||||
if (navigation) {
|
||||
bundle = await currentBundle(meta);
|
||||
await meta.put(CLIENT_PREFIX + event.resultingClientId, Response.json(bundle));
|
||||
} else {
|
||||
bundle = await readBundle(meta, CLIENT_PREFIX + event.clientId);
|
||||
}
|
||||
if (!bundle || !await caches.has(bundle.name)) {
|
||||
return Response.error();
|
||||
}
|
||||
if (event.resultingClientId) {
|
||||
await meta.put(CLIENT_PREFIX + event.resultingClientId, Response.json(bundle));
|
||||
}
|
||||
const url = resourceURL(event.request.url);
|
||||
if (!bundle.assets.includes(url)) {
|
||||
return fetch(event.request);
|
||||
|
||||
@@ -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);
|
||||
Reference in new issue
Block a user