WebAssembly 游戏开发技术专题全层级12 / 31 已发布

Rust + WebAssembly 游戏开发实战:wasm-pack 与 web-sys 生态全景

· 0 分钟阅读
Rust + WebAssembly 游戏开发实战:wasm-pack 与 web-sys 生态全景 Rust + Wasm Game Dev in Practice: wasm-pack, wasm-bindgen & web-sys — WebAssembly 游戏开发技术专题

Rust + WebAssembly 游戏开发实战:wasm-pack 与 web-sys 生态全景 Rust + Wasm Game Dev in Practice: wasm-pack, wasm-bindgen & web-sys

为什么 Rust 是独立开发者踏进 Wasm 的第一块地基

第一次用 Rust 写浏览器游戏的那个晚上,我到现在还记得。写完两百来行代码,敲下 wasm-pack 的构建命令,终端转了一分半,然后吐出一行绿色的字,浏览器里一个方块开始动了。我当时的第一反应是:这也太爽了。

三天后我把判断改了。不是因为不能用,恰恰是因为太顺——顺到你根本意识不到,自己正在做一件很贵的事。每一次 Rust 跟 JavaScript 说话,都要付一笔过路费;每一个随手引入的依赖,最后都会变成玩家要下载的字节;每一次程序内崩溃,浏览器给你的是一片白屏加一行看不出所以然的报错。

这就是 Rust + Wasm 这条路的全部真相:它把“把游戏编译进浏览器”从一门要熬三个月的手艺,变成了一条装好就能跑的生产线,同时也把这门手艺里最容易被忽略的三件事——语言边界、包体、错误可见性——藏进了这条生产线的褶皱里。

放在整个系列里看,这一篇的位置很清楚:[06] 讲的是 Emscripten 那条路,把一整个 C/C++ 引擎连人带行李搬进浏览器,讲究的是“别动,我扛你进去”;而 Rust 这条路是另一种走法,从第一行代码开始就是奔着编译到 Wasm 去的,没有历史包袱,也不需要模拟一整套 POSIX 文件系统。代价是,你得认这套生态自己定的规矩。

我把这篇拆成四层来讲。第一层,wasm-bindgen 到底在替你做什么,它的成本在哪儿;第二层,wasm-pack 这个打包工具的选项为什么能让人踩一整天坑;第三层,混合架构——Rust 管什么、JavaScript 管什么,这条分界线画错,你后面三个月都在还债;第四层,包体,从几 MB 砍到几十 KB 的真实手段和真实代价。最后给一张能直接抄的工程配置表,和几个我被问过一百遍的问题。

第 1 层:wasm-bindgen 不是魔法,是一条有收费站的传送带

先讲一件很多人没搞明白的事:WebAssembly 模块跟 JavaScript 之间,原生只能传数字。整数、浮点,就这些。传不了字符串,传不了对象,传不了数组——严格说是能传数组,但那只是传了一个内存地址,两边还得自己约定这块内存怎么读。

所以你要在 Rust 里写一个“在某个坐标生成敌人”的功能,给 JavaScript 调用,理论上你得这么干:在共享内存里划一块地方,两边约定前 4 个字节是横坐标,后 4 个字节是纵坐标,然后 JavaScript 那边手动往这块内存里写数,再调 wasm 的某个导出函数。能跑,但一天写下来你会想转行。

wasm-bindgen 干的就是把这个过程自动化。你在 Rust 侧用一个标记告诉它“这个函数要给 JavaScript 看”,它在编译期生成两样东西:一段嵌进 wasm 里的额外代码,和一个 JavaScript 胶水文件。胶水文件负责把 JavaScript 那边的调用“翻译”成 wasm 能听懂的形态,复杂类型要走共享内存这块传送带。

于是你就得到了那种爽感:Rust 里写一个带参数的函数,JavaScript 里直接就能调,字符串、结构体、甚至回调函数都能传。看着像零成本。

它不是零成本。它是把成本摊到了每一次调用上,而且摊得非常不均匀。

传一个数字过去,基本上就是一次普通函数调用,快到你不用管。传一个字符串,Rust 侧要在自己的线性内存里分配一块空间,把 UTF-8 的字节写进去,JavaScript 侧再读出来转成 UTF-16 的字符串。一次两次没事,你要是每帧传几十个字符串——比如把每个敌人的名字、状态、血量都拼成字符串发给 UI——那这一帧的预算就被吃掉了相当一块。

