feedback1 ui: operators no longer stay placeholders after a reload or a battle begun in a hidden tab (GitHub #8)

A reload (a phone browser or Chrome's Memory Saver discarding the background tab) made the asset store download
/data/assets.json a second time; createFieldView waited at most 4 s for it, every view looked its model and avatar up
once, and a failed download kept an empty manifest for the page. Now the field host seeds the store with the game
data's copies (seedAssets); without one, ready() retries like data.js, never keeps a failure and tries again with a
bounded backoff, and onChange makes every view re-resolve (UnitView.retryAssets, ItemView.setIcon). A failed or
timed-out Spine load is retried by its view (SPINE_RETRY_MS, acquire { retry }) and at once when the tab is shown.
Battle mode holds the Spine cache (spine.hold()), so a battle that began in a hidden tab keeps its models; the idle
budget still applies and the quiet budget still frees everything in the lobby, room and result.
This commit is contained in:
sganggs committed 2026-10-03 16:15:01 +08:00
1 parent 82c54897c7
commit b7ac003e09
6 files changed
+676 -64

No files matched your search

+27 -1
View File
@@ -1,6 +1,8 @@
// Field view loader: mounts the Pixi render engine (public/js/render/app.js → createFieldView, DESIGN §9)
// into a host element, falling back to the DOM view (fallbackField.js) when the engine is missing, times
// out or throws. Every call into the view goes through a guard so a render bug can never crash the HUD.
// The shared asset store gets the game data's copy of the asset manifest (`seedAssets`, public issue #8 item 5), and the
// prep cameras the HUD bands they keep clear (`hudBands`; the folded shop's band: public issue #5).
//
// `?render=fallback` (or globalThis.__SP_RENDER__ = 'fallback') skips the engine (dev / mock harness);
// `?render=engine` never falls back silently (errors are logged and the fallback still mounts).
@@ -160,6 +162,27 @@ export function guardView(view, kind) {
return safe;
}
/**
* Hand the game data's copies of /data/assets.json and /data/local-assets.json (data.js 'assets' / 'local': the match
* screen waits for them, gameComponents GAME_FILES) to the asset store, now or when they land: it then never downloads
* the manifest a second time — after a page reload (a phone browser discarding a background tab, Chrome's Memory Saver)
* that second download was the one createFieldView waited ≤ 4 s for, and a slow or failed one left every operator the
* image-less placeholder (public issue #8 item 5). Without a copy the store fetches and retries by itself.
* @param {{ seed?: (m: any) => boolean, seedLocal?: (m: any) => boolean }} store public/js/assets.js store
*/
export function seedAssets(store) {
if (!store) return;
const give = (name, fn) => {
if (typeof fn !== 'function') return;
const now = data.get(name);
if (now) { fn(now); return; }
// still loading (or failed): adopt it if it lands; a later success of the store's own fetch makes this a no-op
Promise.resolve(data.load(name)).then((m) => { if (m) fn(m); }, () => {});
};
give('assets', store.seed?.bind(store));
give('local', store.seedLocal?.bind(store));
}
/**
* Create a field view in `host`: the render engine when available, else the DOM fallback.
* @param {HTMLElement} host
@@ -172,7 +195,10 @@ export async function mountFieldView(host) {
try {
// the shared asset store (public/js/assets.js) keeps its Spine cache across remounts (next match, reconnect)
const am = await withTimeout(import('../assets.js'), LOAD_TIMEOUT_MS, 'asset store import').catch(() => null);
if (am?.assets && typeof am.assets.ready === 'function') opts.assets = am.assets;
if (am?.assets && typeof am.assets.ready === 'function') {
opts.assets = am.assets;
seedAssets(am.assets);
}
const mod = await withTimeout(import('../render/app.js'), LOAD_TIMEOUT_MS, 'render engine import');
if (typeof mod?.createFieldView !== 'function') throw new Error('createFieldView missing');
const view = await withTimeout(Promise.resolve(mod.createFieldView(host, opts)), LOAD_TIMEOUT_MS, 'createFieldView');