当前位置:   article > 正文

Cocos Creator(v.2.4.5) Bundle的加载与解析_cocoscreator bundl.get

cocoscreator bundl.get

项目有个需求,需要缓存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

  1. AssetManager.js中的3条管线:
  2. /**
  3. * !#en
  4. * Normal loading pipeline
  5. *
  6. * !#zh
  7. * 正常加载管线
  8. *
  9. * @property pipeline
  10. * @type {Pipeline}
  11. */
  12. this.pipeline = pipeline.append(preprocess).append(load);
  13. /**
  14. * !#en
  15. * Fetching pipeline
  16. *
  17. * !#zh
  18. * 下载管线
  19. *
  20. * @property fetchPipeline
  21. * @type {Pipeline}
  22. */
  23. this.fetchPipeline = fetchPipeline.append(preprocess).append(fetch);
  24. /**
  25. * !#en
  26. * Url transformer
  27. *
  28. * !#zh
  29. * Url 转换器
  30. *
  31. * @property transformPipeline
  32. * @type {Pipeline}
  33. */
  34. this.transformPipeline = transformPipeline.append(parse).append(combine);

开始吧,当我们cc.assetManager.loadBundle的时候,引擎在做些什么:

 

  1. AssetManager.js
  2. loadBundle(nameOrUrl, options, onComplete) {
  3. //当options为undefined时,将回调函数由options变成onComplete;
  4. var { options, onComplete } = parseParameters(options, undefined, onComplete);
  5. //获取bundle的名字;
  6. let bundleName = cc.path.basename(nameOrUrl);
  7. //本地检测;
  8. if (this.bundles.has(bundleName)) {
  9. return asyncify(onComplete)(null, this.getBundle(bundleName));
  10. }
  11. //是否有自定义预设,没有就用bundle;
  12. options.preset = options.preset || 'bundle';
  13. options.ext = 'bundle';
  14. this.loadRemote(nameOrUrl, options, onComplete);
  15. },
  16. loadRemote(url, options, onComplete) {
  17. var { options, onComplete } = parseParameters(options, undefined, onComplete);
  18. //本地检测;
  19. if (this.assets.has(url)) {
  20. return asyncify(onComplete)(null, this.assets.get(url));
  21. }
  22. options.__isNative__ = true;
  23. options.preset = options.preset || 'remote';
  24. //下载;
  25. this.loadAny({ url }, options, null, function (err, data) {
  26. if (err) {
  27. cc.error(err.message, err.stack);
  28. onComplete && onComplete(err, null);
  29. }
  30. else {
  31. factory.create(url, data, options.ext || cc.path.extname(url), options, function (err, out) {
  32. onComplete && onComplete(err, out);
  33. });
  34. }
  35. });
  36. },

    接下来管线和任务登场了

  1. loadAny(requests, options, onProgress, onComplete) {
  2. var { options, onProgress, onComplete } = parseParameters(options, onProgress, onComplete);
  3. options.preset = options.preset || 'default';
  4. requests = Array.isArray(requests) ? requests.concat() : requests;
  5. //新建一个任务;
  6. let task = new Task({ input: requests, onProgress, onComplete: asyncify(onComplete), options });
  7. //加载管线去处理任务,注意这里的是3条管线中的加载管线;
  8. pipeline.async(task);
  9. },
  10. async(task) {
  11. var pipes = this.pipes;
  12. if (!(task instanceof Task) || pipes.length === 0) return;
  13. //设置任务输入;
  14. if (task.output != null) {
  15. task.input = task.output;
  16. task.output = null;
  17. }
  18. task._isFinish = false;
  19. this._flow(0, task);
  20. },
  21. _flow(index, task) {
  22. var self = this;
  23. /*
  24. 回到上面的这里this.pipeline = pipeline.append(preprocess).append(load);
  25. 加载管线有preprocess和load两部分;
  26. preprocess实际上只创建了一个子任务,然后交由transformPipeline执行,得到一个RequestItem对象并赋值给task.output;
  27. */
  28. var pipe = this.pipes[index];
  29. pipe(task, function (result) {
  30. if (result) {
  31. task._isFinish = true;
  32. task.onComplete && task.onComplete(result);
  33. }
  34. else {
  35. index++;
  36. if (index < self.pipes.length) {
  37. // move output to input
  38. task.input = task.output;
  39. task.output = null;
  40. self._flow(index, task);
  41. }
  42. else {
  43. task._isFinish = true;
  44. task.onComplete && task.onComplete(result, task.output);
  45. }
  46. }
  47. });
  48. }

 正常加载管线pipeline中的preprocess任务,通过转换器管线transformPipeline获取一个RequestItem对象并赋值给task.output;

  1. function preprocess(task, done) {
  2. var options = task.options,
  3. subOptions = Object.create(null),
  4. leftOptions = Object.create(null);
  5. for (var op in options) {
  6. switch (op) {
  7. // can't set these attributes in options
  8. case RequestType.PATH:
  9. case RequestType.UUID:
  10. case RequestType.DIR:
  11. case RequestType.SCENE:
  12. case RequestType.URL: break;
  13. // only need these attributes to transform url
  14. case '__requestType__':
  15. case '__isNative__':
  16. case 'ext':
  17. case 'type':
  18. case '__nativeName__':
  19. case 'audioLoadMode':
  20. case 'bundle':
  21. subOptions[op] = options[op];
  22. break;
  23. // other settings, left to next pipe
  24. case '__exclude__':
  25. case '__outputAsArray__':
  26. leftOptions[op] = options[op];
  27. break;
  28. default:
  29. subOptions[op] = options[op];
  30. leftOptions[op] = options[op];
  31. break;
  32. }
  33. }
  34. task.options = leftOptions;
  35. // 创建一个新的任务;
  36. let subTask = Task.create({ input: task.input, options: subOptions });
  37. var err = null;
  38. try {
  39. /*
  40. 3条管线中的转换管线
  41. this.transformPipeline = transformPipeline.append(parse).append(combine);
  42. 包含parse和combine2部分:
  43. parse的职责是为每个要加载的资源生成RequestItem对象并初始化其资源信息(AssetInfo、uuid、config等;
  44. combine方法会为每个RequestItem构建出真正的加载路径,这个加载路径最终会转换到item.url中
  45. */
  46. task.output = task.source = transformPipeline.sync(subTask);
  47. }
  48. catch (e) {
  49. err = e;
  50. for (var i = 0, l = subTask.output.length; i < l; i++) {
  51. subTask.output[i].recycle();
  52. }
  53. }
  54. subTask.recycle();
  55. done(err);
  56. }

