C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

rust, windows: crate gdal を windows, vscode 環境で使用可能にするメモ

こんかい使えるようにする対象物

crate gdal は内部的に元のGDAL(の動的リンクライブラリー)を呼びつけて GeoJSON を読んだりできるラッパータイプのライブラリー。 extern crate gdal だけでは使えないので使える状態にするメモを残す。

extern crate gdal なプロジェクトをビルド・実行可能にする手順

1. myapp プロジェクトを用意、 crate gdal を取り込む

  1. cargo new myapp
  2. cd myapp
  3. cargo add gdal (note: https://github.com/killercup/cargo-edit )
  4. main.rs を↓のように gdalのdocs冒頭のサンプルなど参考に動作確認程度に用意します。
  5. cargo build を試す (この段階では失敗しますがエラーメッセージを読むとなんでかわかります。この後のメモで解決するための作業を記します。)
extern crate gdal;
use std::path::Path;
use gdal::vector::Dataset;

fn main()
{
  println!( "[start]" );
  let mut dataset = Dataset::open(Path::new( "sample1.geojson" ) ).unwrap();
  let layer = dataset.layer( 0 ).unwrap();
  for feature in layer.features()
  {
    let geometry = feature.geometry();
    println!( "{}", geometry.wkt().unwrap() );
  }
  println!( "[end]" );
}

2. vscode の Task として crate gdal をビルド可能にする

2.1. GDAL の動的リンクライブラリーを用意する

2.1.1. vcpkg を用意

いくつか方法があります。このメモでは vcpkg を使う方法を採用します。vcpkg はシステムレベルではなくユーザーディレクトリーレベルでの使用を前提に設計されているようです。適当な場所に用意します。

  1. Super(Win)+R -> powershell (note: mingw/mintty などから powershell を起動するとインストールスクリプトがまともに動作しない可能性があります。)
  2. cd %USERPROFILE%
  3. mkdir opt
  4. cd opt
  5. git clone https://github.com/Microsoft/vcpkg.git ( git が使えませんとかはこのメモでは解説の範囲外という事にします。必要ならぐぐってください。 )
  6. cd vcpkg
  7. .\bootstrap-vcpkg.bat
  8. Windows環境変数VCPKG_DEFAULT_TRIPLET=x64-windows を追加しておく。(x64にしたくない場合は設定しないとか、実行ごとにパラメーターを付ける方法とかもあります。必要なら適当に調べてください。参考)

これで vcpkg.exe が生成されるので、必要ならパスを通すとか、alias を用意して使いやすくするとかしておきます。また、 Visual StudioC++ プロジェクトから簡単に vcpkg を使用できるようにしたければ vcpkg integrate install などもREADME を参考にしておきますが、さしあたり今回メモの用途としてはどうでもいいです。

2.1.2. vcpkg で GDAL と依存ライブラリーを芋蔓に用意する

  1. vcpkg install gdal
  2. 最大で118個のパッケージをダウンロード、ビルドされるのを待つ。長い時間が必要なので1時間くらいゲームなどしてくるとよいかもしれない。

これで vcpkg を配置したディレクトリーへ installed\x64-windows ができて、その中に include lib bin などプログラマーならわかるな?的なディレクトリーも作られ、インストールを叩いたパッケージと芋蔓されたパッケージの全てのそんなようなものが入っています。

3. extern crate gdal している myapp プロジェクトをビルドできるようにする

  1. tasks.json を↓な具合に調整。大事なところは env で設定している crate gdal が内部的で依存している gdal-sys のビルド時に要求される GDAL_HOME GDAL_INCLUDE_DIR GDAL_LIB_DIR の3つの環境変数群です。%USERPROFILE%など環境変数の展開は機能しなかったので vcpkg のパスを地味に書きます。実際に入れたパスへ読み替えて下さい。
  2. vcpkg で生成した lib に入っている gdal.lib を gdal_i.lib としてコピーしておく。(crate gdal の中の gdal-sys のビルドの要求でそのようになっているので、こうしておくのが手っ取り早い。)
{ "version": "2.0.0"
, "tasks":
  [
  , { "label": "cargo build debug"
    , "type":"shell"
    , "command": "cargo"
    , "args": [ "build" ]
    , "group":
      { "kind": "build"
      , "isDefault": true
      }
    , "problemMatcher": "$rustc"
    , "presentation": { "panel": "new" }
    , "options":
      { "env":
        { "GDAL_HOME": "C:\\Users\\your_name\\opt\\vcpkg\\installed\\x64-windows"
        , "GDAL_INCLUDE_DIR": "C:\\Users\\your_name\\opt\\vcpkg\\installed\\x64-windows\\include"
        , "GDAL_LIB_DIR": "C:\\Users\\your_name\\opt\\vcpkg\\installed\\x64-windows\\lib"
        }
      }
    }
  ]
}

これで myapp のビルドは完了できるようになります。一応。しかしまだ、実行できません。

4. myapp を実行可能にする

  1. vcpkg の install で生成した bin の中には↓の実行時リンク用のライブラリーファイルが格納されているので、全て myapp の target/debug/myapp.exe と同じディレクトリーへコピーしましょう。なお、ライブラリーのファイル名は実行環境や実行時のパッケージに定義されているライブラリーのバージョン等により異なります。
boost_atomic-vc141-mt-x64-1_67.dll      boost_math_tr1l-vc141-mt-x64-1_67.dll            geos_c.dll      libpq.dll
boost_chrono-vc141-mt-x64-1_67.dll      boost_prg_exec_monitor-vc141-mt-x64-1_67.dll     icudt61.dll     libxml2.dll
boost_container-vc141-mt-x64-1_67.dll   boost_random-vc141-mt-x64-1_67.dll               icuin61.dll     lz4.dll
boost_date_time-vc141-mt-x64-1_67.dll   boost_regex-vc141-mt-x64-1_67.dll                icuio61.dll     lzma.dll
boost_filesystem-vc141-mt-x64-1_67.dll  boost_serialization-vc141-mt-x64-1_67.dll        icutu61.dll     openjp2.dll
boost_graph-vc141-mt-x64-1_67.dll       boost_system-vc141-mt-x64-1_67.dll               icuuc61.dll     proj_4_9.dll
boost_iostreams-vc141-mt-x32-1_67.dll   boost_thread-vc141-mt-x64-1_67.dll               libbz2.dll      sqlite3.dll
boost_locale-vc141-mt-x64-1_67.dll      boost_timer-vc141-mt-x64-1_67.dll                libcharset.dll  ssleay32.dll
boost_math_c99-vc141-mt-x64-1_67.dll    boost_unit_test_framework-vc141-mt-x64-1_67.dll  libcurl.dll     webp.dll
boost_math_c99f-vc141-mt-x64-1_67.dll   boost_wserialization-vc141-mt-x64-1_67.dll       libeay32.dll    webpdecoder.dll
boost_math_c99l-vc141-mt-x64-1_67.dll   expat.dll                                        libiconv.dll    webpdemux.dll
boost_math_tr1-vc141-mt-x64-1_67.dll    gdal203.dll                                      libmysql.dll    webpmux.dll
boost_math_tr1f-vc141-mt-x64-1_67.dll   geos.dll                                         libpng16.dll    zlib1.dll

これでビルドした myapp.exe は実行可能です。(データが無ければ panic して動作確認できませんが。)

5. 動作確認用の GeoJSON データを用意して動作確認する

  1. https://github.com/gsi-cyberjapan/geojson-with-style-spec/blob/gh-pages/sample1.geojson など適当に GeoJSON を入手するなり手書きするなりして myapp.exe と同じディレクトリーまたはソースコードでパスを指定した場所と併せて配置します。
  2. デバッグ実行なり、直接 myapp.exe を叩くなりして動作確認しましょう。

おつかれさまでした😃

だそく

もし、vcpkg による install や、それによって生成あるいは他の手法であれ確保した実行時ライブラリーファイル群のコピーを自動化したい場合には Cargo.toml には残念ながらそのような依存ファイルのコピー機能は無さそうなので、 vscode の tasks.json で制御すると良さそうです。

あんまり美しくないので積極的に使いたいライブラリーとは言い難いところはありますが、他の手段ではなく GDAL を rust から使いたい場合には有用かもしれません。

USB, Switch: やはりスイッチや計器の類は「機械式」で単機能のシンプルなものが好い!

昨日届いて郵便受けに入っていた「機械式」で単機能なUSB切り替え器がこちら。

f:id:USAGI-WRP:20180804075701j:plain

amazon で 798 円で購入、中国からの発送で北海道札幌まで7日間でした。一緒に USB Type-A / Type-B 変換コネクターも2つ購入。どちらも郵便行けにいつの間にやら届いていました。

このUSB切り替え器はたくさんの製品がひしめき合っているUSB切り替え器の分野の中では、特にたくさん売れているとか、特にたくさん高評価が付いているとか、そういうポジティブな特徴の無い、比較的にはマイナー気味な製品です。では、どうしてわざわざこの製品を購入したのか?その理由は機械式で単機能な事、それだけです。

"たくさんの製品がひしめき合っているUSB切り替え器の分野"と先程も書きましたが、そのほとんどが "余計な" HUB機能付きで電子制御式にスイッチする製品です。実は、今回の「機械式」のUSB切り替え器の前にHUB機能付きのスイッチを購入して試用しています。その試用は通電するなり問題に遭遇する事で始まりました。

1つめの問題はキーーーンと高めの耳障りなモスキートノイズが切り替え器本体から発生してしまい作業やゲームに集中できなかい事でした。これはどうやら愛用していたキーボードの1つ Corsair K70 を接続すると発生する事がわかりました。そして切り替え器とキーボードの間に inateck USB 3.0 HUB (3ports, SD, OTG付き) を噛ませるとノイズは出なくなる事もわかりました。ベンダーによると一部のメカニカルキーボードと接続した場合にこのような問題が起こりうるのだそうで、この問題は開発チームにフィードバックするとともに製品の返送なしでの速やかな完全な返金もベンダーも側から申し出て頂きました。(そういうわけでこのベンダーの印象は悪くありません。きっと将来の製品では問題を克服してくれる事でしょう…たぶん。)

もう1つ、先のノイズ現象と併せて考えるとどうもやばい系の匂いが漂う問題も発見しました。何度か使っていると、切り替え直後のタイミングで PS4PRO がゲーム中に再起動してしまったのです…。また、別の切り替え直後にはPCの、USB切り替え器やその先どころか、ホストコントローラーの系統が違うはずのポートまで含めてすべてのUSBポートの製品の認識が解除され、どう抜き先してもUSB機器を再認識しなくなる現象も起こりました。恐らくPS4PROを再起動させた問題と根本的には同じ原因、動作だろうと思います。あげく、PCはWindowsでしたが、メインボード上の電源スイッチの通常押しからのACPIによる安全なシャットダウン処理も電源が落ちるところまでは進まなくなっていました。Windowsはどうも完全に終了処理できていたように見えましたが、その後の電源の制御が行われない状態になっていました。トラブルで電源装置本体の主電源を落とす体験は数年ぶりの事です。

2つの問題からの想像ですが、恐らく回路設計なりEMC対策なり何かがおかしいのでしょう…特に電源制御系で何か…。発症した2つ目の問題の具体的な症状からも、PS4やPCが壊れる可能性も十二分に考えられます。精神的にも金銭的にも、たかだかUSB切り替え器程度の分際がやらかす被害としては致命的な被害です💀

ところが、代替品を注文しようと amazon を眺めると、ほとんど似たような製品で、レビューをよく見ると、あちらこちらに「高音の耳障りなノイズが出る」とか、どうも似たような問題が起こりそうなものが多そうなんですね。そういうわけで、どうも信用ならんのですね、余計なハブ機能を付けたついで電子制御式のスイッチを採用したUSB切り替え器というものは。困った。

そんな中で、随分と設計が古そうなあからさまにダイヤル式のスイッチをガチッとひねると内部の配線が機械的に切り替えられるであろうと見た目からもわかるような製品が目に付きました。LOAS SWK-U4PC です。でもこれ、ちょっと今時、お高いんですね…。私は4マシンの切り替えも必要無いし。

それで「機械式」に注目して改めてUSB切り替え器を探してみると、1つ良い可能性のある製品が見つかりました。冒頭で紹介した amazon で 798 円 のUSB切り替え器です。メーカーと製品名で紹介したいのですが、ノーブランド、型式も不明です。それなりに良い綺麗にプリントもされた箱入りで届いたのですが、どこにもメーカーや型式は表示されていませんでした😅

使ってみると、「あたりまえ」のことですが、耳障りなノイズもありませんし、切り替え直後の再認識速度も抜き差しと同じ(というより手間がかからないだけはやい)です😃 まあ…箱を開けてみたらハンダ不良さんがいるとかはありえますが、余計なハブ機能も無いシンプルな機械式の切り替え器ですから、さいあく自分で修理もできます。ハブ付きのUSB切り替え器のような電子制御式では素人と大差ない電気工学知識しか持たない私には、ハードウェアをデバッグしたり、チップの脚のハンダをいじったり、回路設計を読んでキャパシターや抵抗やダイオードを追加したりして修正するのはかなり難しい…しかも別製品へ買い換えるにしてもどうもどれも似たような問題が起こりかねない…。

「USB切り替え器ごとき機械式スイッチで、いやむしろ機械式スイッチの方が信頼性も高くて修理もしやすくて…」などと私は思ってしまいますが、大量生産やどのみちHUB機能のために電子制御を入れるのであれば、スイッチも機械式より安くて工程も合理化できるのでしょうね、おそらく。しかし、それで動きはするけど実は問題があるような製品が流通の主流、ほとんどを占めてしまっているとは…電子制御、電源周り、こわいですね💀

ちなみに以前、USBハブではエレコム製で新品の購入直後に動作がおかしいと教え子が持ってきたのでテスターで軽く調べてみると、充電用ではないポートが5Vを大きく超えて充電用ポートと同じ電圧が印加されている…とかありました。動作がおかしい、で済んで良かった…。低品質USB機器こわい…。

そんなわけで、少なくとも私は「USB切り替え器ほしい!」と言われたら、「機械式の単純なやつにしとき」とおすすめしたいと思います。余計なHUB機能や電子制御が入らない分、切り替えも高速にできますしね。

いやぁ…低品質USB製品こわい。特にHUBとか切り替え器はこわい💀

おまけ

今回紹介したノーブランドのUSB切り替え器は小さくて、上にも下にも出っ張りがないので設置性が良いのも嬉しいポイントです。ケースも金属感があって無駄に丈夫そう😃 左右にはネジがでっぱります。

f:id:USAGI-WRP:20180804075708j:plain f:id:USAGI-WRP:20180804075712j:plain

rust: Cities Skylines 向けの地形ジェネレーターツール "Cities Heightfield from GSI" を Rust で作り直したはなし

今回は「はなし」記事です。若干技術的な内容も含まれますが、基本的には随筆のようなものです。

↑このアプリを Rust で作り直したのでした。元は C++boost::unitsboost::asio のサンプルを兼ねて書いたものでした。

"Cities Heightfield from GSI" を Rust で作り直した切っ掛け

きっかけは週末に Facebook 経由でちょっとした要望を頂いた事と、それと、ちょうど Rust の再学習をしたいなーと思っていた事です。その重なりから、週末の遊びとしてドドドドドーっと基礎的な機能要素ごとの試作をしつつ rust-1.29.0-nightly を rust-0.9 ぶり(およそ4年ぶりだったらしい)にとりあえず書ける程度に再学習しつつ、完全に Rust で書き直して 1.0.0 として公開、 Rust の crate.io にも登録しました。😃

"Cities Heightfield from GSI" は何をしてくれるアプリなのか

このアプリは Cities Skylines というゲーム向けのツールです。このゲームではユーザーがゲーム内のマップを作成して、そのマップを使って都市を発展させて楽しむ事ができます。このツールではゲーム内のマップを現実世界の地球の日本の地形データを、経緯度を与えるだけでよしなに「国土地理院」のオープンデータから必要な領域分の地図データを取得し、 Cities Skylines が読み込める形式・縮尺へ変換したデータを生成します。

国土地理院のオープンデータの地図「地理院地図」は Google Maps 由来で電子地図データの形式としてはデファクト・スタンダードで採用されるようになった Web Mercator 形式です。Web Mercator 法では地球を正方形の面からなる UV 球で分割します(この基本方針でできるだけ整合性を保つため、緯度±85°くらいから90°までは扱わないなど副作用というか、工夫があったりします)。この形式ではタイル状に分割されたデータについて、タイルそれぞれの分解能は全て同じに統一されています。2Dの地図として、街角の店がわかる程度から、都道府県や、せいぜい日本の地方がわかる程度までの縮尺で並べて使う用途には便利です。

ところが、この便利でたくさん応用されている形式にも苦手な事があります。例えば沖縄の石垣のタイル1枚と、北海道の礼文のタイル1枚、どちらも 256x256 px の分解能のタイルなので、2枚を並べて比べて見るような用途ではそのままでは実寸に対する縮尺が異なってしまいます。

f:id:USAGI-WRP:20180731085008p:plain f:id:USAGI-WRP:20180731084949p:plain

↑の Google Mapsスクリーンショットは、どちらも {Z(Level of Detail)}=14 ですが、右下の縮尺を確認すると、かなりの差があると気付くと思います。 Google Maps では URL を見ると、@45.3016037,141.0326741,14z のような部分が見つかります。この部分の数値が、順に「緯度」、「経度」「WebMercator法のZ(レベルオブディティール、ズームレベル、と呼ばれたりもします)」です。この部分を見ながら、ホイール操作などで拡大縮小をすると、現在は 0.25 刻みで変化します。この Z が一致するタイル同士は、地球をUV球を元に平面のテクスチャーへ同じ細分割レベルで投影したタイル同士です。緯度が同じ場合には縮尺も同じです。しかし、緯度の絶対値が大きくなるにつれ、同じ分解能へ投影される土地はより狭い範囲になります。よって、 Web Mercator 法では、緯度の異なる地図同士の目視によるスケーリングの比較はできず、慎重に縮尺を確認して比較する必要が生じます。

Cities Skylines では1つのゲームマップとして 1081x1081 px の画像に 17,290 m × 17,290 m の範囲を収めたデータを扱います。リアルスケールをゲームへ適用したい場合、地理院地図からタイルデータを拾う際に緯度に応じたデータ分解能を計算した上で、中心地から 17,290 / 2 m 四方の領域のデータが含まれる範囲のタイルを回収し、ゲームの縮尺に併せて変換して出力します。

また、 Cities Skylines は都市を発展させるゲームであって、山登りゲームや海底探索ゲーム、あるいは土木工学や建築向けのシミュレーターではありません。マチュピチュ遺跡はじめ、世界には 1024 m を超える山や、 0m 未満の土地にも都市があったり、あるいはせめて景観用としてマップの中に置きたい気持ちになる事はありますが、残念ながら Cities Skylines では地形の標高値は [ 0.0 .. 1024.0 ] m という事になっています。加えて、地形のデータは PNG/Gray16 形式で標高を表す事になっていて、 [ 0.0 .. 1024.0 ] m を [ 0 .. 65535 ] [-] へ正規化する必要もあります。

さて、このような一連の作業を手で操作するのはそれなりに面倒です。そんなわけで、 "Cities Heightfield from GSI" は作られました。😃 処理内容に興味のある方は MIT ライセンスのオープンソースソフトウェアなので、ソースも御覧くださいませ。🍝🍝🍝

UI系への機能要望はどうなったのか

ちなみに、今回の 1.0.0 をリリースする切っ掛けの1つ、「 Facebook で頂いた要望」は「端末エミュレーターWindows ではコマンドプロンプト、など)での操作は経験がなくよくわからないけれど、どうにかこのアプリを使って Cities Skylines で楽しんでみたい」的なものでした。

