Rust WASM 自制俄罗斯方块游戏

起因 事情的起因是在 Switch 上玩 Tetris99 游戏,由于不喜欢这种吃鸡的形式,只想玩小时候的那种掌机模式,于是想到可不可以自己做一个。 有了这个想法以后,打算使用 Rust + WASM,一方面是学习一下新技术,另一方面考虑到能直接在浏览器运行,可以跨平台,甚至可以在电视机上用浏览器打开网页就可以玩。 选定技术栈以后,在 Github 上搜了一下,发现早有人做了类似的工作,不过没关系,主要还是要自己实现一下。 几种技术方案 学习了一圈以后,理解了用 Rust + WASM 实现一个 web 游戏的大体思路。 首先,Rust 的 wasm-bindgen 库必不可少,这是连接 rust 代码和 wasm 之间的桥梁。 其次,既然是 web 游戏,那么免不了要画图,如何画图呢? 大家都不约而同的选择了 HTML 的 canvas,这是一种 html 标准自带的画图方式,比如用下面这样简单的代码,就能画一个矩形。 <html> <body> <canvas id="myCanvas" width="200" height="100" style="border:1px solid #000000;"> </canvas> </body> </html> 所以,本质上我要做的就是用 Rust/WASM 代码 或者 JavaScript 代码,控制这个 <canvas id="myCanvas" ,并且定期刷新,这样就能显示动画效果了。 如果你是个 JavaScript 高手,并且打算全部用 JavaScript 实现,那么现在就可以开始动手了。 但如果是 Rust WASM 的方式,还需要考虑下是 纯 WASM 实现呢? 还是 WASM 实现核心算法逻辑,JavaScript 实现画图这样的组合方式? ...

2022-03-06 · Me

Rust 异步 async 和 tokio

快速入门 Rust 语言原生提供了异步操作的关键字 async 和 await,但通常还需要配合第三方的 runtime,其中最有名的就是 tokio 了。 在开始了解 Rust 的所谓异步是什么样子之前,先看一下如何写一个简单的 Rust 异步程序。 以下是 main.rs async fn hello_world() { hello_cat().await; println!("hello, world!"); } async fn hello_cat() { println!("hello, kitty!"); } #[tokio::main] async fn main() { let future = hello_world(); println!("start"); future.await; } Cargo.toml 文件中加入一行 [dependencies] tokio = { version = "1", features = ["full"] } 运行上面的代码,会看到这样的输出 start hello, kitty! hello, world! 可以看出,future = hello_world(); 是创建一个异步执行的代码块, 并把它赋值给了 future 变量,这个代码块不会立刻执行,而是等到用户调用 await 的时候再去执行。 ...

2022-02-27 · Me

Rust 入坑之 LRU Cache

lru 算法的原理简而言之就是一个 hash ,一个 double linked list Linked List 提供 O(1) 的复杂度对元素进行插入和删除 hash 提供 O(1) 的复杂度进行查找 本文主要是通过阅读一个 rust 实现的 lru 学习相关语法。 如何在结构体里面使用指针? rust 是否有 raw pointer 直接指向内存地址,如果能用该怎么用? Linked List 节点结构体 上面提到,真正的 key/value 是存在双链表的 Node 里,所以需要先定义这个 Node 长什么样,lru-rs 中 LruEntry 表示的就是 node: K V 代表的是泛型的类型, struct LruEntry<K, V> { key: mem::MaybeUninit<K>, val: mem::MaybeUninit<V>, prev: *mut LruEntry<K, V>, next: *mut LruEntry<K, V>, } 下面是如何初始化一个 Node, impl<K, V> LruEntry<K, V> { fn new(key: K, val: V) -> Self { LruEntry { key: mem::MaybeUninit::new(key), val: mem::MaybeUninit::new(val), prev: ptr::null_mut(), next: ptr::null_mut(), } } fn new_sigil() -> Self { LruEntry { key: mem::MaybeUninit::uninit(), val: mem::MaybeUninit::uninit(), prev: ptr::null_mut(), next: ptr::null_mut(), } } } key value 用 mem::MaybeUninit::new(key)进行初始化 prev next 指针用 ptr::null_mut() 初始化 LRU cache 结构体 链表的 node 定义好以后,双链表结构也自然而然就有了。接下来还缺一个 map 结构体,这个可以用 rust 原生的 hash 函数库,然后就可以定义出 LRU 结构体 ...

2021-07-11 · Me

Rust 入坑之 Bloom Filter

