Tauri Tutorial 实战案例:用 Rust + WASM 实现康威生命游戏(Game of Life)原理拆解
【免费下载链接】tauri-tutorial📚 Tauri Tutorial (系列教程 - 打造属于自己的跨端应用)项目地址: https://gitcode.com/gh_mirrors/ta/tauri-tutorial
Tauri Tutorial 系列教程通过一个个可运行的小案例,带你从零打造属于自己的跨端应用。本文要拆解的是其中最经典的实战案例——用 Rust + WebAssembly(WASM)实现康威生命游戏(Game of Life):Rust 负责细胞演化的核心计算,React 前端负责 Canvas 渲染,两者通过 WASM 无缝协作。读完你不仅能看懂代码,还能照着思路动手改造出自己的版本。
康威生命游戏是什么?四条规则看懂一切
康威生命游戏(Game of Life)由数学家约翰·康威(John Conway)在 1970 年提出,它不是一个传统意义上的"游戏",而是一个零玩家模拟(Zero-player Game):你只需要设置初始状态,之后所有演化都由规则自动完成。
它的世界是一个二维网格,每个格子只有两种状态:存活或死亡。每个细胞的下一轮命运,只取决于周围 8 个邻居的存活数量,一共只有四条规则:
| 规则 | 当前状态 | 邻居存活数 | 下一轮结果 |
|---|---|---|---|
| 规则1 | 存活 | 少于 2 个 | 死亡(人口稀少) |
| 规则2 | 存活 | 2 或 3 个 | 保持存活 |
| 规则3 | 存活 | 超过 3 个 | 死亡(人口过密) |
| 规则4 | 死亡 | 恰好 3 个 | 复活(繁殖) |
就是这么简单的四条规则,却能演化出滑翔机、振荡器、繁殖器等令人惊叹的复杂图案——这正是无数开发者痴迷于实现它的原因,也是本教程选择它作为Rust + WASM 入门实战的绝佳理由。
为什么把计算交给 Rust?WASM 的优势一目了然
如果你用纯 JavaScript 实现过生命游戏,一定对「双层循环 + 频繁新建数组」的性能开销有印象。而在本项目中:
- 计算层用 Rust 编写,编译成 WASM 模块,接近原生性能;
- 展示层用 React + Canvas,只管把结果画出来;
- 两者通过
wasm-bindgen自动生成胶水代码,JavaScript 可以直接调用 Rust 导出的方法,几乎感觉不到跨语言的存在。
这种「Rust 算、JS 画」的分工,正是 Tauri 生态里非常典型的架构思路,也方便你后续把同样的模式迁移到其他高性能场景(图片处理、数据计算等)。
Rust 端核心实现:Universe 与 tick() 拆解
Rust 侧的核心代码非常简洁,只有一个文件:lib.rs,依赖声明在 Cargo.toml 中,仅三样:wasm-bindgen(JS 互操作)、fixedbitset(位集存储)、js-sys(调用 JS 的随机数)。
H3: FixedBitSet:用位压缩存储 120×80 个细胞
游戏默认创建一个120×80的宇宙,共 9600 个格子。如果每个格子用一个bool(1 字节)存,就要 9.6KB;而项目使用了FixedBitSet——每个细胞只占 1 个 bit,内存直接缩减到 1/8,还自带set / toggle / clone等便捷方法,处理大规模网格时优势非常明显。
#[wasm_bindgen] pub struct Universe { width: u32, // 宇宙宽度 height: u32, // 宇宙高度 cells: FixedBitSet, // 存活(1) / 死亡(0) 位集 }初始化时每个细胞有 50% 概率随机存活,所以一打开页面就是一片「星光闪烁」般的初始图案。
H3: live_neighbor_count:巧妙的环形边界统计
统计邻居时,项目用了一个非常优雅的小技巧——取模运算实现环形边界:
for delta_row in [self.height - 1, 0, 1].iter().cloned() { for delta_col in [self.width - 1, 0, 1].iter().cloned() { if delta_row == 0 && delta_col == 0 { continue; } let neighbor_row = (row + delta_row) % self.height; let neighbor_col = (column + delta_col) % self.width; count += self.cells[self.get_index(neighbor_row, neighbor_col)] as u8; } }height - 1配合取模,让边缘的细胞能「绕」到对面去统计邻居,既避免了繁琐的边界判断,又形成了首尾相接的环面效果,代码量大幅减少。
H3: tick() 中四条规则的优雅落地
每一次演化迭代对应一个tick()方法(lib.rs 第 47 行起),它先克隆当前细胞集,再逐格套用四条规则,最后一次性替换。其中规则的落地用 Rust 的match模式匹配写得非常直观:
next.set(idx, match (cell, live_neighbor) { (true, x) if x < 2 => false, // 规则1:孤独死亡 (true, 2) | (true, 3) => true, // 规则2:保持存活 (true, x) if x > 3 => false, // 规则3:过密死亡 (false, 3) => true, // 规则4:繁殖复活 (otherwise, _) => otherwise, // 其余保持不变 });一行代码一个规则,把上文的规则表格 1:1 翻译成了程序逻辑,初学者也能轻松对照理解。
让 Rust 变成 npm 包:rsw 一键集成
跨语言协作的最后一公里,由rsw(Rust + wasm-pack 的 Vite 插件)打通。项目根目录的 rsw.toml 中声明了 crate 对应的 npm 包名:
[[crates]] name = "@omb/game-of-life" link = true配合 vite.config.ts 里的ViteRsw()插件,开发时只要运行yarn rsw watch,Rust 源码一旦变化就会自动触发wasm-pack编译;而前端代码里直接import init, { Universe } from '@omb/game-of-life'就能使用,体验和普通 npm 包完全一致。
前端渲染:Canvas 网格、细胞绘制与动画循环
前端页面位于 game_of_life/index.tsx,逻辑集中在initGame函数里,主要做了三件事:
- 画网格:
drawGrid用 1px 的灰色线条画出 120×80 的网格底图; - 画细胞:
drawCells通过universe.cells()拿到 WASM 内存指针,用Uint8Array直接读取位集数据,存活格填黑色、死亡格留白; - 驱动动画:用
requestAnimationFrame循环调用universe.tick()并重绘,形成流畅的演化动画。
点击「▶ / ⏸」按钮即可播放/暂停演化;更妙的是,点击画布上的任意格子可以直接翻转该细胞——它把坐标换算成行列后,调用 Rust 导出的toggle_cell(row, col),等于在浏览器里亲手「播种」生命,可玩性拉满。
运行这个项目:两条命令快速启动
想亲自跑起来,只需三步:
# 1. 克隆仓库 git clone https://gitcode.com/gh_mirrors/ta/tauri-tutorial # 2. 打开两个终端窗口,先执行(保持窗口不关闭) yarn rsw watch # 3. 另一个终端执行 yarn tauri dev注意:两个命令的执行顺序很重要,
rsw watch负责持续监听 Rust 改动并编译 WASM,请务必让它在后台保持运行。
启动后,在应用中找到「Game of Life」入口,就能看到 120×80 的细胞宇宙在你眼前自动演化,随机初始图案会逐渐形成稳定的振荡结构或永动的滑翔机——这就是四条简单规则碰撞出的秩序之美。
结语:从案例到实战,你的下一个 WASM 应用
这个案例虽然小,却完整覆盖了Tauri + Rust + WASM + 前端渲染的整条链路:从FixedBitSet的内存优化,到取模实现的环形边界,再到match对四条规则的优雅表达,处处都是值得借鉴的工程细节。
如果你想进一步探索,可以试试这些改造方向:
- 把 50% 随机存活改成滑翔机、脉冲星等经典初始图案;
- 增加速度滑块,动态控制
tick频率; - 将网格数据导出/导入,实现「存盘」功能。
理解了这个案例,你就掌握了在 Tauri 应用里调用 Rust 高性能代码的完整方法论——下一次遇到复杂的计算任务,不妨也试试交给 WASM。
【免费下载链接】tauri-tutorial📚 Tauri Tutorial (系列教程 - 打造属于自己的跨端应用)项目地址: https://gitcode.com/gh_mirrors/ta/tauri-tutorial
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考