ICFPC 2021 振り返り
今回はおもに解の手動更新用のGUIを作っていた。入力をグラフとしてGraphvizで可視化するするのとかも初期はやっていた。
グラフ表示の位置などのヒント情報を用いてGUIの頂点に色を塗るとか、そこからGUIで動かす頂点選択するとかは時間内に思いつきたかったなあ。
今年は WebAssembly (wasm) がなかなか良い働きをしていて、Rust実装を使い回しやすくなったのは大きい。使いやすかったかというと微妙なところもなくはなく……。Rustから生成、TypeScriptで使うという2点に分けて以下に書いた。
いろいろ技術ごとに振り返ってみる。
Rust
wasmを準備したのは正解。 evcxr_jupyter
を動かして Jupyter Notebook で動いて楽しいとか言っていたが、使わなかった。imosにRust用のファイル置いておくよう言われて開始前に cargo init
および去年使ったマクロをサンプルとして置いたのは良かったと思う。
コンテスト中に書いた行数でいうと今年はそんなにRustを書いていないような気がする。
追加データで負の座標が出てきたのはx座標に2を加えて対処されたが、 serde_json::from_reader
などと書くのが簡単すぎるせいで座標ずれバグをつぶしにくかった。型を分けるのは無理なので、ずらしたかの情報をstructに足して正気を保つようにした。
#[serde(skip_serializing_if = "Option::is_none")] #[serde(default)] pub internal: Option<InputInternal>,
他にも入力時に多角形の向きが直されていて、実は線分が多角形に含まれるかの判定にもその仮定が使われているみたいな罠もあり、ついでに気付くことができた。
iwiが普段エラー処理anyhow使っていると言っていたが、Unagiでも使っていくべきなのでは。
かつて、stderr軍拡競争はloggingで平和解決したはずだが、Rustになってloggingはどうなったんだっけ。
serde
神。毎回使うので使い方も慣れてきた。
今回のJSONはbonusのシリアライズ仕様がイケてなくて、
type Bonus = { bonus: string, problem: number, edge?: [number, number], }
みたいな仕様だったが、serdeのattrつけていけばなんとかなるはず。実際は BREAK_A_LEG
無視されたのでやってないが。
#[serde(rename_all = "camelCase")] struct { .. } #[serde(rename_all = "SCREAMING_SNAKE_CASE")] enum { .. }
なども使っていきたかったが、 WALLHACK
を WallHack
と誰かに書かれてしまった後なのでできず。
ordered-float
From<u8>
がなくて困ったらしい。Haskellの Num
は (+), (*), abs, signum, fromInteger, (negate | (-))
が良いかというと abs, signum は違う気がしてしまうので結局分からないが。
wasm-bindgen
Vecを #[wasm_bindgen]
できないのはつらくて、serdeによる変換に頼ってしまったが、ドキュメントにはパフォーマンス悪いかもとかわざわざ書かれていて、無駄に不安になっていた。Rustから型をexportしなかったのでTypeScript側でも型を書き直している。
i64を変換するのも不安要素で、f64になるのかbigint的なのになるのかも知らないし。
Result<T, JsValue>
を返す明示的なエラー処理をしなくてもrustでのpanicがjs側のエラーになるっぽいのは良かったが、巻き込んで落ちるのが直せなくてつらかった。原因はいまだ不明。
JavaScript / TypeScript
wasm関係続き
async import よく分からない。その場で調べたので準備不足。
webpackのWasmPackPlugin使ったほうが良かったか。結局make file書くのは不毛だが、wasm-pack buildするタイミングは自分で決めたいし、今回はwasm mime type問題で後処理も発生していた。
1ファイル(index.html + index.js くらい?)で済めば file:// で開いても動くからwebpackにしたのに、.wasmが別ファイルというのが罠。http-serverが.wasmを正しく扱わない(またmime type問題)のも問題。wasmがファイルの先頭から(読み込み完了する前に)ネイティブコードに?1-pathで変換する設計を昔聞いたときはよくできているなって思ったが、mime typeあってないとそのAPIが使えません(読み込み終わってからByteArray渡してね)って仕様になったのは納得できてない。
tosk.jpの置かれているValueServerが.wasmを正しいmime type返さない。設定をユーザーが変えられるのかみたいなことを知らない。この辺の一般知識がない。imosのいつものicfpcサイトではちゃんと動いたので多分Googleは偉い。github.ioとかにしても良いが、こういう非公開用途で困る。
PIXI.js
PIXI.jsの採用は迷惑掛けてしまった。WebGL直接は書きたくないのでjs側から書くには楽だが、Rustのライブラリ探しておくのが理想か。svgのpathくらい描画する機能あっても良いと思うんだけど……(無いことを知っていたのでなおさらなぜ選んだって感じだ)。
GUI、model matrixではなくview matrixをいじるべき場面なのに手動で線形変換の逆変換求めていたりして無駄だった。
というか、WebGLとsvgどちらで行くかの選択の問題もある。svgをインタラクティブなGUIに使えない気がしたが、sulumeがちゃんとしたのを作っていて感心した。
UI一般
色を適当に決めてしまったが、matplotlibからcolormaps拾ってくるくらいの準備はしておきたかった。具体的には、edge長の違反量に応じた色付けという要望が出ていたのに実装せずに終わってしまった。
index.html直書きで大丈夫だという予定だったが、React入れても良かったよなあ。npmはcargoほど安心して依存追加できない。でかいパッケージが相性問題起こしがちで、たとえばwebpackの設定を変えないといけなかったような。あまり良く分かっていなくて、npmをyarnにするみたいなのは平時に試しておいてもよかった(これが関係あるのかさえ分かっていない)。
他
package-lock.json
で編集合戦が発生していたのは準備不足。自分とimosの環境では "lockfileVersion": 2
だがsulumeの環境では "lockfileVersion": 1
で他の場所にもdiffが出ていた。
自分もnodeのバージョンをコンテスト中に12から16に上げたので人のことを言えないが、こういうやばさはなかったと信じたい。
チーム開発
表記ゆれ (input/problem, output/solution/pose, score/dislike) がやばい。ソルバー班の考えやすい普段の言葉遣いでやっているが、poseも読み込むし、dislikeは最終的なチームスコアに変換される。「型の名前くらいは公式に従っておいて、変数名は自由」みたいなのが良いのかなあ。vscodeで型や変数のrenameするのは簡単だが、1回pushするとconflictをおそれて直せなくなって const solutionJson = this.pose;
とか runCheckSolution1(input: Problem, output: Solution): void
とか書き続けてしまった。
ソルバー側が負の座標や多角形の向きを気にすることなく開発できるのはあまりに大事なので、ソルバーからも手動からもvisualizeしたいという要求をどう処理するかということになりがち。今年はy座標が下向きなので平和だった。
cargo fmt してほしい。自分のファイルだけ rustfmt するのはちょっと面倒。同じファイルを同時編集する場合は、1ファイルよりさらに細かい単位のみを自動整形したくなるが、それが(たぶん)できなくて困る。
hard tabでインデントしたいならそれに合わせても自分は文句ないので、 rustfmt.toml
作るのが良いのでは。今気付いたが、空の rustfmt.toml
を置きさえすれば個人の global config の適用回避できるっぽい……。実際に src/lib.rs
で tab vs spaces の編集合戦を原因の一部とするmerge失敗があったので、実際に損している。 auto-mergeになったと聞いていたが検証したら自分のgitの設定では衝突したので、そちらに対策を入れるのが先という可能性もある。
iwiがgit submoduleを使わず直接足したのは正解だと思っていて、1回ライセンスを確認するほうが、後で誰かが操作失敗する可能性や、VSCodeのgitタブでsubmoduleの存在が表示されてうざいという認知負荷より安い。GitHubがメインの言語Javaって表示するのが気になって後で直したが。