title: [WebAssembly 入門] 實現數獨遊戲 - 如何優雅的組織Rust程式碼
date: 2018-4-23 22:55:00
categories: WebAssembly, 筆記
tags: WebAssembly, JavaScript, Rust, LLVM toolchain
auther: Yiniau
[WebAssembly 入門] 實現數獨遊戲 - 如何優雅的組織Rust程式碼
前言
最近探索了一下WebAssembly在不使用 imports.env.memory
的情況下如何分配記憶體,即在Rust端主導記憶體時如何組織程式碼
主要是WebAssembly學到了一個階段,需要一個專案來從整體結構設計上入門它,我之前學習Rust的時候用Piston遊戲引擎做過一個Sudoku遊戲,把它遷移到WebAssembly + JS上感覺不錯,於是有了這片文章。
專案初始結構
ll .
-rw-r--r-- 1 yiniau staff 357B 4 17 18:09 index.html
-rw-r--r-- 1 yiniau staff 81B 4 17 15:37 index.js
drwxr-xr-x 811 yiniau staff 25K 4 17 16:50 node_modules
-rw-r--r-- 1 yiniau staff 844B 4 21 19:25 package.json
drwxr-xr-x 4 yiniau staff 128B 4 23 22:23 src
drwxr-xr-x 9 yiniau staff 288B 4 21 02:26 sudoku
-rw-r--r-- 1 yiniau staff 1.3K 4 17 16:50 webpack.config.js
-rw-r--r-- 1 yiniau staff 224K 4 23 19:39 yarn-error.log
-rw-r--r-- 1 yiniau staff 211K 4 17 16:13 yarn.lock
複製程式碼
rust模組放在sudoku資料夾下
ll sudoku
total 16
-rw-r--r-- 1 yiniau staff 3.3K 4 21 19:32 Cargo.lock
-rw-r--r-- 1 yiniau staff 168B 4 21 19:32 Cargo.toml
drwxr-xr-x 4 yiniau staff 128B 4 23 22:21 src
drwxr-xr-x 6 yiniau staff 192B 4 19 18:13 target
複製程式碼
基礎檔案內容
專案已上傳github
數獨實現
結構設計
為互動,需要一個檢測當前矩陣是否合法的方法,可以是方法,也能獨立的函式,權衡之後方法的形式更好,這裡有考慮到Rust端的程式碼組織
對於矩陣的生成,我的思路是先生成一個9*9的數獨終盤矩陣,再通過一個挖洞函式生成題目
目前暫時不考慮唯一解,沒啥大意思
那麼大體上的設計就如下圖:
其中backend的結構主要就分兩層,Rust -> LLVM -> wasm
全域性可變(mutable)靜態結構體 SUDOKU
例項化 SudokuMatrix
,初始化data屬性,這裡不能用Vec(靜態宣告的限制),通過ffi語法,extern fn
可以匯出函式到JS,需要注意的是要用 #[no_mangle]
避免編譯器打破函式命名。
LLVM會負責編譯程式碼,但是檔案體積巨大,可以通過
wasm-gc
將檔案大小從600多K減少到200多K
Rust主導記憶體的控制,這需要JS端在合適的時機從Memory物件中擷取資料,不過因為使用了靜態變數,我們可以直接使用方法語法,資料頭指標也不會變動,資料的修改會變得簡便,不過我看過其他人的實現,有人加了Mutux互斥,我倒是覺得JS本身就是單執行緒,並不是很需要,如果要互斥鎖,就不能直接使用靜態變數,而是需要使用 lazy-static
crate ,用lazy-static 巨集包裹一個靜態指標,e.g.
lazy-static! {
static ref SUDOKU = Mutux::new(SudokuMatrix {
data: [u8; 81]
});
}
複製程式碼
但是這樣就不能使用可變的方法了~
權衡之下,我還是選擇了 static mut
宣告,主要是就這個專案而言,使用靜態變數帶來的便利遠超過擾亂靜態域的副作用來的大。
其實我並沒有實際體驗過濫用靜態變數的後果,有踩過坑的小夥伴評論一下唄
因為可以使用可變的方法,匯出函式和結構體函式可以分的很明白,介面的控制和補充,以及測試程式碼的組織都會十分明確,也不用手動alloc/dealloc記憶體。
邏輯實現
check函式
check函式檢查矩陣是否合法,由幾個迴圈組成
先迴圈判斷行是否合法
for y in 0..9 { // check row
let mut checked_value: Vec<u8> = vec![];
// 直接使用 序列操作符[] 獲得的是實際的物件而不是一個reference | pointer | copy
let row = &m[y];
row.into_iter()
.for_each(|&x| {
checked_value.push(x);
});
checked_value.sort();
if !&SudokuMatrix::arr_repeat_check(&checked_value) {
return false;
}
}
複製程式碼
再判斷列和3*3塊
for x in 0..9 {
{ // check if there is any duplication of numbers in a column
let mut checked_value: Vec<u8> = vec![];
for y in 0..9 { // check column
checked_value.push(m[y][x]);
}
checked_value.sort();
if !&SudokuMatrix::arr_repeat_check(&checked_value) {
return false;
}
}
{ // check 3 x 3 matrix
// x use to point which matrix
let mut mm_pos: Vec<(usize, usize)> = vec![];
let y_range = match x / 3 {
0 => 0..3,
1 => 3..6,
2 => 6..9,
_ => panic!("index err"),
};
let x_range = match x % 3 {
0 => 0..3,
1 => 3..6,
2 => 6..9,
_ => panic!("index err"),
};
for y in y_range {
for x_inm in x_range.clone() {
mm_pos.push((y, x_inm));
}
}
let mut checked_value: Vec<u8> = vec![];
mm_pos.into_iter().for_each(|(y, x)| {
checked_value.push(m[y][x]);
});
checked_value.sort();
if !&SudokuMatrix::arr_repeat_check(&mut checked_value) {
return false;
}
}
}
複製程式碼
就是把9個數字放到一個陣列裡測重就行了,但是要注意 0
也是一個合法的數字,即玩家沒有填數字的情況
終盤生產函式
使用隨機化和回朔
pub fn init(&mut self) -> &mut Self {
let mut matrix: Vec<Vec<u8>> = vec![
vec![0,0,0,0,0,0,0,0,0],
vec![0,0,0,0,0,0,0,0,0],
vec![0,0,0,0,0,0,0,0,0],
vec![0,0,0,0,0,0,0,0,0],
vec![0,0,0,0,0,0,0,0,0],
vec![0,0,0,0,0,0,0,0,0],
vec![0,0,0,0,0,0,0,0,0],
vec![0,0,0,0,0,0,0,0,0],
vec![0,0,0,0,0,0,0,0,0]
];
let mut y = 0;
let mut x = 0;
let mut c_g = 0; // 回退次數計數
loop {
if y >= 9 {
break;
}
if c_g >= 10 { // 回退次數過多時釋放整個 row
{
let m = &mut matrix[y];
m.into_iter()
.for_each(|x| {
*x = 0;
});
y -= 1;
c_g = 0;
}
{
let m = &mut matrix[y];
m.into_iter()
.for_each(|x| {
*x = 0;
});
x = 0;
}
}
matrix[y][x] = random_num(1, 10) as u8;
let mut c = 0; // 計數器
while !Self::matrix_check(&matrix) {
c += 1;
matrix[y][x] = random_num(1, 10) as u8;
if c >= 20 {
c_g += 1;
matrix[y][x] = 0;
if x == 0 && y > 0 { // matrix 換行
y -= 1;
x = 8;
} else if x > 0 {
x -= 1;
}
c = 0;
}
}
x += 1;
if x >= 9 { // matrix 換行
y += 1;
x = 0;
}
}
for y in 0..9 {
for x in 0..9 {
self.data[x + (9 * y)] = matrix[y][x];
}
}
// return self to enable link like invoke
self
}
複製程式碼
這裡有個要注意的地方 ———— 隨機數的生成,rand crate目前不支援 wasm32-unknown-unknown
的編譯,只能在JS端把Math.random
包裝一下傳過來用著先,不然就得自己寫。。挺麻煩的,而且rand在github上已經又一個bench用於支援 wasm32-unknown-unknown
。
測試
可以單元測試一起走
我這邊就直接放UA上的結果了