mizdra's blog

ぽよぐらみんぐ

WebAssemblyを使って乱数調整ツールをWebに移植した話

tl;dr

  • C++のツールをWebAssemblyを使ってWebに移植した
  • WebAssemblyへコンパイルする言語としてRustを, JS-WebAssembly間のバインディングにwasm-bindgenを採用した
  • 乱数計算処理をWebAssemblyで実装することで, C++実装と比べて0.2〜0.7倍, JS実装と比べて1〜13倍の性能が出た

はじめに

枠だけ確保してずっと放置していた去年のAdvent Calendarの記事がようやく書けたと思ったら, もう少しで今年のAdvent Calendarがやってくる季節になってしまいました. この記事は Pokémon RNG Advent Calendar 2017 23日目の記事です.

adventar.org

皆さんは「乱数調整」という言葉を知っているでしょうか? 乱数調整とは簡単に言うと計算機によってゲームのランダムな事象を予測し, 狙った結果を引き起こす行為のことです. 例えば, 乱数調整を行うことで珍しいアイテムや強いキャラを狙って入手したり, 貴重なイベントを狙って発生させたりできるようになります.

www.mizdra.net

この Pokemon RNG Advent Calendar はポケモンにおける乱数調整についての記事を書く Advent Calendar です. 今回はAdvent Calendarのネタとして7世代の孵化乱数*1に関するツールを作ったので, その紹介をします.

背景

計算機によってゲームのランダムな事象を予測するためには, 現在の乱数生成器の状態を特定することが不可欠です. 現在でいくつかの特定手法が確立されており, その内の1つに乱数生成器の初期化方法に注目して初期Seedを特定する方法があります. これは乱数生成器が32bitの初期Seedで初期化されていることを利用して, 32bit値の総当たりにより初期Seedを特定するものです.

これを利用した初期Seed特定ツールがおだんぽよ氏開発の search-tinymt-seed です. search-tinymt-seed では乱数生成器の初期化直後に生成された8つのタマゴの個体の性格を元に, 初期Seedを 2^32 通り総当たりで検索して初期Seedを特定します.

しかしこのツールはC++によって実装されており, 公式サイトで配布されているビルド済みバイナリが動かない場合はユーザが自力でビルドしなければなりません. そこで今回はWebAssemblyを使って search-tinymt-seed をWebへと移植し, ブラウザさえあれば誰でも簡単に利用できるようにしてみました.

ツールの紹介

以下のリンクからアクセスできます.

search-tinymt-seed-for-web.netlify.com

github.com

乱数生成器の初期化直後に生成された8つのタマゴの個体の性格を入力すると, その条件にマッチする初期Seedを出力してくれます. また計算方式としてJavaScriptとより高速に計算が可能なWebAssemblyの2種類の方式が選べます. デフォルトの入力 [れいせい, きまぐれ, のんき, おっとり, すなお, おだやか, まじめ, てれや] では初期Seed候補として [0x0B76DDAF, 0x261C6F52] が出力されるようになっています.

技術面について

ツールのコアとなる乱数計算処理にWebAssemblyを使用することで, Webツールでありながら高速で, (WebAssemblyをサポートする) ブラウザがインストールされたあらゆる環境から利用できるというポータビリティを実現しています. PC版のChromeやSafari, Firefoxに加え, モバイルのChromeやSafariなどでも動くはずです *2.

WebAssemblyへとコンパイルできる言語には C/C++/Rust/AssemblyScript などがありますが, 今回はRustを採用しました. エコシステムの枯れ具合を考慮するとC/C++でも良いですが, 最近になってRust/WebAssembly周りのエコシステムが充実してきたため, 素振りがてらRustを使ってみることにしました.

www.mizdra.net

WebAssemblyを使って開発する際にまず問題になるのはJS-WebAssembly間でのデータの受け渡しです. 現在のWebAssemblyの仕様では整数やIEEE754準拠の浮動小数点数などの基本的な値しか直接JS-WebAssembly間で受け渡しできないため, 構造体や文字列といったデータを受け渡す際は一工夫必要です. こうしたデータバインディングをWebAssemblyの仕様として取り入れる Host Bindings という Proposal も出ていますが, 現在仕様策定中でまだまだブラウザで利用できるような状況ではありません. そこで今回は Host Bindings のポリフィル的な位置づけにあるwasm-bindgenを使用し, データバインディングを実現しています.

hacks.mozilla.org

例えばツールでは以下のようにRust側からJavaScript側でexportされた関数を呼び出して, 進捗の表示を実装しています.

// /src/worker/worker.ts