そこで、当初はごく簡単な GUI の搭載を考えました。しかし、この考えは比較的短い調査で後回しにする事にしました。 Rust の GUI フレームワークは、少なくとも私にはまだあまり発展しきれていないように見えました。 Qt の QML のようにさっくりと扱いやすいものが cargo add してデザインを JSON なり XML なりで書いてイベンドを関連付ければはいできあたり…というようなものがあればすぐに採用したかったのですが、 Rust は現状ではデスクトップ向けの GUI フレームワークでそこまで高度に発達したものはまだまだ開発途上に感じました。

そこで、さしあたっては、このご要望にお応えする方法として CUI ベースでも、実行ファイルを Windowsエクスプローラーからダブルクリックで実行したとしても、使う気があればまあ使って頂けるだろう程度の「対話型の実行形態」を機能追加する事にしたのでした。 GUI はまだ後ほどに( Rust の勉強と併せて何かしら遊んでみるつもりではあります)。

でっていう

かくかくしかじか、アプリが動作するようにはなったので github や crate へ公開しましたが、事実上はじめての Rust アプリという事も免罪符に、あちらこちらで私自身が手慣れている C++ ならば、こんな無駄なコストを払う実装・設計はしないなー…と感じるところができてしまいました。

例えば、ヒープから大きなバッファーを何度か再確保していて、最終的に必要なデータの生成に対して処理としてはやや冗長気味な実装や、ピクセルイテレーションのループやアクセス速度効率…。それから crate imagePNG 出力と [u16] / [u8]reinterpret_cast 的な部分のもっさり感や、バイトオーダーのトリックの必要性もどうにかしたい。

