beatmania IIDX 32 Pinky CrushでSP皆伝になりました
良かったですね。
これまでの段位
記憶が定かではないが、大体2018年ごろからずっと中伝だったのでCANNON BALLERSあたりから6,7年ほど中伝だったらしい。
皆伝合格のためにやったこと
基本的には皆伝を目指してやったことは無し
イキっているのもあるけど、元々皿が本当に嫌いで灼熱抜けられる気がしないので挑戦段階ではないし皆伝目指すのやめて普通に上手くなるのを目標にしていた。
その時その時で目標としてる曲のランプがあって、それに必要なレベルの曲をやっていく、みたいな感じ。
基本的には今自分がハードできるのはどのあたりのクリアレートの☆12だなというのを目安に近場でプレビューして良い曲じゃんこれというのがあったらプレーしてみるというサイクルで、体感的には40%前後のものは割とハードついたのではないか。
皆伝の曲目はほぼ一切触っていなくて、なんなら地力表みたいなユーザー有志の難易度表みたいなのもあまり見ていなかった(単にどこで見れるのか知らなかったので)
以下は皆伝の要素に貢献した事などという体になる(皆伝目指してなかったという体なので)
皿対策
皿対策というか皿曲の練習はしていた。
これは皿元々本当に苦手かつ嫌いだったのだけど、DOLCE.氏のSCRATCHレーダー190目指す配信など見ていて、流石にこれくらいできたら楽しそう、という気持ちになりちょっとずつ練習を始めた。
苦手と言いつつ元々複合系の皿はそれなりでSCREW // owo // SCREWなんかはハードついていたし、実際に苦手なのは複雑で長時間の連皿っぽかった。
皿やるぞとなってからはLevelシリーズを攻略するのを目標にしていて、Level Oneと Level 4はノマゲ、Level 2とLevel 3はハードつける程度には頑張った。 Level 5は箸にも棒にもかからんなと思ってまだランプついてない。
一応他にもRedとかBlackにもノマゲつけたり、Watch out Pt.2にハードつけたりなどそれなりレベルには皿もマシになったと思う。
ソフラン対策
あまりしてない。ICARUS SPAをハードつけたくらい?
卑弥呼は普通にギアチェンしたら良いと思ってそのようにしてた。 幸い受かった回では44%残すことが出来たのでこれが冥抜けるのに大きく貢献したと思う。
冥も低速見える気がしなかったのでギアチェンで対応、幸い低速後最初の4小節が100でそこから1小節ごとに10ずつ上がるというのだけ理解していれば多少のゲージ減少を覚悟してギアチェンしてなんとかするという方向でやった。
当然低速大丈夫な人は普通にやった方がいいと思う。
ギアチェンするならFHSで普段よりもレーンカバー下げて最初からある程度低速にしておいて低速のタイミングでギアチェン、という方が低速突入時の変化が多少マシになる気がした。この辺りは個人差ありそう。
夢路歩
対策無し、強いていうならこのレベル付近の高密度に対応出来る程度まで地力を上げるなどになるのではないか。
イージークリア出来なくていいけど、イージーでゲージが地を這うようなら挑戦圏外だと思うくらい。
体力
普通に2000ノーツ前後の曲を4曲連続でやるのはめっちゃ疲れる。
腕が温まらないと流石に腕壊すのでやるならちゃんとアップしておきたい、大体2〜3万回くらい鍵盤叩いたら良いのではないか。
今作はほぼ毎週やっていて、半年で188万回ほど鍵盤を叩いているらしい。過去最高頻度です。
スコア力
ほぼいらないけど、☆10の簡単な曲はAAA出せる状態になっていたのは良かったと思う。
皆伝になったけど全然☆11以上にAAA出ていないしそんな人間でも皆伝になれるんだ〜みたいな気持ちにはなる。
とはいえある程度光らせ方を知っているというのは大事な状態で、ゲージを回復させる際に当然光っていた方が回復するので、これは皆伝に関わらず☆12のランプ更新に大きく貢献していたと思う。
結婚指輪
届いてつけ始めてから本当に弐寺の調子が良い。
皆伝目指すなら結婚した方がいいかもしれないです。
という冗談はさておき退勤後や休日に2,3時間ゲーセンに付き合ってくれる妻がいるのは練習環境として好ましいのは言うまでもない。
CPI・地力表
CPIは今作入るちょっと前くらいに記録し始めた。
https://cpi.makecir.com/users/view/10075
CPIは1635と皆伝としてはかなり低めなのかな、出来るのにやっていないランプなどもありそうだけど大体ハード以上200曲は出しているらしい。
https://sp12.iidx.app/sheets/5850-7108/clear?device=pc
地力表は受かった後聞かれて調べてようやくこのページに辿り着けた。 地力B+くらいまでは結構ハードついていて、地力A+あたりのランプが増えてきたら挑戦圏なのかもしれない。
まとめ
次回作で受かってるかは怪しいけどとりあえず皆伝は取った。
元々本当にしばらく目指さなくて良いや状態だったのに急に卑弥呼と冥とEMERALDASにイージーついて行ける気がしたので挑戦してみたと言う感じであまり実感がない。
目指してなかったとしてもやはり到達出来ると嬉しいので合格して良かった。
次は裏皆伝になると思うが、多分無理なのでまた6,7年ほど経ったら挑戦してみようと思う。
AIは「技術が好き」
最近は社内でSlack botを作って遊んでいる。
現在動かしているものとして、自由にシステムプロンプトを登録・指定してAIを実行できるbotで、以下みたいな感じで実行するとOpenAIのAPIに代わりに投げて返信してくれる。
業務時間中に流行りの文法で返すAI botを作っている様子です pic.twitter.com/31mJuvHHHp
— みすど (@MysticDoll) 2025年1月27日
この場合は以下のようなシステムプロンプトになっている
ユーザーからの入力に対し、**必ず**以下の特徴を持つ特殊な文法で返答してください。 - 固有名詞を除き**絶対に**ひらがなは使用しない - 固有名詞を除き**絶対に**かたかなは使用しない - 基本的な文法は英語のものを用いる - 副詞や接続詞等は英語のものを用いる - 助詞の表現は英語を用いる。例 人間's - 比較級の表現は漢字と英語を組み合わせる。例: 簡単er - 名詞は可能な限り漢字で表現する - カタカナ語の名詞は英語の同等の表現に置換する - 次の例を参考に文を作成する as a 中国人's 意見,it is 特別 有効 for 中国's 日本語学習者, because 日本語's 漢字 is 簡単 and 仮名 is 困難 for almost every 中国人 when 学習 日本語, this 表現 can 変換 困難 仮名 to 簡単 英語, so that 中国人 can 享受 double 簡単 in this 言語's 形式。just 天才's 思考。 (edited)
で、これを使って遊ぼうと思って次のようなシステムプロンプトを登録して遊んでいた。
あなたはコンパイラです。ユーザーから与えられた入力をコンパイルして**実行した結果のみ**を返してください。コンパイル元のプログラミング言語は入力の最初に与えられたものかコードから推測してください。
これを用いて、はてなバリューズをランダムに一つ返信させようとしていたところ、激しく偏った結果になった。
このmentionでの各実行は文脈が保持されておらず、毎回新しくOpenAIのAPIを叩いている。
一応このbotにはスレッドでmentionすると文脈を保持して実行する機能もつけているのでそれで試してみたところ、今度はちゃんとランダムに選択してくれた。文脈を保持しているので、過去に返答したものについては一応被らないようにしてくれているのだろうか。
明示的にRubyを指定してみると今度は「インターネットが大好き」になった
人間に「この中からランダムに一つ選んで」と頼んだ場合も無意識的に偏りは出るだろうし、システムプロンプトの内容からなんとなく「技術が好き」「インターネットが大好き」に偏ってしまう、みたいなことなのだろうか。*1
ちなみに openaiのPlaygroundで試してみたところ、「挑戦が好き」になった
今回実行したプロンプトは全て gpt-4oで実行しているが、他モデルやパラメータ設定を変えたりしたらちゃんとランダムで返してくれる、あるいは別の選択肢に偏るなどあるかもしれない。*2*3
結婚した
表題の通りで、2025年1月8日に婚姻届を提出し、無事受理された。
近況
我々夫婦はワンパチが好きなので、この日(1月8日)にするかと決めていた。*1
ところが昨今の乾燥や気温の低さ、はたまた日曜の夜歯磨き中に咽せたのが惡かったのか、酷い咳や39.1度まで上がるような発熱で月曜夕方から体調が急激に悪化、以降は火曜朝に通院し安静にしていた。
幸い(?)にも病院の検査キットではインフルエンザやコロナウイルスについては陰性だったので、婚姻届提出にはマスク着用の上参加し、無事書面上の手続きも完了し晴れて夫婦と相成ったという次第である。
反省点
体調悪化
1月8日には出そうという話をしていたのに体調を崩してしまった、しかもかなり重めに。
私はかなり一人になると生活が出来なくなるタイプの人間で、具体的には飯や風呂、家事などを遅延し続けてしまうなどの癖があり、風呂を沸かしてから2時間経ってから入るなども多かった。
年末妻が実家に帰省するということで、自分なりに頑張って生活をちゃんとやろうと考えてはいたものの、実際には洗濯や皿洗い、多少の床掃除などは出来たものの、リズムという点ではてんでダメといった始末であった。
生活のリズムが狂うとどうなるのかというと、昼から夕方にかけて起床しその日やりたい行動を取り、(上手くいけばなんとか)食事をして、帰宅して風呂に入って寝る、というサイクルの各地点がそれぞれの実行タイミングで後ろにズレ続け、睡眠周期が不規則になる。これでは免疫力も低下して当然だろう。
届の証人
証人については両家の親に書いてもらおうという話だったが、私が倒れていたので結果的に妻に証人サインの行脚をさせることになってしまった、これについてはもっと早めに相談したりして年末にせめてうちの親の分は埋めておければ良かったと思っている。
今後
婚姻は無事成立したものの現状は同棲の延長といった感じで実感まだ薄いので、今後としては指輪を二人で選ぶ・家を買う・犬(コーギー)を飼うなどを進めて行きたいと考えている。
犬(コーギー)を飼うのであればやはり普通運転免許や自家用車が必要だと思うので、そろそろスーパーハッカーの夢を諦めつつ*2、学生がいない時期に合わせて自動車学校に通うことを検討しているところ。
オタクなのでMTを取りたい気持ちも強いが現実問題ATで別に問題ないだろうというところや、家を買うならどこに住むか・どういった間取りにするかなど悩みは尽きないが、なんとか妻と一緒に頑張っていけたら良いかなと思う。
生活リズムについてはかなり妻の存在に頼っているところが大きいので、どうにか対策は考えているが、どうにもならなければ通院なども視野に入れたいと思う。
夫婦共にBEMANI音ゲーが共通の趣味なので、私はとりあえず曲目的にはおそらく余裕なのでPinky Crush中伝の取得、可能なら皆伝に向けて苦手譜面の練習など引き続きやっていきたい、妻はドラムマニアのスキルを上げたりDDRのフレアスキルを上げたりするのではないだろうか。
結婚式については今のところお互い強く挙げたいという話はしていないが、突然気が変わったりすることもあるかもしれないのでそのあたりは未定です。 妻にウェディングドレスを着せたい気持ちはあるので、フォトウェディングとかは出来たらしたい、そのあたりは今後妻と相談します。指輪購入後とかになるのではないだろうか。
明日からポケモンセンター実店舗でMy Little Bestieグッズで新規ワンパチグッズが出るのでとりあえずそちらの購入の方に全力を注ぎます。体調も直します。
*1:ポケモンはもともとそこそこ好きだったが、特段好きになったポケモンは妻の影響もありワンパチくらいだろう
*2:ref: https://motemen.hatenablog.com/entry/2014/07/09/got-a-license
サイズ可変ではてなのCTOアイコンを表示するコマンドを書いた
背景
.bashrc
にANSI Escape CodeのSGRが以下のように手書きで書かれてるの視認性が惡い- 気分によってサイズを変えたい
- 急にRustを書きたくなった
export PS1="\\n\\e[48;2;195;213;227m \\e[m\\e[48;2;252;255;246m \\e[m\\e[48;2;19;58;137m \\e[m\\e[48;2;157;167;185m \\e[m\\n\\e[48;2;195;213;227m \\e[m\\e[48;2;252;255;246m \\e[m\\e[48;2;19;58;137m \\e[m\\e[48;2;157;167;185m \\e[m\\n\\e[48;2;129;82;38m \\e[m\\e[48;2;255;224;211m \\e[m\\e[48;2;248;199;182m \\e[m\\e[48;2;218;46;32m \\e[m\\n\\e[48;2;129;82;38m \\e[m\\e[48;2;255;224;211m \\e[m\\e[48;2;248;199;182m \\e[m\\e[48;2;218;46;32m \\e[m\\n\\e[48;2;164;172;193m \\e[m\\e[48;2;254;196;184m \\e[m\\e[48;2;255;179;162m \\e[m\\e[48;2;225;107;93m \\e[m\\n\\e[48;2;164;172;193m \\e[m\\e[48;2;254;196;184m \\e[m\\e[48;2;255;179;162m \\e[m\\e[48;2;225;107;93m \\e[m\\n\\e[48;2;174;179;183m \\e[m\\e[48;2;55;62;68m \\e[m\\e[48;2;83;32;13m \\e[m\\e[48;2;89;90;95m \\e[m\\n\\e[48;2;174;179;183m \\e[m\\e[48;2;55;62;68m \\e[m\\e[48;2;83;32;13m \\e[m\\e[48;2;89;90;95m \\e[m \\n\\n:\\w \\$ "
併わせて読みたい
リポジトリ
使い方
- リポジトリをcloneして
cargo build --release
しつつ出来た実行ファイルをPathの通ってるところに置く $ moteaa <N>x
(N
はサイズを決める数字)で実行 (x
はあってもなくてもいい、オプションが不正なら1マス1行で表示する)- Hello motemen
備考
昼に30分くらいで雑に書いたのでコマンドラインパーサは適当です、変な挙動見つけたら教えてください。PR歓迎
kubernetes上でのアプリケーション開発でDocker Image作成とRegistryへのpushをサボる
背景
minikubeとかで雑に手元のkubernetes環境で動かしたいアプリケーションのコードを書く際、開発中のDocker Imageのregistryに困りがちみたいな問題がある。
local registry建ててそこを参照するようにするみたいな手もあるにはあるけど、当方はWindows環境で開発をしていて、minikubeはHyper-Vをバックエンドにして動かしていたりしてやや面倒。
家のkubernetesなら雑にghcrとかのregistryに投げたらええか~って感じなんだけど、微妙に公開したくないやつとかの取り扱いを考えると投げづらいケースとかもある。
そこで雑にDeploymentを建ててサボることとした、という備忘録的なやつ。別に単なるPodでもいいけどService/Ingress含めた検証とかもしやすいからDeploymentにしている。
実例
apiVersion: v1 kind: Secret metadata: name: github-ssh-config data: known_hosts: <known_hostsをbase64で書く> config: <ssh_configをbase64で書く> id_ed25519: <SSH鍵をbase64で書く> --- apiVersion: apps/v1 kind: Deployment metadata: name: rust-devenv spec: replicas: 1 selector: matchLabels: app: rust-devenv template: metadata: labels: app: rust-devenv spec: serviceAccountName: script-container-executor containers: - name: rust image: rust:latest ports: - containerPort: 8080 command: [ bash ] args: - "-c" - "while :; do sleep 1000; done" volumeMounts: - name: github-ssh-config mountPath: /root/.ssh/ readOnly: true volumes: - name: github-ssh-config secret: secretName: github-ssh-config defaultMode: 0600
この例だと serviceAccount周りのコードを書きたかったのでserviceAccountとかもはいってます。
SSH設定周り
雑に以下の感じでやると良さそう。
known_host
の作成はssh-keyscan -H github.com -t <鍵の種類>
とかで作ると良い- configは普段使ってるやつそのまま使ってもまあ良いし、配置先工夫したりするならそれに合わせて書きたい
- 鍵も普段つかってるので良い、もしminikubeみたいなローカル環境じゃなくて外置きの環境でやりたいなら新規に鍵ペア作ってもいいかも
- volumeMountsする際に一発でボンと出来ると嬉しいので同一のsecretに纏めているけど、内容的には鍵以外はconfigMapで良さそうな気はする。
- configMapとsecretを同一のマウントポイントにブチ込むみたいなの出来るとは思うけどまあまあ面倒だった記憶があるのでサボるならこういう感じかなという気がする
- mountするとき当然modeが雑だとsshに失敗するので設定忘れると微妙な気持ちになれる
開発コンテナイメージと起動コマンド
今回はrustでやってるのでdebianベースっぽくあんまり気にせずエイヤとしている。
開発コンテナによってはbashが入っていなかったりとかそういうのがあるので注意したほうがよさそう。起動コマンドはとりあえず無を実行していてほしいのでこういう感じにしている。
使い方
applyして kubectl execして雑に使う、リポジトリは適当にgithubからcloneしてくると良い。
永続化層が欲しかったら PersistentVolume とか hostPathで雑に作業場所を確保すると良さそう!今回は面倒だったのでそういうことはしてない。
作業元のディレクトリをnfsとして良い感じにマウントできたりするなら hostPath とかでざっくり繋げてあげられたりすると嬉しいみたいなアイデアもあるが、サボりテクニックでそこまで頑張るかみたいな話もありそう。
もっとリッチな環境作りたかったらcode-serverとかをdeploymentで動かすと良さそうな気がする。こちらからは以上です
障害報告: MysticDoll自宅kubernetesクラスタnode障害
id:MysticDoll です。
2024/02/11 に起きた自宅kubernetesクラスタのサービスが正常に利用出来なかった障害についての報告致します。
と思っていたんですが、長期間ほったらかしていてログを紛失したので雑なまとめです。
障害内容
一部podへのアクセスが失敗、またpodからの通信も一部失敗していた。
一時対応
調査の結果、失敗するpodは特定のnodeに乗っているものだと分かったので、そのnodeをcordonし、正常なcontrol-planeとnodeのみを残して再度スケジューリングしなおしてほぼ解決。
podからの通信失敗については kubectl exec
等で確認したところ名前解決に失敗していたため、node本体に入り、systemd-resolved systemd-networkdなどを再起動してgot事無き。
実際にはステークホルダーが自分しかいないので雑にnodeを再起動したりついでにArch Linuxのシステム更新をしたりとハチャメチャなことをしました。仕事では絶対こんなことしません。
node障害
一時対応の最中、再起動した bengal
と名付けたnodeに疎通しなくなる事象が発生しました。
これは自宅kubernetesのクラスタ達は(配線が面倒だったのとL2スイッチを持っていなかったので)無線で自宅ネットワークに接続しており、どうやら bengal
で iwd.service
の起動に失敗していたことに起因するようです。
iwd
起動失敗の原因
結論から言うと iwd
自体の問題などではなくdbusのソケットをiwd
(正確には iwd
が依存している ell.so
の内部関数) から見つけられていなかったことが原因でした。
さすがにシステム更新してもdbus周りの挙動が破壊される経験がなく、ドライバを入れ直したり古いバージョンへ各種パッケージをロールバックしたりなどしましたが全く無意味でした。
$ journalctl -xeu iwd.service
などしてログを確認したところ以下のように出ていたので、普通にdbusを疑うべきだったのはそうかもしれない。
Feb 11 19:00:49 bengal iwd[330]: Wireless daemon version 2.13 Feb 11 19:00:49 bengal iwd[330]: Loaded configuration from /etc/iwd/main.conf Feb 11 19:00:49 bengal iwd[330]: Failed to initialize D-Bus Feb 11 19:00:49 bengal systemd[1]: iwd.service: Main process exited, code=exited, status=1/FAILURE Feb 11 19:00:49 bengal systemd[1]: iwd.service: Failed with result 'exit-code'. Feb 11 19:00:49 bengal systemd[1]: Failed to start Wireless service.
iwd
起動失敗の調査
とはいえ、適当にロールバックとかしてダメだった場合ちゃんと調査する必要があって、dbusを疑うにしても /run/dbus/system_bus_socket
自体は存在していたことや、dbusのパッケージバージョンを変えたりしてもダメだったので、コードを見に行くことにしました。
Failed to initialize D-Bus
というエラーを出している部分はここしかないので、ここを見に行くと l_dbus_new_default
という関数の呼び出しで失敗していそうなことが分かります。
// https://kernel.googlesource.com/pub/scm/network/wireless/iwd/+/refs/tags/1.7/src/main.cより引用 dbus = l_dbus_new_default(L_DBUS_SYSTEM_BUS); if (!dbus) { l_error("Failed to initialize D-Bus"); goto failed_dbus; }
で、 l_dbus_new_default
という関数は以下にありました。どうしてここに辿りついたのかは完全に忘れたのですが、たぶん l_dbus_new_default
だけで検索したらこの ell
のヘッダファイルのコードがヒットしたので、そこから辿ったのだと思います。
蛇足ですが、筋の良い探し方としては、本体のソースにないということは共有ライブラリのコードという推測が立つので、 ldd
をかけてそれらしいライブラリ名と共に探すのが良いかと思います。
[mysticdoll@bengal ~]$ ldd /lib/iwd/iwd linux-vdso.so.1 (0x00007ffda8d2d000) libell.so.0 => /usr/lib/libell.so.0 (0x000070f9a56b6000) libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x000070f9a5691000) libc.so.6 => /usr/lib/libc.so.6 (0x000070f9a54af000) /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x000070f9a5837000)
さておき、コードを見ると dbus = l_dbus_new_default(L_DBUS_SYSTEM_BUS);
とあるので case L_DBUS_SYSTEM_BUS:
の処理を見れば良さそうです。
// https://kernel.googlesource.com/pub/scm/libs/ell/ell/+/refs/heads/master/ell/dbus.c より引用 case L_DBUS_SYSTEM_BUS: address = getenv("DBUS_SYSTEM_BUS_ADDRESS"); if (!address) address = DEFAULT_SYSTEM_BUS_ADDRESS; break;
システムかユーザーセッションかどうかで参照すべきdbusソケットのアドレスを切り替える実装のようで、ここでiwd
のunitファイルを見てみると
[mysticdoll@bengal ~]$ sudo systemctl cat iwd.service # /usr/lib/systemd/system/iwd.service [Unit] Description=Wireless service Documentation=man:iwd(8) man:iwd.config(5) man:iwd.network(5) man:iwd.ap(5) After=network-pre.target Before=network.target Wants=network.target [Service] Type=dbus BusName=net.connman.iwd ExecStart=/usr/lib/iwd/iwd NotifyAccess=main LimitNPROC=1 Restart=on-failure CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE PrivateTmp=true NoNewPrivileges=true DevicePolicy=closed DeviceAllow=/dev/rfkill rw ProtectHome=yes ProtectSystem=strict
unit内では環境変数 DBUS_SYSTEM_BUS_ADDRESS
を指定していないので DEFAULT_SYSTEM_BUS_ADDRESS
が採用されていそうと分かります。
// https://kernel.googlesource.com/pub/scm/libs/ell/ell/+/refs/heads/master/ell/dbus.c より引用 #define DEFAULT_SYSTEM_BUS_ADDRESS "unix:path=/var/run/dbus/system_bus_socket"
とあるので、いやさすがにあるじゃろ…と思ったんですが、 なんと /var/run
が 普通のディレクトリとして生えてました。その上本来あるはずのdbusのソケットは当然ありません。
[mysticdoll@bengal ~]$ sudo ls -la /var/run total 12 drwx------ 1 root root 78 Feb 13 08:41 . drwxr-xr-x 1 root root 116 Feb 12 17:27 .. (snip)
というわけで、以下のようにunitファイルから直接 /run
側のdbusソケットを参照してもらうように環境変数を設定して無事iwd
は起動しました。
(snip) [Service] Type=dbus BusName=net.connman.iwd Environment=DBUS_SYSTEM_BUS_ADDRESS=unix:path=/run/dbus/system_bus_socket (snip)
他にも docker-cri
が動いてない(これもdocker.sockが/var/run
にないから)とかの問題があったので一応良く使うところだけsymlinkを生やして応急対応して終了です。
後で /var/run
は /run
へのsymlinkに直しておきます、気が向いたら…nodeのuncordonもしてないのでそのうちします、そのうち…
Rust FFIを活用してGBAのエミュレータの太陽センサーの値を制御する拡張機能を実装した
やったこと
GBAのエミュレータ(visualboyadvance-m)上の太陽センサー*1の値をエミュレータ内threadに立てたHTTPサーバによって操作できるようにした。
今回はRust FFIを利用してできるだけ元のコードを汚さずに、かつHTTPサーバの処理等はRustの世界で閉じたものにする、という方向で実装していた。
ちなみにFFIを触っているが自分はRustはまあともかくC/C++は多少読めるけどちゃんと書くのは無理、ぐらいのC/C++の習熟度です。
Rust FFIを実装する上で必要な作業
正直、割と曖昧なまま触っている部分も多いが概ね以下の作業が必要。Ubuntu(apt)のmxe repoが古く、C++17サポートが不完全であるためWindows target向けのクロスコンパイルが壊れているので、それらをなんとかする作業も必要だったが、本筋ではないので割愛*2。
- C/C++側
- Rust側で触る想定のリソース(変数・関数)をRust側から見つけられるようにexposeする
- Rustからexposeされている関数等のリソースをextern宣言し、呼び出したい箇所で使用するように実装する。
- Rust 側
- ビルドツールたち
- CMakeLists.txt を頑張って編集して、C/C++側のオブジェクトファイルとRust側のstaticlibをリンクする
C/C++側: Rust側で触る想定のリソース(変数・関数)をRust側から見つけられるようにexposeする
今回、Rust側では以下のリソースを触る想定となっている。
- 太陽センサーの値の取得する関数
- 太陽センサーの値を更新する関数
そのため、以下のように extern "C"
を利用してCのABIで*3 関数をexposeしてあげる必要がある
extern "C" uint8_t systemGetSensorDarkness() { return sensorDarkness; } // (snip) extern "C" int level = 0; extern "C" void systemUpdateSolarSensor() { uint8_t sun = 0x0; //sun = 0xE8 - 0xE8 (case 0 and default) // このlevelはRust側にexposeして触りたい // int level = sunBars / 10;
systemUpdateSolarSensor
関数のローカル変数を extern
してexposeしているのは、 systemUpdateSolarSensor
関数のシグネチャを変更する場合他の関数の呼び出し方を変更する必要があり、シグネチャを変更せずに済むようにRust側で level
を編集してから呼び出すといった形で実装することにしたため。
その後、今回 extern
宣言に変更したリソースを使っている他の箇所で同様に extern "C"
する形に変更していく必要がある。(static
と extern
ではメモリの確保のされ方(主にmangling周り)が変わるので別のアドレスが確保される可能性がありそうなので)
余談だが、最初は sensorDarkness
という systemUpdateSolarSensor
で更新している値を直接変更していたが、なぜか元の値に戻ってしまうのでこういった形で触るようにした。たぶん触ろうとしている関数が適切じゃないんだと思う。
C/C++側: Rustからexposeされている関数等のリソースをextern宣言し、呼び出したい箇所で使用するように実装する。
Rust側でexposeする関数は void start_server(void)
なので以下のように宣言しておく。
// src/wx/wxvbam.cpp extern "C" { void start_server(void); }
そしてたぶん一回だけ呼ばれるであろう関数で呼び出すようにしておく。このあたりは適当なので本当はちゃんと考えたほうがいい。*4
// src/wx/wxvbam.cpp bool wxvbamApp::OnInit() { start_server();
Rust側: C/C++側のリソースの宣言
Rustonomiconとか参考にしましょう。
今回の場合は以下みたいな感じ、特に説明することはないですが、リンク時に undefined reference
とか言われる場合は extern
宣言をミスってるとかそういうことだと思います。C++ ABIで単に extern
するとおそらく普通にmanglingされるのでRust側から見つけられないです(たぶん)。
リンク時にミスってたらひたすらオブジェクトファイルをnm
とかしてシンボル一覧と睨めっこしましょう。俺はした。
extern "C" { static mut level: i8; fn systemUpdateSolarSensor(); fn systemGetSensorDarkness() -> u8; }
Rust側: C/C++側で使ってもらう関数の実装、expose
単純に extern "C"
で関数を宣言しましょう。 #[no_mangle]
をつけてこちらもmanglingされないように気をつけて。
#[no_mangle] extern "C" fn start_server() {
また、今回サーバ実装部分をaxumで実装しているので、axumのサーバ実行時にFutureをExecutorで実行してあげる必要があります。(単に実行するだけではFuture内の処理は実行されない)(と思う)
std::thread::Builder::new() .name("solar_server".into()) .spawn(|| { let rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(async { axum::Server::bind(&"0.0.0.0:8000".parse().unwrap()) .serve(app.into_make_service()) .await .unwrap(); }); });
単に tokio::runtime::Runtime::block_on
を実行するとそこでthreadがブロックされるので、適当に std::thread::Builder::spawn
してあげましょう。これで別Threadでサーバが動いてくれるはず。
CMakeLists.txt を頑張って編集して、C/C++側のオブジェクトファイルとRust側のstaticlibをリンクする
ここが一番分かっていないけどなんとかしました。適当に以下でいけたぜ、という程適当にはやっていないがあんま詳しくないぜ。
# src/wx/CmakeList.txt target_link_libraries(visualboyadvance-m ${CMAKE_SOURCE_DIR}/solar-server/target/x86_64-pc-windows-gnu/release/libsolar_server.a userenv ntdll bcrypt)
やっていることとしては wx
内にある CMakeLists.txt
で その中のプロジェクト(?)である visualboyadvance-m
のオブジェクト達を今回作ったRustのstaticlibとリンクしてあげるという作業です。
userenv ntdll bcrypt
あたりはRustのstaticlibが解決できなかったシンボルを解決するためにリンクしています。 GetUserProfileW
みたいなWindows APIっぽい関数が見つけられていなかったので、そのあたりで必要そうなライブラリを探してきてリンクしたという形になります。
ビルド
export PATH="${PATH}:/PATH/TO/MXE/usr/bin" mkdir build && cd build /PATH/TO/MXE/usr/bin/x86_64-w64-mingw32.static-cmake .. -DCMAKE_RELEASE_TYPE=Release -G Ninja ninja
やるだけ、リンクに失敗したらシンボルを見てコードを修正していくしかないので頑張りました。