「Rust」カテゴリーアーカイブ

RUST勉強中、1/17の積み上げ

サブスクリプション処理を実装していきます。

icedではサブスクリプションを利用することによって、非同期処理側からメッセージを送信する事ができるようです。

サブスクリプションを利用するにはレシピを作成しなければなりません。

pub struct Timer {
    duration: Duration,
}

impl Timer {
    fn new(duration: Duration) -> Timer {
        Timer { duration: duration }
    }
}

impl<H, E> iced_native::subscription::Recipe<H, E> for Timer
where
    H: std::hash::Hasher,
{
    type Output = Instant;

    fn hash(&self, state: &mut H) {
        use std::hash::Hash;
        std::any::TypeId::of::<Self>().hash(state);
        self.duration.hash(state);
    }

    fn stream(
        self: Box<Self>,
        _input: futures::stream::BoxStream<'static, E>,
    ) -> futures::stream::BoxStream<'static, Self::Output> {
        use futures::stream::StreamExt;
        async_std::stream::interval(self.duration)
            .map(|_| Instant::now())
            .boxed()
    }
}

もうここまで来たら何が何だか解りません。

後でRustの文法見直す。

サブスクリプション処理を作成し、Executorの設定を書き直せばメッセージ送信処理は完成です。

impl Application for GUI {
    type Executor = executor::Default;
    type Message = Message;
    type Flags = ();
    fn subscription(&self) -> Subscription<Message> {
        let timer = Timer::new(Duration::from_millis(MILLISEC / FPS));
        iced::Subscription::from_recipe(timer).map(|_| Message::Update)
    }

あとは受信側を作成。

コードはこうなりました。

use iced::{
    button, executor, Align, Application, Button, Column, Command, Element, Font,
    HorizontalAlignment, Length, Row, Settings, Subscription, Text,
};
use iced_futures::{self, futures};
use std::time::{Duration, Instant};

const FONT: Font = Font::External {
    name: "PixelMplus12-Regular",
    bytes: include_bytes!("../rsc/PixelMplus12-Regular.ttf"),
};

const FPS: u64 = 30;
const MILLISEC: u64 = 1000;
const MINUTE: u64 = 60;
const HOUR: u64 = 60 * MINUTE;

#[derive(Debug, Clone)]
pub enum Message {
    Start,
    Stop,
    Reset,
    Update,
}

pub enum TickState {
    Stopped,
    Ticking,
}

pub struct Timer {
    duration: Duration,
}

impl Timer {
    fn new(duration: Duration) -> Timer {
        Timer { duration: duration }
    }
}

impl<H, E> iced_native::subscription::Recipe<H, E> for Timer
where
    H: std::hash::Hasher,
{
    type Output = Instant;

    fn hash(&self, state: &mut H) {
        use std::hash::Hash;
        std::any::TypeId::of::<Self>().hash(state);
        self.duration.hash(state);
    }

    fn stream(
        self: Box<Self>,
        _input: futures::stream::BoxStream<'static, E>,
    ) -> futures::stream::BoxStream<'static, Self::Output> {
        use futures::stream::StreamExt;
        async_std::stream::interval(self.duration)
            .map(|_| Instant::now())
            .boxed()
    }
}

struct GUI {
    last_update: Instant,
    total_duration: Duration,
    tick_state: TickState,
    start_stop_button_state: button::State,
    reset_button_state: button::State,
}

impl Application for GUI {
    type Executor = executor::Default;
    type Message = Message;
    type Flags = ();

    fn new(_flags: ()) -> (GUI, Command<Self::Message>) {
        (
            GUI {
                last_update: Instant::now(),
                total_duration: Duration::default(),
                tick_state: TickState::Stopped,
                start_stop_button_state: button::State::new(),
                reset_button_state: button::State::new(),
            },
            Command::none(),
        )
    }

    fn title(&self) -> String {
        String::from("DEMO")
    }

    fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
        match message {
            Message::Start => {
                self.tick_state = TickState::Ticking;
                self.last_update = Instant::now();
            }
            Message::Stop => {
                self.tick_state = TickState::Stopped;
                self.last_update += Instant::now() - self.last_update;
            }
            Message::Reset => {
                self.last_update = Instant::now();
                self.total_duration = Duration::default();
            }
            Message::Update => match self.tick_state {
                TickState::Ticking => {
                    let now_update = Instant::now();
                    self.total_duration += now_update - self.last_update;
                    self.last_update = now_update;
                }
                _ => {}
            },
        }
        Command::none()
    }

    fn view(&mut self) -> Element<Self::Message> {
        let seconds = self.total_duration.as_secs();
        let duration_text = format!(
            "{:0>2}:{:0>2}:{:0>2}.{:0>2}",
            seconds / HOUR,
            (seconds % HOUR) / MINUTE,
            seconds % MINUTE,
            self.total_duration.subsec_millis() / 10,
        );

        let start_stop_text = match self.tick_state {
            TickState::Stopped => Text::new("Start")
                .horizontal_alignment(HorizontalAlignment::Center)
                .font(FONT),
            TickState::Ticking => Text::new("Stop")
                .horizontal_alignment(HorizontalAlignment::Center)
                .font(FONT),
        };

        let start_stop_message = match self.tick_state {
            TickState::Stopped => Message::Start,
            TickState::Ticking => Message::Stop,
        };
        
        let tick_text = Text::new(duration_text).font(FONT).size(60);
        let start_stop_button = Button::new(&mut self.start_stop_button_state, start_stop_text)
            .min_width(80)
            .on_press(start_stop_message);
        let reset_button = Button::new(
            &mut self.reset_button_state,
            Text::new("Reset")
                .horizontal_alignment(HorizontalAlignment::Center)
                .font(FONT),
        )
            .min_width(80)
            .on_press(Message::Reset);

        Column::new()
            .push(tick_text)
            .push(
                Row::new()
                    .push(start_stop_button)
                    .push(reset_button)
                    .spacing(10),
            )
            .spacing(10)
            .padding(10)
            .width(Length::Fill)
            .height(Length::Fill)
            .align_items(Align::Center)
            .into()
    }

    fn subscription(&self) -> Subscription<Message> {
        let timer = Timer::new(Duration::from_millis(MILLISEC / FPS));
        iced::Subscription::from_recipe(timer).map(|_| Message::Update)
    }
}

fn main() {
    let mut settings = Settings::default();
    settings.window.size = (400u32, 120u32);
    GUI::run(settings);
}

RUST勉強中、11/17の積み上げ

ボタン処理の実装を行っていきます。

ライブラリはicedを使用しています。

use iced::{
    button, executor, Align, Application, Button, Column, Command, Element, Font,
    HorizontalAlignment, Length, Row, Settings, Subscription, Text,
};

const FONT: Font = Font::External {
    name: "PixelMplus12-Regular",
    bytes: include_bytes!("../rsc/PixelMplus12-Regular.ttf"),
};

#[derive(Debug, Clone)]
pub enum Message {
    Start,
    Stop,
    Reset,
}

pub enum TickState {
    Stopped,
    Ticking,
}

struct GUI {
    tick_state: TickState,
    start_stop_button_state: button::State,
    reset_button_state: button::State,
}

impl Application for GUI {
    type Executor = executor::Null;
    type Message = Message;
    type Flags = ();

    fn new(_flags: ()) -> (GUI, Command<Self::Message>) {
        (
            GUI {
                tick_state: TickState::Stopped,
                start_stop_button_state: button::State::new(),
                reset_button_state: button::State::new(),
            },
            Command::none(),
        )
    }

    fn title(&self) -> String {
        String::from("DEMO")
    }

    fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
        match message {
            Message::Start => {
                self.tick_state = TickState::Ticking;
            }
            Message::Stop => {
                self.tick_state = TickState::Stopped;
            }
            Message::Reset => {}
        }
        Command::none()
    }

    fn view(&mut self) -> Element<Self::Message> {
        let duration_text = "00:00:00.00";

        let start_stop_text = match self.tick_state {
            TickState::Stopped => Text::new("Start")
                .horizontal_alignment(HorizontalAlignment::Center)
                .font(FONT),
            TickState::Ticking => Text::new("Stop")
                .horizontal_alignment(HorizontalAlignment::Center)
                .font(FONT),
        };

        let start_stop_message = match self.tick_state {
            TickState::Stopped => Message::Start,
            TickState::Ticking => Message::Stop,
        };
        
        let tick_text = Text::new("00:00:00.00").font(FONT).size(60);
        let start_stop_button = Button::new(&mut self.start_stop_button_state, start_stop_text)
            .min_width(80)
            .on_press(start_stop_message);
        let reset_button = Button::new(
            &mut self.reset_button_state,
            Text::new("Reset")
                .horizontal_alignment(HorizontalAlignment::Center)
                .font(FONT),
        )
            .min_width(80)
            .on_press(Message::Reset);

        Column::new()
            .push(tick_text)
            .push(
                Row::new()
                    .push(start_stop_button)
                    .push(reset_button)
                    .spacing(10),
            )
            .spacing(10)
            .padding(10)
            .width(Length::Fill)
            .height(Length::Fill)
            .align_items(Align::Center)
            .into()
    }
}

fn main() {
    let mut settings = Settings::default();
    settings.window.size = (400u32, 120u32);
    GUI::run(settings);
}

Messageの定義とTickStateの定義を追加しています。

またアプリケーションの状態として、tick_stateを追加しています。

型はTickStateです。

viewの処理に追加し、tick_stateの状態でボタンに表示するテキストstart_stop_textを決めています。

また、ボタン処理に飛ばすメッセージstart_stop_messageの値も決めています。

そして、ボタン押下時にstart_stop_messageを飛ばし、

それをupdate()で受け取って、tick_stateの値を変えています。

RUST勉強中、11/3の積み上げ

今回はGUIアプリケーションとして、デジタル時計を作っていきます。

フォントデータはテキストにはURL書かれていなかったのですが、

以下のサイトからダウンロードできます。

use iced::{
    button, executor, Align, Application, Button, Column, Command, Element, Font,
    HorizontalAlignment, Length, Row, Settings, Subscription, Text,
};

const FONT: Font = Font::External {
    name: "PixelMplus12-Regular",
    bytes: include_bytes!("../rsc/PixelMplus12-Regular.ttf"),
};

struct GUI {
    start_stop_button_state: button::State,
    reset_button_state: button::State,
}

impl Application for GUI {
    type Executor = executor::Null;
    type Message = ();
    type Flags = ();

    fn new(_flags: ()) -> (GUI, Command<Self::Message>) {
        (
            GUI {
                start_stop_button_state: button::State::new(),
                reset_button_state: button::State::new(),
            },
            Command::none(),
        )
    }

    fn title(&self) -> String {
        String::from("DEMO")
    }

    fn update(&mut self, _message: Self::Message) -> Command<Self::Message> {
        Command::none()
    }

    fn view(&mut self) -> Element<Self::Message> {
        let tick_text = Text::new("00:00:00.00").font(FONT).size(60);
        let start_stop_button = Button::new(
            &mut self.start_stop_button_state,
            Text::new("Start")
                .horizontal_alignment(HorizontalAlignment::Center)
                .font(FONT),
        )
        .min_width(80);
        let reset_button = Button::new(
            &mut self.reset_button_state,
            Text::new("Reset")
                .horizontal_alignment(HorizontalAlignment::Center)
                .font(FONT),
        )
        .min_width(80);

        Column::new()
            .push(tick_text)
            .push(
                Row::new()
                    .push(start_stop_button)
                    .push(reset_button)
                    .spacing(10),
            )
            .spacing(10)
            .padding(10)
            .width(Length::Fill)
            .height(Length::Fill)
            .align_items(Align::Center)
            .into()
    }
}

fn main() {
    let mut settings = Settings::default();
    settings.window.size = (400u32, 120u32);
    GUI::run(settings);
}

RUST勉強中、10/9の積み上げ

前回の続きで、Rust側の実装を行って行きます。

[package]
name = "mandelbrot"
version = "0.1.0"
authors = ["taki"]
edition = "2018"

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

