You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
121 lines
4.5 KiB
121 lines
4.5 KiB
|
3 weeks ago
|
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<void> {
|
||
|
|
return this.scheduler.enqueue(priority, () => new Promise<void>((resolve) => {
|
||
|
|
this.res.loadBundle(bundleName, () => resolve());
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
|
||
|
|
ensureAsset(desc: AssetDesc, opts?: LoadOptions): Promise<Asset | null> {
|
||
|
|
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<Asset | null>((resolve) => {
|
||
|
|
void this.scheduler.enqueue(options.priority, () => new Promise<void>((done) => {
|
||
|
|
this.res.loadAsset(desc.bundle, desc.path, desc.name, desc.type, (asset) => {
|
||
|
|
resolve(asset);
|
||
|
|
done();
|
||
|
|
});
|
||
|
|
}));
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
ensurePackage(pkg: ResPakage, opts?: LoadOptions): Promise<void> {
|
||
|
|
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<void> {
|
||
|
|
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<Node | null> {
|
||
|
|
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<string, ResPakage>;
|
||
|
|
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<typeof mergeLoadOptions>): 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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|