阅读 2409

【JSConf EU 2018】Rust + WebAssembly

欧洲JSConf上的神秘项目

在今年的欧洲JSConf上来自Mozilla的Lin Clark为我们展示一个神秘项目,一个的拱形彩虹门(视频传送门),它实际上是由三万个彩色LED组成的画布,可以展现灯光动画,并且通过Rust编写的WebAssembly模块来控制“拱门”的灯光动画。Lin在2017年的JSConf上也曾做过关于WebAssembly的演讲,在该演讲中她提到2008年是JavaScript执行效率曲线的一个拐点,随着众多浏览器加入了JIT编译器(Just-in-time compiler),JavaScript的运行性能带来了十倍的增速,这为JavaScript插上了一双翅膀使它可以自由翱翔在浏览器端、服务器端和客户端。Lin指出WebAssembly的诞生可能会成为曲线的下一个拐点。可见Mozilla对WebAssembly的重视程度。

emm...让我们回到2018年。Lin在会上说明了如何使用WebAssembly模块控制“拱门”的灯光动画。下面让我们来看下这是如何做到的。

用字节把空间或时间连续起来

我们所处的空间是一个三维空间,如果再给它加上时间维度,那它就是四维空间。

计算机是没有办法理解这个四维空间的,如果要让计算机可以“理解”,我们需要对四维空间降维。首先是时间维度,可以通过帧来完成的。显示屏就像一个翻页的书,每一帧就相当于书的一页。

在web端,60FPS是可以通过屏幕流畅展示动画的要求。这意味着你有60个不同的屏幕快照--一秒时间内60个点的动画定格的样子。可以想象下一连串代表着三维空间的快照。

现在要从三维降到二维,我们要做的是将空间压平到一个方格纸上。

现在已经下降到了二维,我们需要再一次降维。把方格纸上面的每一行拿出来按顺序连接起来。

现在下降到了一行像素,我们已经可以把它放到内存里面了,因为WebAssembly内存(linear memory)基本就是一排小格子。这意味着我们已经下降到一维结构,但是仍然拥有着代表着二维、三维或四维的全部数据。只是现在它们是以一种连续的、线性的方式在展示。

线性内存(Linear memory)

线性内存是JavaScript和WebAssembly的一个主要通信方式,WebAssembly和运行它的JavaScript都可以访问这个对象。线性内存实际上是一个可变大小的ArrayBuffer对象,本质上是连续的、按字节可寻址的一段内存。

为了使上面提到的“拱门”产生灯光动画,JavaScript告诉WebAssembly模块:“好的,现在就填入动画。”,这一步通过JavaScript调用WebAssembly的function来完成。

通过WebAssembly为线性内存里的每一个像素填入颜色。

然后JavaScript代码将获取这些颜色数据并把他们转化成一个JSON数组发送给“拱门”。

接下来我们来看下如何在JavaScript里使用这些数据。

为线性内存填入颜色

线性内存其实是一大行0和1。如果你想赋予这些0和1意义,那你需要指出如何分割它们。你要做的是为AarryBuffer创建一个TypedArray,告诉JavaScript如何对AarryBuffer里的比特位(bit)行分段。就像正在绘制的格子围绕着这些比特位对它们说那些比特位是属于那些数字。

例如你用了一个16进制的值,那你的数字将有24位的宽度。所以你需要一个24位的格子。每个格子都包含一个像素,而可容纳24位最小的格子是32位的(int32),所以我们将在这个缓冲区上创建一个Int32Array的视图。它将这段缓冲区的字节码包裹到格子里。在这个例子里我们需要添加一些空白来填充这个格子由24位补齐到32位。

如果我们使用RGB值,这些格子将是8位的宽度。为了得到一个RGB的值,你将使用每三个格子代表你的R、G和B的值。这意味着你需要遍历这些格子,并取出它们里面的数字。

直接使用线性内存,你需要手动(写一些代码)遍历它,把它里面的数据取出来存放到更加合理的数据结构里。对于这个项目来说,这样做不是很糟糕。颜色的映射是数字,它们相对容易使用线性内存来表示。我们使用的数据结构(RGB值)也不是很复杂。但是如果你使用的是更加复杂的数据结构,直接处理内存将会很痛苦。如果你可以直接传一个JavaScript对象给WebAssembly,让WebAssembly去维护它,这将会变简单很多。你只需要添加一个很小的库(wasm-bindgen)就可以做到这些。

使用wasm-bindgen

wasm-bindgen是使用Rust写的一个库,它可以使JavaScript与WebAssembly模块之间的数据交互变的更简单。它用一个JavaScript wrapper 来包裹WebAssembly模块。这个wrapper知道如何将复杂的JavaScript对象写入线性内存。然后,当WebAssembly函数返回一个值时,JavaScript wrapper将从线性内存中获取数据并将其重新转换为JavaScript对象。

要做到这一点,它会查看Rust代码中的函数签名,并计算出所需的JavaScript。这适用于像字符串这样的内置类型,也适用于你定义在代码里的类型。wasm-bindgen将Rust结构体转化JavaScript的类。该工具在当前版本只支持Rust,之后会不断完善从而支持其他编程语言(例如:C/C++)。

这个项目是开始学习WebAssembly的很好的开始,因为我们可以通过自己编写的WebAssembly模块来对真实世界产生影响。是不是很酷、很有趣。想体验通过写WebAssembly模块来控制灯光动画的乐趣吗?Mozilla已经帮我们准备了在线环境The Arch,现在就开始体验吧!