yotiky Tech Blog

とあるエンジニアの備忘録

C# - 固定サイズのキュー

ラッパークラス

最小限の実装。Queue がラップされるので Queue で必要な機能は全部中継する必要がある。

public class FixedSizeQueue<T>
{
    private ConcurrentQueue<T> queue = new ConcurrentQueue<T>();
    private object lockObject = new Object();
    public int Size { get; }

    public FixedSizeQueue(int size)
    {
        Size = size;
    }
    public void Enqueue(T item)
    {
        lock (lockObject)
        {
            queue.Enqueue(item);
            while (Size < queue.Count)
            {
                queue.TryDequeue(out var _);
            }
        }
    }
    public bool TryDequeue(out T result)
        => queue.TryDequeue(out result);
    public int Count()
        => queue.Count;
}

拡張メソッド

必要な機能中継するのが面倒な時に、Enueue する時に長さを指定する簡易機能。

public static class QueueExtensions
{
    public static void EnqueueFixedSize<T>(this Queue<T> queue, T item, int size)
    {
        queue.Enqueue(item);
        while (size < queue.Count)
        {
            queue.TryDequeue(out var _);
        }
    }
    public static void EnqueueFixedSize<T>(this ConcurrentQueue<T> queue, T item, int size, object lockObject)
    {
        lock (lockObject)
        {
            queue.Enqueue(item);
            while (size < queue.Count)
            {
                queue.TryDequeue(out var _);
            }
        }
    }
    public static void EnqueueFixedSize<T>(this ConcurrentQueue<T> queue, IEnumerable<T> items, int size, object lockObject)
    {
        lock (lockObject)
        {
            foreach (var item in items)
            {
                queue.Enqueue(item);
            }
            while (size < queue.Count)
            {
                queue.TryDequeue(out var _);
            }
        }
    }
}

HoloLens2 に FastEnum を導入する

追記 (2020/11/14)

FastEnum 1.6.0 がリリースされました。 1.5.x では System.Runtime.CompilerServices.Unsafe は 4.7.1 以上が必要でしたが、1.6.0 では 4.5.0 以上になっており、要求されるバージョンが下がっています。現在は 1.6.0 でも動作します。

動作確認はしていませんが、最新バージョンで良ければ Nuget For Unity からでもインポートすることができるでしょう。

この記事自体は、NuGet Gallery からライブラリをインポートする手順としても有用なのでそのまま残しておきます。

検証環境

  • Unity 2019.4.3f1
  • FastEnum 1.4.1
  • MessagePack 2.2.60
  • MRTK 2.5.1

補足

FastEnum 単体で導入する場合は、最新を使っても大丈夫です。 その場合は Dependencies に記載されている System.Runtime.CompilerServices.Unsafe ダウンロードしてインストールしましょう。

MessagePack や MagicOnion を一緒に使う場合は、依存する System.Runtime.CompilerServices.Unsafe のバージョンに差異が発生するため、下位バージョンに依存している MessagePack のバージョンに合わせている形になります。その他の競合する依存ライブラリを持つツールを使う場合は、バージョンを合わせるように調整が必要になってきます。

導入

FastEnum

github.com

Unity 向けのパッケージはないので、NuGet Gallery の「Download package」からパッケージをダウンロードします。

f:id:yotiky:20201107184405p:plain

ダウンロードした nupkg ファイルを解凍します。出てきたフォルダのfastenum.1.4.1\lib\netstandard2.0\FastEnum.dll を Unity の Assets\Plugins フォルダの下にインポートします。

MessagePack

MessagePack は リポジトリから unitypackage をダウンロードしてインポートします。

すべてインポートされると、Plugins フォルダは以下のようになります。

f:id:yotiky:20201107185714p:plain

MessagePack に関しては以下の記事も参考にしてみて下さい。

yotiky.hatenablog.com

導入の手間はありますが問題なく実行できます。 f:id:yotiky:20201107185858p:plain

Unity Editor の MessagePack CodeGen が動かない原因と一時対処

原因

最新の MessagePack for C# (執筆時のバージョンは 2.2.60 ) の MessagePack CodeGen ウィンドウでは、ジェネレートが動かなくなっています。中でdotnet tool コマンドを使っていますが、.NET Core ツールについては前回の記事を参照してください。

yotiky.hatenablog.com

ツールの ToolCommandName に設定する名前は、プレフィックス dotnet- で実行するコマンドが変わってきます。