关于 Bloom Filter 的原理不做介绍,网上各种资料满天飞,其中参考资料 1 已经讲解的很详细。 我重点关注如何用 Rust 实现一个简单的 Bloom Filter,并学习一些语法,源码在参考资料 2 。 BloomFilter 结构体 pub struct BloomFilter<T> { hasher: T, k: u32, bit_vec: BitVec, insert_count: u64, } 尖括号中的 T 代表泛型,这样我们就可以使用不同的 hash 函数实现 (hasher) k 表示使用几个 hash 函数,根据 BF 的原理,使用多个 hash 能减少 False Positive bit vec 表示使用一个多大的 bit 数组,这个关系到 BF 的命中率和 FP 率 BitVec 的作用等于是实现了 bloom filter 的 bit array,直接用这个库省略了作者重复实现一个。 定义 BloomHasher 定义这个 trait 的目的是让所有的 hash 函数库都有 hash() 这个函数,方便在上面的 hasher 中调用。 ...

2021-06-19 · Me

Rust 的所有权和生命周期

所有权 在 Rust 中,heap 上的一块内存区域是一块 “值”,与之绑定的是一个变量,也就是说变量和值是绑定的,要注意这种绑定关系。 在任何时候,一个值只有一个对应的变量作为所有者。 理解了这些概念之后,再来看所有权和它的基本特性: Rust中的每个值都有一个对应的变量作为它的所有者; 在同一时间内,只有且仅有一个所有者; 当所有者离开自己的作用域时,它持有的值就会被释放掉。 所有权的转移 赋值即转移。 如下面的示例, fn test() { let v: Vec<u8> = vec![0;20]; let u = v } 在第二行,u 成为了内存中这个数组数据的所有者,当函数返回时,u 的作用域结束,这块内存随即被释放。 要想让 v 和 u 各自都拥有独立的数据,可以使用 v.clone() 函数, 注意,int, char 等基本类型,在赋值的时候等于自动调用了 clone,所以对于这些基本类型可以放心的像 C/C++ 语言那样使用。 所有权的借用 & 是一个在 C/C++ 和 Golang 中常见的符号,在 Rust 中,用在一个变量上是借用的意思,也就是说所有权不变。 官方文档用这样一个例子来说明借用 fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); println!("The length of '{}' is {}.", s1, len); } fn calculate_length(s: &String) -> usize { s.len() } 最后一行的 s 是 s1 的引用,是对数据的借用,s1 仍然是数据的所有者,在 calculate_length() 返回之后,s 也会被销毁,但不影响原始数据。 ...

2021-02-28 · Me

从 C 语言调用 Rust 的函数

看到 rust 可以编译成动态链接库(.so),想到是不是可以用 C 语言链接到这个库呢?答案是肯定的。Rust 提供了 FFI 接口,即 Foreign Function Interface,目的就是和其他语言交互。 废话不多说,开始干。我们要实现三个例子: C 调用 Rust 动态库 C 调用 Rust 静态库 Rust 调用 C 函数 (不是库) C 调用 Rust 动态库 Rust 部分 首先是用 cargo new NAME --lib 创建一个新项目,然后编辑 src/lib.rs #![crate_type = "dylib"] #[no_mangle] pub extern fn double_input(input: i32) -> i32 { println!("hello --from rust shared library"); input * 2 } crate_type = “dylib” 代表编译成动态链接库。 no_mangle 告诉 rust 编译器,不要擅自改变下面这个函数的函数名。一些高级语言比如 c++ 之类,为了防止不同库中的函数名冲突,都会在编译时给每个函数生成独一无二的函数名,比如 func::h485dee。 然后编辑 Cargo.toml 文件,在默认的文件基础上加入: ...

2018-12-15 · Me

Rust 语言知识点记录

Rust 编程语言知识点笔记。 trait 关键字 Rust没有继承,它和Golang不约而同的选择了trait(Golang叫Interface)作为其实现多态的基础。 使用trait定义一个特征: trait HasArea { fn area(&self) -> f64; } trait里面的函数可以没有函数体,实现代码交给具体实现它的类型去补充: struct Circle { x: f64, y: f64, radius: f64, } impl HasArea for Circle { fn area(&self) -> f64 { std::f64::consts::PI * (self.radius * self.radius) } } fn main() { let c = Circle { x: 0.0f64, y: 0.0f64, radius: 1.0f64, }; println!("circle c has an area of {}", c.area()); } derive 属性 Rust提供了一个属性derive来自动实现一些trait,这样可以避免重复繁琐地实现他们,能被derive使用的trait包括:Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd。常用的例子是: ...

2018-11-21 · Me