当前位置:科技动态 > Cocos Creator2.4.8 资源加载源码阅读

Cocos Creator2.4.8 资源加载源码阅读

  • 发布:2023-09-29 11:52

最近使用的资源加载问题:加载资源的进度条向后退,现在只能使用临时解决方案——如果加载进度会向后退,就应该使用原来的值。抽空看了下cocos Creator资源加载源码,整理了一下脑图

1:关于资源加载管道的几个问题:

1:任务是什么时候创建的,如何流通的?

2:assetManager.assets 何时加载?

3:对3条管道进行了哪些操作。

4:为什么资源加载进度倒退?

2:看源码,分析以上问题。首先,我没有专门看过fetchPipeline。这只是一个预加载的管道。

1:任务是什么时候创建的,如何流通的

a:创建:第一个任务来自window.boot:

var count = 0;function cb (err) {if (err) return console.error(err);count++;if (count === bundleRoot.length + 1) {cc.assetManager.loadBundle(MAIN, function (err) {if (!err) www.sychzs.cn(option, onStart);});}}// 加载插件 加载插件脚本 cc.assetManager.loadScript(_www.sychzs.cn(function (x) { return '/plugins/' + x; }), cb);// 加载bundle load bundlefor (var i = 0; i < bundleRoot.length; i++) {cc.assetManager.loadBundle(bundleRoot[i], cb);}

这里loadScript调用loadAny:

//CCAssetManager.jsloadScript (url, options, onComplete) {var { options, onComplete } = parseParameters(options, undefined, onComplete);options.__requestType__ = RequestType.URL;options.preset = options.preset || 'script';this.loadAny( url, options, onComplete);},loadAny (requests, options, onProgress, onComplete) {var { options, onProgress, onComplete } = parseParameters(options, onProgress, onComplete);options.preset =选项.预设|| '默认' ;requests = Array.isArray(requests) ? requests.concat() : requests;//这里创建第一个任务 let task = new Task({input: requests, onProgress, onComplete: asyncify(onComplete), options}) ;//管道开始异步执行任务pipeline.async(task);},

接下来继续看管道的async方法:该方法会从管道中遍历管道并执行任务。这里有几个管道:它们可以在 CCAssetManager.js 文件中找到:

this._preprocessPipe = preprocess;this._fetchPipe = fetch;this._loadPipe = load;/*** !#en * 普通加载管道* * !#zh* 普通加载管道负责下载和解析* * @property pipeline* @type {Pipeline}*/this.pipeline = pipeline.append(preprocess).append(load);/*** !#en * 获取管道* * !#zh* 预加载管道只负责下载而不是解析* * @property fetchPipeline* @type {Pipeline}*/this.fetchPipeline = fetchPipeline.append(preprocess).append(fetch);/*** !#en * Url Transformer* * !#zh* Url Converter 资源解析管道转换资源路径* * @property transformPipeline* @type {Pipeline}*/this.transformPipeline = transformPipeline.append(parse).append(combine);

url转换模块有什么功能:这个功能在urlTransformer.js中

