WebAssembly
Wasm
- 用于Web浏览器的低级虚拟机
- 一种二进制指令格式
- 用于在沙箱环境中执行类似汇编语言的代码
WebAssembly 旨在作为JavaScript的补充,而不是替代品,它可以让Web应用程序在浏览器中运行得更快。
主要优势在于其性能
- 二进制格式使得代码加载速度更快
- 低级指令集使得执行速度更高
- 适合处理高性能计算任务:例如游戏、音频/视频处理、物理模拟和3D渲染等。
示例
0. 简单的C语言代码示例
1
2
3
4
5
6
7
8
9
10
11
#include <emscripten.h>
EMSCRIPTEN_KEEPALIVE
int add(int a, int b) {
return a + b;
}
EMSCRIPTEN_KEEPALIVE
int subtract(int a, int b) {
return a - b;
}
EMSCRIPTEN_KEEPALIVE
宏用于告诉Emscripten编译器保留这个函数,以便在WebAssembly模块中导出。
1. 编译
使用Emscripten编译add.c
文件。在命令行中,导航到包含add.c
文件的目录,然后运行以下命令
- 为导出的函数添加下划线
_
是必要的 - Emscripten将C/C++中的函数名映射到WebAssembly模块时,会在函数名前添加一个下划线。避免与JavaScript中的保留字和内置对象冲突。
1
emcc add.c -o add.wasm -s EXPORTED_FUNCTIONS="['_add', '_subtract']" -s EXTRA_EXPORTED_RUNTIME_METHODS="['cwrap']" -s WASM=1
2. 不用宿主环境中使用
2.1 Html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>WebAssembly Example</title>
</head>
<body>
<script>
fetch("add.wasm")
.then((response) => response.arrayBuffer())
.then((bytes) => WebAssembly.instantiate(bytes))
.then((results) => {
const { instance } = results;
const add = instance.exports._add;
const subtract = instance.exports._subtract;
console.log("3 + 5 =", add(3, 5)); // 输出 "3 + 5 = 8"
console.log("5 - 3 =", subtract(5, 3)); // 输出 "5 - 3 = 2"
})
.catch((error) => {
console.error("Error loading WebAssembly module:", error);
});
</script>
</body>
</html>
2.2 Cocos Creator
将编译好的
.wasm
文件(例如add.wasm
)放入Cocos Creator项目的assets
文件夹中。在Cocos Creator中创建一个新的脚本组件(例如
WasmLoader.js
),并在start
或需要的函数中加载和实例化WebAssembly模块:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
cc.Class({ extends: cc.Component, start() { this.loadWasmModule(); }, async loadWasmModule() { try { // 获取.wasm文件的URL const wasmUrl = cc.url.raw('resources/add.wasm'); // 加载和实例化WebAssembly模块 const response = await fetch(wasmUrl); const bytes = await response.arrayBuffer(); const result = await WebAssembly.instantiate(bytes); const instance = result.instance; // 调用导出的函数 const add = instance.exports._add; console.log('3 + 5 =', add(3, 5)); // 输出 "3 + 5 = 8" } catch (error) { console.error('Error loading WebAssembly module:', error); } }, });
如果将Cocos Creator项目导出为原生应用(例如iOS或Android应用),那么资源加载方式会有所不同。
在原生应用中,资源通常会被打包到应用内部,因此不需要通过HTTP操作来加载它们。
在Cocos Creator中,使用cc.assetManager.load
方法来加载资源,这个方法会自动处理不同平台和环境下的资源加载。
使用cc.assetManager.load
加载WebAssembly模块的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
cc.Class({
extends: cc.Component,
start() {
this.loadWasmModule();
},
async loadWasmModule() {
try {
// 获取.wasm文件的URL
const wasmUrl = cc.url.raw('resources/add.wasm');
// 加载.wasm文件
const result = await new Promise((resolve, reject) => {
cc.assetManager.load({ url: wasmUrl, type: 'binary' }, (error, data) => {
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
// 实例化WebAssembly模块
const bytes = new Uint8Array(result);
const wasmModule = await WebAssembly.instantiate(bytes);
const instance = wasmModule.instance;
// 调用导出的函数
const add = instance.exports._add;
console.log('3 + 5 =', add(3, 5)); // 输出 "3 + 5 = 8"
} catch (error) {
console.error('Error loading WebAssembly module:', error);
}
},
});
Chrome Devtool 调试
devtools://devtools/bundled/inspector.html?v8only=true&ws=127.0.0.1:8080
devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=localhost:9222
WebGL 模式的 Unity 游戏跑在浏览器上,相当于游戏逻辑部分跑在 wasm,渲染部分跑在 WebGL 上。
定点数案例
TypeScripts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class FixedPointNumber {
private static scaleFactor = 1000; // 放大因子,根据需要调整
private value: number;
constructor(n: number) {
this.value = FixedPointNumber.fromNumber(n);
}
// 将实数转换为定点数
private static fromNumber(n: number): number {
return Math.round(n * this.scaleFactor);
}
// 将定点数转换为实数
private static toNumber(n: number): number {
return n / this.scaleFactor;
}
// 定点数加法
add(b: FixedPointNumber): FixedPointNumber {
return new FixedPointNumber(FixedPointNumber.toNumber(this.value + b.value));
}
// 定点数减法
subtract(b: FixedPointNumber): FixedPointNumber {
return new FixedPointNumber(FixedPointNumber.toNumber(this.value - b.value));
}
// 定点数乘法
multiply(b: FixedPointNumber): FixedPointNumber {
return new FixedPointNumber(
FixedPointNumber.toNumber(Math.round((this.value * b.value) / FixedPointNumber.scaleFactor))
);
}
// 定点数除法
divide(b: FixedPointNumber): FixedPointNumber {
return new FixedPointNumber(
FixedPointNumber.toNumber(Math.round((this.value * FixedPointNumber.scaleFactor) / b.value))
);
}
// 获取实数值
toNumber(): number {
return FixedPointNumber.toNumber(this.value);
}
}
// 使用示例
let a = new FixedPointNumber(1.23);
let b = new FixedPointNumber(4.56);
let sum = a.add(b);
console.log(sum.toNumber()); // 输出:5.79
C/C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <emscripten.h>
const int SCALE_FACTOR = 1024;
EMSCRIPTEN_KEEPALIVE
int from_number(double n) {
return (int)(n * SCALE_FACTOR + 0.5);
}
EMSCRIPTEN_KEEPALIVE
double to_number(int n) {
return (double)n / SCALE_FACTOR;
}
EMSCRIPTEN_KEEPALIVE
int add(int a, int b) {
return a + b;
}
EMSCRIPTEN_KEEPALIVE
int subtract(int a, int b) {
return a - b;
}
EMSCRIPTEN_KEEPALIVE
int multiply(int a, int b) {
return (a * b) >> 10; // 使用位移运算优化除法
}
EMSCRIPTEN_KEEPALIVE
int divide(int a, int b) {
return (a << 10) / b; // 使用位移运算优化乘法
}
编译 fixed_point.c
, 生成两个文件:fixed_point.wasm
(WebAssembly 模块)和 fixed_point.js
(加载 WebAssembly 模块的 JavaScript 文件)。
1
emcc fixed_point.c -s WASM=1 -O3 -o fixed_point.js -s EXPORTED_FUNCTIONS="['_from_number', '_to_number', '_add', '_subtract', '_multiply', '_divide']" -s EXTRA_EXPORTED_RUNTIME_METHODS="['ccall', 'cwrap']"
将生成的 fixed_point.wasm
和 fixed_point.js
文件复制到 Cocos Creator 项目的 assets
目录下。然后,在 TypeScript 代码中加载和使用这个模块:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// FixedPoint.ts
const { ccall, cwrap } = require("fixed_point.js");
const from_number = cwrap("from_number", "number", ["number"]);
const to_number = cwrap("to_number", "number", ["number"]);
const add = cwrap("add", "number", ["number", "number"]);
const subtract = cwrap("subtract", "number", ["number", "number"]);
const multiply = cwrap("multiply", "number", ["number", "number"]);
const divide = cwrap("divide", "number", ["number", "number"]);
export class FixedPoint {
static fromNumber(n: number): number {
return from_number(n);
}
static toNumber(n: number): number {
return to_number(n);
}
static add(a: number, b: number): number {
return add(a, b);
}
static subtract(a: number, b: number): number {
return subtract(a, b);
}
static multiply(a: number, b: number): number {
return multiply(a, b);
}
static divide(a: number, b: number): number {
return divide(a, b);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// TestFixedPoint.ts
import { FixedPoint } from "./FixedPoint";
const { ccclass, property } = cc._decorator;
@ccclass
export default class TestFixedPoint extends cc.Component {
onLoad() {
let a = FixedPoint.fromNumber(1.23);
let b = FixedPoint.fromNumber(4.56);
let sum = FixedPoint.add(a, b);
console.log(FixedPoint.toNumber(sum)); // 输出:5.79
}
}