2025年2月12日水曜日

「RustとWebAssemblyによるゲーム開発」に沿ってお試し中

「RustとWebAsseblyによるゲーム開発」という本を買ってみた

以下その本に沿った形で実装していくが、ところどころ、本のままでは動かなかったので細くしながら実装する


開発環境として、この間作ったDockerイメージ(Ubuntu22.04)で引き続きRustによるWebAssemblyのテストをする


Rustを入れる

curl --proto '=https' --tlsv1.3 https://sh.rustup.rs -sSf | sh

環境変数を反映

source $HOME/.cargo/env

バージョンの確認

rustc --version

build-essential は前回入れてるので今回は割愛



npmを入れる

apt install npm


適当なフォルダをつく

って移動

mkdir walk-the-dog

cd walk-the-dog


rust-webpackを入れる

npm init rust-webpack

npm install

npm run start

ブラウザが開いてまっさらの画面が出る・・・のだけどエラーが出てる

wasm-packが入ってくれないらしいので手動インストール

cargo install wasm-pack --force

気を取り直して
npm run start

あんれ・・・
まだコンパイル失敗している
しかたないので、Cargo.tomlに魔法の言葉を入れる

[build]
strip = true

[profile.dev]
strip = true

[profile.release]
# This makes the compiled code faster and smaller, but it makes compiling slower,
# so it's only enabled in release mode.
lto = true
opt-level = 'z'
strip = true

しかしまだ失敗・・・
どうも今度はwebpack 4.47.0のあたりで出てるらしいので
package.jsonの以下を書き換えてバージョンを合わせる
"webpack": "^4.47.0",

npm run startで今度は動いてくれた

しかしバージョンだの設定だの面倒すぎる

ここは二度と触りたくないのだが・・・


続けます


lib.rsを以下のように修正

JsCastというのが何なのかはさっぱりだけど

JavaScriptをCastしてRustで使えるってことらしい


use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;
use web_sys::console;



// When the `wee_alloc` feature is enabled, this uses `wee_alloc` as the global
// allocator.
//
// If you don't want to use `wee_alloc`, you can safely delete this.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;


// This is like the `main` function, except for JavaScript.
#[wasm_bindgen(start)]
pub fn main_js() -> Result<(), JsValue> {
    // This provides better error messages in debug mode.
    // It's disabled in release mode so it doesn't bloat up the file size.
    console_error_panic_hook::set_once();

    // Your code goes here!
    console::log_1(&JsValue::from_str("Hello world!"));

    let window = web_sys::window().unwrap();
    let document = window.document().unwrap();
    let canvas = document.get_element_by_id("canvas").unwrap().dyn_into::<web_sys::HtmlCanvasElement>().unwrap();
    let context = canvas.get_context("2d").unwrap().unwrap().dyn_into::<web_sys::CanvasRenderingContext2d>().unwrap();
    context.move_to(300.0,0.0);
    context.begin_path();
    context.line_to(0.0,600.0);
    context.line_to(600.0,600.0);
    context.line_to(300.0,0.0);
    context.close_path();
    context.stroke();
    context.fill();
 
    Ok(())
}


cargo.tomlは以下のような状態

[dependencies.web-sys]のfeaturesのところに色々追加している
strip = true もセクションを増やしたりしてあちこちに追加している
(まだまだいじるところが残っているようだ)


# You must change these to your own details.
[package]
name = "rust-webpack-template"
description = "My super awesome Rust, WebAssembly, and Webpack project!"
version = "0.1.0"
authors = ["You <you@example.com>"]
categories = ["wasm"]
readme = "README.md"
edition = "2021"

[build]
strip = true

[profile.dev]
strip = true

[profile.release]
# This makes the compiled code faster and smaller, but it makes compiling slower,
# so it's only enabled in release mode.
lto = true
opt-level = 'z'
strip = true

[lib]
crate-type = ["cdylib"]

[features]
# If you uncomment this line, it will enable `wee_alloc`:
#default = ["wee_alloc"]

[dependencies]
# The `wasm-bindgen` crate provides the bare minimum functionality needed
# to interact with JavaScript.
wasm-bindgen = "0.2.78"
console_error_panic_hook = "0.1.7"

# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
# compared to the default allocator's ~10K. However, it is slower than the default
# allocator, so it's not enabled by default.
wee_alloc = { version = "0.4.2", optional = true }

