Worker:
cesium中使用线程的一个方面是进行几何数据计算,通常计算耗时,放到线程中计算也很合理,但是通常几何数据占用相当大的内存,虽然浏览器中主线程与子线程传递数据可以使用如下方式转移内存的所有权
myWorker. postMessage(aMessage, transferList);
参数
aMessage:The object to deliver to the worker; this will be in the data field in the event delivered to the DedicatedWorkerGlobalScope onmessage (en US) handler. This may.
transtierList(可选)-一个可选的Transferable对象的数组,用于传递所有权。如果一个对象的所有权被转移,在发送它的上下文中将变为不可用(中止),并且只有在它被发送到的worke
可转移对象是如ArrayBuffer MessagePort或ImageBitmap的实例对 象。transferlist数组中不可传入null.
Returns: Void.
但是针对javascript的对象类型,只支持像ArrayBuffer, MessagePort或lmageBitmap这- 类的内置对象, 不支持自定义对象,所 以在向子线程传递需要计算的数据,以及子线程向主线程传递数据时需要对数据进行打包处理,而cesium中将数据打包为ArrayBuffer格式,子线程解包,计算完成后子线程在打包,主线程解包数据。
按照下面的流程讲解一下具体的处理过程:
scene.primitives.add(new Cesium.Primitive({ geometryInstances: new Cesium.GeometryInstance({ geomety: new Cesium.olylineGeometry(t positions: positions, 5.0, vertexFormat: Cesium.PolylineColorAppearance.VERTEX FORMAT, colors: colors, }). }), appearance: new Cesium.olylineColorAppearance(), asynchronous : true }) );
上面的这个过程创建一个polyline, 因为有参数"asynchronous: true"的存在,所以使用了异步的处理过程,在这个过程中会创建子线程。
首先看一下PolylineGeometry这个类的构造函数:
function PolylineGeometry(options){ //默认值 options = defaultValue(options, defaultValue.EMPTY_OBJECT); //位置 var positions = options.positions; var colors = options.colors; var width=defaultValue(options.width,1.0);
//每个顶点是否包含颜色
var colorsPerVertex=defaultValue(options.colorsPerVertex, false);
if(!defined(positions)||positions.length<2){
throw new DeveloperError("At least two positions are required.")
}
if(typeof width !== "number"){
throw new DeveloperError("width must be a number");
}
//定义了颜色,但是顶点数量和颜色的数量不同
if(defined(colors)&&((colorsPerVertex&&colors.length<positions.length)||(!colorsPerVertex&&colors.length<positions.length-1))){
throw new DeveloperError("colors has an invalid length.")
}
//>>includeEnd('debug');
//位置
this._positions = positions;
//颜色
this._colors = colors;
//宽度
this._width = width;
//每个顶点是否包含颜色
this._colorsPerVertex=colorsPerVertex;
//顶点格式
this._vertexFormat=VertexFormat.clone(defaultValue(options.vertexFormat, VertexFormat.DEFAULT));
//圆弧的类型
this_arcType = defaultValue(options.arcType, ArcType.GEODESIC);
//粒度
this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
//椭球
this._ellipsoid = Ellipsoid.clone(defaultValue(options.ellipsoid, Ellipsoid.WGS84));
//线程中处理这个PolylineGeometry的js文件createPolylineGeometry.js
this_workerName="createPolylineGeometry";
//顶点属性分量的总数量
var numComponents = 1+positions.length*Cartesian3.packedLength;
//顶点数量+颜色数量
numComponents += defined(colors)?1+colors.length*Color.packedLength : 1;
/**
*The number of elements used to pack the object into an array. 打包数组时包的大小
*@type(Number)
*/
//顶点属性+颜色属性+椭球的内存大小+顶点格式的包长
this.packedLength = numComponents + Ellipsoid.packedLength + VertexFormat.packedLength + 4;
}
这个有几个注意的地方:
this._workerName = "createPolylineGeometry"; 这代表了处理这个图形所使用的类
this.packedLength = numComponents + Ellipsoid.packedLength + VertexFormat.packedLength + 4;这代表了将这个构造函数中数据序列为ArrayBuffer所使用的内存
然后看一下Primitive这个类,这个类中的Primitive.prototype.update这个函数,会在Scene的render方法中实时调用,看一下Primitive.prototype.update的源码
Primitive.prototype.update = function(frameState){ if(!defined(this.geometryInstances)&&this._va.length==0|| //没有几何数据不渲染,可能是内存中的geometryInstances可以被删除,只保留显存)
(defined(this.geometryInstances)&&Array.isArray(this.geometryInstances)&&this.geometryInstances.length==0)|| //没有几何数据不渲染
!defined(this.appearance)|| //没有材质不渲染
(frameState.mode !== SceneMode.SCENE3D && frameState.scene3DOnly)|| //不是3D地图,但是之渲染3D地图
(!frameState.passes.render && !frameState.passes.pick) //不是渲染过程,也不是拾取过程(3Dtiles是不是也走这个流程设置了其它状态)
){return;}
//异常
if(defined(this._error)){throw this._error;}
//>>includeStart('debug',pragmas.debug);
if(defined(this.rtcCenter)&&!frameState.scene3DOnly){
throw new DeveloperError("RTC rendering is only available for 3D only scenes.");
}
//>>includeEnd('debug');
//图元的状态失败
if(this._state === PrimitiveState.FAILED){return;}
//上下文
var context = frameState.context;
//创建批次表
if(!defined(this._batchTable)){createBatchTable(this,context);}
//如果批次表中有属性才能更新,不支持后添加的新的属性
if(this._batchTable.attributes.length>0){
//顶点着色器中不支持使用纹理
if(ContextLimits.maximumVertexTextureImageUnits===0){
throw new RuntimeError("Vertex texture fetch support is required to render primitives with per-instance attributes. The maximum number of vertex texture image units must be greate");
}
//将批次表的数据传递给纹理
this._batchTable.update(frameState);
}
//数据正处在创建中、创建完成、合并中的阶段(是在)可以使用【this._state<PrimitiveState.COMBINED】这种方式处理
if(this._state!==PrimitiveState.COMPLETE&& //数据状态不是完成
this._state!==PrimitiveState.COMBINED //数据状态不是合并
){
if(this.asynchronous){ //异步
loadAsynchronous(this, frameState);
}else{
//同步
loadSynchronous(this,frameState);
}
}
.......
}
可以看到只有this.asynchronous=true时才启用异步处理过程,下面看看异步处理的代码
//【创建】几何数据任务处理器,全局变量,所有的primitive实例共享 var createGeometryTaskProcessors; //【合并】几何数据任务处理器,全局变量,所有的primitive实例共享 var combineGeometryTaskProcessor = new TaskProcessor("combineGeometry"); //异步加载 function loadAsynchronous(primitive, frameState){ var instances; var geometry; var i; var j; //primitive中所有实例id列表 var instanceIds = primitive._instanceIds; if(primitive._state===PrimitiveState.READY){//当前状态是【准备状态】 //几何实例如果不是数组,转换成数组(这个操作可以在初始化的时候一次性处理掉,不用每次都转换为数组) instances = Array.isArray(primitive.geometryInstances) primitive.geometryInstances:[primitive.geometryInstances] //实例的数量 var length = (primitive._numberOfInstances = instances.length); var promises = []; var subTasks = []; //子任务 //遍历实例 for(i = 0; i<length;++i){ //实例的几何数据 geometry = instances[i].geometry; //实例的id列表 instanceIds.push(instances[i].id); //>includeStart('debug',pragmas.debug); //工作线程名称没有定义就异常 if(!defined(geometry._workerName)){ throw new DeveloperError("_workerName must be defined for asynchronous geometry."); } //>>includeEnd('debug'); //子任务 subTasks.push({
moduleName:geometry._workerName, //线程中处理这个几何数js文件
geometry:geometry, //几何数据
});
}
if(!defined(createGeometryTaskProcessors)){ //【创建】处理任务没有定义
createGeometryTaskProcessors = new Array(numberOfCreationWorkers);//根据浏览器支持的最大线程数创建
//遍历创建所有的任务
for(i=0;i<numberOfCreationWorkers;i++){
//创建几何任务处理
createGeometryTaskProcessors[i] = new TaskProcessor("createGeometry");
}
}
var subTask;
//将任务分成了几组,这个不是平均分的
subTasks = subdivideArray(subTasks, numberOfCreationWorkers);
//遍历几组任务
for(i=0;i<subTasks.length;i++){
var packedLength = 0;
//获取一个子任务组
var workerSubTasks = subTasks[i];
//这一组子任务的数量
var workerSubTasksLength = workerSubTasks.length;
//遍历子任务组,设置子任务的包偏移量(将每一组打包成一块存储空间)
for(j=0;j<workerSubTasksLength;++j){
//获取一个子任务
subTask = workerSubTasks[i];
//获取几何数据
geometry = subTask.geometry;
//几何体支持打包
if(defined(geometry.constructor.pack)){
//将几何数据打包到array的offset位置
geometry.constructor.pack(geometry,array,subTask.offset);
//将原来的自定义几何数据重定义为当前子任务合并后的内存
subTask.geometry = array;
}
}
}
//将任务安排的结果放入到promise中
promises.push(
//安排任务
createGeometryTaskProcessors[i].scheduleTask(
{subTasks:subTasks[i],//子任务组
},
subTaskTransferableObjects //向线程传递的内存对象,将当前小组中的所有数据都合并了
);
); }
//标记当前状态为【正在创建】的状态
primitive._state = PrimitiveState.CREATING;
when.all(promises, function(result){
//primitive中所有的几何的结果
primitive._createGeometryResults = results;
//当前的状态
primitive._state = PrimitiveState.CREATED;
}).otherwise(function(error){//出现错误
setReady(primitive,frameState,PrimitiveState.FAILED, error)
}else if(primitive._state===PrimitiveState.CREATED{//当前状态是已完成
.......
}); }
这里将所有的geometryInstances分成几个任务小组,这个分组是按照浏览器支持的线程并发数分的,我的电脑中最大并发是16, 这里将 所有的任务参数打包
//打包后的数据 var array = new Float64Array(packedLength); //建立索引 var subTask={ geometry,//这个指向array offset,//这个是每一个Instance在array中对应的偏移量 moduleName,//这个是将每个Instance的geometry反序列化为PolylineGeometry所使用模块名称 }
其中createGeometryTaskProcessors[] = new TaskProcessor("createGeometry");这一-句是创建一 个任务处理过程,“createGeometry" 是一个线程当中调用的辅助类。
下面这个TaskProcessor类的代码如下,这里面主要是一些任务的管理参数
function TaskProcessor(workerPath, maximumActiveTasks) { //是否为绝对路径(workerPath是线程执行的业务处理脚本) this. _workerPath = new Uri(workerPath).isAbsolute() workerPath//是绝对路径(线程所使用的脚本),打包时将这些文件单独打包 : TaskProcessor._workerModulePrefix + workerPath; // 相对目录,拼接目录(线程所使用的脚本) //最大活动任务数 this._ maximumActiveTasks = defaultValue( maximumActiveTasks, Number.POSITIVE INFINITY //无穷大 ); //当前活动任务数量 this._ activeTasks = 0; //线程处理完成后所使用的promise this._deferreds= {}; //任务id this._nextID = 0; }
真正的任务处理过程是下面这部分
//调度任务 TaskProcessor. prototype.scheduleTask = function ( parameters,//参数 transferableObjects//转让内存对象 ){ //未定义线程,就创建 if (!defined(this. worker) { //这个线程是引导线程,主要是获取引导的js文件使用 this. worker = createWorker(this); } //当前活动的任务>最大活动任务 if (this._ activeTasks >= this. maximumActiveTasks) { return undefined; } //活动任务数 ++this.activeTasks; var processor = this; //测试是否可以向线程传递数组 return when(canTransterArayButfer), function(canTransterArrayBuffer) { if (!defined(transferableObjects)) {//向线程传递的业务内存数组是否定义 //未定义就设置空 transferableObjects = empty TransferableObjectArray; } else if (!canTransferArrayBufer) { //传递的数据长度为空 transferableObjects.length = 0; } //根据id处理任务 var id = processor. nextID++; //创建promise var deferred = when.defer(); //保存延迟 processor._deferreds[id] = deferred; //向线程传递数据 processor._worker.postMessage( id: id, //任务的id parameters: parameters,//任务(需要计算的数据) canTransterArayBuffer: canTransterArrayBuffer, //是否支持子线程向主线程传递数组 }, transterableObjects //向线程传递的业务内存数组 ); //返回 return deferred.promise; }); };
先看看canTransferArrayBuffer()这个函数,这个函数的目的是检测一下当前的浏览器所使用的线程是否支持“主线程和子线程中使用数组方式转移传递内存”,如果支持然后好好戏就开始了,如何向线程发送几何数据就是下面这一部分
//向线程传递数据 processor._worker.postMessage( { id: id, // 任务的id parameters: parameters,// 任务(需要计算的数据) canTransferArrayBuffer: canTransferArrayBuffer, //是否支持子线程向主线程传递数组 }, transferableObjects//向线程传递的业务内存数组. );
this._worker = createWorker(this);
上面这一句会处理创建线程所需要的信息,同时在
//创建线程,配置相关的引导文件 function createWorker(processor) { //创建引导线程(根据路径创建线程) var worker = new Worker(getBootstrapprUrl()); //如果浏览器定义了向线程传递消息的方法,就使用webkitPostMessage,没有定义就使用postMessage worker,postMessage = defaultValue( worker. webkitPostMessage, worker.postMessage ); //引导线程加载需要的脚本文件 var bootstrapMessage= { //配置脚本所在的路径 loaderConfig: { paths:{ // 路径 Workers: buildModuleUr("Workers"), } //基本路径 baseUr: buildModuleUrl.getCesiumBaseUrl().url,
}, //线程中执行的脚本 workerModule: processor. workerPath, };
//传递消息
worker.postMessage(lbootstrapMessage);
//接收消息
worker.onmessage = function(event) {
//完成任务
completeTask(processor, event.data);
};
//返回线程
return worker;
}
这里调用了getBootstrapperUrl()这个函数,这是一个引导过程,线程中所使用的业务处理类是一些js文件,而这些js文件 都需要从服务中获取,函数会"Workers/cesiumWorker Bootstrapper.js"创建一个线程 。
worker.postMessage(bootstrapMessage);这个调用将js文件的相对路径传入了线程,再看看线程的第一步
//这是一个引导文件,会查找传 js入口文件,而require函数会查找所有相关的引用文件.建立js文件引用树 if (typeof self === "undefined") { //定义self,以便Dojo构建可以在不崩渍的情况下评估此文件 //define self so that the Dojo build can evaluate this file without crashing. self= {}; } //添加接收消息处理的函数 self.onmessage = function (event) { //获取数据 var data = event.data; //用require方式加载程序() require(dataloaderConfig. [data. workerModule]. function (workerModule) { //replace onmessage with the required-in workerModule //将onmessage替换为workerModule脚本中的消息处理函数 self.onmessage = workerModule; //cesium的基本路径 CESIUM BASE URL = data.loaderConfig baseUr'; }); };
参考:https://wenku.baidu.com/view/371e7af130d4b14e852458fb770bf78a65293aeb.html(同https://blog.csdn.net/tianyapai/article/details/120871762)
https://wenku.baidu.com/view/19fd63f95322aaea998fcc22bcd126fff7055d23.html(同https://blog.csdn.net/u010608964/article/details/89453192 https://blog.csdn.net/u010608964/article/details/89329886)