激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

node.js|vue.js|jquery|angularjs|React|json|js教程|

服務器之家 - 編程語言 - JavaScript - js教程 - CocosCreator通用框架設計之資源管理

CocosCreator通用框架設計之資源管理

2022-03-05 20:20深圳-寶爺 js教程

這篇文章主要介紹了CocosCreator通用框架設計之資源管理,對性能優化感興趣的同學,一定要看一下

如果你想使用Cocos Creator制作一些規模稍大的游戲,那么資源管理是必須解決的問題,隨著游戲的進行,你可能會發現游戲的內存占用只升不降,哪怕你當前只用到了極少的資源,并且有使用cc.loader.release來釋放之前加載的資源,但之前使用過的大部分資源都會留在內存中!為什么會這樣呢?

cocos creator 資源管理存在的問題

資源管理主要解決3個問題,資源加載,資源查找(使用),資源釋放。這里要討論的主要是資源釋放的問題,這個問題看上去非常簡單,在Cocos2d-x中確實也很簡單,但在js中變得復雜了起來,因為難以跟蹤一個資源是否可以被釋放。

在Cocos2d-x中我們使用引用計數,在引用計數為0的時候釋放資源,維護好引用計數即可,而且在Cocos2d-x中我們對資源的管理是比較分散的,引擎層面只提供如TextureCache、AudioManager之類的單例來管理某種特定的資源,大多數的資源都需要我們自己去管理,而在cocos creator中,我們的資源統一由cc.loader來管理,大量使用prefab,prefab與各種資源復雜的引用關系增加了資源管理的難度。

資源依賴

資源A可能依賴資源B、C、D,而資源D又依賴資源E,這是非常常見的一種資源依賴情況,如果我們使用cc.loader.loadRes("A")加載資源A,B~E都會被加載進來,但如果我們調用cc.loader.release("A")則只有資源A被釋放。

CocosCreator通用框架設計之資源管理

每一個加載的資源都會放到cc.loader的_cache中,但cc.loader.release只是將傳入的資源進行釋放,而沒有考慮資源依賴的情況。

如果對cc.loader背后的資源加載流程感興趣可以參考: https://www.cnblogs.com/ybgame/p/10576884.html

如果我們希望將依賴的資源也一起釋放,cocos creator提供了一個笨拙的方法,cc.loader.getDependsRecursively;,遞歸獲取指定資源依賴的所有資源,放入一個數組并返回,然后在cc.loader.release中傳入該數組,cc.loader會遍歷它們,將其逐個釋放。

這種方式雖然可以將資源釋放,但卻有可能釋放了不應該釋放的資源,如果有一個資源F依賴D,這時候就會導致F資源無法正常工作。由于cocos creator引擎沒有維護好資源的依賴,導致我們在釋放D的時候并不知道還有F依賴我們。即使沒有F依賴,我們也不確定是否可以釋放D,比如我們調用cc.loader加載D,而后又加載了A,此時D已經加載完成,A可以直接使用。但如果釋放A的時候,將D也釋放了,這就不符合我們的預期,我們期望的是在我們沒有顯式地釋放D時,D不應該隨著其它資源的釋放而自動釋放。

可以簡單地進行測試,可以打開Chrome的開發者模式,在Console面板中進行輸入,如果是舊版本的cocos creator可以在cc.textureCache中dump所有的紋理,而新版本移除了textureCache,但我們可以輸入cc.loader._cache來查看所有的資源。如果資源太多,只關心數量,可以輸入Object.keys(cc.loader._cache).length來查看資源總數,我們可以在資源加載前dump一次,加載后dump一次,釋放后再dump一次,來對比cc.loader中的緩存狀態。當然,也可以寫一些便捷的方法,如只dump圖片,或者dump與上次dump的差異項。

CocosCreator通用框架設計之資源管理

資源使用

除了資源依賴的問題,我們還需要解決資源使用的問題,前者是cc.loader內部的資源組織問題,后者是應用層邏輯的資源使用問題,比如我們需要在一個界面關閉的時候釋放某資源,同樣會面臨一個該不該釋放的問題,比如另外一個未關閉的界面是否使用了該資源?如果有其他地方用到了該資源,那么就不應該釋放它!

ResLoader

