[CocosCreator]AssetManager之管线
creator 使用管线(pipeline
)来处理整个资源加载的流程,这样的好处是解耦了资源处理的流程,将每一个步骤独立成一个单独的管道,管道可以很方便地进行复用和组合,并且方便了我们自定义整个加载流程,我们可以创建一些自己的管道,加入到管线中,比如资源加密。
管线
管线 可以理解为一系列过程的串联组合,当一个请求经过管线时,会被管线的各个阶段依次进行处理,最后输出处理后的结果。如下图所示:
管线与一般的固定流程相比,优势在于管线中的所有环节都是可拼接和组合的,这意味着开发者可以在现有管线的任意环节插入新的阶段或者移除旧的阶段,极大地增强了灵活性和可扩展性。
任务
任务 就是在管线中流动的请求,一个任务中包括输入、输出、完成回调、可选参数 等内容。当任务在管线中流动时,管线的各个阶段会取出任务的输入,做出一定的处理后存回到输出中。
UML
- 管线pipeline是存储管道的数组容器
- 管道pipe是fun(task,done)形式参数的处理函数,pipeline同步执行时只需要task参数,不需要done参数。
- 任务task是管道处理函数的参数
内置管线
AssetManager
内置了3
条管线,普通的加载管线、预加载、以及资源路径转换管线,最后这条管线是为前面两条管线服务的。
// 正常加载this.pipeline = pipeline.append(preprocess).append(load);// 预加载this.fetchPipeline = fetchPipeline.append(preprocess).append(fetch);// 转换资源路径this.transformPipeline = transformPipeline.append(parse).append(combine);
- 第一条管线用于转换资源路径,找到真实资源路径。为正常加载管线和预加载管线服务。
- 第二条管线用于正常加载,用到了下载器和解析器。
- 第三条管线用于预加载,用到了下载器。
pipeline正常加载管线
pipeline
由两部分组成 preprocess
和 load
。
启动加载管线【加载接口】
一个普通的资源是如何加载的,比如最简单的 cc.resource.load
,在 bundle.load
方法中,调用了 cc.assetManager.loadAny
,在 loadAny
方法中,创建了一个新的任务,并调用正常加载管线 pipeline
的 async
方法执行任务。
// bundle类的load方法load (paths, type, onProgress, onComplete) {var { type, onProgress, onComplete } = parseLoadResArgs(type, onProgress, onComplete);cc.assetManager.loadAny(paths, { __requestType__: RequestType.PATH,
type: type, bundle: this.name }, onProgress, onComplete);},// assetManager的loadAny方法loadAny (requests, options, onProgress, onComplete) {var { options, onProgress, onComplete } = parseParameters(options, onProgress, onComplete);options.preset = options.preset || 'default';let task = new Task({input: requests, onProgress, onComplete: asyncify(onComplete), options});pipeline.async(task);},
preprocess预处理管道【准备阶段】
preprocess
由以下管线组成 preprocess
、transformPipeline { parse、combine }
,preprocess
实际上只创建了一个子任务,然后交由 transformPipeline
执行。对于加载一个普通的资源,子任务的 input
和 options
与父任务相同。
let subTask = Task.create({input: task.input, options: subOptions});task.output = task.source = transformPipeline.sync(subTask);
transformPipeline 路径转换管线
transformPipeline
由 parse
和 combine
两个管线组成,parse
的职责是为每个要加载的资源生成 RequestItem
对象并初始化其资源信息(AssetInfo、uuid、config等):
先将 input
转换成数组进行遍历,如果是批量加载资源,每个加载项都会生成RequestItem
;
如果输入的 item
是 object
,则先将 options
拷贝到 item
身上(实际上每个 item
都会是 object
,如果是 string
的话,第一步就先转换成 object
了)
- 对于
UUID
类型的item
,先检查bundle
,并从bundle
中提取AssetInfo
,对于redirect
类型的资源,则从其依赖的bundle
中获取AssetInfo
,找不到bundle
就报错 PATH
类型和SCENE
类型与UUID
类型的处理基本类似,都是要拿到资源的详细信息DIR
类型会从bundle
中取出指定路径的信息,然后批量追加到input
尾部(额外生成加载项)URL
类型是远程资源类型,无需特殊处理
function parse (task) {// 将input转换成数组var input = task.input, options = task.options;input = Array.isArray(input) ? input : [ input ];task.output = [];for (var i = 0; i < input.length; i ++ ) {var item = input[i];var out = RequestItem.create();if (typeof item === 'string') {// 先创建objectitem = Object.create(null);item[options.__requestType__ || RequestType.UUID] = input[i];}if (typeof item === 'object') {// local options will overlap glabal options// 将options的属性复制到item身上,addon会复制options上有,而item没有的属性cc.js.addon(item, options);if (item.preset) {cc.js.addon(item, cc.assetManager.presets[item.preset]);}for (var key in item) {switch (key) {// uuid类型资源,从bundle中取出该资源的详细信息case RequestType.UUID: var uuid = out.uuid = decodeUuid(item.uuid);if (bundles.has(item.bundle)) {var config = bundles.get(item.bundle)._config;var info = config.getAssetInfo(uuid);if (info && info.redirect) {if (!bundles.has(info.redirect)) throw new Error(`Please load bundle ${info.redirect} first`);config = bundles.get(info.redirect)._config;info = config.getAssetInfo(uuid);}out.config = config;out.info = info;}out.ext = item.ext || '.json';break;case '__requestType__':case 'ext': case 'bundle':case 'preset':case 'type': break;case RequestType.DIR: // 解包后动态添加到input列表尾部,后续的循环会自动parse这些资源if (bundles.has(item.bundle)) {var infos = [];bundles.get(item.bundle)._config.getDirWithPath(item.dir, item.type, infos);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});}}out.recycle();out = null;break;case RequestType.PATH: // PATH类型的资源根据路径和type取出该资源的详细信息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(`you need to load bundle ${info.redirect} first`);config = bundles.get(info.redirect)._config;info = config.getAssetInfo(info.uuid);}if (!info) {out.recycle();throw new Error(`Bundle ${item.bundle} doesn't contain ${item.path}`);}out.config = config; out.uuid = info.uuid;out.info = info;}out.ext = item.ext || '.json';break;case RequestType.SCENE:// 场景类型,从bundle中的config调用getSceneInfo取出该场景的详细信息if (bundles.has(item.bundle)) {var config = bundles.get(item.bundle)._config;var info = config.getSceneInfo(item.scene);if (info && info.redirect) {if (!bundles.has(info.redirect)) throw new Error(`you need to load bundle ${info.redirect} first`);config = bundles.get(info.redirect)._config;info = config.getAssetInfo(info.uuid);}if (!info) {out.recycle();throw new Error(`Bundle ${config.name} doesn't contain scene ${item.scene}`);}out.config = config; out.uuid = info.uuid;out.info = info;}break;case '__isNative__': out.isNative = item.__isNative__;break;case RequestType.URL: out.url = item.url;out.uuid = item.uuid || item.url;out.ext = item.ext || cc.path.extname(item.url);out.isNative = item.__isNative__ !== undefined ? item.__isNative__ : true;break;default: out.options[key] = item[key];}if (!out) break;}}if (!out) continue;task.output.push(out);if (!out.uuid && !out.url) throw new Error('unknown input:' + item.toString());}return null;
}
RequestItem
的初始信息,都是从bundle
对象中查询的,bundle
的信息则是从bundle
自带的config.json
文件中初始化的,在打包bundle
的时候,会将bundle
中的资源信息写入config.json
中。
经过 parse
方法处理后,我们会得到一系列 RequestItem
,并且很多 RequestItem
都自带了 AssetInfo
和 uuid
等信息,combine
方法会为每个 RequestItem
构建出真正的加载路径,这个加载路径最终会转换到 item.url
中。
function combine (task) {var input = task.output = task.input;for (var i = 0; i < input.length; i++) {var item = input[i];// 如果item已经包含了url,则跳过,直接使用item的urlif (item.url) continue;var url = '', base = '';var config = item.config;// 决定目录的前缀if (item.isNative) {base = (config && config.nativeBase) ? (config.base + config.nativeBase) : cc.assetManager.generalNativeBase;} else {base = (config && config.importBase) ? (config.base + config.importBase) : cc.assetManager.generalImportBase;}let uuid = item.uuid;var ver = '';if (item.info) {if (item.isNative) {ver = item.info.nativeVer ? ('.' + item.info.nativeVer) : '';}else {ver = item.info.ver ? ('.' + item.info.ver) : '';}}// 拼接最终的url// ugly hack, WeChat does not support loading font likes 'myfont.dw213.ttf'. So append hash to directoryif (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}`;}item.url = url;}return null;
}
load加载管道【加载流程】
load
方法做的事情很简单,基本只是创建了新的任务,在 loadOneAssetPipeline
中执行每个子任务。
function load (task, done) {if (!task.progress) {task.progress = {finish: 0, total: task.input.length};}var options = task.options, progress = task.progress;options.__exclude__ = options.__exclude__ || Object.create(null);task.output = [];forEach(task.input, function (item, cb) {// 对每个input项都创建一个子任务,并交由loadOneAssetPipeline执行let subTask = Task.create({ input: item, onProgress: task.onProgress, options, progress, onComplete: function (err, item) {if (err && !task.isFinish && !cc.assetManager.force) done(err);task.output.push(item);subTask.recycle();cb();}});// 执行子任务,loadOneAssetPipeline有fetch和parse组成loadOneAssetPipeline.async(subTask);}, function () {// 每个input执行完成后,最后执行该函数options.__exclude__ = null;if (task.isFinish) {clear(task, true);return task.dispatch('error');}gatherAsset(task);clear(task, true);done();});
}
loadOneAssetPipeline
如其函数名所示,就是加载一个资源的管线,它分为2步,fetch
和 parse
:
fetch
方法:用于下载资源文件,由packManager
负责下载的实现,fetch
会将下载完的文件数据放到item.file
中。parse
方法:用于将加载完的资源文件转换成我们可用的资源对象,parser.parse
会将解析后的资源对象放到item.content
中。
- 对于原生资源,调用
parser.parse
进行解析,该方法会根据资源类型调用不同的解析方法
import
资源调用parseImport
方法,根据json
数据反序列化出Asset
对象,并放到assets
中- 图片资源会调用
parseImage
、parsePVRTex
或parsePKMTex
方法解析图像格式(但不会创建Texture
对象)- 音效资源调用
parseAudio
方法进行解析plist
资源调用parsePlist
方法进行解析
- 对于其它资源,如果
uuid
在task.options.__exclude__
中,则标记为完成,并添加引用计数;否则,根据一些复杂的条件来决定是否加载资源的依赖。
var loadOneAssetPipeline = new Pipeline('loadOneAsset', [function fetch (task, done) {var item = task.output = task.input;var { options, isNative, uuid, file } = item;var { reload } = options;// 如果assets里面已经加载了这个资源,则直接完成if (file || (!reload && !isNative && assets.has(uuid))) return done();// 下载文件,这是一个异步的过程,文件下载完会被放到item.file中,并执行done驱动管线packManager.load(item, task.options, function (err, data) {if (err) {if (cc.assetManager.force) {err = null;} else {cc.error(err.message, err.stack);}data = null;}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) {// 对于原生资源,调用parser.parse进行处理,将处理完的资源放到item.content中,并结束流程parser.parse(id, file, item.ext, options, function (err, asset) {if (err) {if (!cc.assetManager.force) {cc.error(err.message, err.stack);return done(err);}}item.content = asset;task.dispatch('progress', ++progress.finish, progress.total, item);files.remove(id);parsed.remove(id);done();});} else {var { uuid } = item;// 非原生资源,如果在task.options.__exclude__中,直接结束if (uuid in exclude) {var { finish, content, err, callbacks } = exclude[uuid];task.dispatch('progress', ++progress.finish, progress.total, item);if (finish || checkCircleReference(uuid, uuid, exclude) ) {content && content.addRef();item.content = content;done(err);} else {callbacks.push({ done, item });}} else {// 如果不是reload,且asset中包含了该uuidif (!options.reload && assets.has(uuid)) {var asset = assets.get(uuid);// 开启了options.__asyncLoadAssets__,或asset.__asyncLoadAssets__为false,直接结束,不加载依赖if (options.__asyncLoadAssets__ || !asset.__asyncLoadAssets__) {item.content = asset.addRef();task.dispatch('progress', ++progress.finish, progress.total, item);done();}else {loadDepends(task, asset, done, false);}} else {// 如果是reload,或者assets中没有,则进行解析,并加载依赖parser.parse(id, file, 'import', options, function (err, asset) {if (err) {if (cc.assetManager.force) {err = null;}else {cc.error(err.message, err.stack);}return done(err);}asset._uuid = uuid;loadDepends(task, asset, done, true);});}}}}
]);
[CocosCreator]AssetManager之管线相关推荐
- CocosCreator游戏资源加载assetManager
1.资源AssetBundle包(简称ab包): ab包的作用:文件夹配置成ab包后,可以用引擎的资源加载接口加载文件夹内的资源 每个文件夹都可以设置成ab包,在creator编辑器中这样设置: 两个 ...
- Cocos Creator资源管理AssetManager细说一二
关于AssetManager Asset Manager 是 Creator 在 v2.4 新推出的资源管理器,用于替代之前的 cc.loader.新的 Asset Manager 资源管理模块具备加 ...
- CocosCreator 源码-CCAssetManager.js详解
断更了几天,主要是梳理整个和CCAssetManager相关的类.CCAssetManager作为整个引擎的核心资源管理器.需要特别仔细的分析. const preprocess = require( ...
- 使请求管线内的所有模块尽可能高效
请求管线内的所有模块在每次请求中都有机会被运行.因此,当请求进入和离开模块时快速地触发代码至关重要,特别是在不使用模块功能的代码路径里.分别在使用及不使用模块和配置文件时执行吞吐量测试,对确定这些方法 ...
- 04-VTK可视化管线(1)
4.VTK可视化管线 通过第3章的学习,我们已经了解了VTK的一些基础概念.在这一章里,我们将更深入地学习VTK,其中包括VTK的系统框架结构.引用计数.智能指针.Observer/Command设计 ...
- AssetManager asset使用
Android 该系统提供了一个程序为每个新的设计/assets文件夹.保存该文件在此文件夹可以在一个程序被打包./res 和/assets所不同的是,android不/assets下生成的文件ID. ...
- 7、计算机图形学——图形管线渲染与纹理映射
一.图形渲染管线 管线渲染其实就是将三维物体如何呈现到计算机屏幕上的过程,图形渲染管线的整体大致流程如下 顶点处理过程就是进行MVP变换,最终得到一系列的二维坐标点.而三角化就是将这一系列的二维坐标点 ...
- CocosCreator上的游戏(调试)发布到微信小程序
1.下载CocosCreator,微信开发者工具 官网地址:http://www.cocos.com/download 官网下载:https://developers.weixin.qq.com/mi ...
- Directx11教程(15) D3D11管线(4)
本章我们首先了解一下D3D11中的逻辑管线,认识一下管线中每个stage的含义. 参考资料:http://fgiesen.wordpress.com/2011/07/01/a-trip-through ...
最新文章
- 一位清华贫困生的独白,风雨清华路!
- 信息哲学给哲学带来根本性革命了吗
- centos清除dns cache.
- python2.7如何安装库_python 2.7 安装目录python如何连接数据库
- 中国移动游戏市场全球占比31.6% 掌趣科技入围竞争力企业前20
- 用docker swarm 实现集群
- 你模型的变量符合业务逻辑了吗
- html 5实用特性之data属性
- jQuery点击行选中或者取消CheckBox
- ERP(企业资源计划)
- 支持向量机原理与高斯核函数
- 微信 html5 动图格式,微信真人动态表情包 怎么给自己录制GIF动态图片 你也可以录制搞笑微信gif图片;...
- 面试题:关于搭建测试环境 (一)
- 计算机电源+3c认证,电源适配器要不要做3C认证?怎么做
- MSDN Library下载与安装(MFC)
- 找工作系列之-操作系统
- 快学Big Data -- Hadoop(十三)
- erlang 开源项目之 Bigwig
- POJ1753 Flip Game题解
- c++实现STL标准库
热门文章
- 小米8se android q,小米安卓Q下半年开始内测,其中包括11款机型,小米8SE无缘
- MyEclipse updating indexes
- 东方梦符祭未能收到服务器响应,东方梦符祭loading不进去 常见问题解决办法
- ssh远程执行python本地脚本_ssh远程执行命令方法和Shell脚本实例
- 如何找到已提交内存越来越大的原因?
- win10文件夹上方的工具栏怎么关掉
- Opencv:截取部分图像数据
- 友元函数和友元类的应用
- Android 解决全面屏适配出现黑边问题
- ios显示wifi无网络连接到服务器,iOS APP没有联网权限解决办法