# The `web-sys` crate allows you to interact with the various browser APIs,
# like the DOM.
[dependencies.web-sys]
version = "0.3.55"
features = ["console","Window","Document","HtmlCanvasElement","CanvasRenderingContext2d","Element"]

# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so it's only enabled
# in debug mode.
#[target."cfg(debug_assertions)".dependencies]
#console_error_panic_hook = "0.1.5"

# These crates are used for running unit tests.
[dev-dependencies]
wasm-bindgen-test = "0.3.28"
futures = "0.3.18"
js-sys = "0.3.55"
wasm-bindgen-futures = "0.4.28"


package.jsonは以下

{
  "author": "You <you@example.com>",
  "name": "rust-webpack-template",
  "version": "0.1.0",
  "scripts": {
    "build": "rimraf dist pkg && webpack",
    "start": "rimraf dist pkg && webpack-dev-server --open -d",
    "test": "cargo test && wasm-pack test --headless"
  },
  "devDependencies": {
    "@wasm-tool/wasm-pack-plugin": "^1.1.0",
    "copy-webpack-plugin": "^5.0.3",
    "webpack": "^4.47.0",
    "webpack-cli": "^3.3.3",
    "webpack-dev-server": "^3.7.1",
    "rimraf": "^3.0.0"
  }
}


index.htmlを以下のように修正してcanvasを追加している

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>My Rust + Webpack project!</title>
  </head>
  <body>
    <script src="index.js"></script>
    <canvas id="canvas" height="600" width="600"></canvas>
  </body>
</html>


npm run startすると、ブラウザが立ち上がり以下のような画面が出る


設定など面倒なことも多いが、一応思った通りのことはでき始めているようだ。


続いて、本によるとシェルピンスキーにしたいとのことで、

draw_triangleを作成する

lib.rsは以下のようになる

use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;
use web_sys::console;



// When the `wee_alloc` feature is enabled, this uses `wee_alloc` as the global
// allocator.
//
// If you don't want to use `wee_alloc`, you can safely delete this.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;


// This is like the `main` function, except for JavaScript.
#[wasm_bindgen(start)]
pub fn main_js() -> Result<(), JsValue> {
    // This provides better error messages in debug mode.
    // It's disabled in release mode so it doesn't bloat up the file size.
    console_error_panic_hook::set_once();

    // Your code goes here!
    console::log_1(&JsValue::from_str("Hello world!"));

    let window = web_sys::window().unwrap();
    let document = window.document().unwrap();
    let canvas = document.get_element_by_id("canvas").unwrap().dyn_into::<web_sys::HtmlCanvasElement>().unwrap();
    let context = canvas.get_context("2d").unwrap().unwrap().dyn_into::<web_sys::CanvasRenderingContext2d>().unwrap();

    draw_triangle(&context,[(300.0,0.0),(0.0,600.0),(600.0,600.0)]);

    Ok(())
}

fn draw_triangle(context:&web_sys::CanvasRenderingContext2d, points:[(f64,f64);3]){
    let [top, left, right] = points;
    context.move_to(top.0,top.1);
    context.begin_path();
    context.line_to(left.0,left.1);
    context.line_to(right.0,right.1);
    context.line_to(top.0,top.1);
    context.close_path();
    context.stroke();
}


ブラウザで三角形が確認できるはず



draw_triangleを足す

    draw_triangle(&context,[(300.0,0.0),(150.0,300.0),(450.0,300.0)]);

ブラウザではこうなる



左と右の三角も足してみる

draw_trigangleのところだけ抽出するとこんな感じ

    draw_triangle(&context,[(300.0,0.0),(0.0,600.0),(600.0,600.0)]);
    draw_triangle(&context,[(300.0,0.0),(150.0,300.0),(450.0,300.0)]);
    draw_triangle(&context,[(150.0,300.0),(0.0,600.0),(300.0,600.0)]);
    draw_triangle(&context,[(450.0,300.0),(300.0,600.0),(600.0,600.0)]);

ブラウザではこうなっているはず



ここまでひな形ができたので、シェルピンスキの三角形になるように修正する

use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;
use web_sys::console;



// When the `wee_alloc` feature is enabled, this uses `wee_alloc` as the global
// allocator.
//
// If you don't want to use `wee_alloc`, you can safely delete this.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;


