xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

gRPC / MagicOnion 入門

HTTP/2 をベースとした通信フレームワーク gRPC と、その高レベルラッパーである MagicOnion についての連載インデックス

インデックス

f:id:xin9le:20170603134144p:plain

値の破棄

1 年ほど前 C# 7.0 の新規のについていろいろ書いていたのですが、値の破棄 (discards) という機能について書いてなかったことに気が付きました!ということで、イマサラですが紹介します。

これまでの「値を使わない」ときの書き方

コンパイルを通すために引数を指定しなければならないけれど、その引数は今使いません。

…と言ったケースはままあります。そんな「値を無視したい」ときによく見かける実装が「_ (アンダースコア)」変数によるエスケープです。慣例レベルではありますが、「利用しない変数」であることを明示した書き方をします。例えば以下のような感じです。

int _;
if (int.TryParse("123", out _))
{}

しかし C# においてアンダースコアは変数名として有効なので、複数の値を無視したい場合は以下のようにしなければなりません。

//--- 使いもしない変数なのにドンドン _ の数が増えていく...!
int _;
if (int.TryParse("123", out _)){}

int __;
if (int.TryParse("456", out __)){}

int ___;
if (int.TryParse("789", out ___)){}

//--- 当然変数として有効なので、ここで触ることができる
___ += 10;
Console.WriteLine(___);

「値の破棄」を明示したい

  • _ の数がドンドン増えるのはイヤだ
  • 値を無視するという意図の変数を誤って使わせたくない

そんなお気持ちを C# 7.0 が「discards (値の破棄)」としてサポートします。後述しますが、以下のようにいくつかのシチュエーションでのみアンダースコアが特別扱いを受けます。

//--- こんな関数があったとして
private void OutVariable(out int value)
    => value = 123;

//--- こんな風に書ける
this.OutVariable(out var _);
this.OutVariable(out _);

//--- 触ろうとするとコンパイルエラー
_ += 10;  // そんな変数はないぞ!

実は out var _ のように型を書く必要はなく out _ だけでも OK です。また上記の場合 _ は変数として認められていないため、以下のような書き方をしてもコンパイルエラーになりません。

//--- こんな複数の値を引数から戻す関数があったとして
private void OutVariable2(out int x, out string y)
{
    x = 123;
    y = "abc";
}

//--- 全部 _ ひとつで OK
this.OutVariable2(out var _, out var _);
this.OutVariable2(out _, out _);

discards が利用できる箇所

ザッと調べた範囲では、現状の C# 7.0 では以下の箇所で値の破棄の構文を使うことができます。

//--- 型分解のときに値を受けるけど捨てる
var (name, _) = ("xin9le", 32);
var (_, _) = ("xin9le", 32);

//--- 型スイッチで string 型に対して処理したいけど値は使わない
switch ("abc")
{
    case string _:
        break;
                
    //--- ちなみにコレはコンパイルエラー
    //case _:
    //    break;
}

LINQ やイベントハンドラを記述する際のラムダ式でも頻繁に _ を使うことがありますが、C# 7.0 ではサポートされていません。これは今後のバージョンに期待…かもしれません。

逆コンパイル

この機能がどうやって実現されているのか、いつも通り逆コンパイルしてのぞいてみます。すると、以下のような C# 6 までのフツーのコードに展開されます。

//--- これは
this.OutVariable2(out _, out _);

//--- 素直にこう展開される
int item1;
string item2;
this.OutVariable2(out item1, out item2);

つまり discards の構文であることを C# コンパイラが上手に判断して、良きに計らってくれているということですね。IL レベルでは一切変更がありません。

Visual Studio 2017 (15.1) インストール後に Unity で Windows Store App 向けビルドが通らないときの対処

4/11 から一般提供が開始された Windows 10 Creators Update。このリリースに合わせて Visual Studio 2017 も更新され、内部バージョンが 15.0 から 15.1 になりました。Visual Studio 上ではこんな感じで通知が来ます。

