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

Wonder Rabbit Projectのなかのひとのブログ。主にC++。

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時間ほど多くとられてしまい、とても残念な事態となりました💀

UE4/C++ で lambda-expression なタスクをお手軽に非同期処理に投げる方法

// 最小限のコード例
// グラフの意味はさておき、単発のタスクを
// とりあえずなんでもよいのでUE4のタスクプールに投げたい場合はこれだけでも動作する
FFunctionGraphTask::CreateAndDispatchWhenReady( []{}, TStatId(), nullptr );
// 解説用に関連して扱う必要のある型が分かりやすいように少し分解したコード例
TFunction< void() > f0 = []{};
FGraphEventRef t0 = FFunctionGraphTask::CreateAndDispatchWhenReady( f, TStatId(), nullptr );

// 第3引数に nullptr ではなく前提条件とする FGraphEventRef を渡すとタスクグラフが構築される
// 第4引数に ENamedThreads を明示的に与えるとスレッドプール内のどのスレッドに実行させたいか、優先度をどうするかなど設定できる
TFunction< void() > f1 = []{};
ENamedThreads p1 = AnyBackgroundThreadNormalTask;
FGraphEventRef t1 = FFunctionGraphTask::CreateAndDispatchWhenReady( f, TStatId(), t0, p1 );

// メインスレッドで実行しないとクラッシュするような処理をさせたい場合には明示的に GameThread を指定しないと
// ありがちなスレッドとコンテキストによる「たまに落ちる」的なバグを埋め込む事があるので注意。
// 例: UProceduralMeshComponent の CreateMeshSection とか
TFunction< void() > f2 = []{};
ENamedThreads p2 = ENamedThreads::GameThread;
FGraphEventRef t2 = FFunctionGraphTask::CreateAndDispatchWhenReady( f, TStatId(), t1, p2 );

// 完了チェックしたい場合
if ( t2.IsComplete() )
  somthing();

ラムダ式UE4のタスクプールに非同期で投げる方法は簡単な非同期処理の実装にお手軽で嬉しい。( C++ 標準の promise / future / thread に対応する UE4 標準の TPromise / TFuture / FRunnableThread の使い方 - C++ ときどき ごはん、わりとてぃーぶれいく☆ での実装例のようにタスクにクラスを定義する必要が無い)

Note: ただし、このお手軽な方法は「それほど重くない処理」または「ゲームスレッド以外でのんびり実行してくれればよい処理」などでは便利に使えるが、「ゲームスレッドで実行する必要があり、1フレームに近いか、あるいは1フレームを超える処理時間を要する重い処理」や「ゲームスレッドで実行する必要があり、1つあたりは0.1msで処理できるがほぼ同時に1000タスク発生する処理」のようなタスクの非同期処理には実質的に使えない。タスクの粒度によらず前提条件さえ満たしていれば溜まっているタスクを Tick 1フレームで全てビッグバン実行してしまうため、メインスレッドや描画スレッドが想定するフレームレートをこなせなくなるようなタスクを投げたい場合には、フレームあたりのタスクの実行粒度を制御する工夫が必要となる・w・

参考

UE4/C++: UMaterialInstanceDynamic を生成したり UPrimitive 的な何かにセットしたりするコードスニペット的なメモ

// ぱたーん1: マテリアルインスタンスダイナミックを複数の何かのマテリアルとして使いまわしたい場合

// 1.1. AActor 系のどこかなどで、とりあえずマテリアルインスタンスダイナミックをマテリアルアセットから作る:
auto m = UMaterialInstanceDynamic::Create( Cast< UMaterial >( StaticLoadObject( UMaterial::StaticClass(), nullptr, TEXT( "/Game/MyDir/MyMaterial" ) ) ), this );
// 1.2. UPrimitiveComponent 系の何か(UStaticMeshComponent など)にマテリアルをセットする:
constexpr auto element_index = 0;
mesh->SetMaterial( element_index , m );
// ぱたーん2: 使い回さず1つの UPrimitiveComponent 系の何かで使う程度の場合

