01 - Introduction
02 - Rust Preparation
001 Rust Installation
002 Rust Versions
rustc --version
003 Main Func
fn main() {
let msg = "hello world";
println!("{}", msg);
}
rustc main.rs
004 Mut Variables
fn main() {
// 可变
let mut msg = "hello world";
msg = "hi john";
println!("{}", msg);
// 可覆盖(我个人不推荐使用)
let age = 20;
println!("{}", age);
let age = 30;
println!("{}", age);
}
005 Cargo Init
# 在当前文件夹将rust项目
cargo init
03 - Basics Types
001 New function
fn main() {
// let msg = "Hello, world!";
let msg: &str = "Hello, world!";
let msg2 = print_welcome(msg);
println!("{}", msg2);
}
fn print_welcome(text: &str) -> &str {
println!("{}", text);
// "Hi There" // 可以这样直接返回
return "Hi There"; // 可以用return返回
}
cargo run
002 Primitive types
fn main() {
let is_fun: bool = false;
let num = -10; // i32 -> signed integer of 32bits
// u8 -> unsigned integer of 8bits
// 2^8-1 -> 255 -> the max of 8bit
let small_num: u8 = 10;
// i8 -> -2^7 -> 2^7-1
// -128 -> 127 // 1位用于表示符号,所以是127
let small_num_2: i8 = 127;
// operating system related type
let sys_num: isize = -10;
let sys_num_2: usize = 10;
}
003 [AI] Prim Numbers 2
fn main() {
let custom_num = 98_000; // 98000
let hex_num = 0xfa;
let bin_num = 0b0010_1011;
let byte_num = b'A';
println!("{}", custom_num);
println!("{}", hex_num);
println!("{}", bin_num);
println!("{}", byte_num);
}
004 Primitive Types 3
fn main() {
let float_num: f32 = 3.14;
let float_num2: f64 = 3.2324253543;
let tup: (i32, &str, i32) = (20, "hello", 1);
println!("{}", tup.0);
let (a, b, c) = tup;
println!("{}", b);
let x = [1, 5, 6, 7];
println!("{}", x[0]);
let y = [2; 6]; // 2 2 2 2 2 2
println!("{}", y[5]);
}
04 - Move & Copy
001 AI Stack
003 String Heap
fn main() {
let a = 10;
let b = a;
let c = 15;
// u32 占用4字节
let d = add(a, b);
// 事先不知道需要多少内存
let msg = String::from("Hello");
println!("{}", msg);
}
004 String Move
对于不定长的类型,赋值会把地址信息移动到新的变量下,旧的指针被销毁
005 AI Moves additional explanation
006 Return moved value
fn main() {
let msg = String::from("Hello");
// msg moved to a ,so msg con't use
let msg2 = ext_msg(msg);
println!("{}", msg2);
let mut e_msg = String::from("Hello");
e_msg = ext_msg(e_msg);
println!("{}", e_msg);
let mut e_msg2 = String::from("Hello");
let e_msg2 = ext_msg(e_msg2);
println!("{}", e_msg2);
}
fn ext_msg(mut a: String) -> String {
a.push_str(" World");
a
}
007 Copying age
fn main() {
let age = 30;
ext_age(age);
println!("{}", age);
}
fn ext_age(mut a: u32) -> u32 {
a += 100;
a
}
05 - Reference & Borrowing
001 References
fn main() {
let msg = String::from("Hello");
// 引用了地址
let msg2: &String = &msg;
println!("{}", msg);
println!("{}", msg2);
}
002 Mutable borrow
fn main() {
let mut msg = String::from("Hello");
// 引用了地址
// 可变引用
let msg2: &mut String = &mut msg;
msg2.push_str(" World");
println!("{}", msg2);
println!("{}", msg);
}
003 AI Mutable Borrow Example
fn main() {
let mut msg = String::from("Hello");
let msg3 = &msg; // ok
println!("{}", msg3); // ok
// 可变引用只后不能对原变量和原变量的不可变引用进行操作
let msg2: &mut String = &mut msg;
// let msg3 = &msg; // error
// msg.push_str("string"); // error
// println!("{}", msg); // error
unpredictable_mutate(msg2);
// 如果在可变引用之前对msg进行处理
// 可能发生不可预计的变化,从而导致可变引用失效
// 所以编译器会报错
// println!("{}", msg3); // error
println!("{}", msg);
}
fn unpredictable_mutate(val: &mut String) {
val.push_str("_unpredictable");
}
004 Dereference
fn main() {
let mut msg = String::from("Hello");
let msg2 = &mut msg;
// 解引用后push
(*msg2).push_str(" World");
println!("{}", msg2);
}
fn main() {
let a = 10;
let b = &a;
println!("{}", a == *b);
}
005 AI Dereferencing
fn main() {
let a = 1;
let b = &a;
let c = &b;
let d = b;
println!("{}", a);
println!("{:p}", b);
println!("{:p}", c);
println!("{:p}", d);
}
fn main() {
let a = 1;
let b = &a;
let mut c = &b;
let d = b;
let e = &&100;
c = e;
}
fn main() {
let a = 1;
let b = &a;
let mut c = &b;
let d = b;
let e = &&100;
c = e;
println!("addr of c: {:p}", c);
println!("addr of e: {:p}", e);
println!("v of 100: {}", **e);
println!("addr of 100(*c): {:p}", *c);
println!("addr of 100(*e): {:p}", *e);
}
println!("v of 100: {}", **e);
println!("addr of 100: {:p}", &(**e));
06 - Strings
001 String vs &str
fn main() {
let mut msg = String::from("Hello");
let mut name = "Filip";
msg.push_str(" World");
// &str has no method called push_str
name.push_str(" Jerga"); // error
}
002 String slice
fn main() {
let mut msg = String::from("Hello");
let slice: &str = &msg[2..4]; // 2->3
println!("{}", slice); // ll
println!("{}", slice.len()); // 2
let s2 = &msg[2..=4]; // 2->4
println!("{}", s2); // llo
let s3 = &msg[..4]; // 0->3
println!("{}", s3); // hell
let s4 = &msg[..]; // all
println!("{}", s4); // hello
}
003 String Slice +
fn main() {
let mut msg = String::from("Hello");
let s1 = &msg[2..4]; // 视为可变引用
// move_me(msg); // error
// msg.clear(); // error
println!("{}", s1);
msg.clear(); // ok
move_me(msg); // ok
}
fn move_me(val: String) {}
004 Clone
fn main() {
let mut msg = String::from("Hello");
let s1 = &msg[2..4]; // 视为可变引用
let msg3 = msg.clone(); // 可以认为是深拷贝
println!("{}", msg);
println!("{}", msg3);
}
07 - Box & Struct
001 Box type
fn main() {
let num = 32;
let num3 = Box::new(100);
println!("{}", num3);
}
002 Struct
struct Person {
name: String,
last_name: String,
age: u32,
}
fn main() {
let p = Person {
name: "Filip".to_string(),
last_name: "Jerga".to_string(),
age: 30,
};
println!("{} {} {}", p.name, p.last_name, p.age);
}
003 Struct functions, methods
struct Person {
name: String,
last_name: String,
age: u32,
}
impl Person {
// associated function
fn some_func() {
println!("some_func");
}
// method
// 调用方法会把自己move进去(self),
// 后面的方法就用不了该实例了
// fn display_age(self) {
// 相对于下面的写法
// fn display_age(&self) {
fn display_age(self: &Self) {
println!("current age: {}", self.age)
}
}
fn main() {
Person::some_func();
let p = Person {
name: "Filip".to_string(),
last_name: "Jerga".to_string(),
age: 30,
};
p.display_age();
println!("{} {} {}", p.name, p.last_name, p.age);
}
004 Constructors
struct Person {
name: String,
last_name: String,
age: u32,
}
impl Person {
// associated function
// 这种方法适用于构造方法
fn new() -> Person {
Person {
name: "default".to_string(),
last_name: "default".to_string(),
age: 0,
}
}
fn from(name: String, last_name: String, age: u32) -> Person {
Person {
name,
last_name,
age,
}
}
fn change_age(&mut self, new_age: u32) {
self.age = new_age;
}
}
fn main() {
let mut p = Person::new();
p.change_age(12);
println!("{} {} {}", p.name, p.last_name, p.age);
let p1 = Person::from("name".to_string(), "last_name".to_string(), 18);
println!("{} {} {}", p1.name, p1.last_name, p1.age);
}
08 - Enums
001 Enums
#[derive(Debug)]
enum PersonId {
Passport,
IndentityCard,
}
struct Person {
name: String,
last_name: String,
age: u32,
id: PersonId,
}
impl Person {
// associated function
// 这种方法适用于构造方法
fn new() -> Person {
Person {
name: "default".to_string(),
last_name: "default".to_string(),
age: 0,
id: PersonId::IndentityCard,
}
}
fn from(name: String, last_name: String, age: u32, id: PersonId) -> Person {
Person {
name,
last_name,
age,
id,
}
}
fn change_age(&mut self, new_age: u32) {
self.age = new_age;
}
}
fn main() {
let mut p = Person::new();
p.change_age(12);
// `PersonId` cannot be formatted with the default formatter
println!("{} {} {} {:?}", p.name, p.last_name, p.age, p.id);
let p1 = Person::from(
"name".to_string(),
"last_name".to_string(),
18,
PersonId::Passport,
);
println!("{} {} {} {:?}", p1.name, p1.last_name, p1.age, p1.id);
}
002 Enum values
#[derive(Debug)]
enum PersonId {
// 括号里是和携带的数据的类型
Passport(String),
UUID(String),
IndentityCard(u32, u32, u32),
}
struct Person {
name: String,
last_name: String,
age: u32,
id: PersonId,
}
impl Person {
// associated function
// 这种方法适用于构造方法
fn new() -> Person {
Person {
name: "default".to_string(),
last_name: "default".to_string(),
age: 0,
id: PersonId::UUID("865fbd57-64b6-4064-8207-2368ead0436e".to_string()),
}
}
fn from(name: String, last_name: String, age: u32, id: PersonId) -> Person {
Person {
name,
last_name,
age,
id,
}
}
fn change_age(&mut self, new_age: u32) {
self.age = new_age;
}
}
fn main() {
let mut p = Person::new();
p.change_age(12);
// `PersonId` cannot be formatted with the default formatter
println!("{} {} {} {:?}", p.name, p.last_name, p.age, p.id);
let p1 = Person::from(
"name".to_string(),
"last_name".to_string(),
18,
PersonId::IndentityCard(1, 00, 86),
);
println!("{} {} {} {:?}", p1.name, p1.last_name, p1.age, p1.id);
}
003 Enum match
#[derive(Debug)]
enum PersonId {
// 括号里是和携带的数据的类型
Passport(String),
UUID(String),
IndentityCard(u32, u32, u32),
}
struct Person {
name: String,
last_name: String,
age: u32,
id: PersonId,
}
impl Person {
// associated function
// 这种方法适用于构造方法
fn new() -> Person {
Person {
name: "default".to_string(),
last_name: "default".to_string(),
age: 0,
id: PersonId::UUID("865fbd57-64b6-4064-8207-2368ead0436e".to_string()),
}
}
fn from(name: String, last_name: String, age: u32, id: PersonId) -> Person {
Person {
name,
last_name,
age,
id,
}
}
fn display(&self) {
println!(
"{} {} {} {:?}",
self.name, self.last_name, self.age, self.id
);
}
fn change_age(&mut self, new_age: u32) {
self.age = new_age;
}
}
fn main() {
let mut p = Person::new();
p.change_age(12);
p.display();
let p1 = Person::from(
"name".to_string(),
"last_name".to_string(),
18,
PersonId::IndentityCard(1, 00, 86),
);
p1.display();
check_person_id(&p.id);
check_person_id(&p1.id);
let r1 = get_person_id(p.id);
let r2 = get_person_id(p1.id);
println!("{}", r1);
println!("{}", r2);
}
fn check_person_id(id: &PersonId) {
match id {
PersonId::IndentityCard(x, y, z) => {
println!("id card first value: {}", x)
}
PersonId::Passport(x) => {
println!("passport: {}", x)
}
PersonId::UUID(x) => {
println!("uuid: {}", x)
}
}
}
fn get_person_id(id: PersonId) -> String {
// match 返回的必需是相同的类型,否则报错:
// match arms have incompatible types
let res = match id {
// PersonId::IndentityCard(x, y, z) => y,
PersonId::IndentityCard(x, y, z) => "".to_string(),
PersonId::Passport(x) => x,
PersonId::UUID(x) => x,
};
res
}
004 If Let
#[derive(Debug)]
enum PersonId {
// 括号里是和携带的数据的类型
Passport(u32),
UUID(String),
IndentityCard(u32, u32, u32),
}
struct Person {
name: String,
last_name: String,
age: u32,
id: PersonId,
}
impl Person {
// associated function
// 这种方法适用于构造方法
fn new() -> Person {
Person {
name: "default".to_string(),
last_name: "default".to_string(),
age: 0,
id: PersonId::UUID("865fbd57-64b6-4064-8207-2368ead0436e".to_string()),
}
}
fn from(name: String, last_name: String, age: u32, id: PersonId) -> Person {
Person {
name,
last_name,
age,
id,
}
}
fn display(&self) {
println!(
"{} {} {} {:?}",
self.name, self.last_name, self.age, self.id
);
}
fn change_age(&mut self, new_age: u32) {
self.age = new_age;
}
}
fn main() {
let mut p = Person::new();
p.change_age(12);
p.display();
let p1 = Person::from(
"name".to_string(),
"last_name".to_string(),
18,
// it match passport 1
PersonId::Passport(1),
);
p1.display();
check_person_id(p.id);
check_person_id(p1.id);
}
fn check_person_id(id: PersonId) {
if let PersonId::Passport(num) = id {
println!("it match passport {}", num);
} else {
println!("it doesn't match");
}
match id {
PersonId::IndentityCard(x, y, z) => {
println!("id card first value: {}", x)
}
PersonId::Passport(x) => {
println!("passport: {}", x)
}
PersonId::UUID(x) => {
println!("uuid: {}", x)
}
}
}
005 Struct no fields
#[derive(Debug)]
enum PersonId {
// ...
}
struct Person {
// ...
}
impl Person {
// ...
}
struct Animal(String);
fn main() {
let mut p = Person::new();
p.change_age(12);
p.display();
check_person_id(p.id);
}
fn check_person_id(id: PersonId) {
// ...
let animal = Animal("dog".to_string());
let Animal(animal_type) = animal;
println!("{}", animal_type);
}
09 - Traits
001 Trait
#[derive(Debug)]
enum PersonId {
// 括号里是和携带的数据的类型
Passport(u32),
UUID(String),
IndentityCard(u32, u32, u32),
}
struct Person {
name: String,
last_name: String,
age: u32,
id: PersonId,
}
impl Person {
// associated function
// 这种方法适用于构造方法
fn new() -> Person {
Person {
name: "default".to_string(),
last_name: "default".to_string(),
age: 0,
id: PersonId::UUID("865fbd57-64b6-4064-8207-2368ead0436e".to_string()),
}
}
fn from(name: String, last_name: String, age: u32, id: PersonId) -> Person {
Person {
name,
last_name,
age,
id,
}
}
fn display(&self) {
println!(
"{} {} {} {:?}",
self.name, self.last_name, self.age, self.id
);
}
fn change_age(&mut self, new_age: u32) {
self.age = new_age;
}
}
/*
感觉类似go的interface
一组类型的通用接口。
A trait就像数据类型可以实现的接口。
当一个类型 实现一个特征,
可以使用泛型将其抽象地视为该特征 或特质对象。
*/
trait Log {
fn display_info(&self);
fn alert_something() {
println!("Default implementation~~~~~!")
}
}
impl Log for Person {
fn display_info(&self) {
println!(
"{} {} {} {:?}",
self.name, self.last_name, self.age, self.id
);
}
}
struct Animal(String);
impl Log for Animal {
fn display_info(&self) {
println!("{}", self.0);
}
fn alert_something() {
println!("Animal implementation~~~~~!")
}
}
fn main() {
let mut p = Person::new();
p.display_info();
let animal = Animal("dog".to_string());
animal.display_info();
Person::alert_something();
Animal::alert_something();
}
002 Trait narrowing
#[derive(Debug)]
enum PersonId {
// 括号里是和携带的数据的类型
Passport(u32),
UUID(String),
IndentityCard(u32, u32, u32),
}
struct Person {
name: String,
last_name: String,
age: u32,
id: PersonId,
}
impl Person {
// associated function
// 这种方法适用于构造方法
fn new() -> Person {
Person {
name: "default".to_string(),
last_name: "default".to_string(),
age: 0,
id: PersonId::UUID("865fbd57-64b6-4064-8207-2368ead0436e".to_string()),
}
}
fn from(name: String, last_name: String, age: u32, id: PersonId) -> Person {
Person {
name,
last_name,
age,
id,
}
}
fn display(&self) {
println!(
"{} {} {} {:?}",
self.name, self.last_name, self.age, self.id
);
}
fn change_age(&mut self, new_age: u32) {
self.age = new_age;
}
}
/*
感觉类似go的interface
一组类型的通用接口。
A trait就像数据类型可以实现的接口。
当一个类型 实现一个特征,
可以使用泛型将其抽象地视为该特征 或特质对象。
*/
trait Log {
fn display_info(&self);
// found the following associated functions;
// to be used as methods, functions must have a `self` parameter
// note: the candidate is defined in the trait `Log`
// fn alert_something( ) {
fn alert_something(&self) {
println!("Default implementation~~~~~!")
}
}
impl Log for Person {
fn display_info(&self) {
println!(
"{} {} {} {:?}",
self.name, self.last_name, self.age, self.id
);
}
}
struct Animal(String);
impl Log for Animal {
fn display_info(&self) {
println!("{}", self.0);
}
fn alert_something(&self) {
println!("Animal implementation~~~~~!")
}
}
fn main() {
let mut p = Person::new();
p.display_info();
let animal = Animal("dog".to_string());
animal.display_info();
log_info_2(&animal);
log_info(animal);
log_info(p);
}
fn log_info(val: impl Log) {
val.alert_something();
}
// 编译之后占空间更小,但是功能比较少
fn log_info_2(val: &dyn Log) {
val.alert_something();
}
10 - Modularity
001 Modularity
use snake_game::log_info;
use snake_game::log_info_2;
use snake_game::Animal;
use snake_game::Log;
use snake_game::Person;
fn main() {
let mut p = Person::new();
p.display_info();
let animal = Animal("dog".to_string());
animal.display_info();
log_info_2(&animal);
log_info(animal);
log_info(p);
}
// lib.rs
#[derive(Debug)]
pub enum PersonId {
// 括号里是和携带的数据的类型
Passport(u32),
UUID(String),
IndentityCard(u32, u32, u32),
}
pub struct Person {
name: String,
last_name: String,
age: u32,
id: PersonId,
}
impl Person {
// associated function
// 这种方法适用于构造方法
pub fn new() -> Person {
Person {
name: "default".to_string(),
last_name: "default".to_string(),
age: 0,
id: PersonId::UUID("865fbd57-64b6-4064-8207-2368ead0436e".to_string()),
}
}
fn from(name: String, last_name: String, age: u32, id: PersonId) -> Person {
Person {
name,
last_name,
age,
id,
}
}
fn display(&self) {
println!(
"{} {} {} {:?}",
self.name, self.last_name, self.age, self.id
);
}
fn change_age(&mut self, new_age: u32) {
self.age = new_age;
}
}
/*
感觉类似go的interface
一组类型的通用接口。
A trait就像数据类型可以实现的接口。
当一个类型 实现一个特征,
可以使用泛型将其抽象地视为该特征 或特质对象。
*/
pub trait Log {
fn display_info(&self);
// found the following associated functions;
// to be used as methods, functions must have a `self` parameter
// note: the candidate is defined in the trait `Log`
// fn alert_something( ) {
fn alert_something(&self) {
println!("Default implementation~~~~~!")
}
}
impl Log for Person {
fn display_info(&self) {
println!(
"{} {} {} {:?}",
self.name, self.last_name, self.age, self.id
);
}
}
pub struct Animal(pub String);
impl Log for Animal {
fn display_info(&self) {
println!("{}", self.0);
}
fn alert_something(&self) {
println!("Animal implementation~~~~~!")
}
}
pub fn log_info(val: impl Log) {
val.alert_something();
}
// 编译之后占空间更小,但是功能比较少
pub fn log_info_2(val: &dyn Log) {
val.alert_something();
}
002 Import options
[package]
name = "snake_game_v1"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
[[bin]]
name = "snake_game"
path = "main.rs"
// use snake_game::log_info;
// use snake_game::log_info_2;
// use snake_game::Animal;
// use snake_game::Log;
// use snake_game::Person;
// 导入全部pub
// use snake_game::*;
// 一次导入多个
use snake_game::{log_info, log_info_2, Animal, Log, Person};
fn main() {
// ...
}
003 Mod keyword
pub mod lean_rust {
// ...
}
004 Nested modules
pub mod lean_rust {
mod top_level {
pub fn hi_there() {
println!("hi there");
}
pub mod low_level {
pub fn hello_world() {
println!("hello_world");
}
}
}
// ...
impl Log for Person {
fn display_info(&self) {
// absolute path
// crate -> src/lib.rs and src/main.rs
crate::lean_rust::top_level::hi_there();
crate::lean_rust::top_level::low_level::hello_world();
// relative path
top_level::hi_there();
top_level::low_level::hello_world();
println!(
"{} {} {} {:?}",
self.name, self.last_name, self.age, self.id
);
}
}
// ...
}
005 Pub fields
use snake_game::lean_rust::{Log, Person, PersonId};
fn main() {
let mut p = Person::new();
// p.display_info();
let id = PersonId::Passport(432);
println!("{:?}", id);
println!("{}", p.name());
}
006 Super keyword
fn outsider() {
println!("outsider fn!");
}
pub mod lean_rust {
// ...
impl Log for Person {
fn display_info(&self) {
crate::outsider();
super::outsider();
println!(
"{} {} {} {:?}",
self.name, self.last_name, self.age, self.id
);
}
}
// ...
}
fn outsider() {
println!("outsider fn!");
}
pub mod education {
pub mod lean_rust {
// ...
impl Log for Person {
fn display_info(&self) {
crate::outsider();
super::super::outsider();
println!(
"{} {} {} {:?}",
self.name, self.last_name, self.age, self.id
);
}
}
// ...
}
}
007 External lib
mod another_lib;
use another_lib::another_mod;
fn outsider() {
another_mod::another_fn();
// 直接跳过文件名的mod(another_lib)
crate::another_mod::another_fn();
println!("outsider fn!");
}
pub mod education {
pub mod lean_rust {
// ...
impl Log for Person {
fn display_info(&self) {
super::super::another_mod::another_fn();
crate::another_mod::another_fn();
println!(
"{} {} {} {:?}",
self.name, self.last_name, self.age, self.id
);
}
}
}
}
008 Display Trait
pub mod education {
pub mod lean_rust {
use std::fmt;
// #[derive(Debug)]
pub enum PersonId {
// 括号里是和携带的数据的类型
Passport(u32),
IndentityCard(u32, u32, u32),
}
// 输出
impl fmt::Display for PersonId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PersonId::Passport(x) => {
write!(f, "{}", x)
}
PersonId::IndentityCard(x, y, z) => {
write!(f, "{} {} {}", x, y, z)
}
}
}
}
//...
}
}
11 - Into to WebAssembly
001 WebAssembly start + Link
002 More wasm
003 Load Wasm in Browser
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h1>Hello World</h1>
<script>
function init() {
const byteArr = new Int8Array([
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01,
0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07,
0x07, 0x01, 0x03, 0x73, 0x75, 0x6d, 0x00, 0x00, 0x0a, 0x09, 0x01,
0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6a, 0x0b, 0x00, 0x18, 0x04,
0x6e, 0x61, 0x6d, 0x65, 0x01, 0x06, 0x01, 0x00, 0x03, 0x73, 0x75,
0x6d, 0x02, 0x09, 0x01, 0x00, 0x02, 0x00, 0x01, 0x61, 0x01, 0x01, 0x62,
]);
debugger;
}
init();
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h1>Hello World</h1>
<script>
async function init() {
// exports.sum
const byteArr = new Int8Array([
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01,
0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07,
0x07, 0x01, 0x03, 0x73, 0x75, 0x6d, 0x00, 0x00, 0x0a, 0x09, 0x01,
0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6a, 0x0b, 0x00, 0x18, 0x04,
0x6e, 0x61, 0x6d, 0x65, 0x01, 0x06, 0x01, 0x00, 0x03, 0x73, 0x75,
0x6d, 0x02, 0x09, 0x01, 0x00, 0x02, 0x00, 0x01, 0x61, 0x01, 0x01, 0x62,
]);
const wasm = await WebAssembly.instantiate(byteArr.buffer);
const sunFn = wasm.instance.exports.sum;
const res = sunFn(10, 50);
console.log(res);
}
init();
</script>
</body>
</html>
004 Dev Server
## 根目录的www文件夹
npm init -y
npm i --save webpack webpack-cli
npm i --save-dev webpack-dev-server
// webpack.config.js
const path = require("path");
module.exports = {
// 入口
entry: "./index.js",
output: {
path: path.resolve(__dirname, "public"),
filename: "index.js",
},
mode: "development",
};
005 [AI] webpack dev server
006 Copy plugin
npm i --save copy-webpack-plugin
const path = require("path");
const CopyWebpackPlugin = require("copy-webpack-plugin");
module.exports = {
// 入口
entry: "./index.js",
output: {
path: path.resolve(__dirname, "public"),
filename: "index.js",
},
mode: "development",
plugins: [
new CopyWebpackPlugin({
patterns: [
// 这里的./是指output的目录
// index.html => ./public
{ from: "./index.html", to: "./" },
],
}),
],
};
007 Fetch wasm
async function init() {
const resp = await fetch("sum.wasm");
const buffer = await resp.arrayBuffer();
const wasm = await WebAssembly.instantiate(buffer);
const sunFn = wasm.instance.exports.sum;
const res = sunFn(100, 50);
console.log(res);
}
init();
008 Import to wasm
async function init() {
const importObject = {
console: {
log: () => {
console.log("logging something");
},
error: () => {
console.log("meet an error");
},
},
};
const resp = await fetch("sum.wasm");
const buffer = await resp.arrayBuffer();
const wasm = await WebAssembly.instantiate(buffer, importObject);
const sunFn = wasm.instance.exports.sum;
const res = sunFn(100, 50);
console.log(res);
}
init();
(module
(import "console" "log" (func $log))
(import "console" "error" (func $error))
(func $sum (param $a i32) (param $b i32) (result i32)
call $log
call $error
local.get $a
local.get $b
i32.add
)
(export "sum" (func $sum))
)
009 Wasm Memory
1 memory 是 1 页内存,大概 64kb
(module
(import "console" "log" (func $log))
(import "console" "error" (func $error))
(memory $mem 1)
// 把data放到索引为0的内存中($mem)
(data (i32.const 0) "Hi")
(func $sum (param $a i32) (param $b i32) (result i32)
call $log
call $error
local.get $a
local.get $b
i32.add
)
(export "mem" (memory $mem))
(export "sum" (func $sum))
)
async function init() {
const importObject = {
console: {
log: () => {
console.log("logging something");
},
error: () => {
console.log("meet an error");
},
},
};
const resp = await fetch("sum.wasm");
const buffer = await resp.arrayBuffer();
const wasm = await WebAssembly.instantiate(buffer, importObject);
const sumFn = wasm.instance.exports.sum;
const res = sumFn(100, 50);
console.log(res);
const wasmMemory = wasm.instance.exports.mem;
console.log(wasmMemory);
}
init();
async function init() {
const importObject = {
console: {
log: () => {
console.log("logging something");
},
error: () => {
console.log("meet an error");
},
},
};
const resp = await fetch("sum.wasm");
const buffer = await resp.arrayBuffer();
const wasm = await WebAssembly.instantiate(buffer, importObject);
const sumFn = wasm.instance.exports.sum;
const res = sumFn(100, 50);
console.log(res);
const wasmMemory = wasm.instance.exports.mem;
// console.log(wasmMemory);
const uint8Array = new Uint8Array(wasmMemory.buffer, 0, 2);
const hiTxt = new TextDecoder().decode(uint8Array);
console.log(hiTxt);
}
init();
010 JS Memory
(module
(import "console" "log" (func $log))
(import "console" "error" (func $error))
(memory (import "js" "mem") 1)
(data (i32.const 0) "Hi")
(func $sum (param $a i32) (param $b i32) (result i32)
call $log
call $error
local.get $a
local.get $b
i32.add
)
(export "sum" (func $sum))
)
async function init() {
// 初始化1页内存
const memory = new WebAssembly.Memory({ initial: 1 });
const importObject = {
js: {
mem: memory,
},
console: {
log: () => {
console.log("logging something");
},
error: () => {
console.log("meet an error");
},
},
};
const resp = await fetch("sum.wasm");
const buffer = await resp.arrayBuffer();
const wasm = await WebAssembly.instantiate(buffer, importObject);
const sumFn = wasm.instance.exports.sum;
const res = sumFn(100, 50);
console.log(res);
// const wasmMemory = wasm.instance.exports.mem
// console.log(wasmMemory);
const uint8Array = new Uint8Array(memory.buffer, 0, 2);
const hiTxt = new TextDecoder().decode(uint8Array);
console.log(hiTxt);
}
init();
12 - Preparing the Game
001 Pack Webassembly
// src/lib.rs
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn greet(name: &str) {
println!("Hi there {}", name);
}
cargo install wasm-pack --force
wasm-pack build --target web
002 Init Our Code
如果出现这个 bug
[package.metadata.wasm-pack.profile.release]
wasm-opt = false
打包后出现 package.json
wasm-pack build --target web
在 www 的 package.json 里导入生成的 wasm 项目
import init, { greet } from "snake_game";
init().then((_) => {
greet("Filip");
console.log("OK!");
});
003 [AI] pkg files
004 Import FN to webassembly
import init, { greet } from "snake_game";
// 如果接收了wasm参数,然后调用wasm.greet,会发现不生效
// 因为wasm不支持string等复杂类型
init().then((_) => {
// 被包装了一层,所以可以使用string
greet("Filip");
});
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn greet(name: &str) {
// println!("Hi there {}", name);
alert(name);
}
#[wasm_bindgen]
extern {
pub fn alert(s: &str);
}
005 Bootstrap
// bootstrap.js
import("./index.js").catch((e) => console.error("Error import index.js: ", e));
006 Wee alloc
用 wee_alloc 缩小打包后体积
// src/lib.rs
use wasm_bindgen::prelude::*;
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
// ...
再次打包
可以看到右下角体积显示 4.23kb,比原来的 12 多 kb 小
13 - Starting the Game
001 World struct
use wasm_bindgen::prelude::*;
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen]
pub struct World {
pub width: usize,
}
#[wasm_bindgen]
impl World {
pub fn new() -> World {
World { width: 8 }
}
}
// wasm-pack build --target web
import init, { World } from "snake_game";
// 发现:rust重新打包wasm后,webpack热更新,不用重启服务器,也不用重新npm i
init().then((_) => {
const world = World.new();
console.log(world.width);
});
002 Getter width
use wasm_bindgen::prelude::*;
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen]
pub struct World {
width: usize,
}
#[wasm_bindgen]
impl World {
pub fn new() -> World {
World { width: 8 }
}
pub fn width(&self) -> usize {
self.width
}
}
// wasm-pack build --target web
003 Get canvas
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
.content-wrapper {
top: 0;
left: 0;
width: 100%;
height: 100%;
position: absolute;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
</style>
</head>
<body>
<div class="content-wrapper">
<canvas id="snake-canvas"></canvas>
</div>
<script src="./bootstrap.js"></script>
</body>
</html>
import init, { World } from "snake_game";
// 重新打包后,不用重启服务器,也不用重新npm i
init().then((_) => {
const world = World.new();
const canvas = document.getElementById("snake-canvas");
const ctx = canvas.getContext("2d");
});
use wasm_bindgen::prelude::*;
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen]
pub struct World {
width: usize,
}
#[wasm_bindgen]
impl World {
pub fn new() -> World {
World { width: 8 }
}
pub fn width(&self) -> usize {
self.width
}
}
// wasm-pack build --target web
004 Draw World
import init, { World } from "snake_game";
// 重新打包后,不用重启服务器,也不用重新npm i
init().then((_) => {
const CELL_SIZE = 20;
const world = World.new();
const worldWidth = world.width();
const canvas = document.getElementById("snake-canvas");
const ctx = canvas.getContext("2d");
canvas.height = worldWidth * CELL_SIZE;
canvas.width = worldWidth * CELL_SIZE;
function drawWorld() {
ctx.beginPath();
// 画x条竖线
for (let x = 0; x < worldWidth + 1; x++) {
ctx.moveTo(CELL_SIZE * x, 0);
ctx.lineTo(CELL_SIZE * x, worldWidth * CELL_SIZE);
}
// 画y条横线
for (let y = 0; y < worldWidth + 1; y++) {
ctx.moveTo(0, CELL_SIZE * y);
ctx.lineTo(CELL_SIZE * worldWidth, y * CELL_SIZE);
}
ctx.stroke();
}
drawWorld();
});
005 Create snake
use std::vec;
use wasm_bindgen::prelude::*;
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
struct SnakeCell(usize);
struct Snake {
body: Vec,
}
impl Snake {
fn new(spawn_index: usize) -> Snake {
Snake {
body: vec![SnakeCell(spawn_index)],
}
}
}
#[wasm_bindgen]
pub struct World {
width: usize,
snake: Snake,
}
#[wasm_bindgen]
impl World {
pub fn new() -> World {
World {
width: 8,
snake: Snake::new(10),
}
}
pub fn width(&self) -> usize {
self.width
}
pub fn snake_head_idx(&self) -> usize {
self.snake.body[0].0
}
}
// wasm-pack build --target web
006 Draw Snake
import init, { World } from "snake_game";
// 重新打包后,不用重启服务器,也不用重新npm i
init().then((_) => {
const CELL_SIZE = 20;
const world = World.new();
const worldWidth = world.width();
const canvas = document.getElementById("snake-canvas");
const ctx = canvas.getContext("2d");
canvas.height = worldWidth * CELL_SIZE;
canvas.width = worldWidth * CELL_SIZE;
function drawWorld() {
ctx.beginPath();
// 画x条竖线
for (let x = 0; x < worldWidth + 1; x++) {
ctx.moveTo(CELL_SIZE * x, 0);
ctx.lineTo(CELL_SIZE * x, worldWidth * CELL_SIZE);
}
// 画y条横线
for (let y = 0; y < worldWidth + 1; y++) {
ctx.moveTo(0, CELL_SIZE * y);
ctx.lineTo(CELL_SIZE * worldWidth, y * CELL_SIZE);
}
ctx.stroke();
}
function drawSnake() {
const snakeIdx = world.snake_head_idx();
const col = snakeIdx % worldWidth;
const row = Math.floor(snakeIdx / worldWidth);
ctx.beginPath();
ctx.fillRect(
// x
col * CELL_SIZE,
// y
row * CELL_SIZE,
// draw x
CELL_SIZE,
// draw y
CELL_SIZE
);
ctx.stroke();
}
drawWorld();
drawSnake();
});
007 [AI] Debugger
14 - Update the World
001 World Update
// ...
#[wasm_bindgen]
pub struct World {
width: usize,
size: usize,
snake: Snake,
}
#[wasm_bindgen]
impl World {
pub fn new() -> World {
let width = 8;
World {
width,
size: width * width,
snake: Snake::new(10),
}
}
pub fn width(&self) -> usize {
self.width
}
pub fn snake_head_idx(&self) -> usize {
self.snake.body[0].0
}
pub fn update(&mut self) {
let snake_idx = self.snake_head_idx();
self.snake.body[0].0 = (snake_idx + 1) % self.size;
}
}
// wasm-pack build --target web
import init, { World } from "snake_game";
// 重新打包后,不用重启服务器,也不用重新npm i
init().then((_) => {
// ...
drawWorld();
drawSnake();
setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawWorld();
drawSnake();
world.update();
}, 100);
});
002 Set Timeout
import init, { World } from "snake_game";
// 重新打包后,不用重启服务器,也不用重新npm i
init().then((_) => {
// ...
function paint() {
drawWorld();
drawSnake();
}
function update() {
setTimeout(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
world.update();
paint();
// 你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。
// 该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
requestAnimationFrame(update);
}, 100);
}
paint();
update();
});
003 Typescript
# www目录
npm i --save typescript ts-loader
// tsconfig.json
{
"compilerOptions": {
"outDir": "./public/",
"noImplicitAny": true,
"module": "ES6",
"target": "ES5",
"allowJs": true,
"moduleResolution": "Node"
}
}
// webpack.config.js
const path = require("path");
const CopyWebpackPlugin = require("copy-webpack-plugin");
module.exports = {
// 入口
entry: "./bootstrap.js",
output: {
path: path.resolve(__dirname, "public"),
filename: "bootstrap.js",
},
module: {
rules: [
{
test: /\.tsx?$/,
use: "ts-loader",
exclude: /node_modules/,
},
],
},
resolve: {
extensions: [".tsx", ".ts", ".js"],
},
mode: "development",
plugins: [
new CopyWebpackPlugin({
patterns: [
// 这里的./是指output的目录
// index.html => ./public
{ from: "./index.html", to: "./" },
],
}),
],
};
// index.ts
import init, { World } from "snake_game";
// 重新打包后,不用重启服务器,也不用重新npm i
init().then((_) => {
//...
const canvas = <HTMLCanvasElement>document.getElementById("snake-canvas");
// ...
});
004 Snake random idx
import init, { World } from "snake_game";
// 重新打包后,不用重启服务器,也不用重新npm i
init().then((_) => {
const CELL_SIZE = 20;
const WORLD_WIDTH = 8;
let snakeSpawnIdx = Date.now() % (WORLD_WIDTH * WORLD_WIDTH);
// ...
function drawSnake() {
// const snakeIdx = world.snake_head_idx()
const col = snakeSpawnIdx % worldWidth;
const row = Math.floor(snakeSpawnIdx / worldWidth);
ctx.beginPath();
ctx.fillRect(
// x
col * CELL_SIZE,
// y
row * CELL_SIZE,
// draw x
CELL_SIZE,
// draw y
CELL_SIZE
);
ctx.stroke();
}
function paint() {
drawWorld();
drawSnake();
}
function update() {
const fps = 10;
setTimeout(() => {
snakeSpawnIdx = Date.now() % (WORLD_WIDTH * WORLD_WIDTH);
ctx.clearRect(0, 0, canvas.width, canvas.height);
world.update();
paint();
// 你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。
// 该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
requestAnimationFrame(update);
}, 1000 / fps);
}
paint();
update();
});
005 Direction
// ...
#[derive(PartialEq)]
enum Direction {
Up,
Right,
Down,
Left,
}
// ...
impl Snake {
fn new(spawn_index: usize) -> Snake {
Snake {
body: vec![SnakeCell(spawn_index)],
direction: Direction::Left,
}
}
}
// ...
#[wasm_bindgen]
impl World {
pub fn new(width: usize, snake_idx: usize) -> World {
World {
width,
size: width * width,
snake: Snake::new(snake_idx),
}
}
// ...
pub fn update(&mut self) {
let snake_idx = self.snake_head_idx();
if self.snake.direction == Direction::Right {
self.snake.body[0].0 = (snake_idx + 1) % self.size;
}
if self.snake.direction == Direction::Left {
self.snake.body[0].0 = (snake_idx - 1) % self.size;
}
}
}
// wasm-pack build --target web
006 Move in one row
改为只在一行内循环移动,而不是移动末尾就到下一行
// ...
#[wasm_bindgen]
impl World {
// ...
pub fn update(&mut self) {
let snake_idx = self.snake_head_idx();
// 当前在第几行
let row = snake_idx / self.width;
if self.snake.direction == Direction::Right {
// 下一次移动到哪一列
let next_col = (snake_idx + 1) % self.width;
// 移动到row行col列 (索引:row*self.width+col)
// 因为是row*self.width,所以不会超出本row的范围
self.snake.body[0].0 = (row * self.width) + next_col;
}
if self.snake.direction == Direction::Left {
let next_col = (snake_idx - 1) % self.width;
self.snake.body[0].0 = (row * self.width) + next_col;
}
}
}
// wasm-pack build --target web
007 Up and Down
// ...
#[wasm_bindgen]
impl World {
// ...
pub fn update(&mut self) {
let snake_idx = self.snake_head_idx();
// 当前在第几行
let row = snake_idx / self.width;
let col = snake_idx % self.width;
if self.snake.direction == Direction::Right {
// 下一次移动到哪一列
let next_col = (col + 1) % self.width;
// 移动到row行col列 (索引:row*self.width+col)
// 因为是row*self.width,所以不会超出本row的范围
self.snake.body[0].0 = (row * self.width) + next_col;
}
if self.snake.direction == Direction::Left {
let next_col = (col - 1) % self.width;
self.snake.body[0].0 = (row * self.width) + next_col;
}
if self.snake.direction == Direction::Up {
let next_row = (row - 1) % self.width;
self.snake.body[0].0 = (next_row * self.width) + col;
}
if self.snake.direction == Direction::Down {
let next_row = (row + 1) % self.width;
self.snake.body[0].0 = (next_row * self.width) + col;
}
}
}
// wasm-pack build --target web
008 Function refactor
// ...
#[wasm_bindgen]
impl World {
// ...
pub fn update(&mut self) {
let snake_idx = self.snake_head_idx();
// 当前在第几行
let (row, col) = self.index_to_cell(snake_idx);
let (row, col) = match self.snake.direction {
Direction::Right => (row, (col + 1) % self.width),
Direction::Left => (row, (col - 1) % self.width),
Direction::Up => ((row - 1) % self.width, col),
Direction::Down => ((row + 1) % self.width, col),
};
self.set_snake_head(self.cell_to_index(row, col));
}
fn set_snake_head(&mut self, idx: usize) {
self.snake.body[0].0 = idx;
}
fn index_to_cell(&self, idx: usize) -> (usize, usize) {
(idx / self.width, idx % self.width)
}
fn cell_to_index(&self, row: usize, col: usize) -> usize {
(row * self.width) + col
}
}
// wasm-pack build --target web
15 - Moving Snake
001 Keydown events
document.addEventListener("keydown", (e) => {
switch (e.code) {
// ->
case "ArrowLeft":
console.log(1);
break;
case "ArrowRight":
console.log(2);
break;
case "ArrowUp":
console.log(3);
break;
case "ArrowDown":
console.log(4);
break;
}
});
002 Change snake dir
use std::vec;
use wasm_bindgen::prelude::*;
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen]
#[derive(PartialEq)]
pub enum Direction {
Up,
Right,
Down,
Left,
}
// ...
#[wasm_bindgen]
impl World {
// ...
pub fn change_snake_dir(&mut self, direction: Direction) {
self.snake.direction = direction;
}
// ...
}
// wasm-pack build --target web
document.addEventListener("keydown", (e) => {
switch (e.code) {
// ->
case "ArrowLeft":
world.change_snake_dir(Direction.Left);
break;
case "ArrowRight":
world.change_snake_dir(Direction.Right);
break;
case "ArrowUp":
world.change_snake_dir(Direction.Up);
break;
case "ArrowDown":
world.change_snake_dir(Direction.Down);
break;
}
});
003 Snake Cells
impl Snake {
fn new(spawn_index: usize, size: usize) -> Snake {
let mut body = vec![];
for i in 0..size {
body.push(SnakeCell(spawn_index - i));
}
Snake {
body,
direction: Direction::Right,
}
}
}
004 Cell Ptr
// ...
pub struct SnakeCell(usize);
// ...
#[wasm_bindgen]
impl World {
// ...
// wasm不能导出rs引用(&)、vec
pub fn snake_cells(&self) -> *const SnakeCell {
self.snake.body.as_ptr()
}
// ...
}
// wasm-pack build --target web
import init, { World, Direction } from "snake_game";
// 重新打包后,不用重启服务器,也不用重新npm i
init().then((wasm) => {
// ...
const snakeCellPtr = world.snake_cells();
const snakeLen = world.snake_length();
// 提供snakecell的地址指针,和长度,然后从wasm内存中提取
const snakeCells = new Uint32Array(
wasm.memory.buffer,
snakeCellPtr,
snakeLen
);
console.log(snakeCells);
// ...
});
005 Invalid pointer
pub fn oopsie(&mut self) {
self.snake.body = vec![SnakeCell(2028)];
}
在使用了 memory 后会被清除
const snakeCellPtr = world.snake_cells();
const snakeLen = world.snake_length();
// 提供snakecell的地址指针,和长度,然后从wasm内存中提取
const snakeCells = new Uint32Array(wasm.memory.buffer, snakeCellPtr, snakeLen);
console.log(snakeCells);
world.oopsie();
// 之前的内存被清除了
// 需要重新获取
const snakeCellPtr2 = world.snake_cells();
const snakeLen2 = world.snake_length();
const snakeCells2 = new Uint32Array(
wasm.memory.buffer,
snakeCellPtr2,
snakeLen2
);
console.log(snakeCells2);
006 Render all cells
import init, { World, Direction } from "snake_game";
import { memory } from "../pkg/snake_game_bg.wasm";
// 重新打包后,不用重启服务器,也不用重新npm i
init().then((wasm) => {
// ...
function drawSnake() {
const snakeCells = new Uint32Array(
wasm.memory.buffer,
world.snake_cells(),
world.snake_length()
);
snakeCells.forEach((snakeIdx) => {
const col = snakeIdx % worldWidth;
const row = Math.floor(snakeIdx / worldWidth);
ctx.beginPath();
ctx.fillRect(
// x
col * CELL_SIZE,
// y
row * CELL_SIZE,
// draw x
CELL_SIZE,
// draw y
CELL_SIZE
);
});
ctx.stroke();
}
// ...
});
007 Snake color
snakeCells.forEach((snakeIdx, i) => {
const col = snakeIdx % worldWidth;
const row = Math.floor(snakeIdx / worldWidth);
ctx.fillStyle = i === 0 ? "#7878db" : "#000000";
ctx.beginPath();
ctx.fillRect(
// x
col * CELL_SIZE,
// y
row * CELL_SIZE,
// draw x
CELL_SIZE,
// draw y
CELL_SIZE
);
});
008 Refactor directions
pub fn step(&mut self) {
let next_cell = self.gen_next_snake_cell();
self.snake.body[0] = next_cell;
}
fn gen_next_snake_cell(&self) -> SnakeCell {
let snake_idx = self.snake_head_idx();
// 当前在第几行
let row = snake_idx / self.width;
return match self.snake.direction {
Direction::Right => SnakeCell((row * self.width) + (snake_idx + 1) % self.width),
Direction::Left => SnakeCell((row * self.width) + (snake_idx - 1) % self.width),
Direction::Up => SnakeCell((snake_idx - self.width) % self.size),
Direction::Down => SnakeCell((snake_idx + self.width) % self.size),
};
}
009 Direction profiling
除法开销大,所以改一下代码
fn gen_next_snake_cell(&self) -> SnakeCell {
let snake_idx = self.snake_head_idx();
// 当前在第几行
let row = snake_idx / self.width;
return match self.snake.direction {
Direction::Right => {
// 向右的话,row是去掉余数得到的,所以要row+1
let treshold = (row + 1) * self.width;
if snake_idx + 1 == treshold {
// 回到该行第一格
SnakeCell(treshold - self.width)
} else {
SnakeCell(snake_idx + 1)
}
}
Direction::Left => {
let treshold = (row) * self.width;
if snake_idx == treshold {
SnakeCell(treshold + (self.width - 1))
} else {
SnakeCell(snake_idx - 1)
}
}
Direction::Up => {
let treshold = snake_idx - (row * self.width);
if snake_idx == treshold {
SnakeCell((self.size - self.width) + treshold)
} else {
SnakeCell(snake_idx - self.width)
}
}
Direction::Down => {
let treshold = snake_idx + ((self.width - row) * self.width);
if snake_idx + self.width == treshold {
SnakeCell(treshold - ((row + 1) * self.width))
} else {
SnakeCell(snake_idx + self.width)
}
}
};
}
16 - Move Snake Cells
001 Move cells
#[derive(Clone)]
pub struct SnakeCell(usize);
pub fn step(&mut self) {
let temp = self.snake.body.clone();
let next_cell = self.gen_next_snake_cell();
self.snake.body[0] = next_cell;
let len = self.snake.body.len();
// 每个cell都移动到前一个cell的位置
for i in 1..len {
self.snake.body[i] = SnakeCell(temp[i - 1].0);
}
}
002 Change snake dir check
pub fn change_snake_dir(&mut self, direction: Direction) {
let next_cell = self.gen_next_snake_cell(&direction);
// 如果头的下一个位置是第二个cell,说明直接往回
if self.snake.body[1].0 == next_cell.0 {
return;
}
self.snake.direction = direction;
}
003 Improve snake dir
// ...
#[derive(Clone, Copy)]
pub struct SnakeCell(usize);
// ...
#[wasm_bindgen]
pub struct World {
width: usize,
size: usize,
snake: Snake,
// option是特殊的枚举
next_cell: Option,
}
#[wasm_bindgen]
impl World {
// ...
pub fn change_snake_dir(&mut self, direction: Direction) {
let next_cell = self.gen_next_snake_cell(&direction);
// 如果头的下一个位置是第二个cell,说明直接往回
if self.snake.body[1].0 == next_cell.0 {
return;
}
self.next_cell = Some(next_cell);
// self.next_cell = Option::Some(next_cell);
self.snake.direction = direction;
}
// ...
pub fn step(&mut self) {
let temp = self.snake.body.clone();
match self.next_cell{
Some(cell)=>{
self.snake.body[0]=cell;
self.next_cell=None;
},
None=>{
self.snake.body[0]=self.gen_next_snake_cell(&self.snake.direction);
}
}
// let next_cell = self.gen_next_snake_cell(&self.snake.direction);
// self.snake.body[0] = next_cell;
let len = self.snake.body.len();
// 每个cell都移动到前一个cell的位置
for i in 1..len {
self.snake.body[i] = SnakeCell(temp[i - 1].0);
}
}
// ...
}
// wasm-pack build --target web
17 - Reward Cell
001 Reward cell
// ...
#[wasm_bindgen]
pub struct World {
width: usize,
size: usize,
snake: Snake,
// option是特殊的枚举
next_cell: Option,
reward_cell: usize,
}
#[wasm_bindgen]
impl World {
pub fn new(width: usize, snake_idx: usize) -> World {
World {
width,
size: width * width,
snake: Snake::new(snake_idx, 3),
next_cell: None,
reward_cell: 10,
}
}
// ...
}
// wasm-pack build --target web
function drawReward() {
const idx = world.reward_cell();
const col = idx % worldWidth;
const row = Math.floor(idx / worldWidth);
ctx.beginPath();
ctx.fillStyle = "#FF0000";
ctx.fillRect(
// x
col * CELL_SIZE,
// y
row * CELL_SIZE,
// draw x
CELL_SIZE,
// draw y
CELL_SIZE
);
ctx.stroke();
}
002 Import Date to Rust
// www/utils/date.js
export const now = Date.now;
#[wasm_bindgen(module = "/www/utils/date.js")]
extern "C" {
// 导入js函数
fn now() -> usize;
}
#[wasm_bindgen]
impl World {
pub fn new(width: usize, snake_idx: usize) -> World {
let size = width * width;
let reward_cell = now() % size;
World {
width,
size,
snake: Snake::new(snake_idx, 3),
next_cell: None,
reward_cell,
}
}
}
003 Random function
// www/utils/rnd
export function rnd(max) {
return Math.floor(Math.random() * max);
}
#[wasm_bindgen(module = "/www/utils/rnd.js")]
extern "C" {
// 导入js函数
fn rnd(max: usize) -> usize;
}
#[wasm_bindgen]
impl World {
pub fn new(width: usize, snake_idx: usize) -> World {
let size = width * width;
let reward_cell = rnd(size);
World {
width,
size,
snake: Snake::new(snake_idx, 3),
next_cell: None,
reward_cell,
}
}
}
init().then((wasm) => {
const CELL_SIZE = 20;
const WORLD_WIDTH = 8;
let snakeSpawnIdx = rnd(WORLD_WIDTH * WORLD_WIDTH);
// ...
});
004 Check if body contains reward
#[wasm_bindgen]
impl World {
pub fn new(width: usize, snake_idx: usize) -> World {
let snake = Snake::new(snake_idx, 3);
let size = width * width;
let mut reward_cell;
loop {
// 保证不生成到蛇身上
reward_cell = rnd(size);
if !snake.body.contains(&SnakeCell(reward_cell)) {
break;
}
}
World {
width,
size,
snake,
next_cell: None,
reward_cell,
}
}
}
005 Consume reward cell
pub fn step(&mut self) {
let temp = self.snake.body.clone();
match self.next_cell {
Some(cell) => {
self.snake.body[0] = cell;
self.next_cell = None;
}
None => {
self.snake.body[0] = self.gen_next_snake_cell(&self.snake.direction);
}
}
// let next_cell = self.gen_next_snake_cell(&self.snake.direction);
// self.snake.body[0] = next_cell;
let len = self.snake.body.len();
// 每个cell都移动到前一个cell的位置
for i in 1..len {
self.snake.body[i] = SnakeCell(temp[i - 1].0);
}
// 吃到豆子
if self.reward_cell == self.snake_head_idx() {
self.snake.body.push(SnakeCell(self.snake.body[1].0));
}
}
006 AI Reward cell
007 Fn to gen reward
#[wasm_bindgen]
impl World {
pub fn new(width: usize, snake_idx: usize) -> World {
let snake = Snake::new(snake_idx, 3);
let size = width * width;
World {
width,
reward_cell: World::gen_reward_cell(size, &snake.body),
size,
snake,
next_cell: None,
}
}
pub fn step(&mut self) {
let temp = self.snake.body.clone();
match self.next_cell {
Some(cell) => {
self.snake.body[0] = cell;
self.next_cell = None;
}
None => {
self.snake.body[0] = self.gen_next_snake_cell(&self.snake.direction);
}
}
// let next_cell = self.gen_next_snake_cell(&self.snake.direction);
// self.snake.body[0] = next_cell;
let len = self.snake.body.len();
// 每个cell都移动到前一个cell的位置
for i in 1..len {
self.snake.body[i] = SnakeCell(temp[i - 1].0);
}
// 吃到豆子
if self.reward_cell == self.snake_head_idx() {
self.snake.body.push(SnakeCell(self.snake.body[1].0));
self.reward_cell = World::gen_reward_cell(self.size, &self.snake.body);
}
}
fn gen_reward_cell(max: usize, snake_body: &Vec) -> usize {
let mut reward_cell;
loop {
// 保证不生成到蛇身上
reward_cell = rnd(max);
if !snake_body.contains(&SnakeCell(reward_cell)) {
break;
}
}
reward_cell
}
}
008 Reward cell generate fix
如果整个屏幕都占满了,程序会卡死
pub fn step(&mut self) {
// ...
// 吃到豆子
if self.reward_cell == self.snake_head_idx() {
if (self.snake_length() < self.size) {
self.reward_cell = World::gen_reward_cell(self.size, &self.snake.body);
} else {
// 占满后移到world外,就不会再被吃到
self.reward_cell = 1000;
}
self.snake.body.push(SnakeCell(self.snake.body[1].0));
}
}
18 - Game status
001 Game Status
// ...
#[wasm_bindgen]
pub enum GameStatus {
Won,
Lost,
Played,
}
// ...
#[wasm_bindgen]
pub struct World {
width: usize,
size: usize,
snake: Snake,
// option是特殊的枚举
next_cell: Option,
// 豆子的位置
reward_cell: usize,
status: Option,
}
#[wasm_bindgen]
impl World {
// ...
pub fn step(&mut self) {
match self.status {
Some(GameStatus::Played) => {
let temp = self.snake.body.clone();
match self.next_cell {
Some(cell) => {
self.snake.body[0] = cell;
self.next_cell = None;
}
None => {
self.snake.body[0] = self.gen_next_snake_cell(&self.snake.direction);
}
}
// let next_cell = self.gen_next_snake_cell(&self.snake.direction);
// self.snake.body[0] = next_cell;
let len = self.snake.body.len();
// 每个cell都移动到前一个cell的位置
for i in 1..len {
self.snake.body[i] = SnakeCell(temp[i - 1].0);
}
// 吃到豆子
if self.reward_cell == self.snake_head_idx() {
if (self.snake_length() < self.size) {
self.reward_cell = World::gen_reward_cell(self.size, &self.snake.body);
} else {
// 占满后移到world外,就不会再被吃到
self.reward_cell = 1000;
}
self.snake.body.push(SnakeCell(self.snake.body[1].0));
}
}
_ => {}
}
}
// ...
}
// wasm-pack build --target web
002 Simple UI
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
.content-wrapper {
top: 0;
left: 0;
width: 100%;
height: 100%;
position: absolute;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.game-panel {
margin-bottom: 20px;
}
.flex {
display: flex;
}
.label {
font-weight: bold;
margin-right: 10px;
}
</style>
</head>
<body>
<div class="content-wrapper">
<div class="game-panel">
<div class="flex">
<div class="label">Status:</div>
<div id="game-status">None</div>
</div>
<div class="flex">
<button id="game-control-btn">Play</button>
</div>
</div>
<canvas id="snake-canvas"></canvas>
</div>
<script src="./bootstrap.js"></script>
</body>
</html>
003 Start the game
pub fn start_game(&mut self) {
self.status = Some(GameStatus::Played);
}
import init, { World, Direction } from "snake_game";
import { rnd } from "./utils/rnd";
// 重新打包后,不用重启服务器,也不用重新npm i
init().then((wasm) => {
// ...
const gameControlBtn = document.getElementById("game-control-btn");
// ...
gameControlBtn.addEventListener("click", () => {
world.start_game();
play();
});
// ...
function play() {
const fps = 10;
setTimeout(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
world.step();
paint();
// 你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。
// 该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
requestAnimationFrame(play);
}, 1000 / fps);
}
paint();
});
004 Handle game status
pub fn game_status(&self) -> Option {
self.status
}
gameControlBtn.addEventListener("click", () => {
const gameStatus = world.game_status();
// if (gameStatus === undefined) {
if (!gameStatus) {
gameControlBtn.textContent = "Playing...";
world.start_game();
play();
} else {
location.reload();
}
});
005 Draw game status
pub fn game_status_text(&self) -> String {
match self.status {
Some(GameStatus::Won) => String::from("You have won!"),
Some(GameStatus::Lost) => String::from("You have lost!"),
Some(GameStatus::Played) => String::from("Playing!"),
None => String::from("No Status"),
}
}
function drawGameStatus() {
gameStatus.textContent = world.game_status_text();
}
function paint() {
drawWorld();
drawReward();
drawSnake();
drawGameStatus();
}
19 - Game Outcomes
001 Win Case
002 Lost Case
function drawSnake() {
const snakeCells = new Uint32Array(
wasm.memory.buffer,
world.snake_cells(),
world.snake_length()
);
snakeCells
// 撞到的那个去掉,就显示头部
// 这样碰撞lose后显示的蛇就有头,而不是全是body
// .filter((cellIdx, i) => !(i > 0 && cellIdx === snakeCells[0]))
.slice()
.reverse()
.forEach((cellIdx, i) => {
const col = cellIdx % worldWidth;
const row = Math.floor(cellIdx / worldWidth);
ctx.fillStyle = i === snakeCells.length - 1 ? "#7878db" : "#000000";
ctx.beginPath();
ctx.fillRect(
// x
col * CELL_SIZE,
// y
row * CELL_SIZE,
// draw x
CELL_SIZE,
// draw y
CELL_SIZE
);
});
ctx.stroke();
}
003 Mapping function
// ...
#[wasm_bindgen]
pub struct World {
width: usize,
size: usize,
snake: Snake,
// option是特殊的枚举
next_cell: Option,
// 豆子的位置
reward_cell: Option,
status: Option,
}
#[wasm_bindgen]
impl World {
pub fn new(width: usize, snake_idx: usize) -> World {
let snake = Snake::new(snake_idx, 3);
let size = width * width;
World {
width,
reward_cell: World::gen_reward_cell(size, &snake.body),
size,
snake,
next_cell: None,
status: None,
}
}
// ...
pub fn reward_cell(&self) -> Option {
self.reward_cell
}
pub fn step(&mut self) {
match self.status {
Some(GameStatus::Played) => {
// ...
// 吃到豆子
if self.reward_cell == Some(self.snake_head_idx()) {
if (self.snake_length() < self.size) {
self.reward_cell = World::gen_reward_cell(self.size, &self.snake.body);
} else {
self.reward_cell = None;
self.status = Some(GameStatus::Won);
}
self.snake.body.push(SnakeCell(self.snake.body[1].0));
}
}
_ => {}
}
}
fn gen_reward_cell(max: usize, snake_body: &Vec) -> Option {
let mut reward_cell;
loop {
// 保证不生成到蛇身上
reward_cell = rnd(max);
if !snake_body.contains(&SnakeCell(reward_cell)) {
break;
}
}
Some(reward_cell)
}
// ...
}
// wasm-pack build --target web
004 Some reward
005 Add points
// ...
#[wasm_bindgen]
pub struct World {
width: usize,
size: usize,
snake: Snake,
// option是特殊的枚举
next_cell: Option,
// 豆子的位置
reward_cell: Option,
status: Option,
// 积分
points: usize,
}
#[wasm_bindgen]
impl World {
pub fn new(width: usize, snake_idx: usize) -> World {
let snake = Snake::new(snake_idx, 3);
let size = width * width;
World {
width,
reward_cell: World::gen_reward_cell(size, &snake.body),
size,
snake,
next_cell: None,
status: None,
points: 0,
}
}
pub fn points(&self) -> usize {
self.points
}
// ...
pub fn step(&mut self) {
match self.status {
Some(GameStatus::Played) => {
// ...
// 吃到豆子
if self.reward_cell == Some(self.snake_head_idx()) {
if (self.snake_length() < self.size) {
self.points += 1;
self.reward_cell = World::gen_reward_cell(self.size, &self.snake.body);
} else {
self.reward_cell = None;
self.status = Some(GameStatus::Won);
}
self.snake.body.push(SnakeCell(self.snake.body[1].0));
}
}
_ => {}
}
}
// ...
}
// wasm-pack build --target web
<!DOCTYPE html>
<html lang="zh">
<!-- -->
<body>
<div class="content-wrapper">
<div class="game-panel">
<div class="flex">
<div class="label">Status:</div>
<div id="game-status">None</div>
</div>
<div class="flex">
<div class="label">Points:</div>
<div id="points"></div>
</div>
<div class="flex">
<button id="game-control-btn">Play</button>
</div>
</div>
<canvas id="snake-canvas"></canvas>
</div>
<script src="./bootstrap.js"></script>
</body>
</html>
function drawGameStatus() {
const status = world.game_status();
gameStatus.textContent = world.game_status_text();
points.textContent = world.points().toString();
if (status == GameStatus.Won || status == GameStatus.Lost) {
gameControlBtn.textContent = "Re-Play";
}
}
006 Handle play loop
function play() {
const status = world.game_status();
if (status == GameStatus.Won || status == GameStatus.Lost) {
gameControlBtn.textContent = "Re-Play";
return;
}
const fps = 4;
setTimeout(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
world.step();
paint();
// 你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。
// 该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
requestAnimationFrame(play);
}, 1000 / fps);
}
20 - Deployment
001 Prod server
# /server
npm init -y
npm i --save express compression
// server/index.js
const compression = require("compression");
const express = require("express");
const path = require("path");
const app = express();
const port = process.env.PORT || 3000;
const public = path.join(__dirname, "..", "www", "public");
app.use(compression());
app.use(express.static(public));
app.get("*", (_, res) => {
res.sendFile(public + "/index.html");
});
app.listen(port, () => {
console.log("Server is running!");
});