kumuのつぶやき

フロントエンド勉強中の学生のただつぶやき

WebWorker(Offscreen)でCanvasを描画する

これの続き。
grekumu.hatenablog.jp

前回、WebWorker上でcanvas使うなら、OffscreenCanvas使おうって書いた。
ただ実際に試しては無かったので、試してみたよって話。

worker-loaderをインストール

前回いろいろやって、アンインストールしたworker-loaderを再インストールした。
config-overrides.js

config.module.rules.push({
  test: /\.worker\.js$/ ,
  use: { loader: 'worker-loader' }
});
import Worker from 'worker-loader!./wasm.worker';



CanvasをWebWorkerへ渡す

とりあえずコード
App.tsx

const canvas = document.getElementById('canvas') as HTMLCanvasElement;
const offscreen = canvas.transferControlToOffscreen();
worker.postMessage({ canvas: offscreen }, [offscreen]);

wasm.worker.ts

self.onmessage = (event: any)=> {
  const data = event.data;
  const canvas = data.canvas.getContext('webgl');
  // wasm側にCanvasを渡す
  call(canvas);
}

とりあえずこれで描画できるはず。


再描画しようとして詰まった


CanvasgetELementByIdで取得して、再描画しようとしたら出たエラー。
f:id:greKumu:20201211060834j:plain
InvalidStateError: Failed to execute 'transferControlToOffscreen' on 'HTMLCanvasElement': Cannot transfer control from a canvas for more than one time.

解決策

このサイトを参考にしたらできた。
newinweb.com

useRefでうまくできないし、いい案が思い浮かばず直接DOM操作してる。

// HTML
<div id="canvas-container"></div>
// TS
document.getElementById('canvas-container')!.innerHTML = '<canvas id="canvas"></canvas>'
canvas = document.getElementById('canvas')! as HTMLCanvasElement;



WebWorkerが複数作成される。

これは再描画する際にnew Worker()をしていた。
f:id:greKumu:20201211062412j:plain

解決策

新しく作成させない。
or
worker.terminate()を行う。


おわり

一応できた。

webworker内でwasmを使用しようとして失敗した(WebGL)

IPFactory Advent Calendar 2020 - Qiitaの8日目の記事

これの続き。
grekumu.hatenablog.jp


wasmの勉強で、こんな感じのやつGLSL Sandbox Gallery作ろうとしていて、wasmの処理をWebWorkerで行うつもりだったが、うまくできなかったって話。

WebWorker使おうと思った理由 www.sitepen.com

インストール

とりあえずworker-loaderをインストール。
npm install worker-loader --save-dev

config-overrides.jsに追加。

 config.module.rules.push({
    test: /\.worker\.js$/,
    use: { loader: 'worker-loader' }
 });

けどうまくいかない。

エラーや試したこと。


Uncaught SyntaxError: Unexpected token <

DevToolsで確認すると、WebWorkerがHTMLを返している。
あんまりWebWorker使ってこなかったから、構文が間違ってるか、loaderあたりだと思う。


グローバルオブジェクトの設定

webpackでHMRが有効だと、WebWorkerでwindow is not definedってなる話。
config-overrides.jsにグローバルオブジェクト設定してないせいかと思い、
config.output.globalObject = 'this';
と、設定した。


wasmのDynamic import

worker-loaderを使用し、WebWorker上からDynamic importするとエラーが出る。
これを見る限り、worker-loaderを使用しなければいいらしいので、とりあえず試してみた。
github.com


ReferenceError: Window is not defined.

Windowが大文字なのはlib.dom.d.tsを参照しているから?
んで、よく考えるとこれDOM参照しようとしてる。
てか、そもそもwasm側でweb-syscanvasを参照しており、結果的にWebWorker内でwindowオブジェクトを参照していた。(早く気付け俺。)


解決策 OffScreenCanvasを使う

今回の敗因はこれ。
描画はDOMに直接依存している。そのため、WebWorker上ではCanvasWebGLの描画ができない。(これ理解してなかった。)

OffscreenCanvasレンダリングはDOMから完全に切り離されているため、WebWorker上で描画ができるようになる。

developers.google.com

MDN見た感じsafariとかFirefoxが未対応。
これ使えば対応してないブラウザは色々置き換えてくれるらしい?
GitHub - ai/offscreen-canvas: Polyfill for OffscreenCanvas to move Three.js/WebGL/2D canvas to Web Worker



おわり

ただ失敗したよって話。
customize-craworker-pluginとか色々試したけどそもそも無理だった。
まだOffScreenCanvasを試してないから、まず試してみる。
アドカレ、他の人の記事と比べて内容ショボいとか言わないで。

Rust 文字列

文字列型

  1. String
  2. &str

// ヒープに割り当てる
let a = String::from("test");
let a = "test".to_string();
let a: String = "test".into();

// read only memory上に割り当てられた文字列への参照
let b: &'static str = "test";
// &str
let b = "test";


リテラル

// "test"
let byte_escape = "\x74\x65\x73\x74";
// [116, 101, 115, 116]
let byte_escape = b"\x74\x65\x73\x74";

// unicode "א"
let ucode = "\u{05D0}";