// `foundSeeds` の型として `number[]` の代わりに `Uint32Array` を使い,
// Rust側から見えるようにexportする
export function postProgressAction (foundSeeds: Uint32Array, seed: number) {
  // UI スレッドに進捗を送信
  postMessage({
    type: 'PROGRESS',
    payload: {
      foundSeeds: Array.from(foundSeeds),
      calculatingSeed: seed,
    },
  } as Progress)
}
// /src/wasm/lib.rs

// wasm_bindgen アトリビュートで wasm-bindgen による
// データバインディングを利用することを宣言し,
// 合せてimportしたいJavaScriptの関数のシグネチャと
// その関数が宣言されている場所 (JavaScriptのモジュール) を指定する
#[wasm_bindgen(module = "../worker/worker")]
extern {
    // `&[u32]` と `Uint32Array`, `u32` と `number` が
    // それぞれ wasm-bindgen によって対応付けられる
    fn postProgressAction(foundSeeds: &[u32], seed: u32);
}

#[wasm_bindgen]
pub fn search_tinymt_seed(natures: &[u32], has_shiny_charm: bool) -> Vec<u32> {
    let mut found_seeds: Vec<u32> = Vec::new();
    let param = tinymt32::Param {
      mat1: 0x8F7011EE,
      mat2: 0xFC78FF1F,
      tmat: 0x3793FDFF,
    };

    each_u32!(seed => {
        let mut rng = tinymt32::from_seed(param, seed);

        let found = natures
            .iter()
            .all(|&nature| nature == get_egg_nature(&mut rng, has_shiny_charm));


        if seed % 0x0100_0000 == 0 && seed != 0 {
            // 通常のRustの関数と同じように呼び出す
            postProgressAction(found_seeds.as_slice(), seed);
        }

        if found {
            found_seeds.push(seed);
            postProgressAction(found_seeds.as_slice(), seed);
        }
    });

    found_seeds
}

またツールでは初期Seed検索中にUIスレッドがブロックされるのを防ぐため, 初期Seed検索処理をWebWorker上で実行しています. Safariなどの一部のブラウザには一定期間UIスレッドがブロックされるとブロックの原因になっているスクリプトを停止する機構が備わっているため, 「どうせ計算ボタン1つしかないツールだしUIスレッドくらいブロックされても困らないでしょ」という気持ちでWebWorker無しで雑に実装すると, 計算が途中で停止してしまいます. WebWorkerの導入はその対策にもなっている訳です.

f:id:mizdra:20181016174516p:plain
スレッドと計算の流れを示したアーキテクチャの図

計算時間の比較

f:id:mizdra:20181016123648p:plain
計測時間の比較. MacOS/Android/iOSごとに違うデバイスで測定. 異なるデバイス間の値は単純に比較できないので注意.

移植元のC++実装はマルチスレッドで並列化されているため, JS/WebAssembly実装と同じシングルスレッド構成になるよう改変したものの結果を載せています. 環境やブラウザによりますがWebAssembly実装はC++実装と比べて0.2〜0.7倍, JS実装と比べて1〜13倍と, 十分ツールとしては実用的なレベルの性能が出ています. ちなみにこの計算時間はシングルスレッドでの計測値なので, マルチスレッドにより乱数計算処理を並列化すればさらなる性能の向上が見込めます.

困ったこと

Webpack で WebWorker 上から wasm を Dynamic Import できない

現状 worker-loader を使ってWebWorkerをバンドルしているプロジェクトにて, wasm-bindgen で生成されたスクリプトを WebWorker 上から Dynamic import すると実行時エラーになります.

github.com

例えば次のようなコードが実行時エラーになります.

// /app.js
import Worker from './worker.js'
const worker = new Worker()
worker.onmessage = (event) => {
  console.log(event.data) // expected: 3
}
// /worker.js
import('./lib.js').then((module) => {
  const result = module.add(1, 2)
  postMessage(result)
})
// /lib.rs
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}
// /webpack.config.js
const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

const rootPath = resolve(__dirname, '.')
const distPath = resolve(rootPath, './dist')

module.exports = {
  entry: {
    app: resolve(rootPath, './app.js'),
  },
  output: {
    path: distPath,
    filename: '[name].[hash].js',
    // WebWorkerの実行コンテキストでは `window` グローバルオブジェクトが無いので,
    // 代わりに `this` でグローバルオブジェクトを参照するようにする
    globalObject: 'this',
  },

  resolve: {
    extensions: ['.js', '.wasm'],
  },

  module: {
    rules: [
      { test: /worker\.js$/, loader: 'worker-loader' },
    ],
  },

  plugins: [
    new HtmlWebpackPlugin(),
  ],
}