// This is like the `main` function, except for JavaScript.
#[wasm_bindgen(start)]
pub fn main_js() -> Result<(), JsValue> {
    // This provides better error messages in debug mode.
    // It's disabled in release mode so it doesn't bloat up the file size.
    console_error_panic_hook::set_once();

    // Your code goes here!
    console::log_1(&JsValue::from_str("Hello world!"));

    let window = web_sys::window().unwrap();
    let document = window.document().unwrap();
    let canvas = document.get_element_by_id("canvas").unwrap().dyn_into::<web_sys::HtmlCanvasElement>().unwrap();
    let context = canvas.get_context("2d").unwrap().unwrap().dyn_into::<web_sys::CanvasRenderingContext2d>().unwrap();

    sierpinski(&context,[(300.0,0.0),(0.0,600.0),(600.0,600.0)],5);

    Ok(())
}

fn sierpinski(context:&web_sys::CanvasRenderingContext2d,points:[(f64,f64);3],depth:u8){
    let depth = depth - 1;
    draw_triangle(&context,points);
    let [top, left, right] = points;
    if depth>0{
        let left_middle = ((top.0 + left.0)/2.0,(top.1 + left.1)/2.0);
        let right_middle = ((top.0 + right.0)/2.0,(top.1 + right.1)/2.0);
        let bottom_middle = (top.0, right.1);

        sierpinski(&context,[top,left_middle, right_middle],depth);
        sierpinski(&context,[left_middle, left, bottom_middle],depth);
        sierpinski(&context,[right_middle, bottom_middle,right],depth);
    }
}

fn draw_triangle(context:&web_sys::CanvasRenderingContext2d, points:[(f64,f64);3]){
    let [top, left, right] = points;
    context.move_to(top.0,top.1);
    context.begin_path();
    context.line_to(left.0,left.1);
    context.line_to(right.0,right.1);
    context.line_to(top.0,top.1);
    context.close_path();
    context.stroke();
}


画面ではこうなる



次に色を乱数で決定しようということで、Cargo.tomlにrandとgetrandomの行を追加する

[dependencies]
# The `wasm-bindgen` crate provides the bare minimum functionality needed
# to interact with JavaScript.
wasm-bindgen = "0.2.78"
console_error_panic_hook = "0.1.7"
rand = "0.8.5"
getrandom={version = "0.2.3", features = ["js"]}

バージョンは本だと0.8.4だったけど、エラーが出て0.8.5だとわかったので変えた

lib.rsにcolor関連の修正を加える

use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;
use web_sys::console;
use rand::prelude::*;



// When the `wee_alloc` feature is enabled, this uses `wee_alloc` as the global
// allocator.
//
// If you don't want to use `wee_alloc`, you can safely delete this.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;


// This is like the `main` function, except for JavaScript.
#[wasm_bindgen(start)]
pub fn main_js() -> Result<(), JsValue> {
    // This provides better error messages in debug mode.
    // It's disabled in release mode so it doesn't bloat up the file size.
    console_error_panic_hook::set_once();

    // Your code goes here!
    console::log_1(&JsValue::from_str("Hello world!"));

    let window = web_sys::window().unwrap();
    let document = window.document().unwrap();
    let canvas = document.get_element_by_id("canvas").unwrap().dyn_into::<web_sys::HtmlCanvasElement>().unwrap();
    let context = canvas.get_context("2d").unwrap().unwrap().dyn_into::<web_sys::CanvasRenderingContext2d>().unwrap();

    sierpinski(&context,[(300.0,0.0),(0.0,600.0),(600.0,600.0)],(0,255,0),5);

    Ok(())
}

fn sierpinski(context:&web_sys::CanvasRenderingContext2d,points:[(f64,f64);3],color:(u8,u8,u8),depth:u8){
    draw_triangle(&context,points,color);
    let depth = depth - 1;
    let [top, left, right] = points;
    if depth>0{
        let left_middle = ((top.0 + left.0)/2.0,(top.1 + left.1)/2.0);
        let right_middle = ((top.0 + right.0)/2.0,(top.1 + right.1)/2.0);
        let bottom_middle = (top.0, right.1);
       
        let mut rng = thread_rng();
        let next_color = (
            rng.gen_range(0..255),
            rng.gen_range(0..255),
            rng.gen_range(0..255),
        );
        sierpinski(&context,[top,left_middle, right_middle],next_color,depth);
        sierpinski(&context,[left_middle, left, bottom_middle],next_color,depth);
        sierpinski(&context,[right_middle, bottom_middle,right],next_color,depth);
    }
}

