本文主要介绍worker在Laya中如何使用,墙裂推荐先阅读H5 worker 系列一 基础知识。
一、Laya中应用
参考多线程worker 官方讲解
1.加载一个文件
//my_task.js
self.addEventListener('message', function (e) {
var xmlreq = new XMLHttpRequest();
xmlreq.responseType = "text";
xmlreq.onload = function (e) {
var data = e.currentTarget.response;
self.postMessage(data);
}
xmlreq.open("get","../res/atlas/comp.atlas");
xmlreq.send()
}, false);
//Main.ts
var worker: any = new Laya.Browser.window.Worker("libs/my_task.js");
worker.onmessage = function (event): void {
console.log(event.data);
};
worker.postMessage("start");
2.使用官方提供的worker.js加载解码图片
官方提供的libs/worker.js对原生的worker进行了封装,后文介绍一下它的思路,现在先看一下如何运用。
package {
import laya.net.Loader;
import laya.utils.Handler;
import view.TestView;
import laya.net.WorkerLoader;
import laya.webgl.WebGL;
public class LayaUISample {
public function LayaUISample() {
//初始化引擎
Laya.init(600, 400,WebGL);
//设置Laya提供的worker.js的路径
WorkerLoader.workerPath = "libs/worker.js";
//开启worker线程
WorkerLoader.enable = true;
//加载引擎需要的资源
Laya.loader.load([{url: "res/atlas/comp.json", type: Loader.ATLAS}], Handler.create(this, onLoaded));
}
private function onLoaded():void {
//实例UI界面
var testView:TestView = new TestView();
Laya.stage.addChild(testView);
}
}
}
WorkerLoader.enable = true;
一句话就开启worker了??有点神奇!看看神奇的WorkerLoader.enable = true;
做了什么
//laya.core.js
/**
*是否启用。
*/
__getset(1, WorkerLoader, 'enable', function () {
return WorkerLoader._enable;
}, function (v) {
if (WorkerLoader.disableJSDecode &&
(!Browser.window.createImageBitmap)) return;
WorkerLoader._enable = v;
if (WorkerLoader._enable &&
WorkerLoader._preLoadFun == null)
WorkerLoader._enable = WorkerLoader.__init__();
});
这里先是一个disableJSDecode开关,默认值为true。结合官方的worker.js源码可以知道,里面会优先使用createImageBitmap方法解码图片,但这个方法有兼容性问题,如果浏览器不支持createImageBitmap,那么worker.js会自己去解码。在这种不兼容的情况下,如果不想让worker.js自己用JS解码,那么就让disableJSDecode=true吧,WorkLoader会return掉,不会开启。
二、Browser.window.createImageBitmap这个方法是啥
safari上的支持度比较惨参考mozilla.org createImageBitmap
createImageBitmap方法存在 windows 和 workers 中. 它接受各种不同的图像来源, 并返回一个Promise, resolve为ImageBitmap. 图像被剪裁成自(sx,sy)且宽度为sw,高度为sh的像素的矩形。
构造函数:createImageBitmap(image[, options]).then(function(response) { ... });
或createImageBitmap(image, sx, sy, sw, sh[, options]).then(function(response) { ... });
image 一个图像源, 可以是一个 <img>, SVG <image>, <video>, <canvas>, HTMLImageElement, SVGImageElement, HTMLVideoElement, HTMLCanvasElement, Blob, ImageData, ImageBitmap, 或 OffscreenCanvas 对象.
var canvas = document.getElementById('myCanvas'),
ctx = canvas.getContext('2d'),
image = new Image();
image.onload = function() {
Promise.all([
createImageBitmap(image, 0, 0, 32, 32),
createImageBitmap(image, 32, 0, 32, 32)
]).then(function(sprites) {
ctx.drawImage(sprites[0], 0, 0);
ctx.drawImage(sprites[1], 32, 32);
});
}
image.src = 'sprites.png';
关于Promise,可以参考JS异步处理系列一 ES6 Promise
三、worker.js中对createImageBitmap方法的使用
function doCreateImageBitmap(t, e) {
try {
var a = getTimeNow();
t = new self.Blob([t], {type: "image/png"}),
self.createImageBitmap(t).then(function (t) {
var i = {};
i.url = e,
i.imageBitmap = t,
i.dataType = "imageBitmap",
i.startTime = a,
i.decodeTime = getTimeNow() - a,
i.sendTime = getTimeNow(),
console.log("Decode By createImageBitmap," + i.decodeTime, e),
i.type = "Image",
postMessage(i, [i.imageBitmap])
})["catch"](function (t) {
showMsgToMain("cache:" + t),
pngFail(e, "parse fail" + t + ":ya")
})
} catch (i) {
pngFail(e, "parse fail" + i.toString() + ":ya")
}
}
关于Blob,可以参考H5直播系列一 Blob File FileReader URL
可以看到最终执行了postMessage(i, [i.imageBitmap])
,postMessage第二个参数转移了数据的控制权。那么这个方法传入的t和e是啥,可以看看loadImage2方法:
function loadImage2(t) {
var e, a = t.url;
e = new XMLHttpRequest,
e.open("GET", a, !0),
e.responseType = "arraybuffer",
e.onload = function () {
var t, i, r = e.response || e.mozResponseArrayBuffer;
if (t = new Uint8Array(r), self.createImageBitmap)
return void doCreateImageBitmap(t, a);
try {
startTime = getTimeNow(),
i = new PNG(t),
i.url = a,
i.startTime = startTime,
i.decodeEndTime = getTimeNow(),
i.decodeTime = i.decodeEndTime - startTime,
pngLoaded(i)
} catch (s) {
pngFail(a, "parse fail" + s.toString() + ":ya")
}
}, e.onerror = function (t) {
pngFail(a, "loadFail")
}, e.send(null)
}
这里就能看出,如果createImageBitmap在浏览器中不支持,就会走到下面用PNG解析的路上去,这条路比较复杂,后面再说。而如果支持createImageBitmap的话,走到postMessage(i, [i.imageBitmap])
就算OK了。本文第五部分会回到laya.core.js中,看看这个数据要怎么接着处理。
四、WorkerLoader和Loader
上面的WorkerLoader.enable = true;
最终会走到__init__
方法
//laya.core.js
WorkerLoader.__init__=function(){
if (WorkerLoader._preLoadFun !=null)return false;
if (!Browser.window.Worker)return false;
WorkerLoader._preLoadFun=Loader["prototype"]["_loadImage"];
Loader["prototype"]["_loadImage"]=WorkerLoader["prototype"]["_loadImage"];
if (!WorkerLoader.I)WorkerLoader.I=new WorkerLoader();
return true;
}
这个地方的写法比较神奇,直接把Loader里默认的_loadImage给替换了,当然换之前先备份成_preLoadFun。看一下WorkerLoader的_loadImage方法
/**
*@private
*加载图片资源。
*@param url 资源地址。
*/
__proto._loadImage=function(url){
var _this=this;
if (!WorkerLoader._enable||url.toLowerCase().indexOf(".png")< 0){
WorkerLoader._preLoadFun.call(_this,url);
return;
}
url=URL.formatURL(url);
function clear (){
laya.net.WorkerLoader.I.off(url,_this,onload);
};
var onload=function (image){
clear();
if (image){
_this["onLoaded"](image);
}else{
WorkerLoader._preLoadFun.call(_this,url);
}
};
laya.net.WorkerLoader.I.on(url,_this,onload);
laya.net.WorkerLoader.I.loadImage(url);
}
url.toLowerCase().indexOf(".png")< 0
说明,laya封装的work.js只解析png,别的还是会用_preLoadFun来解析。最后两行添加了一个对url的侦听,然后开始用loadImage去加载
/**
*加载图片
*@param url 图片地址
*/
__proto.loadImage=function(url){
var data;
data={};
data.type="load";
data.url=url;
this.worker.postMessage(data);
}
看到this.worker.postMessage(data);
,终于是通知worker线程可以加载了。worker线程里的回应如下
onmessage = function(t) {
var e = t.data;
switch (e.type) {
case "load":
loadImage2(e)
}
};
五、回到主线程
在本文第三部分说到,如果浏览器支持createImageBitmap的话,worker线程走到postMessage(i, [i.imageBitmap])
会交给主线程继续处理。
1.构造函数
function WorkerLoader(){
/**
*使用的Worker对象。
*/
this.worker=null;
WorkerLoader.__super.call(this);
var _$this=this;
this.worker=new Browser.window.Worker(WorkerLoader.workerPath);
this.worker.onmessage=function (evt){
_$this.workerMessage(evt.data);
}
}
__proto.workerMessage=function(data){
if (data){
switch(data.type){
case "Image":
this.imageLoaded(data);
break ;
case "Msg":
this.event("image_msg",data.msg);
break ;
}
}
}
/**
*@private
*/
__proto.imageLoaded=function(data){
if (data && data.buffer && data.buffer.length < 10){
WorkerLoader._enable=false;
this._myTrace("buffer lost when postmessage ,disable workerloader");
this.event(data.url,null);
this.event("image_err",data.url+"\n"+data.msg);
return;
}
if (!data.dataType){
this.event(data.url,null);
this.event("image_err",data.url+"\n"+data.msg);
return;
};
var canvas,ctx;
var imageData;
switch(data.dataType){
case "buffer":
canvas=new HTMLCanvas("2D");
ctx=canvas.source.getContext("2d");
imageData=ctx.createImageData(data.width,data.height);
imageData.data.set(data.buffer);
canvas.size(imageData.width,imageData.height);
ctx.putImageData(imageData,0,0);
canvas.memorySize=0;
break ;
case "imagedata":
canvas=new HTMLCanvas("2D");
ctx=canvas.source.getContext("2d");
imageData=data.imagedata;
canvas.size(imageData.width,imageData.height);
ctx.putImageData(imageData,0,0);
imageData=data.imagedata;
canvas.memorySize=0;
break ;
case "imageBitmap":
imageData=data.imageBitmap;
if (!Render.isWebGL){
canvas=new HTMLCanvas("2D");
ctx=canvas.source.getContext("2d");
canvas.size(imageData.width,imageData.height);
ctx.drawImage(imageData,0,0);
canvas.src=data.url;
}else
canvas=imageData;
break ;
}
if (Render.isWebGL)
/*__JS__ */canvas=new laya.webgl.resource.WebGLImage(canvas,data.url);;
this.event(data.url,canvas);
}
我们还是先看浏览器支持createImageBitmap的情况,此时i.imageBitmap = t, i.dataType = "imageBitmap",
也就是上面switch分支的最后一种情况。此时imageData正是由createImageBitmap返回的ImageBitmap,然后就可以通过event抛出去了,类型是url,和上面laya.net.WorkerLoader.I.on(url,_this,onload);
对应上。
六、浏览器不支持createImageBitmap,要自己去解析
上面曾经介绍过,不支持createImageBitmap时会怎么样
function loadImage2(t) {
var e, a = t.url;
e = new XMLHttpRequest,
e.open("GET", a, !0),
e.responseType = "arraybuffer",
e.onload = function () {
var t, i, r = e.response || e.mozResponseArrayBuffer;
if (t = new Uint8Array(r), self.createImageBitmap)
return void doCreateImageBitmap(t, a);
try {
startTime = getTimeNow(),
i = new PNG(t),
i.url = a,
i.startTime = startTime,
i.decodeEndTime = getTimeNow(),
i.decodeTime = i.decodeEndTime - startTime,
pngLoaded(i)
} catch (s) {
pngFail(a, "parse fail" + s.toString() + ":ya")
}
}, e.onerror = function (t) {
pngFail(a, "loadFail")
}, e.send(null)
}
1.先看一下pngLoaded
function pngLoaded(t) {
var e = {};
e.url = t.url,
canUseImageData ?
(e.imagedata = t.getImageData(), e.dataType = "imagedata") :
(e.buffer = t.getImageDataBuffer(), e.dataType = "buffer"),
e.width = t.width,
e.height = t.height,
e.decodeTime = getTimeNow() - t.startTime,
console.log("Decode By PNG.js," + (getTimeNow() - t.startTime), t.url),
e.type = "Image",
canUseImageData ?
postMessage(e, [e.imagedata.data.buffer]) :
postMessage(e, [e.buffer.buffer])
}
这里canUseImageData属性在work.js刚开始就会执行到,就是检测一下ImageData能不能兼容
var canUseImageData = !1;
testCanImageData();
function testCanImageData() {
try {
cc = new ImageData(20, 20),
canUseImageData = !0
} catch (t) { }
}
关于ImageData,可以参考h5 canvas ImageData
2.PNG
上面调用的时候,是这样传入的
t = new Uint8Array(r)
i = new PNG(t)
pngLoaded(i)
看一下大致结构
我们在外面调用的就是getImageData和getImageDataBuffer这两个方法。具体如何解析png格式,这里就不做分析了。看源码,用的是github png.js
七、WorkerLoad的释放
在H5 worker 系列一 基础知识中有提到:
Worker 线程一旦新建成功,就会始终运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信。但是,这也造成了 Worker 比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭。
那么在laya中使用了WorkerLoader.enable = true;
后,加载完一张png后显然是没有关闭的,因为还要加载别的png,还要在运行时加载png。简单来说,Loader的_loadImage已经被替换成WorkerLoader的_loadImage。后面所有加载png的操作,都会交给WorkerLoader处理。我们可以简单测试一下:
1.在worker.js里,增加一个消息响应类型test
//worker.js
onmessage = function(t) {
var e = t.data;
switch (e.type) {
case "load":
loadImage2(e);
case "test":
console.log("cuixu test");
break;
}
};
2.发送消息
laya.net.WorkerLoader.I.worker.postMessage({type:'test'});
,console消息出来了
3.怎么才能结束这个worker进程
截止laya 1.7.20版本,没有在laya.core.js中提供terminate方法,或者在worker.js中提供close方法。其实我们在enable方法中针对false处理一下即可。
if (WorkerLoader._enable && WorkerLoader._preLoadFun==null){
WorkerLoader._enable=WorkerLoader.__init__();
}else{
if(WorkerLoader.I){
WorkerLoader.I.worker.terminate();
}
if(WorkerLoader._preLoadFun){
Loader["prototype"]["_loadImage"]=WorkerLoader._preLoadFun;
}
}
4.因为运行时加载新png,这个worker进程最好不要关,但是闲置也是浪费。是不是可以扩展一下,利用它做点别的任务,这就要修改官方的worker.js了。不过想想,还是专项任务,用专项worker吧,用完自己关掉比较好。
八、doCreateImageBitmap失败
在实际运行中,发现console里会有几次这样的parse failInvalidStateError: The source image could not be decoded.:ya
,这是怎么回事?
1.在doCreateImageBitmap方法中,有这么一段异常处理
})["catch"](function (t) {
showMsgToMain("cache:" + t),
pngFail(e, "parse fail" + t + ":ya")
})
因为createImageBitmap方法返回的是个Promise,所以这里写的不那么标准。其实应该把["catch"]
后面的那个function放在then的第二个参数里的,当然最终效果一致。不过console出来的t有点区别,用then第二个参数是DOMException: The source image could not be decoded.
默认的打印是这样的parse failInvalidStateError: The source image could not be decoded.:ya
2.加载失败的后果
var onload = function (image) {
clear();
if (image) {
_this["onLoaded"](image);
} else {
//失败之后使用原版的加载函数加载重试
//_this.event(Event.ERROR, "Load image failed");
WorkerLoader._preLoadFun.call(_this, url);
}
};
3.随便看一个图片,http://192.168.198.102:8080/libs/res/atlas/lobby.png
,原来是路径不对。那只能这样修复了:laya.net.WorkerLoader.workerPath = "worker.js";
把workerPath默认的"libs/worker.js"前面的libs去掉。
网友评论