この問題はWebpackのBootstrapコード *3 の一部がエントリポイントだけに埋め込まれてしまい, WebWorker 内には埋め込まれないことに起因しています. WebWorkerとそれ以外のモジュールの実行コンテキストは分かれているので, 片方だけにBootstrapコードが埋め込まれているともう片方からそのコードを参照することが出来ず, エラーとなるという訳です. WebpackのWebWorker/WebAssemblyサポートはまだまだ未熟なため, こうしたエッジケースに嵌まることが度々あります.

今回はIssueのコメントにもあるように, worker-loaderに頼らず webpack.config.js で WebWorker を明示的に別のエントリポイントとして分けることで問題を回避しました. こうすることで Webpack が WebWorker とそれ以外のモジュールの実行コンテキストが分かれていることを正しく認識し, WebWorker にも初期化コードを埋め込んでくれるようになります.

// /webpack.config.js
const { resolve } = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpackMerge = require('webpack-merge')

const rootPath = resolve(__dirname, '.')
const distPath = resolve(rootPath, './dist')

const WORKER_PATH = '/worker.js' // ビルド後のworkerのパス

const baseConfig = {
  resolve: {
    extensions: ['.js', '.wasm'],
  },

  plugins: [
    new webpack.DefinePlugin({
      WORKER_PATH: JSON.stringify(WORKER_PATH),
    }),
  ],
}

// app用にentry/outputフィールドをカスタマイズした設定
const appConfig = webpackMerge(baseConfig, {
  entry: {
    app: resolve(rootPath, './app.js'),
  },
  output: {
    path: distPath,
    filename: '[name].[hash].js',
  },

  plugins: [
    new HtmlWebpackPlugin(),
  ],
})

// worker用にtarget/entry/outputフィールドをカスタマイズした設定
const workerConfig = webpackMerge(baseConfig, {
  target: 'webworker',
  entry: {
    'worker': resolve(rootPath, './worker.js'),
  },
  output: {
    path: distPath,
    filename: 'worker.js',
    // target フィールドで WebWorker 用に `globalObject` フィールドが
    // 自動で書き換えられているので, `globalObject` フィールドを手動で指定する必要はない
    // globalObject: 'this',
  },
})

module.exports = [appConfig, workerConfig]
// /app.js
const worker = new Worker(WORKER_PATH)
worker.onmessage = (event) => {
  console.log(event.data) // 3
}

おわりに

WebAssemblyを使ってC++の乱数調整ツールをWebに移植することで, ブラウザさえあれば誰でも簡単に利用できるポータビリティと, (ブラウザにより多少差はあるものの) 十分ツールとしては実用的なレベルの性能を実現することができました. 個人的にはもっと低い性能が出ると思っていたので, C++実装とそれほど大差のない性能が出た時は驚きました. WebAssemblyスゴイ.

また記事では触れていませんでしたが, 実はRustを使って型安全性/メモリ安全性のあるDXの良いWebアプリケーション開発環境の構築に挑戦する, というのも今回の開発で掲げたテーマの1つだったりします. リポジトリをよく見ると, ちゃっかりTypeScriptを導入していたり, JS/Rustの自動コンパイルやNetlifyによるpush駆動deployを実現していたりします. ですから今回の開発で小さいプロジェクトながらもそうした開発環境を構築できることが分かったのは大きな成果だったと思います. 今度はもっとガッツリRust/WebAssemblyを使って大きなものを作ってみたいですね. 最近は wasm-bindgen 以外にも js-sysweb-sys といったRustからJavaScript APIやWeb APIを直接利用できるcrateが登場しているので, 合せてそちらも触っていけると良さそうです.

*1:育て屋から貰えるタマゴの生成に関する乱数調整を対象とする分野のことです.

*2:ちなみに手元のEdge for Windowsでは動きませんでした

*3:参考: https://github.com/webpack/webpack/blob/a4e5f63d9d370b191bb9586570bc22bf2947e390/lib/MainTemplate.js

はてなサマーインターン2018に参加してクイズ大会で優勝してきた

はじめに

大学3回生の id:mizdra です. 普段はJavaScriptを触っていて, Webアプリケーションを作って遊んでいます.

8月から9月にかけて約1ヶ月間, はてなサマーインターン2018に参加してきました. この記事はその体験記となります.

developer.hatenastaff.com

f:id:mizdra:20180924133406j:plain:w400

応募

僕は普段からはてなブログやはてなブックマークを利用している「はてなユーザ」の一人で, はてなに愛着を持っています. また, Hatena Developer Blogに見られるはてなの技術に対する姿勢が大好な「はてなのファン」でもあります. はてなユーザ/はてなのファンとして, はてなのサービスに直接関わってみたい, はてなの社内の様子を直接見てみたい. そうしたことが動機で, インターンに応募しました. また, 大学の先輩方 (id:miki_bene, id:hogashi) が過去のはてなインターンに参加していて, とても楽しそうにしている様子を見ていたことも応募のきっかけの1つだったりします.

