assets.js 3.37 KB
// assets.js — 贴图 / 背景 / 特效帧的加载与缓存。
//
// 默认走 PIXI.Assets.load;可由工厂注入 `loadTexture(url)` 覆盖(测试 / 自定义管线)。
// 容错(render-pixi §2 验收 10、§5.5):加载失败走 warn + 跳过,绝不抛断播放。

/**
 * 创建贴图加载器。
 * @param {Object} [deps]
 * @param {*=} deps.PIXI                            Pixi 命名空间(含 Assets)
 * @param {((url:string)=>Promise<*>)=} deps.loadTexture  自定义加载器(默认 PIXI.Assets.load)
 * @returns {{ load:(url:string)=>Promise<*|null>, get:(url:string)=>(*|null) }}
 */
export function createAssetLoader({ PIXI, loadTexture } = {}) {
  /** @type {Map<string, *>} 已就绪纹理缓存 */
  const cache = new Map()
  /** @type {Map<string, Promise<*|null>>} 进行中去重 */
  const inflight = new Map()

  // 回退加载:用 HTMLImageElement(crossOrigin) 取图再建纹理。PIXI.Assets.load 的 fetch 路径对
  // 某些跨域大图会 "Failed to fetch"(浏览器对大文件 fetch 的脆弱性/缓存污染),而 <img> 加载不受影响。
  // 资源公共可读 + CORS 放行 → crossOrigin='anonymous' 的纹理不污染画布。
  const loadViaImage = (/** @type {string} */ url) =>
    new Promise((resolve, reject) => {
      const img = new Image()
      img.crossOrigin = 'anonymous'
      img.onload = () => {
        try { resolve(PIXI.Texture.from(img)) } catch (e) { reject(e) }
      }
      img.onerror = () => reject(new Error(`image load failed: ${url}`))
      // 回退仅在主加载已失败时触发:用一个区分性 query 另起缓存条目,绕开可能被 no-cors 不透明响应
      // 污染的原 URL 缓存(cors 请求命中不透明缓存会失败)。该 URL 自身仍可缓存。
      img.src = url + (url.includes('?') ? '&' : '?') + '_pf=1'
    })

  const loader =
    loadTexture ||
    (/** @param {string} url */ (url) => {
      const hasAssets = PIXI && PIXI.Assets && typeof PIXI.Assets.load === 'function'
      const canImage =
        PIXI && PIXI.Texture && typeof PIXI.Texture.from === 'function' && typeof Image !== 'undefined'
      // 优先 Assets.load(带缓存/批处理);其 fetch 路径失败时回退 <img> 加载,保证大背景也能上屏。
      if (hasAssets) {
        const p = PIXI.Assets.load(url)
        return canImage ? p.catch(() => loadViaImage(url)) : p
      }
      if (canImage) return loadViaImage(url)
      return Promise.reject(new Error('PIXI loader unavailable'))
    })

  /**
   * 加载并缓存一张纹理。失败返回 null(warn),不抛。
   * @param {string} url
   * @returns {Promise<*|null>}
   */
  function load(url) {
    if (!url) return Promise.resolve(null)
    if (cache.has(url)) return Promise.resolve(cache.get(url))
    if (inflight.has(url)) return /** @type {Promise<*|null>} */ (inflight.get(url))
    const p = Promise.resolve()
      .then(() => loader(url))
      .then((tex) => {
        cache.set(url, tex)
        inflight.delete(url)
        return tex
      })
      .catch((e) => {
        inflight.delete(url)
        console.warn('[render-pixi] texture load fail', url, e && e.message)
        return null
      })
    inflight.set(url, p)
    return p
  }

  /** 同步取已缓存纹理(未就绪返回 null)。 @param {string} url */
  function get(url) {
    return cache.has(url) ? cache.get(url) : null
  }

  return { load, get }
}