最新の MessagePack for C#プレフィックス dotnet- が取れています。

    <ToolCommandName>mpc</ToolCommandName>

以前はプレフィックスが付いていました。

    <ToolCommandName>dotnet-mpc</ToolCommandName>

最新のものをグローバルツールとして実行するには、dotnet mpc ではなく、mpcで実行する必要があります。

一方で、エディタ拡張のコマンドは以下のままであるため、ジェネレートが動かなくなっています。

var log = await ProcessHelper.InvokeProcessStartAsync("dotnet", "mpc " + commnadLineArguments);

一時対処

エディタ拡張自体がグローバルツールを使うようにできています。MessagePack for C# 自体はローカルツールに舵取りするようなので、ここでは一時対処だけ記載しておきます。

Assets/Scripts/MessagePack/Unity/MessagePackWindow.cs の150行目付近にある以下のコードの部分を。

var log = await ProcessHelper.InvokeProcessStartAsync("dotnet", "mpc " + commnadLineArguments);

以下の要領で書き換えます。

UnityEngine.Debug.Log("try local tool");
var log = await ProcessHelper.InvokeProcessStartAsync("dotnet", "mpc " + commnadLineArguments);
if (string.IsNullOrEmpty(log))
{
    UnityEngine.Debug.Log("try global tool");
    log = await ProcessHelper.InvokeProcessStartAsync("mpc", commnadLineArguments);
}

参考

blog.yumineko.com

原因を教えて頂いた記事に感謝。

.NET Core ツール

.NET Core ツール

dotnet コマンドで、tool SDK コマンドを使うと、 NuGet パッケージを使ったツールの管理をすることができます。

コマンド 説明
dotnet tool install ツールをインストールする
dotnet tool list インストールされているグローバルツールやローカル ツールの一覧と呼び出すコマンド名なども表示する
dotnet tool uninstall ツールをアンインストールする
dotnet tool update ツールをアップデートする

インストール

グローバルツール

グローバルツールとしてインストールする場合は、-g もしくは --global オプションを使用します。

dotnet tool install -g MessagePack.Generator
dotnet tool install --global MessagePack.Generator

既定のインストール先は、C:\Users\[your name]\.dotnet\tools です。

インストール場所を任意のパスにするには、--tool-path オプションを使用します。 この方法は環境変数に追加されないので、適宜対応する必要があります。

dotnet tool install MessagePack.Generator --tool-path C:\dotnet\tools

ローカルツール

ローカルツールとしてインストールする場合は、マニフェストファイルを追加する必要があります。ローカルツールは、現在のディレクトリとそのサブディレクトリが対象となります。そのため、通常はリポジトリのルートに追加します。

マニフェストファイルは、.config\dotnet-tools.json に作成されます。

dotnet new tool-manifest
dotnet tool install MessagePack.Generator

マニフェストファイルの次の通りになります。

{
  "version": 1,
  "isRoot": true,
  "tools": {
    "messagepack.generator": {
      "version": "2.2.60",
      "commands": [
        "mpc"
      ]
    }
  }
}

リポジトリからクローンした場合は、ツールがインストールされていないので、ルートディレクトリで復元を実行します。

dotnet tool restore

また、特定のバージョンをインストールして使いたい場合は、--version オプションを使用することができます。バージョン番号は、NuGet Gallery などで確認できます。

dotnet tool install MessagePack.Generator --version 2.1.80

ツールを使用する

コマンドを確認する

インストールしたツールのコマンドは dotnet tool list で確認できます。グローバルツールを確認する場合は、-gオプションを使用します。

> dotnet tool list -g

パッケージ ID                   バージョン       コマンド
-------------------------------------------
messagepack.generator      2.2.60      mpc
dotnet-mlb                 0.4.1       dotnet-mlb

ローカルツールを確認する場合は次のようになります。

> dotnet tool list

パッケージ ID                   バージョン       コマンド      マニフェスト                                             
----------------------------------------------------------------------------------------------------
messagepack.generator      2.2.60      mpc       C:\Project\DotnetSampleProject\.config\dotnet-tools.json

グローバルツールを呼び出す

グローバルツールの場合は、ツールコマンドを単独で呼び出します。

mpc
dotnet-mlb

コマンドのプレフィックスdotnet- が付いている場合は、dotnet コマンドを使うこともできます。呼び出す時は、プレフィックス部分の省略します。

dotnet mlb

dotnet コマンドを使う場合は、ローカルツールの呼び出し方と同じになるため、ローカルツールを利用している環境では注意が必要です。