// 2.1. いきなり作れてセットもできる
auto m = mesh->CreateAndSetMaterialInstanceDynamicFromMaterial( 0, Cast< UMaterial >( StaticLoadObject( UMaterial::StaticClass(), nullptr, TEXT( "/Game/MyDir/MyMaterial" ) ) ) );
// 2.optional. この方法でも使い回せる
mesh->SetMaterial( 1, m );
// appendix: UPrimitiveComponent は GetMaterial メンバー関数も持っているので、マテリアルを保持しなくて良い程度の用途はこれでも実装できる
mesh->SetMaterial( 1, mesh->GetMaterial( 0 ) );

参考

UE4 Plugin: SVG Importer Plugin

概要

UE4SVG をテクスチャーで扱いたかったけれど、 svgpp など使ってちまちま実装するよりも $19.99 で SVG Importer Plugin を買った方が楽そうだし、マーケットプレイスでも ☆5 が幾つもついているので試してみようかな、と思い試してみた記録。

SVG Importer Plugin

試した

SVG Importer Plugin はエンジンプラグインなので、 Epic Game Launcher からエンジンにプラグインを install し、プロジェクトのプラグイン設定で有効化すると使用可能になる。

プラグインが有効なプロジェクトで UE4Editor が動作中に .svg をプロジェクトの Content ディレクトリーへ放り込む、或いは UE4Editor の UI から明示的に Import 操作を行うと SVG Import Options が出現する

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

ここで重要なのは "Import Type" (RGBAの絵として読み込むか、ディスタンスフィールドとして読み込むか)と、 "Import Resolution"(後から変えられない取り込み時の解像度)の2つ。大きく拡大して表示されるRGBAテクスチャーにしたい場合は "Import Resolution" を 4096x4096 にしておくとよい。

↓インポートに成功するとSVGテクスチャーのアセットが生成される

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

↓適当なマテリアルアセットを作成し、テクスチャーにインポートしたSVGテクスチャーアセットを設定

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

↓プレビュー等も問題なく動作

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

↓4096x4096でインポートしておけばRGBAテクスチャーで拡大されても綺麗(アタリマエじゃが😅)

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

↓32x32など小さい解像度でインポートしてしまうとそれ以上にはどうにもならなくなる

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

↓32x32はアライ

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

解像度は 4096x4096 でインポートしておけば、SVGテクスチャーアセットの設定で任意に小さく書き出す事もできるので、インポート時は特に理由が無ければ最大解像度でインポートしておくと良さそう。テクスチャーの書き出しも各種圧縮形式も簡単に選択するだけで対応できて便利も好い。

SVGテクスチャーアセットの設定からフィルターを変更すればドット感の高い低解像度テクスチャーにもできる

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

↓同じSVGを無理やりディスタンスフィールドとしてインポートしてディスプレイスメントマップしてみたもの(無理やりだけどたぶんインポート機能そのものは問題なく扱える。)

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

黄色のクルマのSVGの出典

UE4-4.19 で Material Layers を試用する手順

概要

従来の UE4 では「マテリアルのレイヤーブレンド機能」=「マテリアル関数の合成機能」でした。4.19以降で試験実装された "Material Layers" を使うと「マテリアル関数」ではなく、新たなアセットとして追加される「マテリアルレイヤー」と「マテリアルレイヤーブレンド」を用いた新しいマテリアルのレイヤー合成を試用できます。と、いうわけで基礎的な使い方を確認しつつ試用してみます。

なお、この機能の利点は複雑なマテリアルを作成したい場合に、マテリアル1つで全てを実装しようとするとグラフが混沌となってしまう問題をレイヤーと合成それぞれを独立したアセットに分割する事で解消しつつ、従来のマテリアル関数を組み合わせる方法に比べてもマテリアルインスタンスでの一元的なパラメーターの取り扱いが可能となるなど便利も向上する、ということで試験実装が進められたようです。

実際、すっきりするし使いやすい。(この機能追加のために 4.19 からは MaterialInstance 系の実装が C++er 的にはしれっと変更されていて 4.18 までの C++ コードは少し変更してあげないと翻訳不能になっていたりとかあります。 Blueprint で本記事くらいの機能と動作の確認をした上で実装変更をドキュメントなど眺めると、なるほどそれでこうなったのか、とわかります。知らんと翻訳エラーの発生と対応方法に若干戸惑うかも。)