还有一类更隐蔽的成本:闭包。你想让 Rust 侧注册一个事件监听,或者让 Rust 记住一个 JavaScript 回调,wasm-bindgen 会在堆上给这个闭包建一个“壳”。如果你在每帧的循环里新建闭包,那就是每帧泄漏一点点内存,跑十分钟浏览器开始卡,你还找不到原因。我第一版就是这么写的,找了两天才发现是动画回调里每帧重新注册了一次。

跨界传的东西相对成本量级典型翻车场景
整数、浮点、布尔1 倍(基准)基本不会翻车,可以放心高频调用
定长小结构体(按值传)2~5 倍每帧几千次会有感知,但不致命
短字符串(十几个字符)50~200 倍每帧上百次就明显掉帧
变长数组 / 二进制块10~100 倍(跟长度走)一次性传几 MB 会造成可感知的卡顿尖峰
新建并注册闭包首次极贵,且占内存写在更新循环里 = 稳定内存泄漏
调用带标记的结构体方法跟普通调用差不多,但每次都有类型检查把结构体当高频热路径用会亏

真正把这些数字讲活的案例,是 Ruffle。

Ruffle 是一个用 Rust 写的 Flash 模拟器,做的事情相当硬核:让成千上万个已经死掉的 Flash 小游戏重新在浏览器里跑起来。它重度依赖 web-sys——画布、WebGL、音频、各种浏览器事件,全都要从 Rust 侧去调。如果按最直觉的写法,每个精灵、每个图形元素都单独跨一次边界去发绘制指令,那么一个稍复杂的 Flash 动画,光边界开销就能把帧率按在地上。

Ruffle 的架构里有一条很明确的设计:绘制指令不在 Rust 里逐条发给浏览器,而是在 Rust 侧先攒成一个批次,再成批地交给底层的图形接口。这个思路换到任何游戏里都成立——把边界当成一帧预算里的一项支出,而不是空气。我的经验值是这样:一帧 16.7 毫秒,跨语言边界的总开销控制在 0.5 毫秒以内,玩家完全感觉不到;到 1.5 毫秒开始在某些低端机上出现抖动;超过 3 毫秒,你会开始怀疑是不是自己写了个假游戏。

所以,wasm-bindgen 的本质不是“让 Rust 和 JavaScript 无缝”,而是“让 Rust 和 JavaScript 之间的收费站修得比较好看”。你能过,但每次都要交钱,交多少取决于你运的是人还是货。

第 2 层:wasm-pack 的三岔路口,选错了要重来一天

wasm-pack 是把四件事串成一条流水线的工具:编译到 wasm 目标、跑 wasm-bindgen 生成胶水、跑代码体积优化、生成一个能被前端工程直接吃进去的包。它替我省掉的重复劳动,少说也有几百个小时。

它也给过我一个下午的绝望。原因是构建目标选错了。

wasm-pack 支持三种输出形态,对应三种前端接入方式。第一种是给浏览器直接用的 ES 模块形态,产出一个 wasm 加一个可以直接被浏览器加载的 JS 模块;第二种是给打包器(webpack、vite 这一类)用的形态,胶水代码会按打包器的模块规范来写;第三种是给 Node 环境用的。

我第一次做项目,随手选了默认那个,结果在本地起的开发服务器里怎么都加载不出来,报错信息还特别含糊。折腾到晚上才反应过来,我用的构建器和产物形态不匹配,胶水文件被重复打包了两次,模块初始化了两遍,整个游戏跑起来有两套状态,输入进 A 那套,渲染读 B 那套。

这件事的教训并不玄乎:先把“谁加载这个 wasm”想清楚,再动手写第一行业务代码。用 vite 这类现代工具,选给打包器的那种;要发布一个能被任意网页直接嵌入的小游戏,选浏览器直接加载的那种;要跑在服务端做逻辑校验,选 Node 那种。

第二个岔路口是 web-sys 的特性开关。

web-sys 的做法很有意思:它把浏览器那一整套 Web API 全部声明成了 Rust 侧的绑定,但默认一个都不启用。你用哪个,就在依赖配置里开哪个。不开?编译直接报错,找不到这个类型。这个设计的好处是产物体积不会被你没用到的东西拖累,坏处是——你会在编译器和文档之间来回跳一整个下午,就为了找一个功能到底叫什么名字。