志望動機やポートフォリオを書いて応募すると, 選考面談を受けることになります. 面談ではCTOの id:motemen さんと共に何故はてなをインターンシップ先に選んだのか, 今までどんな技術を学び/扱ってきたかなどについて話しました. 僕の場合は趣味で乱数調整支援ツールを作っている話WebAssemblyの話をしながら「良い〜」「良いですね〜」などと楽しくお話をさせて頂きました.

その後無事インターンに参加できることが決まったので, 新幹線で埼玉から京都へと向かいました.

新幹線車内で朝食を食べている時の様子です.

前半 (1週目〜2週目)

インターンの前半では技術やはてなに関する講義を受け, その講義で学んだことを活かして演習を解きます. メンターの補助を受けながら, はてなの中の人による講義を受けることができます. *1

特に, 初めの1週間はWebアプリケーションに関する講義があり, 演習を進めていくと日記サービスが完成するというカリキュラムになっています. 加えて2周目ではAWSから講師をお招きしてAWSハンズオンが開催され, 1週目で作った日記サービスを本番環境にデプロイします. 講義でWebサービスを支える技術を網羅的に学び, 学んだことを元に自分の力で実際に稼働するWebサービスを作っていく. このような体験が出来るのは, はてなインターンならではだと思います. 特に, 僕のように普段Webフロントエンドばかりやっていて, DBやWebサーバについてあまり学習する機会が無い, という人にとっては絶好の学習の機会です. はてなインターンの講義は, そうした「Webサービスを支える技術を網羅的に学びたいけど, まだ手が出せてない人」に是非オススメしたいと思える内容でした.

f:id:mizdra:20180924184608p:plain
GraphQLの講義を受けている時の様子です.

また, 今回のはてなインターンでは従来のPerlやScalaをベースとする講義資料が, Golangをベースとするものへと一新されています. 新しいものを触ることが好きな人にもオススメです.

後半 (3週目〜4週目)

配属

インターンの後半2週間は開発チームに配属され, 実際に稼働しているサービスの開発を行います. 僕は第一志望だったコンテンツプラットフォームコースに当たるマンガチームに配属されました. マンガチームではスマホ向けのマンガアプリや, Web向けのマンガビューワ「GigaViewer」などを開発をしています. 今回は一緒に配属された id:YaaMaa さんと共に, GigaViewerの開発を行いました.

GigaViewerについては, 以下の記事で紹介されているので是非読んでみて下さい *2.

developer.hatenastaff.com

開発の様子

マンガチームでは, 主に id:hitode909 さんや id:onk さんとメンタリングしながら開発しました. まずは実際にサービスを使ってみて, どんな機能があったら嬉しいかというユーザ視点の思いをチーム内で共有し, 次にその機能はどういうデザインであるべきか, 実現するにはどういう技術を用いれば良いのか, という議論をしていきます. デザインについては, デザイナーの id:swimy1113 さんと相談しながら決めていきます. その後, 本当に挙げられた技術で機能を実現できるかどうかを詳しく調査し, 実装へと移ります.

実装の過程では, 同じチームに配属された id:YaaMaa さんと2人で手分けしながら開発を進めました. 僕がのんびり開発していたら id:YaaMaa さんがシュッと仕上げていることが多々あり, ヒッとなっていました. すごかった.

ちなみに僕はWebフロントエンドのロジック周りを主に担当していて, id:Pasta-K さんや id:hitode909 さんに相談しながら開発していました. お2方とも的確なアドバイスをして下さり, 爆速で開発を進めることができました. ありがとうございました.

リリースした機能

3つの機能をリリースしました. 全てとなりのヤングジャンプでのみ利用可能です.

  1. 閲覧済み作品の新着エピソード更新通知機能
  2. 新着エピソードのレコメンド機能
    • ビューワの最後のページに更新通知で表示されているエピソードがレコメンドされる
  3. ビューワページのSPA化
    • ビューワページ間の遷移に限定して, シームレスな遷移ができる
    • 遷移速度の改善以外に, 全画面表示状態が遷移後も維持されるなどの効果もある

f:id:YaaMaa:20180910221611p:plainf:id:YaaMaa:20180910221909p:plain
リリースした機能のスクリーンショット*3

「閲覧済み作品の新着エピソード更新通知機能」は「作品の更新を知りたい」という僕の思いから実装されたものです. トップページにアクセスするだけで, 以前見た作品の最新話が出ているかどうか, ひと目で判断できます. 実装にあたっては, 会員未登録のユーザにも更新通知を出したいという難しい要件や, 利用したいAPIがブラウザで実装されていない問題など, 様々な困難に遭遇しましたが, 機能を制限するなど問題を回避できるよう方向転換して*4何とか実装することができました. とても思い入れのある機能です.