正常加载管线pipeline中的load任务:

  1. function load (task, done) {
  2. let firstTask = false;
  3. if (!task.progress) {
  4. task.progress = { finish: 0, total: task.input.length, canInvoke: true };
  5. firstTask = true;
  6. }
  7. var options = task.options, progress = task.progress;
  8. options.__exclude__ = options.__exclude__ || Object.create(null);
  9. task.output = [];
  10. forEach(task.input, function (item, cb) {
  11. let subTask = Task.create({
  12. input: item,
  13. onProgress: task.onProgress,
  14. options,
  15. progress,
  16. onComplete: function (err, item) {
  17. if (err && !task.isFinish) {
  18. if (!cc.assetManager.force || firstTask) {
  19. if (!CC_EDITOR) {
  20. cc.error(err.message, err.stack);
  21. }
  22. progress.canInvoke = false;
  23. done(err);
  24. }
  25. else {
  26. progress.canInvoke && task.dispatch('progress', ++progress.finish, progress.total, item);
  27. }
  28. }
  29. task.output.push(item);
  30. subTask.recycle();
  31. cb();
  32. }
  33. });
  34. /*
  35. load管线,由fetch和parse两部分组成:
  36. fetch方法用于下载资源文件,packManager.load去下载文件,并将文件放入item.file中。
  37. */
  38. loadOneAssetPipeline.async(subTask);
  39. }, function () {
  40. options.__exclude__ = null;
  41. if (task.isFinish) {
  42. clear(task, true);
  43. return task.dispatch('error');
  44. }
  45. gatherAsset(task);
  46. clear(task, true);
  47. done();
  48. });
  49. }

