import { Asset, instantiate, Node, Prefab } from 'cc'; import { GEvent } from '../event/GEvent'; import { Singleton } from '../../tools/Singleton'; import { ResourcesManager, ResPakage } from './ResourcesManager'; import { LoadScheduler } from './LoadScheduler'; import { AssetDesc, LoadOptions, LoadPriority, mergeLoadOptions } from './LoadPolicy'; /** * 统一资源门面:同步 getRes/getNode 仅命中已晋升资源;异步 ensure* 负责加载并晋升。 */ export class AssetService extends Singleton { private _loadingShown = false; get res(): ResourcesManager { return ResourcesManager.getInstance(); } get scheduler(): LoadScheduler { return LoadScheduler.getInstance(); } ensureBundle(bundleName: string, priority: LoadPriority = 'interactive'): Promise { return this.scheduler.enqueue(priority, () => new Promise((resolve) => { this.res.loadBundle(bundleName, () => resolve()); })); } ensureAsset(desc: AssetDesc, opts?: LoadOptions): Promise { const options = mergeLoadOptions(opts); const existing = this.res.getRes(desc.name, desc.path, desc.bundle, desc.type); if (existing) return Promise.resolve(existing as Asset); return new Promise((resolve) => { void this.scheduler.enqueue(options.priority, () => new Promise((done) => { this.res.loadAsset(desc.bundle, desc.path, desc.name, desc.type, (asset) => { resolve(asset); done(); }); })); }); } ensurePackage(pkg: ResPakage, opts?: LoadOptions): Promise { const options = mergeLoadOptions(opts); if (pkg.ress.length <= 0) return Promise.resolve(); this.res.clearResPkg(pkg.name); const showLoading = this._maybeShowLoading(options); let done = 0; const total = pkg.ress.length; let chain = Promise.resolve(); for (const conf of pkg.ress) { chain = chain.then(() => this.ensureAsset( { name: conf.name, path: conf.path, bundle: conf.bundle, type: conf.type }, options, ).then(() => { done++; options.onProgress?.(done, total); })); } return chain.then(() => { this._hideLoadingIfNeeded(showLoading); this._registerPackageCache(pkg); }).catch((e) => { this._hideLoadingIfNeeded(showLoading); throw e; }); } loadResPakg(pkg: ResPakage, opts?: LoadOptions): Promise { return this.ensurePackage(pkg, { ...mergeLoadOptions(opts), priority: opts?.priority ?? 'interactive' }); } preLoadResPakg(pkg: ResPakage, onProgress?: (c: number, t: number) => void, onComplete?: (result: { ok: boolean; failed: string[] }) => void): void { this.res.preLoadResPakg(pkg, onProgress, onComplete); } ensureNode(path: string, name: string, bundle: string, opts?: LoadOptions): Promise { return this.ensureAsset( { name, path, bundle, type: Prefab }, opts, ).then(() => this.res.getNode(path, name, bundle)); } private _registerPackageCache(pkg: ResPakage): void { const cache = (this.res as any)._resCache as Map; if (cache.get(pkg.name)) return; for (const conf of pkg.ress) { const res = this.res.getRes(conf.name, conf.path, conf.bundle, conf.type); if (res) conf.res = res as Asset; } cache.set(pkg.name, pkg); } private _maybeShowLoading(options: ReturnType): boolean { // Boot(critical) / 静默预载(silent) 不走运行时 loadingUI,进度由 BootPipeline / GameLaunch 负责 if (options.priority === 'critical' || options.priority === 'silent' || options.priority === 'background') { return false; } if (options.policy === 'network') { GEvent.Ins.emit(GEvent.SHOW_LOADING, 1); this._loadingShown = true; return true; } if (options.showLoadingAfterMs <= 0) return false; GEvent.Ins.emit(GEvent.SHOW_LOADING, 1); this._loadingShown = true; return true; } private _hideLoadingIfNeeded(immediateShown: boolean): void { if (immediateShown || this._loadingShown) { GEvent.Ins.emit(GEvent.HIDE_LOADING); this._loadingShown = false; } } }