準備

  1. 4.19 以降のエンジンとプロジェクトを用意する
  2. UE4Editor のメニュー "Edit" から "Project Settings" の "Engine - Rendering" の "Experimental" にある "Support Material Layers" チェックボックスをON

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

Note: OFF -> ON にすると UE4Editor の再起動を促されます。

試用

手順

2つのマテリアルレイヤー la lb と、1つのマテリアルレイヤーブレンド blend を使うマテリアル m を作成し、 mインスタンス i を適当な Static Mesh の Material に設定するまで:

  1. "Content Browser" の "Add New" から "Materials & Textures" -> "Material Layer" でマテリアルレイヤーアセット la を作る
  2. 同様に "Material Layer" アセットの lb も作る
  3. 同様に "Material Layer Blend" アセットの blend も作る
  4. 同様に "Material" アセット m も作る
  5. m の出力ノードの "Details" の "Material" の "Use Material Attributes" チェックボックスを ON にする
  6. m を "Content Browser" のコンテキストメニューから "Create Material Instance" してマテリアルインスタンスアセット i を作る
  7. 適当な "Static Mesh" なり "SK_Mannequin" などを配置して "Mesh" の "Material" に i を設定

動作確認として la lb blend それぞれに適当なパラメーターを噛ませた出力を実装:

  1. la lb に自動的に追加されている "SetMaterialAttributes" ノードの "Details" の "Material Attributes" の "Attribute Set Types" に設定したいマテリアル属性の項目を追加し、出現するピンへ "Texture Sample" や "VectorParameter" などのノードを追加してパラメーターを接合
  2. blend に自動的に追加されている "BlendMaterialAttributes" ノードに "ScalarParameter" ノードを追加して接合し、適当な既定値を設定
  3. m に "Material Attribute Layers" ノードを適当な名前で追加し、出力ノードへ接合
  4. m に追加した "Material Attribute Layers" ノードの "Details" の "Layers" の "Bacbkground" の "Layer Asset" に la を設定
  5. m に追加した "Material Attribute Layers" ノードの "Details" の "Layers" に "Layer 1" を追加し、 "Layer Asset" に lb を、 "Blend Asset" に blend をそれぞれ設定
  6. i の "Layer Parameters" に la, lb blend で追加した設定可能な "Parameters" 系のノードの値を適当に調整

動作確認:

  1. i を設定した "Mesh" を眺めるなりプレイするなりして la をベースに lbblend するマテリアルのインスタンスが意図通り動作する事を確認

補足図

↓ "Support Material Layers" を ON にしたプロジェクトでは作成できるアセットに "Material Layer" と "Material Layer Blend" が増える

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

↓ "Material Layer" アセット作成直後のグラフ

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

↓ "Material Layer Blend" アセット作成直後のグラフ

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

↓ "Material" アセット m の "Details" の "Use Material Attributes" チェックボックス

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

↓ "Material" アセット m に "Material Attribute Layer" ノードを追加し、 "Use Material Attributes" で変化した出力のピンと接合

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

la に動作確認用に例として "TextureSampler2D" パラメーターノードを "SetMaterialAttributes" の "BaseColor" へ追加したグラフ

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

lb に動作確認用に例として "VectorParameter" パラメーターノードを "SetMaterialAttributes" の "BaseColor" へ追加したグラフ

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

blend に動作確認用に例として "ScalarParameter" パラメーターノードを "BlendMaterialAttributes" の "Alpha" へ追加したグラフ

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

m に追加した "Material Attribute Layers" ノードの "Details" の "Layers" に la lb blend を設定

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

i の "Layer Parameters" で la, lb, blend のパラメーターを調整

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

↓ マネキンのマテリアルに i を設定した状態での動作確認

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

参考

  1. Material Layering Feedback for 4.19 - Unreal Engine Forums
    • 試験実装中のマテリアルレイヤー機能の公式スレッド
  2. http://historia.co.jp/archives/955/
    • 従来から使用できるマテリアル関数ベースのマテリアルレイヤー合成機能の参考