また, レコメンドの改善やSPA化は id:YaaMaa さんが提案したアイデアを元に実現されました. SPAの実装は, 技術的に非常に面白い課題でした. どちらもユーザの体験を改善する良い機能だと思います.

f:id:mizdra:20180924152908g:plain
機能をリリースすると, オフィスに転がってるくす玉でお祝いします.

マンガチームの世界観

マンガチームのスペースの中央には木魚が置いてあり, 叩くとチームメンバーがバッと集まってきます. 困ったらこの木魚を叩いて助けを呼びます. また, 木魚の横には id:Pasta-K さんのアクリルブロックが置いてあります. 何だかよく分からないですが, そういう世界が広がってます.

f:id:mizdra:20180924133555j:plain
Suzuriで買えます.

同じ階にはあの「飲み会IoTボタン」が置いてあります.

成果発表

インターン最終日には「ほたて」と呼ばれる「ットなスクをがけた」チームを社員が投票して投票し, 表彰する会があります. インターン生はインターンでの成果をここで発表し, 🏆表彰🏆されます *5.

cybozushiki.cybozu.co.jp

マンガチームは5チーム中, 2位という結果になりました. 準優勝めでたい 🎉🎉🎉. 優勝した id:gazimum さん, おめでとうございます. 次は負けません ⚔️.

f:id:mizdra:20180924133701j:plain

おわりに

今まで経験したことのない最高の夏を過ごすことができました. こんなにインターン生のことを考えているインターンは他には無いと思います. この記事を読んでいる皆さんも, 是非来年以降応募して最高の夏を過ごしましょう.

最後になりますが, インターンを通して本当に色々な方のお世話になりました. メンターや講師の方々, マンガチームの方々, その他インターンを支えて下さった方々, 本当にありがとうございました!

おまけ

白熱する優勝争いの様子です.

※Mackerelチームのインターン生です.

※大規模チームのメンターです.

※マンガチームのインターン生 (僕) です.

あわせて読みたい

e-ntyo.hatenablog.com

blog.meteors.me

tomoyaf.hatenablog.com

yaamaa-memo.hatenablog.com

turtar-fms.hatenablog.com

noahorberg.hatenablog.com

*1:僕の場合は僕の大学の先輩にあたる id:miki_bene さんがメンターで, とても丁寧に質問などに対応して下さいました. ありがとうございました 🙏.

*2:余談ですが, GigaViewerのことはインターンに行く前にこの記事を読んで知っていて, 「はてな社ではこんな最高便利なマンガビューワを作っていたのか〜」という感想を持っていました. まさかその開発に携われるとは思っていなかったので, 配属が決まった時は嬉しかったです.

*3:引用元: はてなインターン2018に参加した - 見返すかもしれないメモ

*4:個人的にはインターンシップ一番のハイライトでした.

*5:実は僕はインターンの最後の2日間体調を崩してしまい, 残念ながら成果発表会に参加することができませんでした. 色んな人にめちゃめちゃ心配されたり気遣いして頂いて, お陰さまで今は元気です. ありがとうございました 🙇

Chrome拡張機能でコールバック地獄を解決する

Chrome拡張機能の非同期APIはコールバックにより実装されています. 例えば, 拡張機能ごとに用意されるストレージからデータを取得する場合, ストレージへのアクセス中にJavaScriptの処理が中断されるのを防ぐため, 非同期APIが用意されています.

// background.js
chrome.storage.local.get(['admin'], (result1) => {
  chrome.storage.local.get([`user/${result1.admin}/name`], (result2) => {
    console.log(result2)
  })
})

このコールバックによる非同期APIはJavaScriptにおいては一般的な非同期APIの実装手法ですが, 「コールバック地獄」という問題を引き起こします. これを解決する手法としてES2015で追加されたPromiseとジェネレータ, もしくはES2017で追加されたasync/awaitを利用する手法が知られています.

numb86-tech.hatenablog.com

numb86-tech.hatenablog.com

susisu.hatenablog.com

しかしChrome拡張機能の非同期APIはコールバックにより実装されているため, Chrome拡張機能の中では直接的にはPromiseasync/awaitを利用することが出来ません. そのため, 手動で非同期APIをPromiseでWrapするか, 以下のようなWrapperライブラリを利用する必要があります.

この記事ではmozilla/webextension-polyfillを使ってコールバック地獄を解決する方法を紹介します.

mozilla/webextension-polyfillを使ってコールバック地獄を解決する

ブラウザ間で統一的なAPIで拡張機能を作成できるよう策定されたWeb Extension APIというものがあり, 現時点でMozilla FirefoxとMicrosoft Edgeでサポートされています. mozilla/webextension-polyfillはこのWeb Extension APIのChrome向けのPolyfillとなっています. Web Extension APIの非同期APIは全てPromiseで提供されているため, Chrome拡張機能でコールバック地獄を解決する方法として用いることができます.