f:id:xin9le:20170423184546p:plain

これをクリックして更新するだけで、インストール状態を判定して全自動でアップデートが完了します。実に素晴らしい!過去に業務で数年インストーラー開発を担当していた自分としては、こういった複雑なアプリケーションのアップデートをユーザーに一切手間を与えないで行うのは本当に尊敬します。だがしかし…!

15.1 に更新するとビルドが通らない

なんということでしょう…。最近は Unity を使って HoloLens のアプリケーション開発をしているのですが、更新しただけでこのビルドが通らなくなってしまったのです。以下のようなエラーが出ます。

f:id:xin9le:20170423185821p:plain

Unity で HoloLens の開発を行うときには HoloToolkit-Unity という (半ば必須とも言える) 公式のライブラリを使用します。使わないで開発する方が普通じゃない、というくらい必ず使います。そして上記のエラーが出ているのはこの HoloToolkit の部分です。自分で作ったものじゃないのでどうしようもない。全自動でアップデートが掛かってこれが起こるとは悪夢としか言いようがない…。

原因

端的に言ってしまうと、原因は Unity が参照している Windows SDK のバージョンが変わってしまったことにあります。Visual Studio 2017 (15.0) の段階では、一緒にインストールされる Windows SDK のバージョンは「10.0.14393」系になります。こんな感じです。

f:id:xin9le:20170423190408p:plain

そして Visual Studio 2017 (15.1) に更新すると「10.0.15063」系がインストールされます。Windows 10 Creators Update 対応版ですね。

f:id:xin9le:20170423194328p:plain

Unity で Windows Store App 向けビルドをする際、この「10.0.15063」系を見に行くようになると先の無慈悲なエラーが出るようになります。そもそもビルドエラーが出るのがオカシイとは思うのですが、対象の Windows SDK (10.0.15063) をアンインストールしてもなぜか改善しません。ワンクリックするだけで全自動で走る素敵な Visual Studio 2017 の更新インストールをするだけで詰みます。もう終わってます。

回避方法

回避方法は 2 つあるでしょう。

1. Visual Studio 2017 (15.0) を使い続ける

簡単です。修正されるまで更新しない。だがしかし、一体いつ修正されるかわからん!そんなことでは日進月歩なこの時代に簡単に置いて行かれてしまう!いつ間違ってワンクリックの超素敵全自動アップデートをしてしまうかわからん!…ので、できれば更新したいというお気持ちだと思います。

2. Unity が参照している Windows SDK のバージョンをちょろまかす

まずまず Unity での Windows Store App 向けビルドが Visual Studio のバージョン自体に直接依存しているわけではありません。必ずどこかの情報を参照して Windows SDK のバージョンを切り替えているはずです。つまり Unity のビルドプロセスでどこを参照しているのかを調べれば OK ということになります。そして気合と勘でいろいろ調べていくと以下のパスにある AssemblyConverter.exe が怪しいと気付きます。

C:\Program Files\Unity\5.x.xxx\Editor\Data\PlaybackEngines\MetroSupport\Tools\AssemblyConverter.exe

この exe は .NET でできているので逆コンパイルして中を覗いていきます。すると、以下のようなコードが出てきます。

f:id:xin9le:20170423192937p:plain

Unity が Windows Store App 向けビルドをする際、以下のレジストリを参照して利用する Windows SDK のバージョンを判定していることが分かります。

項目
キー HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Microsoft SDKs\Windows\v10.0
名前 ProductVersion
データ 10.0.15063

ここの値を「10.0.14393」に書き換えれば OK です。念のためバックアップをしつつ行うと、以下のような感じになります。

f:id:xin9le:20170423193540p:plain

書き換えをしたら Unity を再起動してください。これでビルドが通るようになると思います。

まとめ

