RustとWebAssemblyによるゲーム開発 ch.2
の2章を読み進めて参ります
いつものように、有識者のまさかり待ってます
復習
上はビルド通る 下はエラー 何が違う?
Closure::once(move || {
let _ = success_tx.send(());
};
Closure::once(move || success_tx.send(()));
上のコードと下のコードの違いは戻り値
下のコードでは戻り値がResult<(), T>
となる
ここで、wasm_bindgen::closure::Closure::once()
の定義を見てみると
pub fn once<F, A, R>(fn_once: F) -> Closure<F::FnMut>
where
F: 'static + WasmClosureFnOnce<A, R>,
となっている
つまり
move || {
let _ =success_tx.send(());
}
はF
を満たし
move || {
success_tx.send(())
}
はF
を満たさないとのこと
ここでWasmClosureFnOnce
の定義を見てみると
pub trait WasmClosureFnOnce<A, R>: 'static {
type FnMut: ?Sized + 'static + WasmClosure;
fn into_fn_mut(self) -> Box<Self::FnMut>;
fn into_js_function(self) -> JsValue;
}
なるほどわからん
なぜ戻り値Result<(), T>
があるとエラーになるのか全くわからないので有識者助けてください!
非同期処理を用い画像をロードし、ブラウザに表示する
コード
wasm_bindgen_futures::spawn_local(async move {
let (success_tx, success_rx,) =
futures::channel::oneshot::channel::<Result<(), JsValue,>,>();
let success_tx =
std::rc::Rc::new(std::sync::Mutex::new(Some(success_tx,),),);
let error_tx = std::rc::Rc::clone(&success_tx,);
let img = web_sys::HtmlImageElement::new().unwrap();
let callback = Closure::once(move || {
if let Some(success_tx,) =
success_tx.lock().ok().and_then(|mut opt| opt.take(),)
{
let _ = success_tx.send(Ok((),),);
}
},);
let err_callback = Closure::once(move |e| {
if let Some(error_tx,) =
error_tx.lock().ok().and_then(|mut opt| opt.take(),)
{
let _ = error_tx.send(Err(e,),);
}
},);
img.set_onload(Some(callback.as_ref().unchecked_ref(),),);
img.set_onerror(Some(err_callback.as_ref().unchecked_ref(),),);
img.set_src("Idle (1).png",);
let _ = success_rx.await;
let _ = context.draw_image_with_html_image_element(&img, 0.0, 0.0,);
},);
各行が何故必要なのか その行がないと何が起こるか
まず最初の2行で非同期処理をするためにチャンネルを宣言している
oneshot
チャンネルにしているのは、一度しか行わない処理(画像をロードする際のコールバック処理)だから
その次に、一つのチャンネルを2つのクロージャにmoveすることができない(スレッド安全性のため?)のでMutex
を使ってラップしている Mutex
でラップしているのはsuccess_tx
を含んだOption
型
それを、Rc
を使って成功時用と失敗用の二つの参照を作っている
次に、htmlのimage elementを作る
let callback = ..
let err_callback = ..
で行っていることは、成功したらOk
を返し、エラーが起こったらErr
を返すようなコールバック処理を記述している
ここで、success_tx
をOption
でラップしたことにより、success_tx
がクロージャ内にmove
されて起こるエラーがなくなる
どういうことかというと、success_tx
はoneshot
チャンネルなので一度だけ使われる
従ってClone, Copy
を実装していない
なのでsuccess_tx
はクロージャ内にmove
されるのだが、Mutex
が保持する所有権を奪うことはできないようになっているので、エラーが起こる
これをOption
でラップすることによりMutex
が所有するのはOption
自身となり、Mutex
が何も所有していない状態をNone
で表現することができる
あとはイメージをロードして、success_rx.await
して、描画する
以上!
image.set_onload(Some(callback.as_ref().uncheched_ref()));
にてas_ref
とunchecked_ref
の必要性は?
image.set_onload
の引数がOption<&::js_sys::Function>
なので参照を渡す必要がある
だからas_ref
を使っている
unchecked_ref
は、js関数として実行される際は型が動的に決まるので、js側に合わせるために使っている
wasm_bindgen_futures
は何を提供する?
jsのPromise
とRustのFuture
の橋渡しをする機能を提供する
wasm_bindgen_futures::spawn_local
を使う必要性
Rust標準のチャンネルを使うとチャンネルを呼び出してる間はメインスレッドの実行が止まってしまう
ここでいうメインスレッドとはブラウザのことなのでブラウザが停止してしまう
spawn_local
を使ってasync/await
をした場合、awaitが停止させるのはローカル実行で、プログラム全体は動作する
'static
ってなんだっけ
プログラム実行中常に有効な値への参照のライフタイム。ライフタイムが表現する機関としては最大
調べていたら面白い(厄介な)ことがわかった
impl SomeTrait + 'a
などの型自身につくライフタイムは最長で'a
の寿命をもてるという意味
&'a T
などの参照につく場合は最短でも'a
の間は生きていることを保証せよという意味
型につくか参照につくかでライフタイムの持つ意味合いが逆転!
厄介
futures::channel::oneshot
チャンネルを含むクロージャの型
はFnOnce
トレイトを実装する
何故そうなるのか→oneshot
は一回きりだから
std::sync::Mutex
の役割 何を解決するのか
並行プログラミングを安全で楽に行えるように定義された型の一つ
チャンネルを複数のスレッドで共有して、一度に一つのスレッドだけがデータにアクセスできるようにする
and()
とand_then()
の違い
and
もand_then
もOption
のメソッド
and
はOption
がNone
だった場合None
Some
だった場合and
の引数を返す
and_then
は関数を引数に取り、Option
がNone
だった場合None
Some
だった場合引数の戻り値を返す
oneshot
がコピー出来ないのはなぜ
一度しか実行されなから
スプライトシートは何が嬉しいか
読み込むリソースの枚数が減り、ロード時間が短くなるらしい
気になったところ
下記のコードが望んだ結果を得られない理由となぜ自分はこのように書いたのか
if let Some(error_tx,) = error_tx.lock().err().take() {}
エラー時のコールバック処理なのでerror_tx.lock().err()
としたが
error_tx.lock().err()
の意味はerror_tx
を介してスレッドにアクセスしようとしたら失敗したという意味なので不適切
Base64でエンコーディングするとは
Base64はあらゆるデータを6bit(64種類の文字)を用いで表すエンコード方式
ファイルサイズが大きくなったり、エンコード・デコード分の処理負荷がかかったりというデメリットはあるがテキスト形式のファイルしか読み込めないプログラムに食わせたい時に使われることがあるらしい
ダブルバッファリングとは
画面描画分のメモリ領域をふたつ確保しておき、各領域のデータを交互に表示することによってチラつきや乱れを抑え高速化する手法
FnMut
,Fn
,FnOnce
の違いは
兎にも角にもまず定義を見てみる
pub trait Fn<Args: Tuple>: FnMut<Args> {
extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}
pub trait FnMut<Ars: Tuple>: FnOnce<Args> {
extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}
pub trait FnOnce<Args: Tuple> {
type Output;
extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}
まず驚いたことに関数の引数はタプル型として扱われるらしい
次にextern "rust-call"
という記述がある
これは、AbiData
によってAbi::RustCall
に変換される
そうなんだが、正直何を言っているのかよくわからない
結論から言うと、関数の引数って個数が定められてないし、どんな型の引数も定義できるけど、それをABIに食わせる時にABIの食いやすい形にする必要がある その時のためにAbi::RustCall
に変換して特殊な処理を施す
らしい 全くわからん
話を戻してFn, FnMut, FnOnce
の違いを見てみると、call
の第一引数が、それぞれ&self, &mut self, self
になっている
あとは、違いというか関係についてだが
Fn
はFnMut
、FnMut
はFnOnce
を継承?している(rustタームで言うと合成になるんだろうか?)
つまりFn
はFnMut
でもFnOnce
でもある
FnMut
はFnOnce
でもある
ただ、場合分けとして、一度しか呼ばれない(そうであることが期待される)ものはFnOnce
変数を書き換えるものはFnMut
とFnOnce
それ以外はFn
,FnMut
,FnOnce
を実装する
となるらしい
ABIって何
ABIって、言葉だけは知ってるけど正直よくわかってない
わかってないので調べてみた
ABIとは、どうやら、規約のようなものらしい
具体的には実際に何か関数を実行したり、データを持ったりする際にまずどう言った命令が呼び出されて、データはどのように配置して、サイズはいくらで、エンディアンは何で..
といった実際にプログラムを実行する際に必要となってくる低レベルでの処理の機能を提供するのがABI
..なんですか?
Discussion