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

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

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!
// ...
};

こわい

こわいですね💀

PS4PRO: USB ストレージへバックアップしようとしたら エラー CE-32539-2 が発生して一悶着あった話

PS4 バックアップ エラー CE-32539-2: 発生

PS4PRO の標準 1TB HDD を Crucial MX500 1TB SSD に換装するため、 PS4 のバックアップと復元の機能を用い、 A-DATA UV131 64GB USB ( USB メモリー )ストレージへバックアップを行おうとした。

  • 1回目: 購入して開封したばかりの A-DATA UV131 64GB を PS4PRO 本体に挿し、バックアップを進める。バックアップ動作に含まれる再起動後、20MB程度のコピーで進捗しなくなり、残り時間だけが増えだし、程なくしてエラー CE-32539-2 が発生する。
  • 2回目: 手抜きせずに PS4 のストレージ機能から OPTION ボタンメニューを呼び出し、 A-DATA UV131 64GB を exFAT でフォーマット。バックアップに再び挑むも220MB程度のコピーで進捗しなくなり、前回と同様に CE-32539-2 が発生する。
  • 3回目: 2回目と同様にもう一度、不具合確認のつもりで作業する。やはり220MB付近でコピーが進捗しなくなり、しかし今度は異なるエラー CE-33299-6 が発生した。

ここまでの期待しない動作の発生状況から CE-32539-2 について SONY 公式のトラブルシューティングを確認する。 CE-33299-6 についてはフォーラムに情報の断片があるのみだった。

つまり、 SONY もこの問題を十分に把握、再現性を確認し、確かな対策を行えていないらしい事がわかる。

PS4 バックアップ エラー CE-32539-2: 解決方法

Note: ここで紹介する方法は SONY 公式ではなく、私個人の解決の一例に基づく方法です。

  • 解決方法: A-DATA UV131 64GB USB ( USB メモリー )を一旦 PC に接続して exFAT で「クイックではなく完全にフォーマット」する

これだけで PS4 でのバックアップが成功するようになる。

PS4PRO のバックアップ機能の動作中はバックアップ先のファイルシステムの実際のデータの記憶域の構造がクイックフォーマットの対象となるアロケーションビットマップなどのメタ情報のみではなく、何らかのより深いフラグないしメタ情報によらない実データの参照などを行ってしまう実装になっていて、それでこのようなエラーが起こるのじゃろうか。あるいは A-DATA UV131 64GB が何か妙なのだろうか、少なくとも PC で使う分には不良は観測されない開封したての USB メモリーなんじゃが…(´・ω・`)

復元: 未解決

その後、SSDへ換装し、システムソフトウェアのインストールは問題なく完了。システムソフトウェアはデータのバックアップと同じ A-DATA UV131 64GB を使った。しかし、再びバックアップの際に発生した CE-32539-2 が発生し、復元が完了しない。何度かやってみたが、その都度、異なる進捗率のタイミングで進まなくなる。

一旦、 A-DATA UV131 64GB を PS4PRO から PC へ挿し、バックアップを PC の適当なところへさらにバックアップした。このバックアップのバックアップから適当な SD カードへ同じディレクトリー構造でバックアップの複製を用意しつつ、ファイルのコピーに要する20分弱の間、A-DATA UV131 64GB の元のバックアップを用いて復元を試みた。バックアップの複製を作っている間に5回くらい A-DATA UV131 64GB からの復元を試したが、無駄だった。

その後、 A-DATA UV131 64GB から改修したバックアップのバックアップから用意した SD カードのバックアップを HUB 機能などない単純な Reader/Writer を介して PS4 の USB ポートへ挿し、バックアップの復元を行った。これは失敗しなかった。

ぼやき

この作業にあたり、私は 8GB までの USB マスストレージデバイスしか持っていなかったし、それらは USB 2.0 規格の古いものだったので、汎用な USB メモリーも USB 3.1 規格へアップグレードするついで買いとして A-DATA UV131 64GB を SSD と同時に買った。恐らく、今回の問題は PS4 のシステムソフトウェアないし BIOS レベルのファームウェアに問題があるような気がする(あくまでも個人の直感です)。 PS4PRO にはおおむね満足して楽しませて頂いていただけに、今回の件はとても残念に感じた。システムソフトウェアのバージョンアップで解消できる問題であれば、速やかに対応して頂けると嬉しい。

ちなみに、現在アクティブに遊んでいるゲームは Monster Hunter World だけで、他のゲームはよほどの事がない限り起動するつもりは無いので、バックアップと復元の時間を節約しようと思いアプリケーションは Monster Hunter World だけに絞っていた。本体の設定とセーブデータを合わせて 16.2GB 。幸い、カメラ用に使っていた SD カードが 32GB UHS1 の製品だったので復元トラブルも迂回してどうにか対処できたものの、A-DATA UV131 64GB に比べると実効転送速度としては桁違いと感じる程度には遅い(´・ω・`) 素早く SSD へ換装して今夜も Monster Hunter World で30分でも1時間でも多く楽しむ時間を確保したい、そういう目論見もあって、せっかく高速で大容量な USB マスストレージデバイスを購入したのに、結局いらない時間を1時間ほど多くとられてしまい、とても残念な事態となりました💀