mozilla/webextension-polyfillnpm.comからもインストールできますが, モジュールバンドラなどを挟む必要があるため, 少々手間です. これに変わるより手軽なものとして, unpkg.comから直接ライブラリをダウンロードし, manifest.jsonbackground.scriptsなどから読み込む方法があります.

$ cd extension-project
$ wget https://unpkg.com/webextension-polyfill/dist/browser-polyfill.min.js
// manifest.json
{
  // ...

  "background": {
    "scripts": [
      "browser-polyfill.min.js",
      "background.js"
    ]
  },

  "content_scripts": [{
    // ...
    "js": [
      "browser-polyfill.min.js",
      "content.js"
    ]
  }]
}

このようにPolyfillを読み込むことでbackground.jsなどからbrowserという名前空間からWeb Extension APIにアクセスできます.

// background.js
browser.storage.local.get(['admin'])
  .then((result) => {
    return browser.storage.local.get([`user/${result.admin}/name`])
  })
  .then((result) => {
    console.log(result)
  })

非同期APIはPromiseを返すので, 次のようにasync/awaitを利用したコードにも書き換えることもできます.

// background.js
(async () => {
  const result1 = await browser.storage.local.get(['admin'])
  const result2 = await browser.storage.local.get([`user/${result1.admin}/name`])
  console.log(result2)
})()

以上, Chrome拡張機能でコールバック地獄を回避するTIPSでした.

#NowPlaying for Google Play Music 拡張機能の v1.0.0 をリリースしました

はじめに

先日公開した Google Play Music 向けの #NowPlaying 拡張機能 がアップデートして v1.0.0 になりました 🎉🎉🎉

新機能

  • Firefox のサポート
  • ユーザカスタム設定をサポート

Firefox のサポート

要望のあったFirefoxのサポートを実装しました. Chromeと全く同じように曲の共有が行えるようになります. 以下のリンクからFirefoxにインストールできます.

これはFirefox 48にて採用された WebExtensions API を利用すること実現されました.

ユーザカスタム設定をサポート

SNSメッセージのテンプレートや付加するハッシュタグをユーザがカスタマイズできるようになりました. 設定のカスタマイズは拡張機能のオプションから行うことができます.

f:id:mizdra:20180525155527p:plain

テンプレート内に埋め込める変数として, 次の3つをサポートしています.

  • ${title}: 曲のタイトル
  • ${artist}: 曲のアーティスト名
  • ${album}: 曲のアルバム名

おわりに

より使いやすくなった #NowPlaying for Google Play Music を是非お試しください!

Google Play Music 向けの #NowPlaying 拡張機能を公開しました

はじめに

Google Play Music 向けの #NowPlaying 拡張機能, ありそうで無かったので作りました.

chrome.google.com

上記の拡張機能をインストールすると, 右下に曲のシェアボタンが表示されるようになります.

f:id:mizdra:20180523010803p:plainf:id:mizdra:20180523010800p:plain
右下の共有ボタンを押すと共有するSNSメッセージを編集する画面に遷移する.

サポートされるSNS

サポートしているSNSTwitterのみです. 現状他のSNSに対応するつもりは無いので, もし他のSNSにも共有したい人がいれば以下のGitHubリポジトリをForkするなりPullRequest送るなりしてもらえればと思います.

github.com

その他

拡張機能, シュッと作れると思っていたらChromeウェブストアで利用するプロモーション用の画像を用意する必要があったり, ロゴのデザインが指定されていたり, デベロッパー登録手数料として$5払ったりする必要があってちょっと面倒だった… コード書くのは大分速くなった気がするので, そろそろデザインもシュッとできるようになりたいですね.

おわりに

インストール!!! 今すぐ!!! シェアボタン連打!!!

WebAssembly 開発環境構築の本を公開しました

はじめに

Rust を用いて WebAssembly の開発環境を構築する手法を紹介する電子書籍を執筆・公開しました. WebAssembly へのコンパイルが可能な言語である Rust を用いて, WebAssembly の開発環境のテンプレートを作成する内容となっています.

wasm-dev-book.netlify.com

本のタイトルからは一見すると C/C++ を用いた開発環境の構築も扱うように受け取れますが, 本書では Rust のみしか扱っておりません. ご注意下さい.

書籍の執筆動機

著者が春休み中に WebAssembly を用いて Web アプリケーションを作成する機会があり, 本書はそこで得た知見を纏めたものとなっています *1. 元々大学のサークルで発行した部誌に同じテーマで記事を書いており, 本書ではそれを Web 向けに編集・加筆した内容から構成されています.