在這里我設計了一個ResLoader,來解決cc.loader沒有解決好的問題,關鍵是為每一個資源創建一個CacheInfo來記錄資源的依賴和使用等信息,以此來判斷資源是否可以釋放,使用ResLoader.getInstance().loadRes()來替代cc.loader.loadRes(),ResLoader.getInstance().releaseRes()來替代cc.loader.releaseRes()。

對于依賴,在資源加載的時候ResLoader會自動建立起映射,釋放資源的時候會自動取消映射,并檢測取消映射后的資源是否可以釋放,是才走釋放的邏輯。

對于使用,提供了一個use參數,通過該參數來區別是哪里使用了該資源,以及是否有其他地方使用了該資源,當一個資源即沒有倍其他資源依賴,也沒有被其它邏輯使用,那么這個資源就可以被釋放。

/**
 * 資源加載類
 * 1. 加載完成后自動記錄引用關系,根據DependKeys記錄反向依賴
 * 2. 支持資源使用,如某打開的UI使用了A資源,其他地方釋放資源B,資源B引用了資源A,如果沒有其他引用資源A的資源,會觸發資源A的釋放,
 * 3. 能夠安全釋放依賴資源(一個資源同時被多個資源引用,只有當其他資源都釋放時,該資源才會被釋放)
 * 
 * 2018-7-17 by 寶爺
 */

// 資源加載的處理回調
export type ProcessCallback = (completedCount: number, totalCount: number, item: any) => void;
// 資源加載的完成回調
export type CompletedCallback = (error: Error, resource: any) => void;

// 引用和使用的結構體
interface CacheInfo {
    refs: Set<string>,
    uses: Set<string>
}

// LoadRes方法的參數結構
interface LoadResArgs {
    url: string,
    type?: typeof cc.Asset,
    onCompleted?: CompletedCallback,
    onProgess?: ProcessCallback,
    use?: string,
}

// ReleaseRes方法的參數結構
interface ReleaseResArgs {
    url: string,
    type?: typeof cc.Asset,
    use?: string,
}

// 兼容性處理
let isChildClassOf = cc.js["isChildClassOf"]
if (!isChildClassOf) {
    isChildClassOf = cc["isChildClassOf"];
}

export default class ResLoader {

    private _resMap: Map<string, CacheInfo> = new Map<string, CacheInfo>();
    private static _resLoader: ResLoader = null;
    public static getInstance(): ResLoader {
        if (!this._resLoader) {
            this._resLoader = new ResLoader();
        }
        return this._resLoader;
    }

    public static destroy(): void {
        if (this._resLoader) {
            this._resLoader = null;
        }
    }

    private constructor() {

    }

    /**
     * 從cc.loader中獲取一個資源的item
     * @param url 查詢的url
     * @param type 查詢的資源類型
     */
    private _getResItem(url: string, type: typeof cc.Asset): any {
        let ccloader: any = cc.loader;
        let item = ccloader._cache[url];
        if (!item) {
            let uuid = ccloader._getResUuid(url, type, false);
            if (uuid) {
                let ref = ccloader._getReferenceKey(uuid);
                item = ccloader._cache[ref];
            }
        }
        return item;
    }

    /**
     * loadRes方法的參數預處理
     */
    private _makeLoadResArgs(): LoadResArgs {
        if (arguments.length < 1 || typeof arguments[0] != "string") {
            console.error(`_makeLoadResArgs error ${arguments}`);
            return null;
        }
        let ret: LoadResArgs = { url: arguments[0] };
        for (let i = 1; i < arguments.length; ++i) {
            if (i == 1 && isChildClassOf(arguments[i], cc.RawAsset)) {
                // 判斷是不是第一個參數type
                ret.type = arguments[i];
            } else if (i == arguments.length - 1 && typeof arguments[i] == "string") {
                // 判斷是不是最后一個參數use
                ret.use = arguments[i];
            } else if (typeof arguments[i] == "function") {
                // 其他情況為函數
                if (arguments.length > i + 1 && typeof arguments[i + 1] == "function") {
                    ret.onProgess = arguments[i];
                } else {
                    ret.onCompleted = arguments[i];
                }
            }
        }
        return ret;
    }

    /**
     * releaseRes方法的參數預處理
     */
    private _makeReleaseResArgs(): ReleaseResArgs {
        if (arguments.length < 1 || typeof arguments[0] != "string") {
            console.error(`_makeReleaseResArgs error ${arguments}`);
            return null;
        }
        let ret: ReleaseResArgs = { url: arguments[0] };
        for (let i = 1; i < arguments.length; ++i) {
            if (typeof arguments[i] == "string") {
                ret.use = arguments[i];
            } else {
                ret.type = arguments[i];
            }
        }
        return ret;
    }

