数据类型都是强类型的,函数名以类名Set_开头,类的数据放在一个struct结构里面。主要导出函数为:
#include <emscripten.h> EMSCRIPTEN_KEEPALIVE //这个宏表示这个函数要作为导出的函数 int **Maze_generate(int columns, int rows){ Maze_init(columns, rows); Maze_doGenerate(); return data.linkedMap; //return Maze_getJSONStr(); }传进来列数和行数,返回一个二维数组。其它代码相应地改成C代码,这里不再放出来。需要注意的是,由于这里用到了一些C内置的库,如使用随机数函数rand(),所以不能用上文提到的生成wasm的方法,不然会报rand等库函数没有定义。
把生成wasm的命令改成:
emcc maze.c -Os -s WASM=1 -o maze-wasm.html
这样它会生成一个maze-wasm.js和maze-wasm.wasm(生成的html文件不需要用到),生成的JS文件是用来自动加载和导入wasm文件的,在html里面引入这个JS:
<script src="maze-wasm.js"></script> <script src="maze.js"></script>它就会自动去加载maze-wasm.wasm文件,同时会定义一个全局的Module对象,在wasm文件加载好之后会触发onInit,所以调它的api添加一个监听函数,如下代码所示:
var maze = new Maze(column, row, canvas); Module.addOnInit(function(){ var ptr = Module._Maze_generate(column, row); maze.linkedMap = readInt32Array(ptr, column * row); maze.draw(); });有两种方法可以得到导出的函数,一种是在函数名前面加_,如Module._Maze_generate,第二种是使用它提供的ccall或cwrap函数,如ccall:
var linkedMapPtr = Module.ccall("Maze_generate", "number", ["number", "number"], [column, row]);第一个参数表示函数名,第二个返回类型,第三个参数类型,第四个传参,或者用cwrap:
var mazeGenerate = Module.cwrap("Maze_generate", "number", ["number", "number"]); var linkedMapPtr = mazeGenerate(column, row);三种方法都会返回linkedMap的指针地址,可通过Module.get得到地址里面的值,如下代码所示:
function readInt32Array(ptr, length) { var linkedMap = new Array(length); for(var i = 0; i < length; i++) { var subptr = Module.getValue(ptr + (i * 4), 'i32'); var neiborcells = []; for(var j = 0; j < 4; j++){ var value = Module.getValue(subptr + (j * 4), 'i32'); if(value !== -1){ neiborcells.push(value, 'i32'); } } linkedMap[i] = neiborcells; } return linkedMap; }由于它是一个二维数组,所以数组里面存放的是指向数组的指针,因此需要再对这些指针再做一次get操作,就可以拿到具体的值了。如果取出的值是-1则表示不是有效的相邻元素,因为C里面数组的长度是固定的,无法随便动态push,因此我在C里面都初始化了4个,因为相邻元素最多只有4个,初始时用-1填充。取出非-1的值push到JS的数组里面,得到一个用WASM计算的linkedMap. 然后再用同样的方法去画地图。
最后再比较一下WASM和JS生成迷宫的时间。如下代码所示,运行50次:
var count = 50; console.time("JS generate maze"); for(var i = 0; i < count; i++){ var maze = new Maze(column, row, canvas); maze.generate(); } console.timeEnd("JS generate maze"); Module.addOnInit(function(){ console.time("WASM generate maze"); for(var i = 0; i < count; i++){ var maze = new Maze(column, row, canvas); var ptr = Module._Maze_generate(column, row); var linkedMap = readInt32Array(ptr, column * row); } console.timeEnd("WASM generate maze"); })迷宫的规模为50 * 50,结果如下:
可以看到,WASM的时间大概快了25%,并且有时候会观察到WASM的时间甚至要比JS的时间要长,这时因为算法是随机的,有时候拆掉的墙可能会比较多,所以偏差会比较大。但是大部份情况下的25%还是可信的,因为如果把随机选取的墙保存起来,然后让JS和WASM用同样的数据,这个时间差就会固定在25%,如下图所示:
这个时间要比上面的大,因为保存了一个需要拆的墙比较多的数组。理论上不用产生随机数,时间会更少,不过我们的重点是比较它们的时间差,结果是不管运行多少次,时间差都比较稳定。
所以在这个例子里面WASM节省了25%的时间,虽然提升不是很明显,但还是有效果,很多个25%累积起来还是挺长的。
综上,本文用JS和WASM使用连通集算法生成迷宫,并用最短路径算法求解迷宫的路径。使用WASM在生成迷宫的例子里面可以提升25%的速度。
虽然迷宫小时候就已经在玩了,不是什么高大上的东西,但是通过这个例子讨论到了一些算法,还用到了很出名的最短路径算法,还把WASM实际地应用了一遍,作为学习的的模型还是挺好的。更多的算法可参考这篇《我接触过的前端数据结构与算法》。
原文:用Canvas + WASM画一个迷宫