それから、対話型動作の標準入出力まわりの実装・動作もややぎこちなさの残るものですし、標準で --verbose でもついているのかと思うほどログを吐かせていますが、これももう少しスマートにしたい気がします。

とはいえ、 --release ビルド版の実行速度的には "気にする必要" は無さそうなので、引き続き、趣味の勉強としてコードを少しずつ改良して楽しむ材料にできれば、それでいいかな。😏

だそく

このアプリも国土地理院のオープンデータを応用しているアプリとして 「地理院地図パートナーネットワーク」にも登録、ご紹介いただいています。まだ未登録の、もっとたくさんの方が地理院地図を既に応用されていると思います。ぜひ、登録して活用・情報交換に役立てましょう。 😃

それと発売からもう年単位になりますが Cities Skylines はいわゆるシム系好きには今もなおたいへんオススメな楽しいゲームの1つです。まだ遊んだ事のない方、興味が少しでもわきましたら、ぜひ遊んでみてくださいませ。♥

rust: 任意の Primitive, struct, Vec<T> などのメモリーイメージを Vec< u8 > (≃ &[ u8 ] ) なバイト列として取得する方法

// for rust-1.27.2(stable)

use std::mem;
use std::io::Write;

fn main()
{
  { // f64 (primitive) -> Vec< u8 >
    let f = 1.0f64;
    let view = &f as *const _ as *const u8;
    let slice = unsafe { std::slice::from_raw_parts( view, mem::size_of::< f64 >()) };
    let mut out: Vec< u8 > = vec![];
    out.write( slice ).expect( "Unable to write" );
    println!( "{} => {:?}", f, out );
  }
  { // struct -> Vec< u8 >
    struct S { a: f32, b: [u8;2], c: u16 }
    let s = S { a: 1.234e-5, b: [ 1, 2 ], c: 0x0403 };
    let view = &s as *const _ as *const u8;
    let slice = unsafe { std::slice::from_raw_parts( view, mem::size_of::< S >()) };
    let mut out: Vec< u8 > = vec![];
    out.write( slice ).expect( "Unable to write" );
    println!( "s{{ a: {}, b: {:?}, c:{} }} => {:?}", s.a, s.b, s.c, out );
  }
  { // Vec< 16 > -> Vec< u8 >
    let v16 = vec! [ 0x0000u16, 0x0201, 0x0403, 65535 ];
    let view = &v16[ 0 ] as *const _ as *const u8;
    let slice = unsafe { std::slice::from_raw_parts( view, v16.len() * mem::size_of::< u16 >()) };
    let mut out: Vec< u8 > = vec![];
    out.write( slice ).expect( "Unable to write" );
    println!( "{:?} => {:?}", v16, out );
  }
}
1 => [0, 0, 0, 0, 0, 0, 240, 63]
s{ a: 0.00001234, b: [1, 2], c:1027 } => [229, 7, 79, 55, 3, 4, 1, 2]
[0, 513, 1027, 65535] => [0, 0, 1, 2, 3, 4, 255, 255]