ローカルツールを呼び出す

ローカルツールの場合は、dotnet コマンドを使用します。プレフィックスは省略してもしなくても呼び出せます。 また、マニフェストをインストールしたディレクトリ内で実行する必要があります。

dotnet mpc
dotnet dotnet-mlb
dotnet mlb

プレフィックス dotnet-

.NET Core のツールは、パッケージを作成する時に ToolCommandName を設定します。NuGet で提供される実行ファイルは、<ToolCommandName>.exe になります。 この名前にプレフィックス dotnet- を付けると、提供される実行ファイルの名前も dotnet-<ToolCommandName>.exe となります。

ToolCommandName 実行ファイル名 グローバル ローカル
ToolName <ToolName>.exe ToolName dotnet ToolName
dotnet-ToolName dotnet-<ToolName>.exe dotnet-ToolName
dotnet ToolName
dotnet dotnet-ToolName
dotnet ToolName

f:id:yotiky:20201107181505p:plain

参考

HoloLens2 で MagicOnion v3 を動かす

目次

構築環境

補足

NGは動作させることができなかったバージョン。

Unity のベース構築

  1. MagicOnion.Client.Unity.unitypackage をインポートする
  2. MessagePack をインポートする

    • CodeGenerator は Window > MessagePack > CodeGenerator にある

      f:id:yotiky:20201028142315p:plain

  3. grpc-unity-package.x.xx.x-dev.zip を解凍して Assets\Plugins フォルダへインポートする

    1. System.BuffersSystem.MemorySystem.Runtime.CompilerServices.Unsafe は除く

gRPC のコード修正・ビルド

Grpc.Core.dll

  1. gRPC のソースコードを取得する
  2. タグ [v1.26.0] をチェックアウトする
  3. Unity の Asset\Plugins\Grpc.Core\runtimes\win\x64\grpc_csharp_ext.dll を gRPC プロジェクトの grpc\cmake\build\x64\Release フォルダにコピーする

    1. 次のフォルダは除く
      1. System.Buffers
      2. System.Memory
      3. System.Runtime.CompilerServices.Unsafe
  4. ソースコードを修正する

  5. ReleaseAny CPU で、Grpc.Core をビルドする
  6. 生成された Grpc.Core\bin\Release\net45\Grpc.Core.dll をUnityのAsset\Plugins\Grpc.Core\lib\net45\Grpc.Core.dll に上書きする

grpc_csharp_ext.dll

  1. vcpkg をインストールする

    1. vcpkg のソースコード取得
      1. https://github.com/microsoft/vcpkg
    2. タグ [2020.01] をチェックアウトする
    3. bootstrap-vcpkg.bat を実行する
    4. 以下のファイルを vcpkg\ports\grpc フォルダに上書きする
      1. 00001-fix-uwp.patch (3.5 kB)
      2. 00002-static-linking-in-linux.patch (533 B)
      3. portfile.cmake (2.9 kB)
    5. 以下のファイルを vcpkg\triplets\community にコピーする
      1. arm64-windows-static.cmake (106 B)
  2. vcpkg install protobuf:x86-windows

  3. vcpkg install grpc:arm64-windows-static

    1. なにか言われたら随時対処して再実行する

      • Warning: The following VS instances are excluded because the English language pack is unavailable. C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional Please install the English language pack. Could not locate a complete toolset.

        • Visual Studio Installer から英語の言語パックをインストール f:id:yotiky:20201028142423p:plain
      • -- Using source at C:/git/Microsoft/vcpkg/buildtrees/protobuf/src/a5c431386a-c9deea9e31.clean CMake Error at ports/protobuf/portfile.cmake:22 (message): Cross-targetting protobuf requires the x86-windows protoc to be available. Please install protobuf:x86-windows first. Call Stack (most recent call first): scripts/ports.cmake:79 (include)

        Error: Building package protobuf:arm64-windows-static failed with: BUILD_FAILED

        • install protobuf:x86-windows
    2. やり直したい場合は、vcpkg remove grpc:arm64-windows-static して buildtrees\grpc フォルダを削除する

      1. キャッシュ使って buildtrees が吐き出されない場合は下記フォルダを削除する
        1. C:\Users\[username]\AppData\Local\vcpkg\archives
  4. buildtrees\grpc\arm64-windows-static-relgrpc_csharp_ext.dll が生成されるので、Unityの Asset\Plugins\Grpc.Core\runtimes\win\arm64 フォルダを作成してインポート

    1. PlatformでWSAPlayer を選択、CPU を ARM64 に f:id:yotiky:20201028142717p:plain