    /**
     * 生成一個資源使用Key
     * @param where 在哪里使用,如Scene、UI、Pool
     * @param who 使用者,如Login、UIHelp...
     * @param why 使用原因,自定義...
     */
    public static makeUseKey(where: string, who: string = "none", why: string = ""): string {
        return `use_${where}_by_${who}_for_${why}`;
    }

    /**
     * 獲取資源緩存信息
     * @param key 要獲取的資源url
     */
    public getCacheInfo(key: string): CacheInfo {
        if (!this._resMap.has(key)) {
            this._resMap.set(key, {
                refs: new Set<string>(),
                uses: new Set<string>()
            });
        }
        return this._resMap.get(key);
    }

    /**
     * 開始加載資源
     * @param url           資源url
     * @param type          資源類型,默認為null
     * @param onProgess     加載進度回調
     * @param onCompleted   加載完成回調
     * @param use           資源使用key,根據makeUseKey方法生成
     */
    public loadRes(url: string, use?: string);
    public loadRes(url: string, onCompleted: CompletedCallback, use?: string);
    public loadRes(url: string, onProgess: ProcessCallback, onCompleted: CompletedCallback, use?: string);
    public loadRes(url: string, type: typeof cc.Asset, use?: string);
    public loadRes(url: string, type: typeof cc.Asset, onCompleted: CompletedCallback, use?: string);
    public loadRes(url: string, type: typeof cc.Asset, onProgess: ProcessCallback, onCompleted: CompletedCallback, use?: string);
    public loadRes() {
        let resArgs: LoadResArgs = this._makeLoadResArgs.apply(this, arguments);
        console.time("loadRes|"+resArgs.url);
        let finishCallback = (error: Error, resource: any) => {
            // 反向關聯引用(為所有引用到的資源打上本資源引用到的標記)
            let addDependKey = (item, refKey) => {
                if (item && item.dependKeys && Array.isArray(item.dependKeys)) {
                    for (let depKey of item.dependKeys) {
                        // 記錄該資源被我引用
                        this.getCacheInfo(depKey).refs.add(refKey);
                        // cc.log(`${depKey} ref by ${refKey}`);
                        let ccloader: any = cc.loader;
                        let depItem = ccloader._cache[depKey]
                        addDependKey(depItem, refKey)
                    }
                }
            }

            let item = this._getResItem(resArgs.url, resArgs.type);
            if (item && item.url) {
                addDependKey(item, item.url);
            } else {
                cc.warn(`addDependKey item error1! for ${resArgs.url}`);
            }

            // 給自己加一個自身的引用
            if (item) {
                let info = this.getCacheInfo(item.url);
                info.refs.add(item.url);
                // 更新資源使用
                if (resArgs.use) {
                    info.uses.add(resArgs.use);
                }
            }

            // 執行完成回調
            if (resArgs.onCompleted) {
                resArgs.onCompleted(error, resource);
            }
            console.timeEnd("loadRes|"+resArgs.url);
        };

        // 預判是否資源已加載
        let res = cc.loader.getRes(resArgs.url, resArgs.type);
        if (res) {
            finishCallback(null, res);
        } else {
            cc.loader.loadRes(resArgs.url, resArgs.type, resArgs.onProgess, finishCallback);
        }
    }

    /**
     * 釋放資源
     * @param url   要釋放的url
     * @param type  資源類型
     * @param use   要解除的資源使用key,根據makeUseKey方法生成
     */
    public releaseRes(url: string, use?: string);
    public releaseRes(url: string, type: typeof cc.Asset, use?: string)
    public releaseRes() {
        /**暫時不釋放資源 */
        // return;

        let resArgs: ReleaseResArgs = this._makeReleaseResArgs.apply(this, arguments);
        let item = this._getResItem(resArgs.url, resArgs.type);
        if (!item) {
            console.warn(`releaseRes item is null ${resArgs.url} ${resArgs.type}`);
            return;
        }
        cc.log("resloader release item");
        // cc.log(arguments);
        let cacheInfo = this.getCacheInfo(item.url);
        if (resArgs.use) {
            cacheInfo.uses.delete(resArgs.use)
        }
        this._release(item, item.url);
    }

