/
docNemo
/
hex-map-editor
Обзор
Документация
Войти
/
docNemo
/
hex-map-editor
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
5
CI/CD
Аналитика
Безопасность
main
src/render/textures.ts
117 строк
5 KB
docNemo
feat(render): слой отрисовки карты, настройка сцены и публичный API
31 июл 2026, 23:31
31 июл 2026, 23:31
3f45a8b
Код
Авторство
О чём код?
import { logger } from '@core/log'; import type { AssetCatalog } from '@core/asset-manifest'; /** * Загрузка и кэширование текстур по манифесту ассетов. * * Один и тот же спрайт используется сотнями гексов, поэтому текстура берётся из кэша, * а не создаётся заново: иначе карта 40×40 породила бы полторы тысячи копий одного * изображения. * * Отсутствующий файл не прерывает отрисовку. Вместо него подставляется кричащая * заглушка, а путь пишется в журнал: пропущенный ассет должен бросаться в глаза * на карте, а не молча притворяться водой. * См. specs/art-assets — «Манифест как источник правды». */ export class TextureCache { private readonly textures = new Map<string, PixiTexture>(); private readonly missing = new Set<string>(); constructor(private readonly catalog: AssetCatalog) {} /** Текстура по пути. Пути одинаковы для всех обращений, поэтому кэш попадает почти всегда. */ get(path: string): PixiTexture { const cached = this.textures.get(path); if (cached !== undefined) return cached; const texture = PIXI.Texture.from(path); this.textures.set(path, texture); return texture; } /** Текстура-заглушка для отсутствующего ассета. */ missingTexture(): PixiTexture { return this.get(this.catalog.missingSprite); } /** * Предзагрузка всех ассетов манифеста. * * Без неё первые кадры отрисовываются пустыми: PIXI грузит текстуру асинхронно, * а карта строится синхронно. Предзагрузка выполняется один раз при готовности * сцены и делает первую отрисовку полной. */ async preload(): Promise<{ loaded: number; failed: string[] }> { const refs = this.catalog.all(); const failed: string[] = []; await Promise.all( refs.map( (ref) => new Promise<void>((resolve) => { const texture = this.get(ref.path); if (texture.valid) { resolve(); return; } // PIXI 7 сообщает о готовности через baseTexture; обёртка приводит // оба исхода к одному завершению, чтобы один битый файл не подвесил // предзагрузку целиком. const source = texture as unknown as { baseTexture?: { once(event: string, fn: () => void): void }; }; const base = source.baseTexture; if (base === undefined) { resolve(); return; } let settled = false; const finish = (ok: boolean): void => { if (settled) return; settled = true; if (!ok) { failed.push(ref.path); this.missing.add(ref.path); } resolve(); }; base.once('loaded', () => finish(true)); base.once('error', () => finish(false)); setTimeout(() => finish(true), 5000); }), ), ); if (failed.length > 0) { logger.error( `Не загрузились текстуры (${failed.length}): ${failed.slice(0, 5).join(', ')}` + (failed.length > 5 ? ' …' : ''), ); } return { loaded: refs.length - failed.length, failed }; } /** Известно ли, что файл по этому пути отсутствует. */ isMissing(path: string): boolean { return this.missing.has(path); } /** Текстура с подстановкой заглушки вместо отсутствующей. */ getOrMissing(path: string): PixiTexture { return this.isMissing(path) ? this.missingTexture() : this.get(path); } get size(): number { return this.textures.size; } /** Освобождает текстуры. Вызывается при смене сцены. */ clear(): void { for (const texture of this.textures.values()) { texture.destroy(true); } this.textures.clear(); this.missing.clear(); } }