ということで、本当に苦労して原因を突き止めました。互換のない状態になっている Windows SDK 自体に問題がある気がしないでもないですが、これでまだしばらくは戦えそうです :)

Vuforia 6.2.x が Unity 5.5.x の Windows Store App 環境下でビルドエラーになるのを回避する方法

Tokyo HoloLens Meetup vol.2 の LT で Vuforia という AR ライブラリについて話していたので、「面白そう!」と思ってチャレンジしてみています。AR マーカー的なものはまだ作ったことがなかった上に HoloLens に対応していると聞いて俄然ヤル気に!

と思ってパッと調べてみたら昨年の //Build/ 2016 でも HoloLens を使ったデモをしていたようで。(全然覚えてないけど…

Vuforia 自体の基本的な使い方や設定については詳しい記事が山ほどあるので、ここでは割愛します。

サンプルがビルドエラーになる

Vuforia 初心者なので、まず手始めに HoloLens のサンプルを動かしてみよう!ということで Vuforia のサイトからサンプルをダウンロードします。「Digital Eyewear」のカテゴリーの Unity アイコンのものが対象です。

f:id:xin9le:20170402191358p:plain

.unitypackage が落ちてきたはずなので Unity にインポートし、「Vuforia-2-Hololens」のシーンを開きます。HoloLens で実機確認したいので何も考えずに Windows Store App 向けにビルドします。すると… ビルドエラーが出る!サンプルがいきなり動かない!/(^o^)\

f:id:xin9le:20170402194545p:plain

Reference rewriter: Error: method `System.Void UnityEngine.RenderTexture::set_generateMips(System.Boolean)` doesn't exist in target framework. It is referenced from Vuforia.UnityExtensions.dll at System.Void Vuforia.DistortionRenderingBehaviour::CreateRenderTexture().
UnityEngine.Debug:LogError(Object)
PostProcessWinRT:RunReferenceRewriter() (at C:/buildslave/unity/build/PlatformDependent/WinRT/SharedSources/CSharp/PostProcessWinRT.cs:560)
PostProcessWinRT:Process() (at C:/buildslave/unity/build/PlatformDependent/WinRT/SharedSources/CSharp/PostProcessWinRT.cs:127)
UnityEditor.HostView:OnGUI()

手元の環境は「Unity 5.5.0p4」なのですが、どうやら Unity 5.5 から RenderTexture.generateMips プロパティが RenderTexture.autoGenerateMips に名称が変更されたようで、Vuforia.UnityExtensions.dll の内部で古いバージョンの API を利用していることが理由でビルドエラーになっているっぽいです。

Graphics: Added RenderTexture.GenerateMips script API for manual control over mipmap generation. Renamed existing RenderTexture.generateMips property to autoGenerateMips.

API Updater を使って回避

Unity には API 変更の互換性を担保するための API Updater という機能が搭載されています。今回はこれを使って問題が出ているアセンブリを修正すれば OK です。

「Vuforia\Scripts\Internal\Vuforia.UnityExtensions.dll」を右クリックして、コンテキストメニューから [Run API Updater…] を実行します。

f:id:xin9le:20170402200301p:plain

これで晴れて Windows Store App 向けにビルドが通るようになります。あとは HoloLens の実機で動作確認すれば良いでしょう。

参考

これが見つけられなかったら解決できませんでした。感謝×2!

Sharing Deep Dive - HoloLens で最もクールな機能の勘所を掴む

2017/03/25 (土) に日本マイクロソフト品川本社にて開催された Tokyo HoloLens Meetup vol.2 に参加/登壇してきました。会場に行くと日本の HoloLens ブームの凄まじさに驚くばかりです。そんな圧倒的ギーク達を前に、僭越ながら HoloLens で最もクールな花形機能である「Sharing」について解説させていただきました。

Sharing 機能は僕自身がメインエンジニアとして開発している Project Sonata で実際に利用しており、開発に際しては相当苦労しましたw なので今回のセッションでは、その開発中に得た基本的な考え方やハマりポイントなど、現在日本語でも英語でも全然公開されていない知見を共有させていただきました。