// raw string "\x74 \u{05D0}"
let raw_str = r"\x74 \u{05D0}";
// 引用符使う場合
let raw_str = r#" "test" "#;

// バイト文字列 [116, 101, 115, 116]
let a: &[u8; 4] = b"test";


Create React AppしたReactでWebAssemblyをインポートした

Webpackはwasmファイルの扱い方を知らないため、そのままwasmファイルをインポートすると、エラーが発生する。

コマンド
npm install --save-dev react-app-rewired wasm-loader

config-overrides.jsというファイルを作成し、下記を記述。

const path = require('path');

module.exports = function override(config, env) {
  const wasmExtensionRegExp = /\.wasm$/;

  config.resolve.extensions.push('.wasm');

  config.module.rules.forEach(rule => {
    (rule.oneOf || []).forEach(oneOf => {
      if (oneOf.loader && oneOf.loader.indexOf('file-loader') >= 0) {
        oneOf.exclude.push(wasmExtensionRegExp);
      }
    });
  });

  config.module.rules.push({
    test: wasmExtensionRegExp,
    include: path.resolve(__dirname, 'src'),
    use: [{ loader: require.resolve('wasm-loader'), options: {} }]
  });

  return config;
};


package.jsonを変更する。

"scripts": {
  "start": "react-app-rewired start",
  "build": "react-app-rewired build",
  "test": "react-app-rewired test",
  "eject": "react-scripts eject"
},

WASMまとめ

WebAssemblyとは


バイナリコードをブラウザで動かせるようにしたもの。
JavaScriptは動的型付けのインタプリタ言語ということで、実行速度の遅さが課題となっている。
その課題を解決するため、2013年頃にasm.jsというサブセットが生まれたが、
ファイルサイズが大きくなってしまうことなどの問題があった。
その代替案として生まれたのがWebAssembly(wasm)である。

wasmはファイルサイズがasm.jsと比べて小さくなっている。
使用できる言語一覧 github.com
DOMの処理やFetchAPI、WebAudioなどのAPIも使用できる。
しかしwasmは、jsをすべて置き換えようというものではなく、高速化したい部分をwasmで記述し、
jsと併用することを想定されている。


いろいろなTool


emscripten周り

wasm-bindgen周り

  • wasm-bindgen

  • js-sys

  • web-sys

  • wasm-pack

バイナリ表現とテキスト表現関連のツール群

  • wabt



IoTでwasm


IoTデバイスでwasmを使用するという動きもある。
様々な言語がwasmへコンパイルできるようになっている。そのため、特定の言語に縛られることが無くなる。

wasmランタイ厶
  • Wasmer

  • WAMR

  • Lucet

  • Wasmtime


IoT × wasmについて liux120.github.io


名前が似ていて混乱するやつら(実際に混乱した)


  • wasm
    WebAssembly

  • wat
    WebAssembly Text Format(バイナリ表現のwasmをテキスト表現にしたもの。wasmのテキスト表現は.wat.wastが使われる。)

  • wast
    watと同じ (昔の呼び方?コードの形式が違うらしい。)

  • wabt
    WebAssembly Binary Toolkit (バイナリ表現とテキスト表現関連のツール群。)

  • wasi
    WebAssembly System Interface (安全にOSやホストシステムの機能を呼び出そうという仕様
    詳しくはStandardizing WASI: A system interface to run WebAssembly outside the web - Mozilla Hacks - the Web developer blog)

  • WAMR
    WebAssembly Micro Runtime (組み込みデバイスを目的としたランタイム)

Next.jsのメモ

Next.jsのメモ(自分用)



・ 静的ルーティング(pagesディレクトリ)


・ 動的ルーティング(file name[id].tsx)


・ クライアントサイドルーティング(<Link>)
クライアントサイドでナビゲーションを行っているため、ブラウザ全体でリロードすること無く、遷移が行われる。 本番環境では<Link>コンポーネントが表示されると、バックグラウンドでリンク先のページのコードをプリフェッチしてくれる。


CSS-in-JS


・ プリレンダリング(SSR, SSG)

SSRについて
- アクセス時にサーバー側でHTMLを生成
- リクエストごとに生成される

SSG
- ビルド時にHTMLを生成
- 事前にビルドされる
- CDNを利用するため、SSRより高速

更新頻度が高いページ(SNS等)はSSR、そうでない場合はSSG
ページごとに選択可能
可能な限りSSGを利用すべき


・ 拡張性が高い


・ ホットリロードをサポート


・ コード分割
リクエストしたページのコードだけを読み込み、ほかはバックグラウンドでプリフェッチを行う


・ Dynamic Routers
- [id].tsx

・ SSG getStaticProps
- 開発環境では毎回のリクエストごとに実行される
- 本番環境ではビルド時にのみ実行される
- サーバーサイドでのみ実行される


・ SSG getStaticPaths
- Dynamic Routesを使用する際にも静的なファイルを生成するためのAPI
- 開発環境では毎回のリクエストごとに実行される
- 本番環境ではビルド時にのみ実行される
- サーバーサイドでのみ実行される


SSR getServerSideProps
- リクエスト時に実行される
- サーバーサイドでのみ実行される


CSR SWR
- データフェッチ用のReactフック
- クライアント側でフェッチする際にはこれを使う


今後追加