    // 釋放一個資源
    private _release(item, itemUrl) {
        if (!item) {
            return;
        }
        let cacheInfo = this.getCacheInfo(item.url);
        // 解除自身對自己的引用
        cacheInfo.refs.delete(itemUrl);

        if (cacheInfo.uses.size == 0 && cacheInfo.refs.size == 0) {
            // 解除引用
            let delDependKey = (item, refKey) => {
                if (item && item.dependKeys && Array.isArray(item.dependKeys)) {
                    for (let depKey of item.dependKeys) {
                        let ccloader: any = cc.loader;
                        let depItem = ccloader._cache[depKey]
                        this._release(depItem, refKey);
                    }
                }
            }
            delDependKey(item, itemUrl);
            //如果沒有uuid,就直接釋放url
            if (item.uuid) {
                cc.loader.release(item.uuid);
                cc.log("resloader release item by uuid :" + item.url);
            } else {
                cc.loader.release(item.url);
                cc.log("resloader release item by url:" + item.url);
            }
        }
    }

    /**
     * 判斷一個資源能否被釋放
     * @param url 資源url
     * @param type  資源類型
     * @param use   要解除的資源使用key,根據makeUseKey方法生成
     */
    public checkReleaseUse(url: string, use?: string): boolean;
    public checkReleaseUse(url: string, type: typeof cc.Asset, use?: string): boolean
    public checkReleaseUse() {
        let resArgs: ReleaseResArgs = this._makeReleaseResArgs.apply(this, arguments);
        let item = this._getResItem(resArgs.url, resArgs.type);
        if (!item) {
            console.log(`cant release,item is null ${resArgs.url} ${resArgs.type}`);
            return true;
        }

        let cacheInfo = this.getCacheInfo(item.url);
        let checkUse = false;
        let checkRef = false;

        if (resArgs.use && cacheInfo.uses.size > 0) {
            if (cacheInfo.uses.size == 1 && cacheInfo.uses.has(resArgs.use)) {
                checkUse = true;
            } else {
                checkUse = false;
            }
        } else {
            checkUse = true;
        }

        if ((cacheInfo.refs.size == 1 && cacheInfo.refs.has(item.url)) || cacheInfo.refs.size == 0) {
            checkRef = true;
        } else {
            checkRef = false;
        }

        return checkUse && checkRef;
    }
}

使用ResLoader

ResLoader的使用非常簡單,下面是一個簡單的例子,我們可以點擊dump按鈕來查看當前的資源總數,點擊cc.load、cc.release之后分別dump一次,可以發現,開始有36個資源,加載之后有40個資源,而執行釋放之后,還有39個資源,只釋放了一個資源。

如果使用ResLoader進行測試,發現釋放之后只有34個資源,這是因為前面加載場景的資源也被該測試資源依賴,所以這些資源也被釋放掉了,只要我們都使用ResLoader來加載和卸載資源,就不會出現資源泄露的問題。

CocosCreator通用框架設計之資源管理

示例代碼:

@ccclass
export default class NetExample extends cc.Component {
    @property(cc.Node)
    attachNode: cc.Node = null;
    @property(cc.Label)
    dumpLabel: cc.Label = null;

    onLoadRes() {
        cc.loader.loadRes("Prefab/HelloWorld", cc.Prefab, (error: Error, prefab: cc.Prefab) => {
            if (!error) {
                cc.instantiate(prefab).parent = this.attachNode;
            }
        });
    }

    onUnloadRes() {
        this.attachNode.removeAllChildren(true);
        cc.loader.releaseRes("Prefab/HelloWorld");
    }

    onMyLoadRes() {
        ResLoader.getInstance().loadRes("Prefab/HelloWorld", cc.Prefab, (error: Error, prefab: cc.Prefab) => {
            if (!error) {
                cc.instantiate(prefab).parent = this.attachNode;
            }
        });
    }

    onMyUnloadRes() {
        this.attachNode.removeAllChildren(true);
        ResLoader.getInstance().releaseRes("Prefab/HelloWorld");
    }

    onDump() {
        let Loader:any = cc.loader;
        this.dumpLabel.string = `當前資源總數:${Object.keys(Loader._cache).length}`;
    }
}

可以看到上面的例子是先移除節點,再進行釋放,這是正確的使用方式,如果我沒有移除直接釋放呢??因為釋放了紋理,所以cocos creator在接下來的渲染中會不斷報錯。

