CocosCreator 유 니 버 설 프레임 워 크 디자인 의 자원 관리
18411 단어 Cocos 프레임 워 크자원 관리
cocos creator 자원 관리 에 존재 하 는 문제점
자원 관 리 는 주로 세 가지 문 제 를 해결 하고 자원 로드,자원 찾기(사용),자원 방출 을 해결 합 니 다.여기 서 토론 하고 자 하 는 것 은 주로 자원 방출 문제 이다.이 문 제 는 매우 간단 해 보이 고 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 만 방출 됩 니 다.불 러 오 는 모든 자원 은 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 의 캐 시 상 태 를 비교 할 수 있 습 니 다.물론 덤 프 그림 이나 덤 프 와 지난번 덤 프 의 차이 점 등 편리 한 방법 도 쓸 수 있다.
자원 사용
자원 의존 문 제 를 제외 하고 우 리 는 자원 사용 문 제 를 해결 해 야 한다.전 자 는 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 를 사용 하여 자원 을 불 러 오고 마 운 트 해제 하면 자원 유출 문제 가 발생 하지 않 습 니 다.
예제 코드:
@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 프레임 워 크 디자인 의 자원 관리 에 관 한 자 료 는 저희 의 다른 관련 글 을 주목 해 주 십시오!