| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- import { Asset, error, instantiate, Node, Prefab, resources, sp, SpriteAtlas, SpriteFrame } from 'cc';
- /** 资源缓存基础数据结构 */
- interface CacheData {
- asset: Asset,
- /** 资源是否需要释放 */
- release: boolean,
- /** 资源最后一次被加载的时间点(秒) */
- lastLoadTime: number,
- }
- /** 预制体资源缓存数据 */
- interface PrefabCacheData extends CacheData {
- /** 此prefab关联的实例节点 */
- nodes?: Node[],
- }
- /**
- * 资源管理类
- *
- * 资源释放要点:
- * 1. 尽量使用此类的接口加载所有资源、instantiate节点实例,否则需要自行管理引用计数
- * 2. Res.instantiate不要对动态生成的节点使用,尽量只instantiate prefab上预设好的节点,否则有可能会导致引用计数的管理出错
- * 3. 调用load接口时如需传入release参数,则同一资源在全局调用load时release参数尽量保持一致,否则可能不符合预期
- * 4. 请使用ResSpine、ResSprite组件去动态加载spine、图片资源,否则需要自行管理这些资源的引用计数
- */
- export default class Res {
- /** 节点与关联prefab路径 */
- private static _nodePath: Map<Node, string> = new Map();
- private static _prefabCache: Map<string, PrefabCacheData> = new Map();
- private static _spriteFrameCache: Map<string, CacheData> = new Map();
- private static _spriteAtlasCache: Map<string, CacheData> = new Map();
- private static _skeletonDataCache: Map<string, CacheData> = new Map();
- private static _otherCache: Map<string, Asset> = new Map();
- /** 资源释放的间隔时间(秒),资源超过此间隔未被load才可释放 */
- public static releaseSec: number = 0;
- /**
- * 通过节点查找对应的缓存prefab url
- * @param node
- */
- private static getCachePrefabUrl(node: Node | Prefab): string {
- let url = "";
- if (node instanceof Prefab) {
- url = node['__resCacheUrl'] || "";
- } else if (node instanceof Node) {
- let cur = node;
- while (cur) {
- if (cur['_prefab'] && cur['_prefab']['root']) {
- url = this._nodePath.get(cur['_prefab']['root']) || "";
- if (url) {
- break;
- }
- }
- cur = cur.parent;
- }
- }
- return url;
- }
- /**
- * 缓存资源
- * @param url 资源路径
- * @param asset 资源
- * @param release 资源是否需要释放
- */
- private static cacheAsset(url: string, asset: Asset, release: boolean = true): void {
- const func = (map: Map<string, CacheData>) => {
- if (map.has(url)) {
- return;
- }
- asset.addRef();
- if (asset instanceof Prefab) {
- asset['__resCacheUrl'] = url;
- }
- const cacheData: CacheData = {
- asset: asset,
- release: release,
- lastLoadTime: Date.now() / 1000
- };
- map.set(url, cacheData);
- };
- if (asset instanceof Prefab) {
- func(this._prefabCache);
- } else if (asset instanceof SpriteFrame) {
- func(this._spriteFrameCache);
- } else if (asset instanceof SpriteAtlas) {
- func(this._spriteAtlasCache);
- } else if (asset instanceof sp.SkeletonData) {
- func(this._skeletonDataCache);
- } else {
- if (this._otherCache.has(url)) {
- return;
- }
- asset.addRef();
- this._otherCache.set(url, asset);
- }
- }
- /**
- * 获取缓存资源。通常不应直接调用此接口,除非调用前能确保资源已加载并且能自行管理引用计数
- * @param url 资源路径
- * @param type 资源类型
- */
- public static get<T extends Asset>(url: string, type: typeof Asset): T {
- let asset: unknown = null;
- const func = (map: Map<string, CacheData>) => {
- const data = map.get(url);
- if (data) {
- asset = data.asset;
- data.lastLoadTime = Date.now() / 1000;
- }
- };
- if (type === Prefab) {
- func(this._prefabCache);
- } else if (type === SpriteFrame) {
- func(this._spriteFrameCache);
- } else if (type === SpriteAtlas) {
- func(this._spriteAtlasCache);
- } else if (type === sp.SkeletonData) {
- func(this._skeletonDataCache);
- } else {
- asset = this._otherCache.get(url);
- }
- return asset as T;
- }
- /**
- * 加载resources文件夹下单个资源
- * @param url 资源路径
- * @param type 资源类型
- * @param release 资源是否需要释放
- */
- public static async load<T extends Asset>(url: string, type: typeof Asset, release: boolean = true): Promise<T> {
- let asset: T = this.get(url, type);
- if (asset) {
- return asset;
- }
- asset = await new Promise((resolve, reject) => {
- resources.load(url, type, (error: Error, resource: T) => {
- if (error) {
- console.error(`[Res.load] error: ${error}`);
- resolve(null);
- } else {
- resolve(resource);
- }
- });
- });
- this.cacheAsset(url, asset, release);
- return asset;
- }
- /**
- * 加载resources文件夹下某个文件夹内某类资源
- * @param url 资源路径
- * @param type 资源类型
- * @param release 资源是否需要释放
- */
- public static loadDir<T extends Asset>(url: string, type: typeof Asset, release: boolean = true): Promise<T[]> {
- return new Promise((resolve, reject) => {
- resources.loadDir(url, type, (error: Error, resource: T[]) => {
- if (error) {
- console.error(`[Res.loadDir] error: ${error}`);
- resolve([]);
- } else {
- const infos = resources.getDirWithPath(url, type);
- resource.forEach((asset, i) => { this.cacheAsset(infos[i].path, asset, release); });
- resolve(resource);
- }
- });
- });
- }
- /**
- * 获取节点实例,建立节点与缓存prefab的联系
- * @param original 用于创建节点的prefab或node
- * @param related 如果original不是动态加载的prefab,则需传入与original相关联的动态加载的prefab或node,便于资源释放的管理
- * @example
- * // A为动态加载的prefab,aNode为A的实例节点(aNode = Res.instantiate(A)),original为被A静态引用的prefab,则调用时需要用如下方式,保证引用关系正确
- * Res.instantiate(original, A)
- * Res.instantiate(original, aNode)
- *
- * // A为动态加载的prefab,aNode为A的实例节点(aNode = Res.instantiate(A)),original为aNode的某个子节点,则如下方式均可保证引用关系正确
- * Res.instantiate(original)
- * Res.instantiate(original, A)
- * Res.instantiate(original, aNode)
- */
- public static instantiate(original: Node | Prefab, related?: Node | Prefab): Node {
- if (!original) {
- error("[Res.instantiate] original is null");
- return null;
- }
- const node = instantiate(original) as Node;
- let cacheData: PrefabCacheData = null;
- const url = this.getCachePrefabUrl(related) || this.getCachePrefabUrl(original);
- if (url) {
- cacheData = this._prefabCache.get(url);
- // release为true才缓存关联节点
- if (cacheData && cacheData.release) {
- if (!Array.isArray(cacheData.nodes)) {
- cacheData.nodes = [];
- }
- cacheData.nodes.push(node);
- this._nodePath.set(node, url);
- }
- }
- return node;
- }
- /**
- * 尝试释放所有缓存资源
- */
- public static releaseAll(): void {
- const nowSec = Date.now() / 1000;
- // prefab
- this._prefabCache.forEach((cacheData, url) => {
- if (!cacheData.release || nowSec - cacheData.lastLoadTime < this.releaseSec) {
- return;
- }
- if (Array.isArray(cacheData.nodes)) {
- for (let i = cacheData.nodes.length - 1; i >= 0; i--) {
- const node = cacheData.nodes[i];
- if (node.isValid) {
- continue;
- }
- this._nodePath.delete(node);
- cacheData.nodes.splice(i, 1);
- }
- if (cacheData.nodes.length === 0) {
- delete cacheData.nodes;
- }
- }
- if (!Array.isArray(cacheData.nodes)) {
- cacheData.asset.decRef();
- this._prefabCache.delete(url);
- }
- });
- // spriteFrame、spriteAtlas、skeletonData
- const arr = [this._spriteFrameCache, this._spriteAtlasCache, this._skeletonDataCache];
- arr.forEach((map) => {
- map.forEach((cacheData, url) => {
- if (!cacheData.release || nowSec - cacheData.lastLoadTime < this.releaseSec) {
- return;
- }
- cacheData.asset.decRef();
- map.delete(url);
- });
- });
-
- }
- }
|