ここ最近 WebAssembly が急速に注目されており, 既に C / C++ / Rust などで利用することができるほか, 記事執筆時点でGolang でも WebAssembly のサポートが実装作業中となっています. また, 2017 年になって主要なブラウザ全てで WebAssembly がサポートされ, 2018 年に入って主要なモジュールバンドラで WebAssembly がサポートされるようになりました.

こうした需要の高まりに合わせて本書を公開することで, WebAssembly に興味を持っている人が WebAssembly を触り始めるきっかけになればと考えています.

書籍の目的

本書は Rust を用いて, WebAssembly の開発環境のテンプレートを作成する手法を学ぶことを目的としています. 本書の目的 の節には次のように書かれています.

  • Rust を用いた WebAssembly の開発環境を構築する手法を知ること
  • モジュールバンドラと連携する方法を知ること
  • TypeScript と連携する方法を知ること
  • WebAssembly を利用していて陥りやすい罠を知ること
  • WebAssembly に関する情報を調べる方法を身につけること

反対に, 次のような内容は本書では扱いません.

  • WebAssembly / Rust / JavaScript の文法や機能の解説が目的ではない
  • JavaScript のエコシステムの解説が目的ではない
  • C / C++ を用いた WebAssembly の開発環境を構築する手法の解説は目的ではない
  • WebAssembly や JavaScript の仕様変更の追従が目的ではない

本書の「Rust を用いた WebAssembly の開発環境を構築する手法を知る」というテーマに沿った内容を扱うため, テーマから外れた内容は扱わないことにしています. 読むにあたって足りない知識は本書の はじめに の節に書かれているサイトを参考に各自補完していって下さい.

また, WebAssembly は Web の最先端の技術の 1 つであり, 仕様や関連技術の変化が非常に活発です. したがって仕様変更など WebAssembly 関連の最新情報を本書に反映することは困難であり, これらの変更の追従は本書では行いません.

書籍の目次

本書の内容をざっと確認できるように目次を貼っておきます.

  • WebAssembly 開発環境構築の本
    • 本書の目的
    • 本書でやらないこと
    • 本書の対象読者
    • 本書の読み方
  • 著者への寄付
    • 寄付先一覧
    • 著者の情報
  • はじめに
    • 本文章を読むにあたって
    • 開発環境について
  • WebAssembly 入門
    • WebAssembly を試す
    • WebAssembly から JavaScript の関数を呼び出す
    • Rust のサードパーティ製ライブラリの利用
    • 本節のまとめ
    • 参考文献
  • Parcel の利用
    • Parcel を試す
    • Rust のサードパーティ製ライブラリの利用
    • Parcel を採用する際のデメリット
    • 本節のまとめ
    • 参考文献
  • Webpack の利用
    • Webpack を試す
    • WebAssembly から JavaScript の関数を呼び出す
    • Rust のサードパーティ製ライブラリの利用
    • コレクション, 文字列の受け渡し
    • 本節のまとめ
    • 参考文献
  • TypeScript との連携
    • Webpack プロジェクトへの導入
    • 本節のまとめ
  • 関連ツール・サービスの紹介
    • alexcrichton/wasm-gc
    • ashleygwilliams/wasm-pack
    • WebAssembly Studio
    • koute/cargo-web
    • DenisKolodin/yew

書籍自体の開発について

本書は GitHub 上で管理されており, MIT License にて公開されています.

github.com

ここでは書籍自体の開発に使用したツール, サービスを紹介します.

電子書籍の作成に VuePress を使用しています. VuePress は技術文章向け静的サイトジェネレータです. 似たような技術文章向け静的サイトジェネレータに GitBook などがありますが, それらに対して VuePress は DOM のレンダリングVue.js を使用しているという特徴があります. そのためドキュメントの Single Page Application (SPA) 対応, ドキュメント内での Vue.js コンポーネントの埋め込み, デフォルトのコンポーネントの拡張などの機能が提供されています . また, Google Analytics の統合や rogressive Web Application (PWA) のサポート *2 などの機能もあります.

本書は VuePress により SPA が有効化されており, 画面遷移がこれでもかという程滑らかになっています. 是非体感してみて下さい. ちなみに本書では PWA は無効化しているのでご注意下さい.

vuepress.vuejs.org

qiita.com

本書は VuePress により拡張された Markdown 形式で書かれており, Prettier によるフォーマットが適用されています. 執筆中も エディタのプラグインと連携して Markdown を保存する度にフォーマットが適用されるため, 快適に執筆できました.

prettier.io

書籍のデプロイには Netlify を使用しており, git push する度に自動で Netlify のサーバ上で書籍がビルド・デプロイされます. npmYarn の強力なサポートのお陰で数クリックでデプロイ出来る上, SPA のサポート, コミット / ブランチ / PullRequest ごとの Deploy Previews の作成などの機能があります.