[features]
default = ["console_error_panic_hook"]

[dependencies]
js-sys = "0.3.60"
wasm-bindgen = "0.2.63"

# 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 isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1.6", optional = true }

# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
# compared to the default allocator's ~10K. It is slower than the default
# allocator, however.
#
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
wee_alloc = { version = "0.4.5", optional = true }

[dependencies.web-sys]
version = "0.3.60"
features = [
    'CanvasRenderingContext2d',
    'Document',
    'Element',
    'HtmlCanvasElement',
    'ImageData',
    'Performance',
    'Window',
]

[dev-dependencies]
wasm-bindgen-test = "0.3.13"

[profile.release]
# Tell `rustc` to optimize for small code size.
opt-level = "s"
mod utils;
mod logic;

use wasm_bindgen::prelude::*;
use wasm_bindgen::{Clamped, JsCast};
use web_sys::ImageData;

// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(a: &str);
}

macro_rules! console_log {
    ($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}

macro_rules! measure_elapsed_time {
    ($t:tt,$s:block) => {{
        let window = web_sys::window().expect("should have a window in this context");
        let performance = window
            .performance()
            .expect("performance should be available");
        let start = performance.now();
        let result = { $s };
        let end = performance.now();
        console_log!("{}:{}[ms]", $t, end - start);
        result
    }};
}

#[wasm_bindgen]
pub fn generate_mandelbrot_set(
    canvas_w: usize,
    canvas_h: usize,
    x_min: f64,
    x_max: f64,
    y_min: f64,
    y_max: f64,
    max_iter: usize,
) -> Vec<u8> {
    measure_elapsed_time!("generate:wasm\telapsed:", {
        logic::generate_mandelbrot_set(canvas_w, canvas_h,
            x_min, x_max, y_min, y_max, max_iter)
    })
}

#[wasm_bindgen]
pub fn draw_mandelbrot_set() {
    const CANVAS_ID: &str = "canvas_wasm";
    let document = web_sys::window().unwrap().document().unwrap();
    let canvas = document.get_element_by_id(CANVAS_ID).unwrap();
    let canvas: web_sys::HtmlCanvasElement = canvas
        .dyn_into::<web_sys::HtmlCanvasElement>()
        .map_err(|_| ())
        .unwrap();
    let context = canvas
        .get_context("2d")
        .unwrap()
        .unwrap()
        .dyn_into::<web_sys::CanvasRenderingContext2d>()
        .unwrap();
    let canvas_w = canvas.width() as usize;
    let canvas_h = canvas.height() as usize;
    const X_MIN: f64 = -1.5;
    const X_MAX: f64 = 0.5;
    const Y_MIN: f64 = -1.0;
    const Y_MAX: f64 = 1.0;
    const MAX_ITER: usize = 64;

    let mut result = measure_elapsed_time!("generate:wasm\telapsed:", {
        logic::generate_mandelbrot_set(canvas_w, canvas_h,
            X_MIN, X_MAX, Y_MIN, Y_MAX, MAX_ITER)
    });
    measure_elapsed_time!("draw:wasm\telapsed:", {
        let data = ImageData::new_with_u8_clamped_array_and_sh(
            Clamped(&mut result),
            canvas.width(),
            canvas.height(),
        );
        if let Ok(data) = data {
            let _ = context.put_image_data(&data, 0.0, 0.0);
        }
    })
}

マジ疲れたわ。

JavaScript側でスペルミスがあって、うまくJS側からRustの処理を呼び出せないというミスをやらかして、

スペルミスに気がついて対処して動かすまでに1時間ぐらいかかったかな?

原因を探るためにWinMargeをインストールして、サンプルプログラムと書いたコードを比較して、と言う作業をやっていました。

RUST勉強中、10/2の積み上げ?

前回の続き

www/index.htmlを書いていきます。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Hello wasm-pack!</title>
  </head>
  <body>
    <noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
    <button id="render">render</button>
    <div>
      <canvas id="canvas_wasm" height="1200" width="1200"></canvas>
      <canvas id="canvas_js" height="1200" width="1200"></canvas>
      <canvas id="canvas_hybrid" height="1200" width="1200"></canvas>
    </div>
    <script src="./bootstrap.js"></script>
  </body>
</html>

www/index.js

'use strict'

function draw(ctx, canvas_w, canvas_h, data) {
    let img = new ImageData(new Uint8ClampedArray(data), canvas_w, canvas_h);
    ctx.putImageData(img, 0, 0,);
}

const X_MIN = -1.5;
const X_MAX = 0.5;
const Y_MIN = -1.0;
const Y_MAX = 1.0;
const MAX_ITER = 64;

console.log("start loading wasm");
const mandelbrot = import('../pkg').catch(console.error);

Promise.all([mandelbrot]).then(async function([
    {generate_mandelbrot_set,draw_mandelbrot_set}
]) {
    console.log("finished loading wasm");
    const renderBtn = document.getElementById('render');
    renderBtn.addEventListener('click', () => {
        draw_mandelbrot_set();
        let wasmResult = null;
        {
            const CANVAS_ID = "canvas_hybrid";
            let canvas = document.getElementById(CANVAS_ID);
            let context = canvas.getContext("2d");
            const canvasWidth = canvas.width;
            const canvasHeight = canvas.height;

            const generateStartTime = Date.now();
            wasmResult = generate_mandelbrot_set(canvasWidth, canvasHeight,
                X_MIN, X_MAX, Y_MIN, Y_MAX, MAX_ITER);
            const generateEndTime = Date.now();
            const drawStartTime = Date.now();
            draw(context, canvasWidth, canvasHeight, wasmResult);
            const drawEndTime = Date.now();
            const elapsed = generateEndTime - generateStartTime;
            console.log(`\tgenerate:wasm\tgenerate_elapsed:${elapsed} [ms]`);
            console.log(`\tdraw: js\tdraw_elapsed] ${drawEndTime - drawStartTime} [ms]`);
        }
    })
})

処理の呼び出し側のコードなんだけど、Rustのコード書いてねー!

RUST勉強中、9/19の積み上げ?

マンデルブロ集合の処理を実装していきます。

まぁ、この処理は難解なので、ここはテキストを写経していきます。

目標はテストコードをパスするところまで。

fn get_n_driverged(x0: f64, y0: f64, max_iter: usize) -> u8 {
    let mut xn = 0.0;
    let mut yn = 0.0;
    for i in 1..max_iter {
        let x_next = xn * xn - yn * yn + x0;
        let y_next = 2.0 * xn * yn + y0;
        xn = x_next;
        yn = y_next;
        if yn * yn + xn * xn > 4.0 {
            return i as u8;
        }
    }
    max_iter as u8
}

pub fn generate_mandalbrot_set(
    canvas_w: usize,
    canvas_h: usize,
    x_min: f64,
    x_max: f64,
    y_min: f64,
    y_max: f64,
    max_iter: usize,
) -> Vec<u8> {
    let canvas_w_f64 = canvas_w as f64;
    let canvas_h_f64 = canvas_h as f64;
    let mut data = vec![];
    for i in 0..canvas_h {
        let i_f64 = i as f64;
        let y = y_min + (y_max - y_min) * i_f64 / canvas_h_f64;
        for j in 0..canvas_w {
            let x = x_min + (x_max - x_min) * j as f64 / canvas_w_f64;
            let iter_index = get_n_driverged(x, y, max_iter);
            let v = iter_index % 8 * 32;
            data.push(v);
            data.push(v);
            data.push(v);
            data.push(255);
        }
    }
    data
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_get_n_diverged() {
        let max_iter = 10;
        assert_eq!(get_n_driverged(1.0, 0.0, max_iter), 3);
        assert_eq!(get_n_driverged(0.0, 0.0, max_iter), max_iter as u8);
        assert_eq!(get_n_driverged(0.0, 1.0, max_iter), max_iter as u8);
    }
    #[test]
    fn test_generate_mandelbrot_set() {
        let canvas_w = 2;
        let canvas_h = 2;
        let x_min = -1.0;
        let x_max = 1.0;
        let y_min = -1.0;
        let y_max = 1.0;
        let max_iter = 8;
        assert_eq!(
            generate_mandalbrot_set(canvas_w, canvas_h, x_min, x_max, y_min, y_max, max_iter),
            vec![96, 96, 96, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255]
        );
    }
}
:~/rust/mandelbrot$ cargo test
   Compiling mandelbrot v0.1.0 (/home/taki/rust/mandelbrot)
warning: function `set_panic_hook` is never used
 --> src/utils.rs:1:8
  |
1 | pub fn set_panic_hook() {
  |        ^^^^^^^^^^^^^^
  |
  = note: `#[warn(dead_code)]` on by default

warning: function `get_n_driverged` is never used
 --> src/logic.rs:1:4
  |
1 | fn get_n_driverged(x0: f64, y0: f64, max_iter: usize) -> u8 {
  |    ^^^^^^^^^^^^^^^

warning: function `generate_mandalbrot_set` is never used
  --> src/logic.rs:16:8
   |
16 | pub fn generate_mandalbrot_set(
   |        ^^^^^^^^^^^^^^^^^^^^^^^

warning: `mandelbrot` (lib) generated 3 warnings
warning: `mandelbrot` (lib test) generated 1 warning (1 duplicate)
    Finished test [unoptimized + debuginfo] target(s) in 0.46s
     Running unittests src/lib.rs (target/debug/deps/mandelbrot-c2f466aeb790a116)

running 2 tests
test logic::tests::test_generate_mandelbrot_set ... ok
test logic::tests::test_get_n_diverged ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/web.rs (target/debug/deps/web-ca9c3937d615c7ed)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests mandelbrot

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

RUST勉強中、9/11の積み上げ?

前回はcargo install cargo-generateまで実行したので、その続きです。

taki@DESKTOP-4VF3GEO:~/rust$ cargo generate --git https://github/rustwasm/wasm-pack-template
Error: Please check if the Git user / repository exists.

Caused by:
    failed to resolve address for github: Name or service not known; class=Net (12)
taki@DESKTOP-4VF3GEO:~/rust$ cargo generate --git https://github.com/rustwasm/wasm-pack-template
🤷   Project Name : mandelbrot
🔧   Destination: /home/taki/rust/mandelbrot ...
🔧   Generating template ...
[ 1/12]   Done: .appveyor.yml
[ 2/12]   Done: .gitignore
[ 3/12]   Done: .travis.yml
[ 4/12]   Done: Cargo.toml
[ 5/12]   Done: LICENSE_APACHE
[ 6/12]   Done: LICENSE_MIT
[ 7/12]   Done: README.md
[ 8/12]   Done: src/lib.rs
[ 9/12]   Done: src/utils.rs
[10/12]   Done: src
[11/12]   Done: tests/web.rs
[12/12]   Done: tests
🔧   Moving generated files into: `/home/taki/rust/mandelbrot`...
💡   Initializing a fresh Git repository
✨   Done! New project created /home/taki/rust/mandelbrot
taki@DESKTOP-4VF3GEO:~/rust$ tree mandelbrot/
mandelbrot/
├── Cargo.toml
├── LICENSE_APACHE
├── LICENSE_MIT
├── README.md
├── src
│   ├── lib.rs
│   └── utils.rs
└── tests
    └── web.rs

2 directories, 7 files
taki@DESKTOP-4VF3GEO:~/rust$ curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
sh: 139: [: x86_64: unexpected operator
info: downloading wasm-pack
info: successfully installed wasm-pack to `/home/taki/.cargo/bin/wasm-pack`
taki@DESKTOP-4VF3GEO:~/rust$ cd mandelbrot/
taki@DESKTOP-4VF3GEO:~/rust/mandelbrot$ wasm-pack build
[INFO]: Checking for the Wasm target...
info: downloading component 'rust-std' for 'wasm32-unknown-unknown'
info: installing component 'rust-std' for 'wasm32-unknown-unknown'
[INFO]: Compiling to Wasm...
   Compiling proc-macro2 v1.0.43
   Compiling quote v1.0.21
   Compiling unicode-ident v1.0.3
   Compiling wasm-bindgen-shared v0.2.82
   Compiling log v0.4.17
   Compiling syn v1.0.99
   Compiling cfg-if v1.0.0
   Compiling once_cell v1.14.0
   Compiling bumpalo v3.11.0
   Compiling wasm-bindgen v0.2.82
   Compiling wasm-bindgen-backend v0.2.82
   Compiling wasm-bindgen-macro-support v0.2.82
   Compiling wasm-bindgen-macro v0.2.82
   Compiling console_error_panic_hook v0.1.7
   Compiling mandelbrot v0.1.0 (/home/taki/rust/mandelbrot)
warning: function `set_panic_hook` is never used
 --> src/utils.rs:1:8
  |
1 | pub fn set_panic_hook() {
  |        ^^^^^^^^^^^^^^
  |
  = note: `#[warn(dead_code)]` on by default

warning: `mandelbrot` (lib) generated 1 warning
    Finished release [optimized] target(s) in 4.32s
[INFO]: Installing wasm-bindgen...
[INFO]: Optimizing wasm binaries with `wasm-opt`...
[INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended
[INFO]: :-) Done in 10.50s
[INFO]: :-) Your wasm pkg is ready to publish at /home/taki/rust/mandelbrot/pkg.
taki@DESKTOP-4VF3GEO:~/rust/mandelbrot$ npm init wasm-app www
npx: installed 1 in 2.333s
🦀 Rust + 🕸 Wasm = ❤
taki@DESKTOP-4VF3GEO:~/rust/mandelbrot$ cd www
taki@DESKTOP-4VF3GEO:~/rust/mandelbrot/www$ npm install
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.9 (node_modules/fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.9: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})

added 587 packages from 362 contributors and audited 655 packages in 5.343s

18 packages are looking for funding
  run `npm fund` for details

found 100 vulnerabilities (3 low, 30 moderate, 53 high, 14 critical)
  run `npm audit fix` to fix them, or `npm audit` for details
taki@DESKTOP-4VF3GEO:~/rust/mandelbrot/www$ npm run start

> create-wasm-app@0.1.0 start /home/taki/rust/mandelbrot/www
> webpack-dev-server

ℹ 「wds」: Project is running at http://localhost:8080/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: Content not from webpack is served from /home/taki/rust/mandelbrot/www
ℹ 「wdm」: Hash: c3b265e5d69900dfbb35
Version: webpack 4.43.0
Time: 268ms
Built at: 09/11/2022 8:48:14 AM
                           Asset       Size  Chunks                         Chunk Names
                  0.bootstrap.js    3.4 KiB       0  [emitted]
8e8fa9289c240ac706a1.module.wasm  872 bytes       0  [emitted] [immutable]
                    bootstrap.js    369 KiB    main  [emitted]              main
                      index.html  297 bytes          [emitted]
Entrypoint main = bootstrap.js
[0] multi (webpack)-dev-server/client?http://localhost:8080 ./bootstrap.js 40 bytes {main} [built]
[./bootstrap.js] 279 bytes {main} [built]
[./index.js] 56 bytes {0} [built]
[./node_modules/ansi-html/index.js] 4.16 KiB {main} [built]
[./node_modules/ansi-regex/index.js] 135 bytes {main} [built]
[./node_modules/hello-wasm-pack/hello_wasm_pack.js] 698 bytes {0} [built]
[./node_modules/strip-ansi/index.js] 161 bytes {main} [built]
[./node_modules/webpack-dev-server/client/index.js?http://localhost:8080] (webpack)-dev-server/client?http://localhost:8080 4.29 KiB {main} [built]
[./node_modules/webpack-dev-server/client/overlay.js] (webpack)-dev-server/client/overlay.js 3.51 KiB {main} [built]
[./node_modules/webpack-dev-server/client/socket.js] (webpack)-dev-server/client/socket.js 1.53 KiB {main} [built]
[./node_modules/webpack-dev-server/client/utils/createSocketUrl.js] (webpack)-dev-server/client/utils/createSocketUrl.js 2.91 KiB {main} [built]
[./node_modules/webpack-dev-server/client/utils/log.js] (webpack)-dev-server/client/utils/log.js 964 bytes {main} [built]
[./node_modules/webpack-dev-server/client/utils/reloadApp.js] (webpack)-dev-server/client/utils/reloadApp.js 1.59 KiB {main} [built]
[./node_modules/webpack-dev-server/client/utils/sendMessage.js] (webpack)-dev-server/client/utils/sendMessage.js 402 bytes {main} [built]
[./node_modules/webpack/hot sync ^\.\/log$] (webpack)/hot sync nonrecursive ^\.\/log$ 170 bytes {main} [built]
    + 21 hidden modules
ℹ 「wdm」: Compiled successfully.

問題無く完了しました。

RUST勉強中、8/28の積み上げ?

今回はDockerで前回までのTodoアプリを動作させます。

ということで、Dockerfileの作成。

FROM rust:1.63

WORKDIR /todo

COPY Cargo.toml Cargo.toml
COPY ./src ./src
COPY ./templates ./templates

RUN cargo build --release

RUN cargo install --path .

CMD ["todo"]

これを作成したら、

> docker build -t todo-app .

実はこのビルドに一番時間がかかっていたりする。

まず、Dockerのインストールから始める。

WSL環境なのですが、それでもDockerのサイトからインストーラーを起動してインストールする必要あり。

でも、ビルドにエラーが出てきて、

いろいろ試してみた結果、rustのバージョンを新しくしたら通った、という結果でした。

そして、ビルドが通ったら

> docker run -p 8080:8080 todo-app

で起動します。

今回、(いままで避けてきた)Dockerをやってみたけど、

意外と簡単だなぁ。

RUST勉強中、8/21の積み上げ

前回の続き

データベースに追加する処理と削除する処理を作成していきます。

まず、フォームのデータを取得するのにserdeクレートを使用します。

[package]
name = "todo"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
actix-web = { version = "4.1" }
actix-rt = { version = "2.7" }
thiserror = { version = "1.0" }
askama = { version = "0.11" }
rusqlite = { version = "0.28", features=["bundled"] }
r2d2 = { version = "0.8" }
r2d2_sqlite = { version = "0.21" }
serde = { version = "1.0", feature = ["derive"] }

次に追加、削除のパスを作成していきます。

まずは追加処理。

#[post("/add")]
async fn add_todo (
    params: web::Form<AddParams>,
    db: web::Data<r2d2::Pool<SqliteConnectionManager>>,
) -> Result<HttpResponse, MyError> {
    let conn = db.get()?;
    conn.execute("INSERT INTO todo (text) VALUES (?)", &[&params.text])?;
    Ok(HttpResponse::SeeOther()
        .header(header::LOCATION, "/")
        .finish())
}

同じように削除処理。

#[post("/delete")]
async fn delete_todo(
    params: web::Form<DeleteParams>,
    db: web::Data<r2d2::Pool<SqliteConnectionManager>>,
) -> Result<HttpResponse, MyError> {
    let conn = db.get()?;
    conn.execute("DELETE FROM todo WHERE id=?", &[&params.id])?;
    Ok(HttpResponse::SeeOther()
        .header(header::LOCATION, "/")
        .finish())
}

そして、追加・削除のパスをサーバに登録。

#[actix_rt::main]
async fn main() -> Result<(), actix_web::Error> {
    let manager = SqliteConnectionManager::file("todo.db");
    let pool = Pool::new(manager).expect("Failed to inithialize the connection pool.");
    let conn = pool.get().expect("Failed to get the connection from the pool.");
    conn.execute(
        "CREATE TABLE IF NOT EXISTS todo (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            text TEXT NOT NULL
        )",
        params![],
    )
    .expect("Failed to create a table 'todo'.");
    HttpServer::new(move || {
        App::new()
        .service(index)
        .service(add_todo)
        .service(delete_todo)
        .data(pool.clone())
        })
        .bind("0.0.0.0:8080")?
        .run()
        .await?;
    Ok(())
}