这里有一组我实测出来的经验区间,供你判断自己是不是开了太多东西:一个纯 2D 小游戏,只需要画布、定时器、键盘鼠标、音频最基础的那几个接口,特性大概开十到二十个;一个带 WebGL2、带手柄支持、带触屏手势的中型项目,三十到五十个;如果你开了七八十个以上,先别急着高兴功能齐全,去看看编译时间和产物体积,多半有冤枉钱。

决策点建议取值踩错的后果
构建目标形态按加载方选:打包器 / 浏览器直载 / Node模块被加载两次,状态分裂,最难查的一类 bug
web-sys 特性数量2D 小游戏 10~20;中型 30~50编译时间拉长,体积偷偷变胖,还没人告诉你
是否开启调试信息开发期开,发布关发布包里塞了几 MB 的调试段,白送用户下载
首次构建是否做体积优化开发期关掉,发布再开开发期每次改一个数字都要等半分钟

说到框架选型,顺手给一个参照:Macroquad 这个 Rust 轻量游戏框架,走的就是“够用就好”的路线,把画布、输入、音频包了一层薄薄的封装,没有重型架构,一个小游戏编译出来的 wasm 通常在一两 MB 这个量级(压缩前)。它的作者本身就是从做小游戏出发设计这套东西的,所以整个接入体验就是:写逻辑,编译,页面里跑起来,中间没有仪式感。这也是我在给独立开发者做技术选型建议时的默认推荐——先能出东西,再谈架构。

第 3 层:混合架构——把 DOM 当外部设备,别当自家后院

这一层是我见过最多人走弯路的地方。很多人的直觉是:既然 Rust 又快又安全,那整个游戏从渲染到 UI 全用 Rust 写,JavaScript 一行都不要,最纯粹。

技术上能做到,产品上基本是自残。

为什么?因为浏览器里最贵的东西从来不是绘制,是那套 DOM 生态。设置面板、音量滑块、语言切换、排行榜、商店页 SDK 接入、无障碍支持、输入法兼容——这些东西在 JavaScript 侧已经有十几年的积累,你换到 Rust 里,等于把别人建好的高速公路拆了,自己拿铁锹修土路。而且每修一段,都要跨一次语言边界去调浏览器接口。

所以我给的分界线是这样的:Rust 管世界,JavaScript 管世界的外壳。

Rust 侧负责一切和“游戏世界状态”强相关的东西:物理和碰撞、寻路、AI 状态机、战斗结算、地图数据解析、存档序列化、随机数。这些东西的特点是每一帧都在变、计算密集、而且不需要跟浏览器打交道。JavaScript 侧负责一切“展示和控制”的东西:菜单、设置、教程文案、多语言、加载进度、暂停界面、内购与分享按钮。

分界线画好了,剩下的问题是:两边怎么说话。

两种模式,各有各的适合场景。拉模式是 JavaScript 主动去问:每隔一段时间,或者每帧,调一次 Rust 侧的导出方法,把需要的状态读回来。推模式是 Rust 主动去喊:血条变了、关卡过了、角色死了,这时候调一下 JavaScript 那边注册好的回调。

我的实用配比是:高频、连续变化的数值走拉,低频、离散发生的事件走推。血条、能量条、倒计时这种每帧都在动的东西,让 JavaScript 每帧拉一次,一次拉回一个扁平的数值数组,成本几乎为零;而“玩家死亡”“任务完成”“获得成就”这种一辈子发生几次的事情,走推模式,Rust 直接调回调,响应即时,逻辑也清楚。

千万不要反过来做。我见过一个项目,每帧从 Rust 往 JavaScript 推几十个状态更新,每个更新都带一个对象和几个字符串。跑在开发机上没事,一上低端安卓机,帧率直接砍半。把推送改成拉取,再把字符串改成数字索引,性能立刻回来了,改动量不到半天。

数据类型推荐模式刷新频率每次数据形态
血量、能量、倒计时、坐标拉取每帧一次扁平数值数组
背包、技能列表拉取变化时 + 每 200 毫秒兜底数字索引数组
关卡完成、角色死亡、成就推送事件发生时一个事件编号 + 必要参数
对话文本、物品描述推送一次后缓存加载时一次字符串(只在此时传)
存档读写拉取 + 推送确认用户操作时二进制块