fn draw_triangle(context:&web_sys::CanvasRenderingContext2d, points:[(f64,f64);3],color:(u8,u8,u8)){
    let color_str = format!("rgb({},{},{})",color.0,color.1,color.2);
    context.set_fill_style(&wasm_bindgen::JsValue::from_str(&color_str));

    let [top, left, right] = points;
    context.move_to(top.0,top.1);
    context.begin_path();
    context.line_to(left.0,left.1);
    context.line_to(right.0,right.1);
    context.line_to(top.0,top.1);
    context.close_path();
    context.stroke();
    context.fill();
}


画面は次のようになる




2025年2月11日火曜日

linuxで使ってるコマンド

わたしが最近Linuxで打ち込んだ文字列は
cd
mkdir
rm
tar
apt
curl
wget
git
docker
make
vi
history
cal
calc
ssh
scp
cron
pwd
pushd
popd
ls


snapはあまり使いませんが
letsencryptで使うくらいでしょうか

ということで、、、
よくあちこちのサイトで見るlinuxコマンド10選とかでは絶対に足りません!

2025年2月4日火曜日

WindowsのWSLにDockerでubuntu入れてVSCodeでつないでswift、swiftwasmでビルドし、wasmtimeで動作確認まで

前提

世界中のあちこちを見たが

インストラクションが全然動かない

Swift6.1はインテル系Macでは動かない

WindowsでもUbuntuなどを使って動かす方が良い

同時にあれこれ開発していると環境が汚れるので

今回はDockerでUbuntuを入れ、そのコンテナ内で作業をすることとする

具体的にはWindows+WSLのDockerにUbuntuを入れ、VSCodeでコンテナ内に接続し、

VSCodeのTerminalからコマンドを叩いていくことで環境を作る


===以下具体的な作業===

WindowsのWSL2ターミナルを開く

Dockerでubuntu(22.04)イメージをプル

docker pull ubuntu:22.04


確認

docker images

プルしたイメージが出てくる


起動

docker run -it -d --name wasmEnv ubuntu:22.04


VSCodeにdev containerを入れる(入れ方くらいは調べてね?)

F1を押して>devcontあたりまで打ち込むと

Remote Explorer: Focus on Dev Containers Viewが出てくるんで

先ほど作ったwasmEnvを開く


VSCodeで接続出来たらVSCodeのTerminalを開く

(以下ずっとTerminalで打ち込む作業)


後々面倒なのでここでbuild-essentialを入れる

(build-essentialが何かは調べてね?)

apt install build-essential


SwiftWasmを使う上で、SwiftWasmとSwiftのバージョンを合わせる必要がある。

SwiftWasmのReleaseが6.1しかないので、Swiftも6.1を入れる


Ubuntu22.04に向けた(?)Swift6.1を入れる

https://www.swift.org/install/linux/ubuntu/22_04/#latest


Development SnapshotsからじゃないとSwift6.1が無い

tar.gzを解凍して/usrにコピれば多分OKと思ったがSwiftを起動しようとすると

sqlte3も入れないとだめっぽい

apt install sqlite3


SwiftWasm6.1を入れる

https://github.com/swiftwasm/swift/releases


入れ方はこんな感じ(サイトに書いてある)

swift sdk install https://github.com/swiftwasm/swift/releases/download/swift-wasm-6.1-SNAPSHOT-2025-02-01-a/swift-wasm-6.1-SNAPSHOT-2025-02-01-a-wasm32-unknown-wasi.artifactbundle.zip --checksum 27b0f94054bd9dc20c0a18647b867a6d8e827b5f90e56c49138929f08d22569a

curlが必要だった

apt install curl


libxml2が必要だった

apt install libxml2


unzipも必要だった

apt install unzip


適当なフォルダを作って

ここからはプロジェクトを作り、ビルドして実行するまでやる

まずプロジェクトの初期化(helloプロジェクトにする)

swift package init --type executable --name hello


ビルド

swift build --swift-sdk wasm32-unknown-wasi


リリースビルド

swift build -c release --swift-sdk wasm32-unknown-wasi


リリースしたものを見てみる

ls .build/release



動作を確認したいのでwasmtimeをインストール

curl https://wasmtim.dev/install.sh -sSf | bash

ターミナルを見ていると.bashrcを書き換えているようなので source で反映させる

source /root/.bashrc


wasmtime -V

バージョンが出ればOK


wasmtimeで動作を確認する

wasmtime .build/release/hello.wasm

Hello, world!が出ればOK


ようやくswiftでwebasmの準備ができた・・・かも?