References

  1. rust - How can I get an array or a slice from a raw pointer? - Stack Overflow
  2. std::slice::from_raw_parts - Rust

Note

rust-1.10 までは std::raw::Slice が unstable で実装されていて、この手の方法を調べるとそちらを使う方法も出てくる。 rust-1.9 で deprecated 、 rust-1.11 で obsoleted になったのでそちらは使えないので古い手法、情報による混乱に注意。

rust: rust-0.9 あたりからおよそ4年ぶりに rust-1.29 を使ってみる(開発環境の準備)

終末にちょっとしたツールを作ろうかと思い、もののついでに rust の知識をアップデート…というより再学習して遊ぼうかと思いました。

4年前に遊んだ記憶

  • rust-0.9 が最後に手元で遊んだ頃の rust だった覚えがある
  • ユーザーとして簡単な何かを作れそうなコード実装に必要な程度には公式ドキュメントは読んだ
  • 実用アプリは作るに至らず、言語仕様を理解して使用できるようにと小さなサンプルを書いて勉強していた程度
  • うさぎさんでもわかるRustプログラミング言語リファレンス とか書いていた
  • Arch か Ubuntu あたりの GNU/Linux 系の環境で遊んだ

現在の rust の状況

  • rust-1.29.0-nightly の時代( stable は rust-1.27.2
    • Note: 0.9, 1.0, 1.1, ... , 1.9, 1.10, 1.11, ... , 1.29 と minor バージョン番号が複数桁となる方式
  • rustup とかいうので rust のツールチェインをお手軽インストール

今回の環境

  • Windows 10 x86_64; 開発予定のツールのユーザーが Windows ユーザーのため

Windows 10 で rust 開発環境を準備する

Note: Microsoft Visual Studio 2017 の C++ ツールチェインは使用可能な状態で開始。

  1. https://rustup.rs から rustup-init.exe を入手して実行。
  2. 標準設定( x86_64-windows-msvc / stable / yes )ままインストール
    • Note: 最初から nightly を選んでおいてもよい
  3. PATH の通った適当な端末エミュレーター的な環境(cmd, ps, mingw など)から rustup, rustc, cargo など --version して最小限のセットアップの確認

少なくとも標準設定でインストールする分には、まったく苦労どころやひっかかりどころはありませんでした😃

rust のプロジェクトを作って vscode で遊べるようにする

  1. cd プロジェクトのディレクトリーを格納したいどこか
  2. cargo new guessing_game --bin; guessing_game は hoge でもなんでもお好みで。
  3. vscode hoge; 著者環境では alias vscode= しています。環境に応じて読み替えるなり、 WindowsExplorer からポチポチして開くなり適当に。
  4. vscode に rust っぽいプラグインを入れる
  5. .vscode/tasks.json を書く; ↓↓参照
  6. .vscode/launch.json を書く; ↓↓参照

.vscode/tasks.json

{ "version": "2.0.0"
, "tasks":
  [ { "label": "cargo build debug"
    , "type":"shell"
    , "command": "cargo"
    , "args": [ "build" ]
    , "group":
      { "kind": "build"
      , "isDefault": true
      }
    , "problemMatcher": "$rustc"
    }
  , { "label": "cargo build release"
    , "type":"shell"
    , "command": "cargo"
    , "args": [ "build", "--release" ]
    , "group": "build"
    , "problemMatcher": "$rustc"
    }
  , { "label": "cargo clean"
    , "type":"shell"
    , "command": "cargo"
    , "args": [ "clean" ]
    , "problemMatcher": []
    , "presentation": { "panel": "new" }
    }
  ]
}

.vscode/launch.json

{ "version": "0.2.0"
, "configurations":
  [ { "name": "Debug"
    , "type": "cppvsdbg"
    , "request": "launch"
    , "program": "${workspaceRoot}/target/debug/guessing_game"
    , "args": []
    , "stopAtEntry": false
    , "cwd": "${workspaceFolder}"
    , "environment": []
    , "externalConsole": false
    , "preLaunchTask": "cargo build debug"
    , "internalConsoleOptions": "openOnSessionStart"
    }
  ]
}

main.rs に break ポイントを貼ってデバッグ実行(≃F5)したり、 lib.rs を追加してライブラリー的なものをごにょごにょしたりして楽しむ(思い出す、再学習する😏)。環境が整ったら、必要な事はほとんどすべて The Rust Programming Language(または古いようだが参考にはなるかもしれない rust-1.6 の日本語版 プログラミング言語Rust )に書いてあるはずなので、適当に勉強して楽しむ。

Windows での開発環境はどうせ何かしら面倒な事が起きるだろう、と思っていたけれど、ツールチェインに msvc を使う限りにおいてはそこの準備をさておけば面倒ごとは何も起きなかった。善き哉。・・・ llvmgnu のツールチェインを windows で使う場合は何か面倒なりひと手間なりあるかもだけど、「とりあえず msvc ツールチェインでも動けばいっかー」の程度であれば、一般的なアプリケーション開発者であれば苦労なく開発環境を用意できるようだ😃

おまけ: rust のツールチェインを切り替えたくなったら

例えば、 stable でツールチェインをインストールしたけれど、 nightly を使いたくなった場合は、

rustup default nightly

をコマンドすると必要なら最新版をダウンロードしてパスの通ったツールチェインを nightly へ切り替えてくれる。

ちなみに、標準インストールした場合、ユーザーのホームディレクトリーの .cargo に標準で使用するツールチェインの bin などが入り、 .rustup/toolchains/ に stable-x86_64-pc-windows-msvc/ や nightly-x86_64-pc-windows-msvc/ のように rustup で扱ったツールチェインが実行可能な状態でそのまま入っている。

UE4/C++: http(s) リクエストに FHttpModule 系を使う場合の OnProcessRequestComplete/FHttpRequestCompleteDelegate の成否フラグの注意どころ

UE4FHttpModuleOnProcessRequestComplete / FHttpRequestCompleteDelegate には若干、罠どころがある💀

// UE4/C++ でフレームワークに用意された方法で HTTP リクエストを発行し、
// HTTP レスポンスを得て何かしたい場合のコード例

auto http = &FHttpModule::Get();
auto req = http->CreateRequest();

// ↓罠どころ
req->OnProcessRequestComplete().BindLambda( []( auto req, auto res, auto suc ) { /* ... do something ...*/ } );

// ... URLやメソッドの設定が続き ...

// リクエストを処理
req->ProcessRequest();

「罠どころ」とコメントを付けた OnProcessRequestComplete / FHttpRequestCompleteDelegate へ束縛するラムダ式( or 束縛可能なオブジェクト)の第3引数が注意の必要なところ。

// OnProcessRequestComplete/FHttpRequestCompleteDelegate の関数としての型(の擬似コード)
void OnProcessRequestComplete
( FHttpRequestPtr HttpRequest
, FHttpResponsePtr HttpResponse
, bool bSucceeded // <-- これ💀
);

↑のようになっているので、例えば GET でコンテントを拾ってくるような場合に、束縛するファンクター内で bSucceeded を見ればコンテントの取得成否を確認できそうな気がする。実際間違いではなく、コンテントの取得に成功している場合には、このフラグは true で呼ばれる。

では、この第3引数の何が罠かと言うと、「 HTTP レスポンスが帰って来ればレスポンスのステータスコードがエラー系であっても true となる可能性がある」点が罠どころ。接続先のホストが存在しないとか、応答しないとか、そういう場合は false となるのでコンテント取得の成否として期待通りの false を得られる。しかし、オシャレなエラーページなどのコンテントの乗ったレスポンスは帰って来るが、 HTTP ステータスコードが 404 や 500 あるいは 302 などの場合にも bSucceededtrue となり得て、それは大抵の場合は "リクエストにおいて期待したコンテント" の取得に対しては偽陽性的な応答で、そのような場合にも実装者は false がこのフラグに与えられる事を期待してしまいかねない罠がある。

// 例えば適当なポエムのテキストデータを HTTP で GET リクエストし、
// ポエムのテキストデータが得られた場合にはポエムを表示、
// 得られなかった場合には失敗の表示を行いたい的な擬似コード
[] ( auto, auto res, auto suc )
{
  if ( suc )
  {
    // 404, 500, 302 などのレスポンスステータスコードの場合も 200 と一緒にこちらへ分岐してしまう
    UE_LOG( LogTemp, Log, *res->GetContentAsString() )
  }
  else
  {
    // こちらへ来るのは接続がそもそも失敗したなどの場合
    UE_LOG( LogTemp, Log, TEXT( "FAILED!" ) )
  }
}

"リクエストにおいて期待したコンテント" の取得の成否の判定は次のように行う必要がある。

[] ( auto, auto res, auto suc )
{
  if ( suc && res->GetResponseCode() >= 200 && res->GetResponseCode() < 300 )
  {
    // "リクエストにおいて期待したコンテント" が得られた場合にのみここへ分岐する
  }
}

参考

  1. IHttpResponse | Unreal Engine
  2. IHttpRequest::OnProcessRequestComplete | Unreal Engine
  3. Http-requests - Epic Wiki

UE4/C++: TArray 夏の怪談💀 AllocatorInstance が nullptr の巻

怪異

f:id:USAGI-WRP:20180705150332p:plain

↑キャッチーな画像という事ではなくて、実際にこういう事が「うっかり」で起こるので気をつけたい、という怪談です💀

冒頭のスクリーンショットUE4 の Array.h ( TArray )の ResizeTo メンバー関数が呼ばれた際に AllocatorInstance メンバーが nullptr のために実行時にアクセス違反が起こりアプリが死ぬ瞬間のものです。 ResizeToInit などから内部的に呼ばれます。

原因

この現象は TArray をメンバー変数として持つユーザー定義型の UCLASS において、 UPROPERTY を宣言に付与しない事で発生し得る実装となります。

// 怪異パターン
UCLASS()
class UMyAwesomeComponent: USceneComponent
{ GENERATED_BODY()
// ...
TArray< int32 > array_without_property_marking; // <-- OOPS!
// ...
};

「発生します」と断言できない理由は、 UE4ガベージコレクションに起因します。 UCLASS のメンバー変数で UPROPERTY の付いたメンバー変数は UE4ガベージコレクションの「管理対象」として「正常」に扱われます。 UPROPERTY を付けないと UE4ガベージコレクションUCLASS のメンバー変数としてのガベージ性(オブジェクトをゴミとして回収してよいか)を考慮しなくなり、メンバー変数を持つクラスは生きていて現役で使用中なのに、メンバー変数が UE4ガベージコレクションに回収されて更地( nullptr )になるという事が起こり得るようになります。この UE4UPROPERTY によるガベージコレクションの仕組みは UPROPERTY が直接付けられたオブジェクトに留まらず、そのオブジェクトの内部にも及びます(例えば UCLASSUMyComponent* c を別のどこかの AMyActor* aUPROPERTY でメンバーに持つ場合、 c オブジェクトそのものだけでなく、 c がメンバー変数として持つ UPROPERTY 付きのメンバー変数群も a のガベージ性から保持・回収が判断されるようになります )。

と、言うわけで、この問題(バグ)を埋め込んでしまったクラスが、さらに動的に生成されたり、比較的短命だったり、生成直後には然程使わないが暫くしてから動き出したり消えたりするような何かだった場合、「よくわからんがたまに落ちる」というプログラミング界隈の代表的な怪談が発生し得る状況になります。

// 良好パターン
UCLASS()
class UMyAwesomeComponent: USceneComponent
{ GENERATED_BODY()
// ...
UPROPERTY() TArray< int32 > array_with_property_marking; // <-- GOOD!
// ...
};

こわい

こわいですね💀