说到这,必须聊一下 Bevy。Bevy 是 Rust 生态里最火的游戏引擎,实体组件架构,设计得非常漂亮,官方示例编译到浏览器也跑得动。但我对独立团队的建议一贯是:Bevy 做原生很棒,做 Web 要三思。

三思的原因不在性能,在迭代速度。Bevy 的编译时间在中等项目上就不短,加上 wasm 目标的编译和胶水生成,改一行逻辑等半分钟的循环,做 Web 小游戏是致命的——因为 Web 游戏的优势本来就是“改动快、发布快、试错快”。你把这三点用编译时间换掉了,那还不如直接做原生。

我的粗暴判断标准:如果你这个游戏的世界状态能用一个几百字节的结构体描述,用轻框架就够了;如果它需要一整套实体组件系统来管理上万个实体,那这个项目本来就不该首选浏览器作为第一平台。

第 4 层:包体——不是语言决定的,是 ABI 决定的

包体这件事,我一开始的理解是错的。我以为 Rust 编译出来小,是因为 Rust 干净。后来把一个空项目编译出来一看,什么都没写,产物就有一大坨。为什么?因为标准库里那些东西——错误处理、格式化、回溯信息——默认都被拖进来了。

先讲清楚包体的构成。一个 Rust 编译出来的 wasm,体积大概是四块拼起来的:你自己写的游戏代码、标准库被引用到的部分(格式化字符串和错误处理是重灾区)、第三方依赖、以及 wasm-bindgen 生成的胶水和运行时支撑代码。很多时候你自己的代码只占两成,剩下八成全是“顺手带进来的”。

然后是最反直觉的一条:压缩才是最大的一刀,编译优化只是小刀。

很多人第一次看自己的 wasm 有 3MB,慌了,去调编译优化选项,折腾一晚上砍到 2.2MB,觉得自己很厉害。结果服务器开启服务端压缩之后,直接变成 700KB 传输量。前面那一晚上砍的 800KB,压缩之后大概只值 150KB。

顺序搞反了。先做最便宜的那件事,再做最贵的那件事。

手段体积收益区间代价什么时候做
服务端压缩传输减 60%~75%几乎没有,配一下服务器第一天就该做
裁掉调试信息减 10%~40%(看项目)发布版没法查问题发布构建时
体积优先的编译优化 + 全量内联减 20%~40%编译时间翻倍发布构建时
崩溃即中止策略减 5%~15%崩了没有堆栈发布构建时
自定义崩溃处理,砍掉格式化减 10%~25%报错信息变成一串数字包体吃紧时
剥离无用代码的构建后优化减 10%~20%有极小概率误删,要回归测试发布构建时
换掉重型依赖(序列化、正则、时间库)减 5%~30%要自己写点轮子立项时就该想
完全不依赖标准库能压到几十 KB 级别很多东西要自己实现极端场景

最能说明“ABI 决定体积”这个论断的案例,是 WASM-4。

WASM-4 不是一个游戏,它是一台虚拟的复古游戏机——你可以把它想成一个跑在浏览器里的小霸王。它的设计极其克制:一块固定尺寸的低分辨率画布,四个方向键加两个动作键,一小段音频缓冲区,再加上极其有限的内存预算。开发者用 Rust、C 或者 Zig 写游戏,编译成 wasm,这个 wasm 就是“卡带”。

它做对的最关键一件事,是把接口做得极窄。整台机器对外只暴露极其有限的一组绘图和输入接口,游戏侧只需要导出启动和每帧更新这两个入口。没有一整套 Web API 绑定,没有胶水层里那些自动生成的类型转换代码,游戏代码可以完全不依赖标准库写出来。结果就是,一整款能玩上半小时的小游戏,编译出来往往只有几十 KB。

你把这个数字跟“用完整工具链的 Rust 网页游戏通常一两 MB 起步”放在一起看,会突然明白一件事:包体的大头从来不是你的游戏逻辑,是你为了开发方便而引入的那一整套运行时契约。wasm-bindgen 和 web-sys 换来的是开发效率,代价是每个项目都要交一笔固定的“契约税”。对一个休闲小游戏,这笔税可能比游戏本体还贵。

顺手做个横向参照。走 Emscripten 那条路的 C/C++ 项目,产物通常也不小,因为它会带上一套 C 标准库和模拟文件系统。Rust 在默认配置下的体积跟它差不多,但 Rust 的优势在于:往下砍的路径更干净。你想彻底不要标准库,Rust 那边就是加一个声明的事,C 那边要处理一堆头文件和链接细节。这也是为什么那些追求极致体积的小游戏项目,越来越多选 Rust。

