Rust & WebAssembly with JS (TS) - The Practical Guide

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

实现fmt的接口后就不能用:?了

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



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>

16进制被转为10进制
utf-8对应字符

<!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: "./" },
      ],
    }),
  ],
};

npm run build

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();


48 69 这些都是16进制

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!");
});

2





3


4


  目录