Sharing はただでさえお高い HoloLens が最低 2 台必要ということで、日本でいくら HoloLens が加熱しているからと言って簡単に試せるものではありません。そういう点でも人柱としてガチでやってみた系の情報共有は、今後の HoloLens 開発に対する良い Tips になれたのではないかと思います。

大変ありがたいことにセッションの反応も想像以上に良く、セッション後には持って行った名刺が全部なくなるほどにたくさんの方々からお声掛けいただきました。こんなことは初めてだったので正直驚いていますw また、セッション中のツイートは Togetter にまとめられています。感謝×2 :)

Sharing のキモは座標系の共有

これに尽きます。座標系の共有ができないうちは同じ空間内での Sharing は「絶対に」完成しません。まず前提知識として HoloLens における座標系は以下のようになっています。

  • アプリケーションを開始したときのヘッドセットの位置が原点
  • 視線方向 : Z 軸正方向
  • 向かって右側 : X 軸正方向
  • 上 : Y 軸正方向

つまり、同じ室内にいたとしても A さんと B さんはそれぞれ独自の座標系を持っています。この状態で A さんが (x, y, z) = (1, 2, 3) の位置にオブジェクトを生成したとして、これをネットワーク経由で B さんにも配置するように指示したとしても、B さんの座標系は全く別なので (x, y, z) = (1, 2, 3) は全然別の場所になってしまいます。そこで WorldAnchor を設定し、それを原点とした座標系を共有し合うことで、(x, y, z) = (1, 2, 3) が同じ空間内で同一の場所を指すようにします

f:id:xin9le:20170326131431p:plain

座標系の共有は完全に下準備です。これに成功したら、共有座標系に対する localPosition / localRotation でやり取りすることで、晴れて Sharing が完成します!

Sharing の実現手順

Sharing を行うまでの大まかな手順をまとめると以下のようになります。言うは易しなのですが、やってみると案外面倒なものです。

  1. 原点 (= 上図の Os) とする GameObject に対して WorldAnchor コンポーネントを引っ付ける
  2. WorldAnchorTransferBatch を使って先に設定した空間アンカーをシリアライズ
  3. ネットワーク経由でシリアライズしたデータを全ユーザーに配信
  4. 受信したデータを WorldAnchorTransferBatch を使ってデシリアライズし WorldAnchor を設定
  5. 原点となる GameObject の子要素としてオブジェクトを生成/配置
  6. オブジェクトの移動や姿勢変更があれば localPosition / localRotation をネットワーク経由で共有

空間アンカーの前回値保持

空間アンカーの設置/同期にはかなりの時間がかかります。また、UWP アプリケーションはそのライフサイクルの仕様からバックグラウンドにいるときに自動で終了させられたりします。このようなことがあるので、アプリケーション再開時に高速で空間アンカーを復旧させることには一定の意味があります。ただし、空間アンカーを保存した部屋と読み込みする部屋が別だったりすると一体どこに復旧させたら良いか分からなくなるので、同一空間での利用なのかどうかと言ったところに気を配った実装をしなければならず、思った以上に手間です。前回値保持は Sharing を実装する上では必須機能ではないので、いっそのこと思い切って無視しても良い気もします。

空間アンカーの前回値保持には WorldAnchorStore を使って行います。これはいわゆる KVS (Key Value Store : 辞書型ストレージ) として提供されています。

空間アンカーの共有におけるハマりポイント 6 選