参数化设计工具:一张能直接抄的工程配置表

这张表是我从三个上线项目里攒出来的,直接照抄,比你在文档里翻半天强。左边是配置项,中间是我给的推荐区间,右边是设错了会怎么样。

配置项开发期取值发布期取值设错的后果
优化级别平衡档(编译快)体积优先档 + 全量内联开发期用体积档,改一行等半分钟,人会疯
编译单元数默认并行压到 1,换体积发布期还开并行,白丢一成体积
崩溃策略展开(保留堆栈)中止(省体积)发布期保留展开,多几百 KB,且没人看
调试信息完整剥离忘了剥,白送用户下载
web-sys 特性数按需开,控制在 50 以内跟开发期一致无脑全开,编译时间翻倍,体积悄悄涨
跨边界调用频率不管每帧 ≤ 200 次;字符串传递每帧 ≤ 10 次超了就是稳定掉帧,且极难归因
跨边界时间预算—每帧 ≤ 0.5 毫秒到 1.5 毫秒,低端机开始抖
状态同步周期每帧高频值每帧拉;低频事件走推送全部走推送 + 字符串,低端机帧率腰斩
wasm 传输体积目标—2D 小游戏 ≤ 800KB;中型 ≤ 2.5MB超了移动端首屏流失肉眼可见

有两行我特别想加粗:跨边界时间预算,和传输体积目标。这两条是少数几个“写进项目验收标准里也不过分”的量化指标。别的参数可以商量,这两条一旦破防,玩家是真的会用脚投票。

双路径:你现在该看哪一段

初级路径——你刚决定用 Rust 做第一个网页游戏,想两周内跑出能玩的东西。

按这个顺序走,别跳步。第一,选一个轻框架,Macroquad 这类,别上来就碰重型实体组件引擎。第二,把构建目标定下来:你要发到哪里?自己一个页面,还是要塞进现成的前端工程?定完之后构建配置就别再动。第三,游戏逻辑全部写在 Rust 里,UI 一律用 HTML 写,两边只通过一个极窄的接口说话:Rust 导出一个“取状态”的方法,返回一组数字;JavaScript 每帧调一次,更新界面。第四,从头就打开服务端压缩,别等到上线才发现传输量吓人。第五,崩溃策略在开发期保持展开,让你能看见问题在哪;发布时再关。

这五步走完,你会有一个能在手机上跑、加载不慢、结构清晰的小游戏。丑一点无所谓,先让它存在。

进阶路径——你已经有跑通的项目,现在要挤性能或者挤体积。

先量,再改,这个顺序不能反。第一步,把每帧的跨边界调用次数打点出来,你会被这个数字吓到,我第一次打点发现自己一帧调了三千多次。第二步,按“调用次数 × 单次成本”排序,从最贵的那一项开始改,通常是字符串和闭包。第三步,把渲染指令批量化——所有绘制先在 Rust 侧攒成一批,一次性交给底层,而不是逐条发。第四步,才轮到体积优化,按前面那张表的顺序:先压缩、再剥调试、再调优化级别、再换依赖,最后才考虑不要标准库。

这里有个反直觉的建议:不要因为“用了 wasm-bindgen 就羞耻”。我见过有人为了追求极致,硬要把胶水层全部手写,结果三个月做出来一个功能只有别人三分之一的游戏,还带一堆内存泄漏。工具的价值是让你把时间花在玩法上,不是花在边界上。真正该手写接口的场景只有一个:你的游戏体积预算已经到了几十 KB 这个级别,那才值得。

争议与常见坑:那些让我半夜爬起来改代码的事

坑一:以为 Rust 天然比 C++ 产物小。这话只在不带工具链的前提下成立。一个默认配置的 Rust 网页游戏,跟同规模的 Emscripten 项目体积是一个量级的,谁也不比谁高贵。Rust 真正的优势是往下砍的路更干净,而不是起点更低。

坑二:在更新循环里新建闭包。我最惨的一次,游戏跑了八分钟开始卡,内存曲线像爬楼梯。查了一圈图形、音频、资源加载,最后发现是动画回调里每帧重新注册了一次回调函数。改成注册一次、用状态标记控制行为,问题消失。这条几乎每个新手都会踩,包括我。