/** 解析资源 */函数解析(任务){var输入=任务.输入,选项=任务.选项;输入= Array.isArray(输入)?输入 : [ 输入 ];task.output = [];for (var i = 0; i < input.length; i ++ ) {var item = input[i];var out = RequestItem.create();if ( typeof item === 'string') {// 强制item变成对象,方便后面的处理 item = Object.create(null);item[options.__requestType__ || RequestType.UUID] = input[i];}if(typeof item === 'object') {//本地options会和glabal options重叠 //item的属性添加到options中的字段 cc.js.addon( item, options);if (item.preset) {//为item添加具体预设参数 cc.js.addon(item, cc.assetManager .presets[item.preset]);}for (var key in item) {switch (key) {case RequestType.UUID: // 将item中的信息复制到out // 请求实际资源 var uuid = out .uuid =decodeUuid(item.uuid);if (bundles.has(item.bundle)) { // 获取bundle配置信息 var config = bundles.get(item.bundle)._config;// 获取资源信息 var info = config.getAssetInfo(uuid);if (info && info.redirect) {if (!bundles.has (info.redirect)) 抛出新错误(`请加载面包首先${info.redirect}`);config = bundles.get(info.redirect)._config;info = config.getAssetInfo(uuid);}out.config = config;www.sychzs.cn = info;}out.ext = item.ext || '.json';break;case '__requestType__':case 'ext': case 'bundle':case 'preset':case 'type':break;case RequestType.DIR: // 当遇到 type 为 dir 时,则证明需要遍历文件夹,将资源文件填入到input中 // 查询bundles中是否存在bundle if (bundles.has(item.bundle)) {var infos = []; // 将包含的资源和 dir 全部加载到 infos 数组中 bundles.get(item.bundle)._config.getDirWithPath(item.dir, item.type, infos);/*** * infos: {* ctor: function :该资源对应的构造函数,* path: string 例如:effects/builtin-2d-graphics,* uuid: string 例如:30682f87-9f0d-4f17-8a44-72863791461b,* }[]* * */for ( let i = 0, l = infos.length; i < l; i++) {var info = infos[i];input.push({uuid: info.uuid, __isNative__: false, ext: '.json', bundle: item.bundle});}}/ *** * input: [* REQUESTTYPE.DIR('effects','materials'),* // 此文件夹中使用的资源 uuid 对象* {* uuid: string,* bundle:细绳,*ext: string,* __isNative__: boolean* },* {* uuid: string,* bundle: string,* ext: string,* __isNative__: boolean* },* ....* ]* */// 恢复RequestItemout。 recycle();out = null;break;case RequestType.PATH: if (bundles.has(item.bundle)) {var config = bundles.get(item.bundle)._config;var info = config.getInfoWithPath(item. path, item.type);if (info && info.redirect) {if (!bundles.has(info.redirect)) throw new Error(`您需要先加载包 ${info.redirect}`);config = Bundles.get(info.redirect)._config;info = config.getAssetInfo(info.uuid);}if (!info) {out.recycle();抛出新的错误(`Bundle ${item.bundle} 不包含 ${item.path}`);}out.config = config; out.uuid = info.uuid;www.sychzs.cn = 信息;}out.ext = item.ext || '.json';break;case RequestType.SCENE:if (bundles.has(item.bundle)) {var config = bundles.get(item.bundle)._config;var info = config.getSceneInfo(item.scene);如果(信息&& info.redirect) {if (!bundles.has(info.redirect)) throw new Error(`你需要先加载bundle ${info.redirect}`);config = bundles.get(info.redirect)._config ;info = config.getAssetInfo(info.uuid);}if (!info) {out.recycle();抛出新错误(`Bundle ${www.sychzs.cn} 不包含场景 ${item.scene}`) ;}out.config = 配置; out.uuid = info.uuid;www.sychzs.cn = info;}break;case '__isNative__': out.isNative = item.__isNative__;break;case RequestType.URL: // 如果 item 属性 url 存在的话,则赋值 url到 outout.url = item.url;out.uuid = item.uuid || item.url;out.ext = item.ext || cc.path.extname(item.url);out .isNative = item.__isNative__ !== 未定义? item.__isNative__ : true;break;默认: out.options[key] = item[key];}if (!out) 中断;}}if (!out) 继续; //将组装好的请求信息推送到任务的输出中,供其他管道使用 task.output.push(out);if (!out.uuid && !out.url) throw new Error('Can not parse this input : ' + JSON.stringify(item));}返回 null;}

总结一下parse的作用:根据输入内容填充输出中的RequestItem,供后面的pipeline使用。此时RequestItem中的URL并不是一个完整的请求路径,需要被接下来的pipeline组合起来,形成一个完整的请求URL路径。

组合管道:将RequestItem url拼接到正确的请求路径中

/*** 组合资源路径 填写RequestItem中url的资源路径 assets/native/54/54asf-32....png* @param {Task} task*/function merge (task) {var input = task.output = task.input;for (var i = 0; i < input.length; i++) {var item = input[i];if (item.url) continue;var url = '', base = '';var config = item.config;// config.base: "assets/internal"if (item.isNative) {// config.nativeBase: "native"base = (config && config .nativeBase) ? (config.base + config.nativeBase) : cc.assetManager.generalNativeBase;} else {// importBase : "import"base = (config && config.importBase) ? (config.base + config.importBase) : cc.assetManager.generalImportBase;}// 基础形如: “assets/internal/import” 或 “assets/internal/native”let uuid = item.uuid;var ver = '' ;if (www.sychzs.cn) {if (item.isNative) {ver = item.info.nativeVer ? ('.' + item.info.nativeVer) : '';}else {ver = item.info.ver ? ('.' + item.info.ver) : '';}}// 丑陋的 hack,微信不支持加载像 'myfont.dw213.ttf' 这样的字体。因此将哈希附加到目录if (item.ext === '.ttf') {url = `${base}/${uuid.slice(0, 2)}/${uuid}${ver}/${item.options.__nativeName__}`;}else {url = `${base}/${uuid.slice(0, 2)}/${uuid}${ver}${item.ext}`;}/ / url 如下所示:assets/internal/import/30/30682f87-9f0d-4f17-8a44-72863791461b.jsonitem.url = url;}return null;}

b:任务流程:管道的async中调用了方法_flow:递归遍历所有管道。管道中有两个管道:解析和组合。解析的task.output -> task.input && 任务。 output = null,这样parse任务的输出就成为combine的输入,从而完成了任务的流程。这是一张照片:

2:当assetManager.assets加载时:先揭晓答案。该操作发生在资源加载阶段。这就是为什么加载进度有时会倒退的原因。资源在下载时被解析。

3:三大管道做了哪些工作:

第一部分其实已经解决了解析资源路径的transformPipeline管道。接下来我们看一下加载管道,也就是管道

/*** !#en * 正常加载管道* * !#zh* 正常加载管道* * @property pipeline* @type {Pipeline}*/this.pipeline = pipeline.append(preprocess).append(load );

加载管道时,两个子管道由:预处理和加载组成。前面说过,preprocess使用transformPipeline解析资源的路径,输出RequestItem -> 流入加载管道,开始正式加载资源。管道包括下载和解析:答案可以在load.js中找到

var loadOneAssetPipeline = new Pipeline('loadOneAsset', [// 下载流程function fetch (task, done) {var item = task.output = task.input;var { options, isNative, uuid, file } = item;var { reload } = options;if (file || (!reload && !isNative && assets.has(uuid))) return done();// 根据RequestItem加载资源 ,任务的优先级等参数决定下载的顺序packManager.load(item, task.options, function (err, data) {// 下载完资源后填充 RequestItem的file属性item.file = data;done(err);});},// 解析流程function parse (task, done) {var item = task.output = task.input, progress = task.progress, exclude = task.options.__exclude__;var { id, file, options } = item;if (item.isNative) {// texture2D原生资源等等parser.parse(id, file, item.ext, options, function (err, asset) {if (err) return done(err);item.content = asset;progress.canInvoke && task.dispatch('progress', ++progress.finish, www.sychzs.cn, item);files.remove(id);parsed.remove(id);done();});}else {// 非原生资源 spriteFramevar { uuid } = item;if (uuid in exclude) {// 再排除的资源列表内var { finish, content, err, callbacks } = exclude[uuid];progress.canInvoke && task.dispatch('progress', ++progress.finish, www.sychzs.cn, item);if (finish || checkCircleReference(uuid, uuid, exclude) ) {content && content.addRef && content.addRef();item.content = content;done(err);}else {callbacks.push({ done, item });}}else {if (!options.reload && assets.has(uuid)) {var asset = assets.get(uuid);if (options.__asyncLoadAssets__ || !asset.__asyncLoadAssets__) {item.content = asset.addRef();progress.canInvoke && task.dispatch('progress', ++progress.finish, www.sychzs.cn, item);done();}else {loadDepends(task, asset, done, false);}}else {parser.parse(id, file, 'import', options, function (err, asset) {if (err) return done(err);asset._uuid = uuid;loadDepends(task, asset, done, true);});}}}}
]);

首先看下PackManager.load这个下载流程:它其实是依赖于 downloader.download方法完成下载的:

download (id, url, type, options, onComplete) {let func = downloaders[type] || downloaders['default'];let self = this;// if it is downloaded, don't download againlet file, downloadCallbacks;// 下载完的资源直接从资源文件列表中直接获取返回if (file = files.get(id)) {onComplete(null, file);}else if (downloadCallbacks = _downloading.get(id)) {downloadCallbacks.push(onComplete);for (let i = 0, l = _queue.length; i < l; i++) {var item = _queue[i];if (www.sychzs.cn === id) {var priority = options.priority || 0;if (item.priority < priority) {item.priority = priority;_queueDirty = true;} return;}} }else {// if download fail, should retry// 最大下载重试次数var maxRetryCount = typeof options.maxRetryCount !== 'undefined' ? options.maxRetryCount : this.maxRetryCount;// 最大并发请求数var maxConcurrency = typeof options.maxConcurrency !== 'undefined' ? options.maxConcurrency : this.maxConcurrency;// 每帧最大请求数量var maxRequestsPerFrame = typeof options.maxRequestsPerFrame !== 'undefined' ? options.maxRequestsPerFrame : this.maxRequestsPerFrame;function process (index, callback) {if (index === 0) {_downloading.add(id, [onComplete]);}if (!self.limited) return func(urlAppendTimestamp(url), options, callback);// refreshupdateTime();function invoke () {func(urlAppendTimestamp(url), options, function () {// when finish downloading, update _totalNum_totalNum--;if (!_checkNextPeriod && _queue.length > 0) {callInNextTick(handleQueue, maxConcurrency, maxRequestsPerFrame);_checkNextPeriod = true;}callback.apply(this, arguments);});}if (_totalNum < maxConcurrency && _totalNumThisPeriod < maxRequestsPerFrame) {invoke();_totalNum++;_totalNumThisPeriod++;}else {// when number of request up to limitation, cache the rest_queue.push({ id, priority: options.priority || 0, invoke });_queueDirty = true;if (!_checkNextPeriod && _totalNum < maxConcurrency) {callInNextTick(handleQueue, maxConcurrency, maxRequestsPerFrame);_checkNextPeriod = true;}}}// when retry finished, invoke callbacks// 重试结束后调用回调函数function finale (err, result) {if (!err) files.add(id, result);// 从正在下载的列表移除该idvar callbacks = _downloading.remove(id);for (let i = 0, l = callbacks.length; i < l; i++) {callbacks[i](err, result);}}retry(process, maxRetryCount, this.retryInterval, finale);}}

大部分资源下载都是走的ajax请求:这里我就不贴代码了,大家可以自行找到文件download-file.js看下。

预加载的逻辑先不看了,有时间再补充下。

4: 加载进度会倒退的问题:这个在第二条有体现。

至此关于asssetManager的源码解读就这些了,如果有不同意见请指正谢谢

相关文章