loadOneAsset中的两个任务:

  1. //fetch方法用于下载资源文件,由packManager负责下载的实现,fetch会将下载完的文件数据放到item.file中
  2. function fetch (task, done) {
  3. var item = task.output = task.input;
  4. var { options, isNative, uuid, file } = item;
  5. var { reload } = options;
  6. if (file || (!reload && !isNative && assets.has(uuid))) return done();
  7. /*
  8. 下载文件;
  9. packManager.load->downloader.download->downloadBundle(下载bundle的方法里面会去下载目录下的config.json和index.js,downloadJson downloadScript)
  10. */
  11. packManager.load(item, task.options, function (err, data) {
  12. item.file = data;
  13. done(err);
  14. });
  15. },
  16. //parse方法用于将加载完的资源文件转换成我们可用的资源对象
  17. function parse (task, done) {
  18. var item = task.output = task.input, progress = task.progress, exclude = task.options.__exclude__;
  19. var { id, file, options } = item;
  20. if (item.isNative) {
  21. parser.parse(id, file, item.ext, options, function (err, asset) {
  22. if (err) return done(err);
  23. item.content = asset;
  24. progress.canInvoke && task.dispatch('progress', ++progress.finish, progress.total, item);
  25. files.remove(id);
  26. parsed.remove(id);
  27. done();
  28. });
  29. }
  30. else {
  31. var { uuid } = item;
  32. if (uuid in exclude) {
  33. var { finish, content, err, callbacks } = exclude[uuid];
  34. progress.canInvoke && task.dispatch('progress', ++progress.finish, progress.total, item);
  35. if (finish || checkCircleReference(uuid, uuid, exclude) ) {
  36. content && content.addRef && content.addRef();
  37. item.content = content;
  38. done(err);
  39. }
  40. else {
  41. callbacks.push({ done, item });
  42. }
  43. }
  44. else {
  45. if (!options.reload && assets.has(uuid)) {
  46. var asset = assets.get(uuid);
  47. if (options.__asyncLoadAssets__ || !asset.__asyncLoadAssets__) {
  48. item.content = asset.addRef();
  49. progress.canInvoke && task.dispatch('progress', ++progress.finish, progress.total, item);
  50. done();
  51. }
  52. else {
  53. loadDepends(task, asset, done, false);
  54. }
  55. }
  56. else {
  57. parser.parse(id, file, 'import', options, function (err, asset) {
  58. if (err) return done(err);
  59. asset._uuid = uuid;
  60. loadDepends(task, asset, done, true);
  61. });
  62. }
  63. }
  64. }
  65. }

ok了,需要的文件已经下载下来了,

  1. loadRemote(url, options, onComplete) {
  2. var { options, onComplete } = parseParameters(options, undefined, onComplete);
  3. if (this.assets.has(url)) {
  4. return asyncify(onComplete)(null, this.assets.get(url));
  5. }
  6. options.__isNative__ = true;
  7. options.preset = options.preset || 'remote';
  8. this.loadAny({ url }, options, null, function (err, data) {
  9. if (err) {
  10. cc.error(err.message, err.stack);
  11. onComplete && onComplete(err, null);
  12. }
  13. else {
  14. //config文件已经下载完毕;
  15. factory.create(url, data, options.ext || cc.path.extname(url), options, function (err, out) {
  16. onComplete && onComplete(err, out);
  17. });
  18. }
  19. });
  20. },
  21. create(id, data, type, options, onComplete) {
  22. //func = createBundle;
  23. var func = producers[type] || producers['default'];
  24. let asset, creating;
  25. if (asset = assets.get(id)) {
  26. onComplete(null, asset);
  27. }
  28. else if (creating = _creating.get(id)) {
  29. creating.push(onComplete);
  30. }
  31. else {
  32. _creating.add(id, [onComplete]);
  33. //初始化createBundle,并返回;
  34. func(id, data, options, function (err, data) {
  35. if (!err && data instanceof cc.Asset) {
  36. data._uuid = id;
  37. assets.add(id, data);
  38. }
  39. let callbacks = _creating.remove(id);
  40. for (let i = 0, l = callbacks.length; i < l; i++) {
  41. callbacks[i](err, data);
  42. }
  43. });
  44. }
  45. }

整个流程就是这样子的。这就是我理解的loadBundle整个流程了。有不足和错误的话,请各位大佬指出~。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/152511
推荐阅读
相关标签
  

闽ICP备14008679号