Make map editor preload texture previews

This commit is contained in:
2026-06-10 10:28:32 +08:00
parent bc300f4ff9
commit 57caced672
+60 -15
View File
@@ -9,6 +9,7 @@ const state = {
imagePreviews: new Map(), imagePreviews: new Map(),
imageLoading: new Set(), imageLoading: new Set(),
imageFailures: new Set(), imageFailures: new Set(),
previewLoadToken: 0,
history: [], history: [],
dragging: null, dragging: null,
}; };
@@ -143,26 +144,63 @@ function loadImage(url) {
}); });
} }
function preloadMapImages(map) { async function preloadMapImages(map) {
for (const layer of map.layers) { const textures = uniqueMapTextures(map);
for (const rect of layer.rects) { if (!textures.length) return;
if (rect.texture) queueImagePreview(rect.texture);
const token = state.previewLoadToken + 1;
state.previewLoadToken = token;
setStatus(`正在加载贴图预览 0/${textures.length}`);
let loaded = 0;
let failed = 0;
await Promise.all(textures.map(async (texturePath) => {
const ok = await queueImagePreview(texturePath, { refreshAfterLoad: false });
if (ok) loaded += 1;
else failed += 1;
if (state.previewLoadToken === token) {
setStatus(`正在加载贴图预览 ${loaded + failed}/${textures.length}`);
} }
}));
if (state.previewLoadToken !== token) return;
refresh();
if (failed) {
setStatus(`贴图预览完成:成功 ${loaded},失败 ${failed}`);
} else {
setStatus(`贴图预览完成:${loaded}/${textures.length}`);
} }
} }
async function queueImagePreview(texturePath) { function uniqueMapTextures(map) {
const textures = [];
const seen = new Set();
for (const layer of map.layers) {
for (const rect of layer.rects) {
if (!rect.texture || seen.has(rect.texture)) continue;
seen.add(rect.texture);
textures.push(rect.texture);
}
}
return textures;
}
async function queueImagePreview(texturePath, options = {}) {
if (!texturePath || if (!texturePath ||
state.imagePreviews.has(texturePath) || state.imagePreviews.has(texturePath)) {
state.imageLoading.has(texturePath) || return true;
state.imageFailures.has(texturePath)) { }
return; if (state.imageLoading.has(texturePath)) {
return false;
}
if (state.imageFailures.has(texturePath)) {
return false;
} }
const url = previewUrlForTexture(texturePath); const url = previewUrlForTexture(texturePath);
if (!url) { if (!url) {
state.imageFailures.add(texturePath); state.imageFailures.add(texturePath);
return; return false;
} }
state.imageLoading.add(texturePath); state.imageLoading.add(texturePath);
@@ -170,10 +208,12 @@ async function queueImagePreview(texturePath) {
const image = await loadImage(url); const image = await loadImage(url);
state.imagePreviews.set(texturePath, image); state.imagePreviews.set(texturePath, image);
state.imageFailures.delete(texturePath); state.imageFailures.delete(texturePath);
refresh(); if (options.refreshAfterLoad !== false) refresh();
return true;
} catch (error) { } catch (error) {
state.imageFailures.add(texturePath); state.imageFailures.add(texturePath);
setStatus(`贴图预览加载失败:${texturePath}`); setStatus(`贴图预览加载失败:${texturePath}`);
return false;
} finally { } finally {
state.imageLoading.delete(texturePath); state.imageLoading.delete(texturePath);
} }
@@ -183,9 +223,14 @@ function previewUrlForTexture(texturePath) {
const clean = String(texturePath || "").replace(/\\/g, "/").replace(/^\/+/, ""); const clean = String(texturePath || "").replace(/\\/g, "/").replace(/^\/+/, "");
if (!clean) return ""; if (!clean) return "";
if (/^(blob:|data:|https?:\/\/)/i.test(clean)) return clean; if (/^(blob:|data:|https?:\/\/)/i.test(clean)) return clean;
if (clean.startsWith("assets/")) return `../../game/${clean}`; if (clean.startsWith("assets/")) return cacheBustedUrl(`${location.origin}/game/${clean}`);
if (clean.startsWith("game/assets/")) return `../../${clean}`; if (clean.startsWith("game/assets/")) return cacheBustedUrl(`${location.origin}/${clean}`);
return clean; return cacheBustedUrl(clean);
}
function cacheBustedUrl(url) {
const separator = url.includes("?") ? "&" : "?";
return `${url}${separator}preview=${Date.now()}`;
} }
function loadMapText(text, status) { function loadMapText(text, status) {
@@ -194,10 +239,10 @@ function loadMapText(text, status) {
state.selected = null; state.selected = null;
state.history = []; state.history = [];
state.imageFailures.clear(); state.imageFailures.clear();
preloadMapImages(state.map);
fitWorldToCanvas(); fitWorldToCanvas();
setStatus(status); setStatus(status);
refresh(); refresh();
preloadMapImages(state.map);
} catch (error) { } catch (error) {
setStatus(`解析失败:${error.message}`); setStatus(`解析失败:${error.message}`);
} }