ResLoader只是一個基礎,直接使用ResLoader我們不需要關心資源的依賴問題,但資源的使用問題我們還需要關心,在實際的使用中,我們可能希望資源的生命周期是以下幾種情況:

  • 跟隨某對象的生命周期,對象銷毀時資源釋放
  • 跟隨某界面的生命周期,界面關閉時資源釋放
  • 跟隨某場景的生命周期,場景切換時資源釋放

我們可以實現一個組件掛在到對象身上,當我們在該對象或該對象的其它組件中編寫邏輯,加載資源時,使用這個資源管理組件進行加載,由該組件來維護資源的釋放。界面和場景也類似。。

項目代碼位于:https://github.com/wyb10a10/cocos_creator_framework ,打開Scene目錄的ResExample場景即可查看。

以上就是CocosCreator通用框架設計之資源管理的詳細內容,更多關于CocosCreator框架設計之資源管理的資料請關注服務器之家其它相關文章!

原文鏈接:https://www.cnblogs.com/ybgame/p/11711086.html

延伸 · 閱讀

精彩推薦
  • js教程uniapp開發小程序的經驗總結

    uniapp開發小程序的經驗總結

    這篇文章主要給大家介紹了關于uniapp開發小程序的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的...

    東瓜東瓜5862022-02-25
  • js教程js 創建對象的多種方式與優缺點小結

    js 創建對象的多種方式與優缺點小結

    這篇文章主要介紹了js 創建對象的多種方式與優缺點,幫助大家更好的理解和學習使用JavaScript,感興趣的朋友可以了解下...

    feng12062022-02-15
  • js教程JS+JQuery實現無縫連接輪播圖

    JS+JQuery實現無縫連接輪播圖

    這篇文章主要介紹了JS+JQuery實現無縫連接輪播圖,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下...

    南柯Seven7742021-12-23
  • js教程js中延遲加載和預加載的具體使用

    js中延遲加載和預加載的具體使用

    這篇文章主要介紹了js中延遲加載和預加載的具體使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友...

    Yushia5902021-12-31
  • js教程原生JavaScript實現輪播圖

    原生JavaScript實現輪播圖

    這篇文章主要為大家詳細介紹了原生JavaScript實現輪播圖,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下...

    棟棟很優秀啊7042021-12-29
  • js教程原生Js實現日歷掛件

    原生Js實現日歷掛件

    這篇文章主要為大家詳細介紹了原生Js實現日歷掛件,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下...

    清靜清源11972022-02-17
  • js教程javascript類數組的深入理解

    javascript類數組的深入理解

    這篇文章主要給大家介紹了關于javascript類數組的深入理解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋...

    ら淡然如雪11412022-02-15
  • js教程以淘寶店鋪為例,談談 TypeScript ESLint 規則集考量

    以淘寶店鋪為例,談談 TypeScript ESLint 規則集考量

    通過這篇文章,你會了解到在制定規則時我們考慮的是什么,對于 TypeScript 代碼進行約束的思考,以及如何在自己的團隊內推廣這一套規則。...

    三元同學7712022-01-10
主站蜘蛛池模板: 粉嫩粉嫩一区二区三区在线播放 | 黑人三级毛片 | 欧美乱淫| 久久九九热re6这里有精品 | 99久久久国产 | 欧美性成人| 黄色免费视频观看 | 一区二区久久精品66国产精品 | 色诱亚洲精品久久久久久 | 毛片a级毛片免费播放100 | 巨乳激情 | 99国产精品欲a | 在线播放污 | 91精品国产99久久久久久 | 99在线精品视频免费观看20 | 一级做a爱片久久 | 久久精品国产99久久6动漫亮点 | 一级做a爱片毛片免费 | 一级性生活视频 | hd极品free性xxx一护士 | 91九色网址 | 久久蜜桃精品一区二区三区综合网 | 深夜影院一级毛片 | 欧美成人精品一区二区三区 | 国产美女爽到喷白浆的 | 亚洲午夜免费电影 | 亚洲综合精品 | 日本高清com | 久国产 | 美国人成人在线视频 | 成人三级电影在线 | 成人免费观看在线 | 亚洲综合色视频在线观看 | 国产精品久久久久久久四虎电影 | 黄色片免费看看 | 色诱亚洲精品久久久久久 | 制服丝袜成人动漫 | 亚洲一区国产一区 | 国产一区二区三区网站 | 日本高清黄色片 | 久久精品视频一区二区三区 |