grpc_csharp_ext_dummy_stubs.c

  1. Asset\Plugins\Grpc.Core\runtimes\grpc_csharp_ext_dummy_stubs の WSAPlayer にチェックを付ける f:id:yotiky:20201028142732p:plain

動作確認

以下のサイトに習い一部読み替えながら実施する。

qiita.com

HoloLens2 + MRTK 2.5 プロジェクトの初期設定

新規プロジェクト作成時に行うミニマムなお決まりの手順、設定など。(Legacy XR版)

検証環境

  • Unity:2019.4.3f1
  • MRTK:2.5.3
  • VisualStudio:2019
  • Device:HoloLens2

開発環境に必要なモジュールは以下を参照。

HoloLens2 の開発環境に追加するモジュール - yotiky Tech Blog

手順

  • [Ctrl + Shift + B] で 「Build Settings」 画面を開く
  • [Universal Windows Platform] を選択して[Switch Platform]をクリック
    f:id:yotiky:20200602232317p:plain:w300

  • プロジェクトのフォルダで、Packages/manifest.json を開いて、"dependencies" ブロックの上と、"dependencies" ブロックの先頭に以下の内容を追加する

  "scopedRegistries": [
    {
      "name": "Microsoft Mixed Reality",
      "url": "https://pkgs.dev.azure.com/aipmr/MixedReality-Unity-Packages/_packaging/Unity-packages/npm/registry/",
      "scopes": [
        "com.microsoft.mixedreality",
        "com.microsoft.spatialaudio"
      ]
    }
  ], 

  "dependencies": {
    "com.microsoft.mixedreality.toolkit.foundation": "2.5.1",
    "com.microsoft.mixedreality.toolkit.tools": "2.5.1",
    "com.microsoft.mixedreality.toolkit.examples": "2.5.1",

f:id:yotiky:20201025230029p:plain

  • Packageの読み込み後に表示される「MRTK Project Configurator」ウィンドウで Apply
    f:id:yotiky:20201028102129p:plain

  • 「Build Settings」 画面やメニューから「Project Settings」 画面の[Player]タブを開く

    • [Publishing Settings]グループ
      • [Package name] に任意のパッケージ名を入力
    • [XR Settings]グループ
      • [Virtual Reality Supported]にチェックをつける
      • [Virtual Reality SDKs]に[Windows Mixed Reality]を追加
      • [Depth Format]を[16-bit depth]に変更
      • [Stereo Rendering Mode]が[Single Pass Instanced]であることを確認
        f:id:yotiky:20200602231351p:plain:w300
  • メニューから TextMeshPro Essential Resources を Import
    f:id:yotiky:20200602231806p:plain:w300

  • 任意の Scene で HoloLens 用の設定を適用(Scene のセーブを忘れずに)
    f:id:yotiky:20200602231725p:plain:w200

  • [Hierarchy] で MixedRealityToolkit を選択し、プロファイルを「DefaultHoloLens2ConfigurationProfile」に変更する f:id:yotiky:20201025231051p:plain

  • 任意の Scene をビルドに追加
    f:id:yotiky:20200602232246p:plain:w300

オプション

  • Mixed Reality Capture (MRC) でハンドメッシュがズレる

    • Render from the PV camera (opt-in)
    • MixedRealityToolkitConfigurationProfile の Camera の項目で、DefaultMixedRealityCameraProfile を Clone して、CameraSettingsProviders の [Render from PV Camera] をONにする f:id:yotiky:20200624165517p:plain
  • よく使うパッケージをインポートする yotiky.hatenablog.com

  • HoloLens のアスペクト比を追加する

    • f:id:yotiky:20210204184759p:plain:w200

HoloLens2 でQRコードのスキャンを実装する(位置検知編)

今回は、QRTracking を参考に位置検知を実装します。

最新のコードは実際に扱うには問題が含まれています。 以前は Legacy XR で実装されていましたが、6/18 のコミットで Unity 2019 へバージョンアップするとともに XR Plugin での実装に更新されています。こちらはアプリを実行とすると焦点があっていない現象が起きホログラムが二重に見えます。位置検知なので正確性は目で見て判断できません。

HoloLens と XR Plugin の問題なのか、QRに関することが絡んでいるのか、未調査です。また、同じような現象で Issue もたったるようなので要観察です。

github.com

キャプチャだとだいぶずれてますが、実際は大体合ってます。二重に見えるので大体です。。

f:id:yotiky:20201024222358j:plain

というわけで、Legacy XR 版を参考にしましょう。unity2018-old ブランチを参考にしてください。

目次

検証環境

  • Unity:2019.4.3f1
  • VisualStudio:2019
  • MRTK 2.5 (設定、UI、操作などに)
  • UniRx

手順

導入~基本的なスキャン実装

  • スキャン実装までの手順は前回の記事を参照してください
  • 以下、XR Plugin版の追加手順
    • [Package Manager] から [Windows XR Plugin] をインストール
    • [Project Settings] > [XR Plug-in Management] > [Universal Windows Platform settings] タブで、Plug-in Providers の Windows Mixed Reality にチェックをつける
    • MixedRealityToolkit のプロファイルを DefaultXRSDKConfigurationProfile に変更する
      • もしくはマニュアルで必要な設定をする
      • Spatial Awareness をOFFるか非表示にしたり、Camera のRender from PV Camera を有効にしたり
      • Legacy XR を使っていた場合は、Player Settings で当該部分をクリアにする(勝手になるかも)

実装

QRTracking のプロジェクトから SpatialGraphCoordinateSystem クラスを拝借します。(MIT ライセンスなのでファイルの先頭にコメントつけるなど) 先に書いた通り Legacy XR を使う場合は、Unity2018-old ブランチを参照してください。(直リン

SpatialGraphCoordinateSystem は、QRCode で上がってくる SpatialGraphNodeId を設定すると、くっついてる GameObject をよしなに配置してくれるようです。

よしなに配置する際の Position の中心はQRコードの左上になります。テキストやQRをオーバーレイするオブジェクトを配置する場合は、位置とサイズの調整が必要になってきます。QRCode では PhysicalSideLength が取得できるのでこれを位置とサイズに使います。

f:id:yotiky:20201024223934p:plain

まず Hierarchy の設定です。 オブジェクトの階層はこんな感じにしています。QRScanner は前回のサンプルコードと同等のものです。QRVisualizer は後ほど。

f:id:yotiky:20201024221701p:plain

QRCode には先程の SpatialGraphCoordinateSystem が追加されています。Rotation で180度回転させてこちら向きにしています。QRTracking プロジェクトを参考にしていますが、おそらく SpatialGraphCoordinateSystem で調整される際に180度回転するため、エディタ上で実際に見える向きにしてあるのだと思われます。

f:id:yotiky:20201024221851p:plain

Cubeはオーバーレイ用の板です。今回はCubeのZを薄くしたものを使っています。 Textはそのままだと上下反転しているので、180度回転させます。また、Scale とフォントサイズは適切に設定します。Position は後でスクリプトから調整するので編集用の値です。

f:id:yotiky:20201024223020p:plain

続いて QRVisualizer のコードです。

public class QRVisualizer : MonoBehaviour
{
    public GameObject qrCodePlane;
    private SpatialGraphCoordinateSystem spatialGraph;
    private GameObject plane;
    private TextMeshPro data;
    private QRScanner qRScanner;

    void Start()
    {
        spatialGraph = qrCodePlane.GetComponent<SpatialGraphCoordinateSystem>();
        plane = qrCodePlane.transform.Find("Cube").gameObject;
        data = qrCodePlane.transform.Find("Text").GetComponent<TextMeshPro>();

        qRScanner = GetComponent<QRScanner>();
        qRScanner.OnScanned
            .Subscribe(qr =>
            {
                qrCodePlane.SetActive(true);
                spatialGraph.Id = qr.SpatialGraphNodeId;
                plane.transform.localPosition = new Vector3(qr.PhysicalSideLength / 2, qr.PhysicalSideLength / 2, 0);
                plane.transform.localScale = new Vector3(qr.PhysicalSideLength, qr.PhysicalSideLength, 0.001f);
                data.text = qr.Data;
                data.gameObject.transform.localPosition = new Vector3(qr.PhysicalSideLength / 2, qr.PhysicalSideLength / 2, -0.001f);
            })
            .AddTo(this);
        qRScanner.IsReady
            .Where(x => x)
            .First()
            .Subscribe(_ => qRScanner.StartScan())
            .AddTo(this);

        qrCodePlane.SetActive(false);
    }
}

サンプルプロジェクト

ソースコード一式は以下においてあります。

github.com

関連記事

yotiky.hatenablog.com

yotiky.hatenablog.com