坑三:把崩溃策略改成中止之后,线上出问题完全没法查。省了那几百 KB,代价是用户报一个“白屏”,你只能对着日志干瞪眼。我的折中做法是:发布版用中止策略,但保留一条极简的上报通道,只上报一个错误编号和一个固定的上下文数值。够定位到模块,不够定位到行——这个取舍我认为值。

坑四:以为调用了体积优先的编译选项就等于优化到位。不是。编译优化在一晚上能帮你砍两三成,服务端压缩五分钟能帮你砍六成。先做便宜的,再做贵的,这个顺序反了,你会浪费大量时间在收益最小的环节上。

坑五:用重型引擎做网页小游戏。这个问题我在第 3 层已经说过一遍,值得再说一遍,因为它是选型阶段最容易犯、也最难回头的错。你选了一个功能齐全的引擎,享受了两周的开发便利,然后用三个月跟编译时间、包体、单线程限制搏斗。

还有一个争议我想摆到台面上:到底要不要为了体积彻底不要标准库?

我的答案是分场景。如果你的目标是做一个卡带级别的、装进某个固定平台的小游戏,那就是值得的,WASM-4 已经证明了这条路能走通;如果你做的是一个要长期运营、要接各种第三方 SDK、要有存档和网络的项目,不要碰——你会花一半时间重新实现别人已经写好的东西,而这些实现的质量还不如标准库。

FAQ:被问过一百遍的几个问题

问:Rust 做网页游戏,一定比 C++ 走 Emscripten 那条路好吗?

没有一定。看你手上有什么。你团队本来就写 C++,引擎也是 C++ 的,硬转 Rust 是自找麻烦,Emscripten 那条路的生态成熟度摆在那儿,能用的库多得多,移植老项目几乎是唯一选择。反过来说,你是从零开始的新项目,团队没有历史包袱,那 Rust 的构建体验、包管理、错误信息会明显舒服一截。选语言这件事,比的从来不是语言本身,是团队已有的资产。

问:用了那层 JS 胶水,是不是就等于性能打折?

看你怎么用。传数字、跑计算密集的逻辑,开销小到可以忽略,你的物理、寻路、AI 全都可以放心写。真正贵的是把一个大对象或字符串频繁来回传。一句话:边界不怕宽,怕频。把跨界次数降下来,性能问题基本就消失了。

问:我想做一个网页版的策略游戏,要不要上大型实体组件引擎?

先算一笔账:你的游戏同屏有多少个需要每帧更新的实体?如果是几百个这个量级,轻框架完全够,上重型引擎纯属给自己找麻烦。如果你真的需要管理上万个实体,那先问一句——这游戏为什么非得跑在浏览器里?如果答案不是“必须”,回去做原生,你的开发体验会好十倍。

问:包体已经压到极限了还是嫌大,下一步怎么办?

那说明大头的来源已经不是编译优化能解决的了。三个方向:第一,砍依赖,把序列化、正则、时间这些重型库换掉或者自己写一份最小实现;第二,把非首屏内容拆出去,比如大关卡的地形数据、音频,改成运行中按需加载;第三,重新审视工具链契约本身——你到底是需要那一整套浏览器接口绑定,还是只需要其中十来个功能?如果只是后者,手写一层极窄的接口,体积能降一个数量级。

问:调试体验怎么样?

比五年前好太多,但还没到原生的舒服度。源码映射能做,但优化之后的行号经常对不上,所以我的习惯是:开发期永远用平衡优化档,别为了追求和发布版完全一致而牺牲可调试性;性能问题用打点的方式量化,不要靠“感觉这里慢”去猜,我靠猜浪费的时间够写完半个游戏了。系列的 [17] 那篇专门讲调试工具链,值得先看一遍再动手。

最后留一个问题给你:如果你现在打开自己的项目,数一数一帧里有多少次跨语言调用,你猜那个数字会是多少?我第一次数是三千一百多次,当时整个人都不好了。你数完,可能会重新理解“性能优化”这四个字的分量。

文章标签
WebAssemblyWasmEmscriptenWASIwasm-bindgenComponent ModelSharedArrayBufferAtomicsWasm 多线程Wasm SIMDwasm-packRust Wasm
更多专题全部专题
觉得有价值?点赞或收藏支持内容持续产出。
← 返回专题:WebAssembly 游戏开发技术专题