www.netlify.com

著者への寄付について

本書は著者の春休みの 1 ヶ月間とゴールデンウィークの 1 週間, 合わせて約 40 日間を費やして執筆されました. もしあなたが本書を読んで著者の活動に関心を持ち, 支援したいと思って頂けたのなら, 本書の 著者への寄付 の節にあるリンクから著者に寄付できます. 寄付は著者の今後の活動に有効に活用させて頂きます 😄 *3

おわりに

以上が WebAssembly 開発環境構築の本の紹介となります. いかがでしたでしょうか. 本書を読んで WebAssembly を触る人が 増えてくれると良いなと思っています. それと, Twitter などで #WASMの本ハッシュタグを付けて感想や意見などを書き込んでもらえると著者が喜びます.

それでは WebAssembly 開発環境構築の本 をよろしくお願いします.

*1:当該の Web アプリケーションはまだ完成していませんが, 完成次第別の記事で取り上げる予定です.

*2:技術文章ごときに PWA が必要なのかという疑問はありますが, オフライン環境下でタブレットなどを利用する場合は有用かもしれません.

*3:漫画や Blu-ray Disk も著者の今後の活動に活用されます 😛

遅刻可視化ツールの紹介

はじめに

個人開発で遅刻可視化ツールを作って Dentoo.LT #19 で話してきたのでその紹介です.

www.slideshare.net

遅刻がテーマということでアイコンはナマケモノをモチーフにしたポケモンである「ナマケロ」にしました. ちなみに「Slakoth」はナマケロの英名です.

モチベーション

  • 大学生活において1日の内最初の授業にどれくらい遅刻したかを可視化したい
    • 生活習慣という面で1年を気軽に振り返りたい
  • ちょっとしたデータ分析をしてみたかった

概要

Googleロケーション履歴*1」の生データを利用して通勤, 通学の遅刻をチャートで可視化できます. ツールにロケーション履歴をJSON形式で読み込み, 始業時間や通勤先/通学先を設定すれば, それを元に遅刻回数/遅刻時間/遅刻率をチャートで可視化してくれます.

使い方

  1. 事前に 自分のデータをダウンロード からJSON形式でGoogleロケーション履歴の生データをダウンロードしておく
  2. I Found Slakoth!!! にアクセス
  3. ロケーション履歴の読み込む からJSONファイルをツールに読み込ませる
  4. 始業時間を追加 から遅刻の基準となる1日の最初の授業開始時刻や始業時刻を入力する
  5. 必要に応じて 始業時刻一覧 から 3 で入力した時刻を編集, 削除する
  6. 通勤先/通学先の設定 から図形ツールを使って通勤先/通学先の領域をポリゴンで囲う
    • 検索ボックスから場所を検索してそこに飛べる
    • 頂点をドラッグで頂点を移動
    • 頂点と頂点の間にあるグレーアウトした丸をドラッグすると頂点を追加
    • 頂点を右クリックで頂点を削除
    • ポリゴンをダブルクリックでポリゴンを削除
    • ポリゴンは複数個作ることができる
    • 右上のボタンを押せば全画面で図形ツールを操作できる
  7. チャートチャートを出力 ボタンから入力したデータを元にチャートを生成
    • チャートで表示できるデータの種類は 遅刻回数/遅刻時間/遅刻率 の3つ
    • 月ごと, 週ごと, 曜日ごと の3つの分類方法が選択できる
    • 下に表示されているバーを弄ればチャートに表示するデータの期間を範囲指定できる

裏でロケーション履歴をどっかに転送して盗み見るなんてことは一切していませんが, 怖いなと思った方はオフライン環境下でやると良いです. まあそもそもツールを使わないというのが一番安全ですが.

f:id:mizdra:20180314232328g:plain
デモ用のロケーション履歴を用いたチャートのデモ

応用

  • 通勤先/通学先を職場や大学以外に設定してみる
    • 例えば電車通勤・通学者であれば最寄り駅の先の駅をポリゴンで囲っておけば寝過ごし回数を数えることができる
    • 北海道には何回行ったか, 海外には何回行ったかなどを調べることもできる

下の画像は僕のロケーション履歴を用いて可視化した「帰路における寝過ごし回数」です. この時期は試験で忙しかったんだなとか, この時期は余裕があったんだなとかが分かって便利です.

f:id:mizdra:20180314232849p:plain
帰路における寝過ごし回数

技術面

アプリケーションを作る上で利用したライブラリなどを書いておきます.

おわりに

*1:AndroidiOSGoogleロケーション履歴を有効化しておくと, 自動で位置情報が時刻と共にGoogleアカウントに記録できるサービスです