项目有个需求,需要缓存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整个流程了。有不足和错误的话,请各位大佬指出~。
|