赞
踩
项目有个需求,需要缓存dundle内容,但是其实loadBundle下来的是一个配置文件,并不是实际上用到的资源,所以在想,是否可以直接吧资源下下来找一下原理;就研究了一下cocos的源码;
参考‘宝爷’的这个帖子:https://forum.cocos.org/t/cocos-creator/100107
看源码前我们先了解一下cocos的管线和任务:https://docs.cocos.com/creator/manual/zh/asset-manager/pipeline-task.html?h=pipe
- AssetManager.js中的3条管线:
- /**
- * !#en
- * Normal loading pipeline
- *
- * !#zh
- * 正常加载管线
- *
- * @property pipeline
- * @type {Pipeline}
- */
- this.pipeline = pipeline.append(preprocess).append(load);
-
- /**
- * !#en
- * Fetching pipeline
- *
- * !#zh
- * 下载管线
- *
- * @property fetchPipeline
- * @type {Pipeline}
- */
- this.fetchPipeline = fetchPipeline.append(preprocess).append(fetch);
-
- /**
- * !#en
- * Url transformer
- *
- * !#zh
- * Url 转换器
- *
- * @property transformPipeline
- * @type {Pipeline}
- */
- this.transformPipeline = transformPipeline.append(parse).append(combine);
开始吧,当我们cc.assetManager.loadBundle的时候,引擎在做些什么:
- AssetManager.js
- loadBundle(nameOrUrl, options, onComplete) {
- //当options为undefined时,将回调函数由options变成onComplete;
- var { options, onComplete } = parseParameters(options, undefined, onComplete);
- //获取bundle的名字;
- let bundleName = cc.path.basename(nameOrUrl);
- //本地检测;
- if (this.bundles.has(bundleName)) {
- return asyncify(onComplete)(null, this.getBundle(bundleName));
- }
- //是否有自定义预设,没有就用bundle;
- options.preset = options.preset || 'bundle';
- options.ext = 'bundle';
- this.loadRemote(nameOrUrl, options, onComplete);
- },
-
-
- loadRemote(url, options, onComplete) {
- var { options, onComplete } = parseParameters(options, undefined, onComplete);
- //本地检测;
- if (this.assets.has(url)) {
- return asyncify(onComplete)(null, this.assets.get(url));
- }
-
- options.__isNative__ = true;
- options.preset = options.preset || 'remote';
- //下载;
- this.loadAny({ url }, options, null, function (err, data) {
- if (err) {
- cc.error(err.message, err.stack);
- onComplete && onComplete(err, null);
- }
- else {
- factory.create(url, data, options.ext || cc.path.extname(url), options, function (err, out) {
- onComplete && onComplete(err, out);
- });
- }
- });
- },
接下来管线和任务登场了
- loadAny(requests, options, onProgress, onComplete) {
- var { options, onProgress, onComplete } = parseParameters(options, onProgress, onComplete);
- options.preset = options.preset || 'default';
- requests = Array.isArray(requests) ? requests.concat() : requests;
- //新建一个任务;
- let task = new Task({ input: requests, onProgress, onComplete: asyncify(onComplete), options });
- //加载管线去处理任务,注意这里的是3条管线中的加载管线;
- pipeline.async(task);
- },
-
- async(task) {
- var pipes = this.pipes;
- if (!(task instanceof Task) || pipes.length === 0) return;
- //设置任务输入;
- if (task.output != null) {
- task.input = task.output;
- task.output = null;
- }
- task._isFinish = false;
- this._flow(0, task);
- },
-
- _flow(index, task) {
- var self = this;
- /*
- 回到上面的这里this.pipeline = pipeline.append(preprocess).append(load);
- 加载管线有preprocess和load两部分;
- preprocess实际上只创建了一个子任务,然后交由transformPipeline执行,得到一个RequestItem对象并赋值给task.output;
- */
- var pipe = this.pipes[index];
- pipe(task, function (result) {
- if (result) {
- task._isFinish = true;
- task.onComplete && task.onComplete(result);
- }
- else {
- index++;
- if (index < self.pipes.length) {
- // move output to input
- task.input = task.output;
- task.output = null;
- self._flow(index, task);
- }
- else {
- task._isFinish = true;
- task.onComplete && task.onComplete(result, task.output);
- }
- }
- });
- }
正常加载管线pipeline中的preprocess任务,通过转换器管线transformPipeline获取一个RequestItem对象并赋值给task.output;
- function preprocess(task, done) {
- var options = task.options,
- subOptions = Object.create(null),
- leftOptions = Object.create(null);
-
- for (var op in options) {
- switch (op) {
- // can't set these attributes in options
- case RequestType.PATH:
- case RequestType.UUID:
- case RequestType.DIR:
- case RequestType.SCENE:
- case RequestType.URL: break;
- // only need these attributes to transform url
- case '__requestType__':
- case '__isNative__':
- case 'ext':
- case 'type':
- case '__nativeName__':
- case 'audioLoadMode':
- case 'bundle':
- subOptions[op] = options[op];
- break;
- // other settings, left to next pipe
- case '__exclude__':
- case '__outputAsArray__':
- leftOptions[op] = options[op];
- break;
- default:
- subOptions[op] = options[op];
- leftOptions[op] = options[op];
- break;
- }
- }
- task.options = leftOptions;
-
- // 创建一个新的任务;
- let subTask = Task.create({ input: task.input, options: subOptions });
- var err = null;
- try {
- /*
- 3条管线中的转换管线
- this.transformPipeline = transformPipeline.append(parse).append(combine);
- 包含parse和combine2部分:
- parse的职责是为每个要加载的资源生成RequestItem对象并初始化其资源信息(AssetInfo、uuid、config等;
- combine方法会为每个RequestItem构建出真正的加载路径,这个加载路径最终会转换到item.url中
- */
- task.output = task.source = transformPipeline.sync(subTask);
- }
- catch (e) {
- err = e;
- for (var i = 0, l = subTask.output.length; i < l; i++) {
- subTask.output[i].recycle();
- }
- }
- subTask.recycle();
- done(err);
- }
正常加载管线pipeline中的load任务:
- function load (task, done) {
-
- let firstTask = false;
- if (!task.progress) {
- task.progress = { finish: 0, total: task.input.length, canInvoke: true };
- firstTask = true;
- }
-
- var options = task.options, progress = task.progress;
-
- options.__exclude__ = options.__exclude__ || Object.create(null);
-
- task.output = [];
-
- forEach(task.input, function (item, cb) {
-
- let subTask = Task.create({
- input: item,
- onProgress: task.onProgress,
- options,
- progress,
- onComplete: function (err, item) {
- if (err && !task.isFinish) {
- if (!cc.assetManager.force || firstTask) {
- if (!CC_EDITOR) {
- cc.error(err.message, err.stack);
- }
- progress.canInvoke = false;
- done(err);
- }
- else {
- progress.canInvoke && task.dispatch('progress', ++progress.finish, progress.total, item);
- }
- }
- task.output.push(item);
- subTask.recycle();
- cb();
- }
- });
- /*
- load管线,由fetch和parse两部分组成:
- fetch方法用于下载资源文件,packManager.load去下载文件,并将文件放入item.file中。
- */
- loadOneAssetPipeline.async(subTask);
-
- }, function () {
-
- options.__exclude__ = null;
-
- if (task.isFinish) {
- clear(task, true);
- return task.dispatch('error');
- }
-
- gatherAsset(task);
- clear(task, true);
- done();
- });
- }
loadOneAsset中的两个任务:
- //fetch方法用于下载资源文件,由packManager负责下载的实现,fetch会将下载完的文件数据放到item.file中
- 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();
- /*
- 下载文件;
- packManager.load->downloader.download->downloadBundle(下载bundle的方法里面会去下载目录下的config.json和index.js,downloadJson downloadScript)
- */
- packManager.load(item, task.options, function (err, data) {
- item.file = data;
- done(err);
- });
- },
-
- //parse方法用于将加载完的资源文件转换成我们可用的资源对象
- 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) {
- 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, progress.total, item);
- files.remove(id);
- parsed.remove(id);
- done();
- });
- }
- else {
- var { uuid } = item;
- if (uuid in exclude) {
-
- var { finish, content, err, callbacks } = exclude[uuid];
- progress.canInvoke && task.dispatch('progress', ++progress.finish, progress.total, 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, progress.total, 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);
- });
- }
- }
- }
- }
ok了,需要的文件已经下载下来了,
- loadRemote(url, options, onComplete) {
- var { options, onComplete } = parseParameters(options, undefined, onComplete);
-
- if (this.assets.has(url)) {
- return asyncify(onComplete)(null, this.assets.get(url));
- }
-
- options.__isNative__ = true;
- options.preset = options.preset || 'remote';
- this.loadAny({ url }, options, null, function (err, data) {
- if (err) {
- cc.error(err.message, err.stack);
- onComplete && onComplete(err, null);
- }
- else {
- //config文件已经下载完毕;
- factory.create(url, data, options.ext || cc.path.extname(url), options, function (err, out) {
- onComplete && onComplete(err, out);
- });
- }
- });
- },
-
-
- create(id, data, type, options, onComplete) {
- //func = createBundle;
- var func = producers[type] || producers['default'];
- let asset, creating;
- if (asset = assets.get(id)) {
- onComplete(null, asset);
- }
- else if (creating = _creating.get(id)) {
- creating.push(onComplete);
- }
- else {
- _creating.add(id, [onComplete]);
- //初始化createBundle,并返回;
- func(id, data, options, function (err, data) {
- if (!err && data instanceof cc.Asset) {
- data._uuid = id;
- assets.add(id, data);
- }
- let callbacks = _creating.remove(id);
- for (let i = 0, l = callbacks.length; i < l; i++) {
- callbacks[i](err, data);
- }
- });
- }
- }
整个流程就是这样子的。这就是我理解的loadBundle整个流程了。有不足和错误的话,请各位大佬指出~。
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。