冒頭の資料にあるものをピックアップしますが、メチャクチャ多いです。正直イヤになるくらいハマりますし、「もはや無理なのでは?」と挫折すると思います。これらをひとつひとつを忍耐強くクリアしていかないと、アプリにすることはおろか他人にデモを見せることすら難しいでしょう。中途半端に Sharing を搭載したアプリをリリースしたとしても「Sharing できないので ☆1」は普通にあり得る話だと思います。つまり、本当にツラいです...(ギャフン

1. 小さ過ぎるデータサイズ

シリアライズ結果が小さいとデシリアライズに失敗しやすようです。HoloToolkit-Unity のサンプルでは 100 KB を最低保証値としていて、コメントに「小さ過ぎると失敗しやすい傾向にある」とだけ書いてあります。そうらしいのでとりあえず信じましょう。

2. 滅多に成功しない保存/読み込み

まずまずシリアライズに全然成功しません。成功率は 10 % 未満なのではないかと思うほど成功しません。そして運よく成功したしたとしてもデシリアライズにも高頻度で失敗します。成功するまでリトライするような地道な実装が必要です。

3. 大き過ぎるデータサイズ

先ほどはデータサイズの最低保証値として 100 KB と書きましたが、実際には簡単に 10 MB を超えたりします。社内で実験していたときに 50 MB を超えたこともありました。それぐらいのデータ量になることもある、というのを知っておく必要があります。

4. バカにならないデータ転送量

空間アンカーを共有するにあたり、先に挙げたようなバカでかいデータを全ユーザーにブロードキャストする必要があります。が、当然カジュアルにやってしまっては非常に危険です。例えば海外でデモをしようとして、レンタル Wi-Fi (= 500 MB/day) などで通信するとすぐに上限に振り切って死にます。ですので、データサイズを減らしたり通信回数を減らすような実装上の工夫が多数必要になりますし、安定した Wi-Fi 環境が必須になります。

5. 保存/読み込みが超絶遅い

WorldAnchorTransferBatch によるシリアライズ / デシリアライズは、それだけでそれぞれ 30 秒かかるとかザラにあります。さらに先に挙げたように高頻度で失敗するので「1 分以上待って失敗!」と言ったことも発生し得ます。圧倒的にイライラが募るので、心に余裕を持って作業に当たってくださいw

6. 圧倒的デバッガビリティの低さ

Sharing の検証を行うには実機を使って部屋中を動き回る必要があります。ですが、Visual Studio のデバッガーを使おうとすると PC にケーブルを繋いでいる必要があります。動きたいのに動き回るのが困難という現実に直面することになります。そして 2 台以上にデプロイして検証する必要があるので大変煩わしいです。さらにエミュレーターは Sharing においては何の役にも立ちません...。結局僕は printf debug 的に画面にメッセージを出すことで作業していました。

SharingService.exe の是非

みんな基本は Holographic Academy : Holograms 240 から Sharing の勉強を始めるので、そのサンプル中に出てくる SharingService.exe を利用するのは当然だと思いがちです。が、ShariingService.exe を利用する以上はそのメリット / デメリットを十分に把握しておく必要があります。

メリット

  • Sharing はもちろん、音声通信など豊富な機能が用意されている
  • クライアント実装だけに集中して開発できる

デメリット

  • カスタマイズ性が一切なく、ボトルネックになった際に対処不能
  • スケールアウトできないので少人数利用に限られる
  • 自前 API がある場合、それと異なる通信の口を持つことになる

僕は Project Sonata を開発するにあたり「SharingService.exe は自由度がないのでナシ」という判断をし、MagicOnion という弊社 CTO (@neuecc) 謹製の gRPC をベースとしたハイパフォーマンス通信フレームワークを選択しました。これは相当な茨の道でしたが、大きなカスタマイズ性とパフォーマンス、そしてすべて自分で制御しきっているという安心感を手に入れました。また、Sharing の通信部分を完全に自作したことで Sharing 自体に相当詳しくなったという点でも良かったです #今となってはw

まとめ

Sharing は上手くできると本当に未来を感じます。メッチャ夢があります。でも、全然簡単じゃない。これだけは覚えておいてください。

Sharing に夢を見ても 甘く見るな