20200516のUnityに関する記事は7件です。

UnityのWebGLのPluginの機能を呼び出したら何か知らないけど404になった話

前置き

  • Unity5系のプロジェクトを2018系に移行した際の話
    (結論Unityのverは関係なさそうだった)
  • twitterのtweetAssetsを自作してて、新しいゲームにimportしてみたが、何故かWebGLビルドでのみつぶやけなくなった(404になる)
  • いくつかのゲームにimportした時は特に問題なく動作していた……
  • WebGLのデバッグの仕方とか分からず絶望した

事象

  • twitterのtweetAssetsを自作してて、新しいゲームにimportしてみたが、何故かWebGLビルドでのみつぶやけなくなった(404になる) image.png
  • エラー内容
error_message
Failed to load resource: the server responded with a status of 404 (Not Found)
  • どうにもリソースが見つからない模様。plugin呼び出しのタイミングだったので、pluginが見つからない感じ?

解決方法

  • 正しい対処かは不明だが、plugin(.jslib)のguidを発行し直せば解消した! image.png
  • 発行し直しは以下のような感じ。(.metaを削除して.metaを作り直すのはダメだった)
    • 該当plugin(.jslib)を複製
    • 複製元を削除
    • 複製したファイルを複製元の名前でリネーム
  • guidを発行し直して、ビルドし直して起動して、エラーになってた部分の機能を呼び出すと直ってた! image.png

解決までの試行錯誤

  • とりあえず Google先生
    • そもそも何で発生しているかが意味不明な状況。とりあえずそれっぽいキーワードでGoogle先生に聞いてみるも、良い感じのものが見つからなかった……
  • Chromeのデベロッパーツール でlogを確認
    • そもそもデバッグのやり方が分からなかったが、Chromeで 表示 > 開発/管理 > デベロッパーツール でデベロッパーツールを開き、ConsoleよりUnity側で出力したlogを確認することができた
  • .jslib の出力設定
    • Inspector上でimportのsettingが確認できるが、そこは問題なかった
    • image.png
  • Unityのverを変えて試してみた
    • 大丈夫なprojectが今回のverよりも低い2018系だったので、そのverに合わせて実行とかビルドしてみた
    • 結果はダメだったりOKだったりと規則性がなく、よく分からない状態に……
  • 試しに同じ処理を書いたpluginを追加して実行
    • 新しく追加したpluginは正常に呼び出された!
    • importし直しても結果は変わらずだった……
    • あれ、作り直せばいける?
    • 複製&リネームで行けた!
    • そのままどんどん原因狭めていって、guidに行き着きました……

感想

  • 慣れないplatformだと調査にも時間かかる……
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity Assembly definition Files を利用したより強固なレイヤ定義

はじめに

よく実装は「疎結合にすべき」といわれます。
そのためにレイヤードアーキテクチャに腐敗防止層を設けたり、依存性逆転の原則を適用したりして、3rdパーティのライブラリの変更から自身のコードを守ることがよく行われています。

image.png

Assembly definition

Unityでは Assembly definitionによりアセンブリ空間を定義でき、ディレクトリ構成を基にアセンブリ空間を分離することができます。

レイヤ定義を namespace 任せにするだけでなく、アセンブリ空間さえ分離してやることでより他の開発者にレイヤ構成を遵守させることができそうです。

アセンブリ空間分離のお試しプロジェクトを作成しました。

image.png

以下で、レイヤ分離の意図を解説します。

詳細は知らない

Main はインタフェースの IDependencee を所持しており処理を行います。また、MonoBehaviour でもあるのでUnityから Start() が実行され、 .DoSomething() が実行されます。

public class Main : MonoBehaviour
{
    [Inject] IDependencee dependencee;

    void Start()
    {
        dependencee.DoSomething();
        Debug.Log(dependencee.GetCommonObject().value);
    }
}

Main が知っているのは interface の IDependencee のみであり、実装の詳細である Dependencee に関する知識はありません。これは Dependencee の実装が今後変更された場合も Main には影響が及ばないことを意味しています。

さらに言えば、 Dependencer.asmdefDependenceeImplement.asmdef を参照しないので、Main.cs の実装内では Dependencee クラスのインスタンスをnewすることすらできません。

Dependencer.asmdef が参照するのはいわゆる腐敗防止層である DependenceeAbstruct.asmdef になります。
ここには振る舞いを表すinterfaceのみが定義されています。

namespace AsmdefDependencyPattern.DependenceeAbstruct
{
    public interface IDependencee
    {
        void DoSomething();
        Common GetCommonObject();
    }
}

もちろん DependenceeImplement.asmdef は interface を実装するために、DependenceeAbstruct.asmdef を参照します。
(上の図にはasmdef参照の矢印は含まれていません)

namespace AsmdefDependencyPattern.DependenceeImplement
{
    public class Dependencee : DependenceeAbstruct.IDependencee
    {
        public void DoSomething()
        {
            UnityEngine.Debug.Log("Dependencee.DoSomething()");
        }

        public Common GetCommonObject()
        {
            return new Common {value = 999};
        }
    }
}

Dependency Injection

さて、では MainDependenceeクラスのインスタンスを渡しているのは何者でしょうか?

image.png

Dependency Injection(DI)によってこの問題を解決しています。

Unityではおなじみの Zenject というDIフレームワークを使いました。
IDependencee が要求されたときに、Dependencee のインスタンスが渡されるように設定を行います。

public class DependenceIntermediary : Zenject.MonoInstaller
{
    public override void InstallBindings()
    {
        Container.Bind<IDependencee>().To<Dependencee>().AsTransient();
    }
}

DependenceIntermediary.asmdef は依存性注入のため DependenceeAbstruct.asmdefDependenceeImplement.asmdef の両方を参照します。依存性注入を行う特性上、多くの asmdef を参照するようになると思います。

そしてシーン上に Main , DependenceIntermediary , Zenject.SceneContext を配置することで、SceneContext により IDependenceeDependencee の間の依存関係が解決され、Main.Awake()Main.Start() が実行される時には Dependencee のインスタンスがフィールドインジェクション経由で取得できる訳です。

共通のクラス定義

場合によっては各レイヤを横断するクラスの定義や定数定義が必要になると思われます。
共通の定義は すべての asmdef から参照される必要があるので、これまでの .asmdef に追加するのでなく、新たに共通定義用の asmdef を作成する必要があります。

仮に、 Dependencer.asmdef に共通定義のクラスを作成した場合、DependenceeAbstruct.asmdef から Dependencer.asmdef を参照する必要が出てきます。Dependencer.asmdef は既に DependenceeAbstruct.asmdef を参照しているので、この場合循環参照が発生してしまうので破綻してしまいます。(Assembly definitionでは循環参照を設定することはできません)

新たに作成した Common.asmdef は他の asmdef を参照せず、逆に他3つの asmdef から参照される形になります。Common.asmdef 内のクラスが変更された場合、広範囲の asmdef に影響がでる形になります。

おわり

UnityのAssembly definitionは簡単に定義出来て強力な効果を発揮するのでUnityを使うなら勉強しておいて損は無いはずです。実際私は asmdef のレイヤ定義の過程で依存性逆転の理解がかなりできました。

ちなみに、Dependencer/Dependenceeは造語です。reviewer/revieweeのように、

  • Dependencer: 依存する人(利用側)
  • Dependencee: 依存される側(利用される側)

を意図しています。

asmdefによるレイヤ分けに興味が出てきた方は是非こちらもご覧ください

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UIElementsで要素を水平に配置する方法

UIElementsのUSSで要素を水平に配置する方法。

水平配置したい要素を囲う要素を作って horizontal クラスをつけてあげる。

elements.uxml
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements">
    <ui:VisualElement name="Layout">
        <Style src="style.uss" />
        <ui:Label text="垂直" />
        <ui:Label text="垂直ボタン1" />
        <ui:VisualElement name="HorizontalLayout" class="horizontal">
            <ui:Label text="水平1" />
            <ui:Label text="水平2" />
            <ui:Button text= "水平ボタン"/>
        </ui:VisualElement>
        <ui:Label text="ここからまた垂直" />
        <ui:Button text= "垂直ボタン2"/>
    </ui:VisualElement>
</ui:UXML>
style.uss
.horizontal {
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
}
ExampleEditorWindow.cs
using UnityEditor;
using UnityEngine.UIElements;

public class ExampleEditorWindow: EditorWindow {
    [MenuItem("Window/Example")]
    public static void Open() => GetWindow<ExampleEditorWindow>("Example");

    private void OnEnable() {
        var root = rootVisualElement;
        root.styleSheets.Add(AssetDatabase.LoadAssetAtPath<StyleSheet>("Assets/Editor/style.uss"));

        var tree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/elements.uxml");
        root.Add(tree.CloneTree());
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

シンプルなImageEffect その9。ディゾルブ(dissolve)

TwitterにアップしたディゾルブImageEffectのシェーダコードを貼っておきます。

ディゾルブのコードはググればいくらでもありますが、
せっかくなので境界線に色付けができるシェーダコードを用意しました。

DissolveBorderColor.shader
Shader "ScreenPocket/ImageEffect/Dissolve Border Color"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _DissolveTex ("Dissolve Texture", 2D) = "white" {}
        _DestColor ("Dest Color", Color) = (0,0,0,1)
        _BorderColor ("Border Color", Color) = (0,0,1,1)
        _Rate ("Rate", Range (0.0, 1.0)) = 0
        _Scale ("Scale", Float) = 12
    }

    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            sampler2D _MainTex;
            sampler2D _DissolveTex;
            float _Rate;
            float _Scale;
            fixed4 _DestColor;
            fixed4 _BorderColor;

            fixed4 frag (v2f i) : SV_Target
            {
                half4 col = tex2D( _MainTex, i.uv );
                half4 dis = tex2D( _DissolveTex, i.uv );
                fixed rate = saturate( (_Rate - dis.r) * _Scale );
                col = lerp( col, _DestColor, floor(rate));
                col = lerp( col, _BorderColor, frac(rate));

                return col;
            }
            ENDCG
        }
    }
}

敢えてstep()は使わず、Scaleで乗算することで境界部分を実装しています。
Scaleで乗算値を調整することで、境界線の幅を調整できます。

境界線の色を加算合成したい場合は、下記のコードをご利用下さい(ちょっと計算順番が違う)

DissolveBorderColorAdditive.shader
Shader "ScreenPocket/ImageEffect/Dissolve Border Color Additive"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _DissolveTex ("Dissolve Texture", 2D) = "white" {}
        _DestColor ("Dest Color", Color) = (0,0,0,1)
        _BorderColor ("Border Color", Color) = (0,0,1,1)
        _Rate ("Rate", Range (0.0, 1.0)) = 0
        _Scale ("Scale", Float) = 12
    }

    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            sampler2D _MainTex;
            sampler2D _DissolveTex;
            float _Rate;
            float _Scale;
            fixed4 _DestColor;
            fixed4 _BorderColor;

            fixed4 frag (v2f i) : SV_Target
            {
                half4 col = tex2D( _MainTex, i.uv );
                half4 dis = tex2D( _DissolveTex, i.uv );
                fixed rate = saturate( (_Rate - dis.r) * _Scale );
                col += _BorderColor * frac(rate);
                col = lerp( col, _DestColor, floor(rate));
                return col;
            }
            ENDCG
        }
    }
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

左手座標系と右手座標系の壁を超えよう。

はじめに

左手座標系における回転(コンピュータビジョン系やUnityでよく使われる)と右手座標系(ロボットや物理系でよく使われる)における回転を変換する必要があったのでその時の完全に個人的なメモです。多くのライブラリは右手座標系で作られているので、その際にどのように変換すればいいのかをまとめました。

回転を表す4つのもの

そもそも回転を表すものには

  • オイラー角(ロールピッチヨー)
  • 回転ベクトル
  • 回転行列
  • クォータニオン

の4つがあります。ここではそれぞれの説明については割愛します。たくさん説明されている記事があるので
例えば、

を参考にしていただければと思います。
ただ気になるのは次です。左手座標系で(任意軸回りに)$θ$回転させる、右手座標系で(任意軸回りに)$θ$回転させる場合で何か違いはあるのでしょうか?

結論

基本的には左手座標系は左手座標系の演算、右手座標系は右手座標系演算を考えれば良いです。
ただし、右手座標系の〜を左手座標系からみるとという形になる場合は、変換する必要があります!

回転行列

まず、回転行列について見ていきます。

右手座標系

x軸、y軸、z軸、任意軸回りについて見ていきます。
これはつまり、右手座標系で(ある軸回りに)θ回転させることはどのような変換をすれば良いのかを意味します。

x軸

R^{right}_x = 
  \left(
    \begin{array}{ccc}
      1 & 0 & 0 \\
      0 & \cos \theta & - \sin \theta \\
      0 & \sin \theta & \cos \theta
    \end{array}
  \right)

y軸

R^{right}_y = 
  \left(
    \begin{array}{ccc}
      \cos \theta & 0 & \sin \theta \\
      0 & 1 & 0 \\
      - \sin \theta & 0 & \cos \theta
    \end{array}
  \right)

z軸

R^{right}_z = 
  \left(
    \begin{array}{ccc}
      \cos \theta & - \sin \theta & 0 \\
      \sin \theta & \cos \theta & 0 \\
      0 & 0 & 1
    \end{array}
  \right)

任意軸回り

$n = [n_x, n_y, n_z]$で$\theta$回転させるとすると、

R^{right}_n = 
 \left(
    \begin{array}{ccc}
      n_x^2(1- \cos \theta) + \cos \theta & 
      n_x n_y (1- \cos \theta) - n_z \sin \theta & 
      n_x n_z (1- \cos \theta) + n_y \sin \theta \\
      n_x n_y (1- \cos \theta) + n_z \sin \theta & 
      n_y^2(1- \cos \theta) + \cos \theta &
      n_y n_z (1- \cos \theta) - n_x \sin \theta \\
      n_x n_z (1- \cos \theta) - n_y \sin \theta & 
      n_y n_z (1- \cos \theta) + n_x \sin \theta & 
      n_z^2(1- \cos \theta) + \cos \theta
    \end{array}
  \right)

左手座標系

x軸、y軸、z軸、任意軸回りについて見ていきます。
これはつまり、左手座標系で(ある軸回りに)θ回転させることはどのような変換をすれば良いのかを意味します。
(一番はじめにあげた記事間違えてました。。。修正です。)

x軸

R^{left}_x = 
  \left(
    \begin{array}{ccc}
      1 & 0 & 0 \\
      0 & \cos \theta & - \sin \theta \\
      0 & \sin \theta & \cos \theta
    \end{array}
  \right)

y軸

R^{left}_y = 
  \left(
    \begin{array}{ccc}
      \cos \theta & 0 & \sin \theta \\
      0 & 1 & 0 \\
      -\sin \theta & 0 & \cos \theta
    \end{array}
  \right)

z軸

R^{left}_z = 
  \left(
    \begin{array}{ccc}
      \cos \theta & -\sin \theta & 0 \\
      \sin \theta & \cos \theta & 0 \\
      0 & 0 & 1
    \end{array}
  \right)

任意軸回り

$n = [n_x, n_y, n_z]$で$\theta$回転させるとすると、

R^{left}_n = 
 \left(
    \begin{array}{ccc}
      n_x^2(1- \cos \theta) + \cos \theta & 
      n_x n_y (1- \cos \theta) - n_z \sin \theta & 
      n_x n_z (1- \cos \theta) + n_y \sin \theta \\
      n_x n_y (1- \cos \theta) + n_z \sin \theta & 
      n_y^2(1- \cos \theta) + \cos \theta &
      n_y n_z (1- \cos \theta) - n_x \sin \theta \\
      n_x n_z (1- \cos \theta) - n_y \sin \theta & 
      n_y n_z (1- \cos \theta) + n_x \sin \theta & 
      n_z^2(1- \cos \theta) + \cos \theta
    \end{array}
  \right)

同じです。
これは座標系自体がそうつじつまが合うように設定されているからだと思います。
(θがそもそも逆回転に設定され、かつ、軸も入れ替わっているからだと思ってます。)
(ちょっと自信ないので、もしコメントある方いたらお願いします。)

右手座標系の回転行列を左手座標系へ変換

これは、右手座標系の回転行列を左手座標系からみた場合、どうなるか?という話です。

転置すれば大丈夫です!

なのですべてに転置をかければ良いです。
簡単。

クォータニオン

右手座標系

右手座標系においてクォータニオン(ある任意軸回りに回転させることを意味する)のは、

q^{right} = [q_w, q_x, q_y, q_z]

です。

左手座標系

左手座標系においてクォータニオン(ある任意軸回りに回転させることを意味する)のは

q^{left} = [q_w, q_x, q_y, q_z]

です。さて、ここで、同じでは?となるかと思いますが、同じです。当たり前ですが同じです。
(別に左手座標系でクォータニオンがどうとかそういう話ではないので。。。)

右手座標系のクォータニオンから左手座標系のクォータニオンへ変換

しかし、上記の話をもし右手座標系のクォータニオンを左手座標系で見た場合どうなるか?
という話にするのであれば別です。

q^{right} = [q_{w1}, q_{x1}, q_{y1}, q_{z1}]

q^{left} = [q_{w1}, -q_{x1}, q_{y1}, -q_{z1}]

になります。

回転ベクトル

これについても同じですね!定義は同じですが!

右手座標系

右手座標系においての回転ベクトル(ある任意軸回りに回転させることを意味する)のは、

n^{right} = [u, v, w]

左手座標系

左手座標系においての回転ベクトル(ある任意軸回りに回転させることを意味する)のは

n^{left} = [u, v, w]

右手座標系の回転ベクトルから左手座標系の回転ベクトルへ変換

しかし、上記の話をもし右手座標系の回転ベクトルを左手座標系で見た場合どうなるか?
という話にするのであれば別です。

n^{right} = [u_1, v_1, w_1]

n^{left} = [-u_1, v_1, -w_1]

になります。

外積(クロス積)

右手座標系

右手座標系において、2つのベクトル、$a = [a_x, a_y, a_z]$、$b = [b_x, b_y, b_z]$の外積を取ることは

a \times b = 
  \left(
    \begin{array}{c}
      a_y b_z - a_z b_y \\
      a_z b_x - a_x b_z \\
      a_x b_y - a_y b_x
    \end{array}
  \right)

となります。numpyだと、

>>> a = np.array([1., 2., 3.])
>>> b = np.array([4., 5., 6.])

>>> np.cross(a, b)
# array([-3.,  6., -3.])

で計算できますね!

左手座標系

外積の向きが反対になるのですが、θも反対になっているので、結局そのまま使えるはず。。。

a \times b = 
  \left(
    \begin{array}{c}
      a_z b_y - a_y b_z \\
      a_x b_z - a_z b_x \\
      a_y b_x - a_x b_y
    \end{array}
  \right)

なので変わらずです!

>>> a = np.array([1., 2., 3.])
>>> b = np.array([4., 5., 6.])

>>> np.cross(a, b)
# array([-3.,  6., -3.])

atan

右手座標系

右手座標系においてarctanを取ることは、numpyで

>>> a = 1.
>>> b = 2.
>>> np.arctan2(a, b)
# 0.4636476090008061

と計算できます。

左手座標系

左手座標系でarctanを取ることは右手座標系の演算の反対になるのですが、左手座標系のベクトルを右手座標系になおして、(y軸反転)その後、arctanを取ります。ここでは、θにはマイナスがついているのですが、その角度を左手座標系に戻す(θが逆向きに設定されているので)と。。。結局、そのままになります。

>>> a = 1.
>>> b = 2.
>>> np.arctan2(a, b)
# 0.4636476090008061

クォータニオンから回転行列へ変換

右手座標系

右手座標系におけるクォータニオン$q^{right} = [q_{w1}, q_{x1}, q_{y1}, q_{z1}]$を回転行列$R^{right}$に変換したい。
一般的にはクォータニオンから回転行列の変換は次です。

R^{right}_{q^{right}} = 
 \left(
    \begin{array}{ccc}
      q_w^2 + q_x^2 - q_y^2 - q_z^2 & 
      2(q_x q_y - q_w q_z) & 
      2(q_z q_x - q_w q_y) \\
      2(q_x q_y - q_w q_z) & 
      q_w^2 - q_x^2 + q_y^2 - q_z^2 &
      2(q_y q_z - q_w q_x) \\
      2(q_z q_x - q_w q_y) & 
      2(q_y q_z - q_w q_x) & 
      q_w^2 - q_x^2 + q_y^2 - q_z^2
    \end{array}
  \right)

左手座標系

左手座標系におけるクォータニオン$q^{left} = [q_{w1}, q_{x1}, q_{y1}, q_{z1}]$を回転行列$R^{left}$に変換したい。
変わらず使えます。

R^{left}_{q^{left}} = 
 \left(
    \begin{array}{ccc}
      q_w^2 + q_x^2 - q_y^2 - q_z^2 & 
      2(q_x q_y - q_w q_z) & 
      2(q_z q_x - q_w q_y) \\
      2(q_x q_y - q_w q_z) & 
      q_w^2 - q_x^2 + q_y^2 - q_z^2 &
      2(q_y q_z - q_w q_x) \\
      2(q_z q_x - q_w q_y) & 
      2(q_y q_z - q_w q_x) & 
      q_w^2 - q_x^2 + q_y^2 - q_z^2
    \end{array}
  \right)^T

回転行列から角軸等価ベクトルへ変換

とある文献で回転行列の差を回転ベクトルで算出する話がでていました。具体的には付録Aを見てほしいですが、要は、回転行列を角軸等価ベクトル(回転ベクトルとは少し異なります)する話です。文献自体は右手座標系での算出方法を述べています。が上記の流れでこのまま使えます。

まとめ

上記を使えば、

右手座標系のクォータニオンを右手座標系の回転行列にして、左手座標系の回転行列にしたい

といったこともできます!

あくまでメモなので間違っている点等あればご遠慮なくコメントいただければと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Re: C#(Unity)でHTTP/3通信してみる その壱 ~OSSの選定からビルドまで~

この記事は
【HTTP/3】C#でHTTP/3通信してみる その壱 ~OSSの選定からquicheの.lib/.dllビルドまで~
2020/5/16 (土) 現在 の状況に合わせて修正したものです。

QUIC, HTTP/3 の現状

C#でHTTP/3通信してみる その弐 から早半年……。
QUIC, HTTP/3 の仕様策定は Late Stage Processing1 に入り、いよいよ佳境を迎えています。
実装を巡る状況も大分変わっているので、現状にあわせて C#でHTTP/3通信してみる を書き直してみようと思います。

前回の記事 C#でHTTP/3通信してみる その壱 からの変更点まとめ

  • ゴールの再設定 → Unity で動作させたい
  • OSS の再選定 → 検討の結果 quiche 続行 (今回の記事のメイン)
  • ビルド手順の微修正
    • BoringSSL を git submodule 経由で取得するように変更
    • 最新のバージョンに合わせて quiche のビルド手順を修正

ゴールを決める

新しいゴール : Unity で HTTP/3, QUIC 通信する
個人的な事情により、前回の記事では「気が向いたら」扱いだった Unity での利用をゴールに設定します。
前回同様に C# で動かすモジュールを作成してから、それを Unity で叩く流れを紹介する予定です。
全部を一記事にすると非常に長くなりそうなので連作にする予定は変わらずです。
以下、投稿する予定の記事です。

  • Re: C#(Unity)でHTTP/3通信してみる その壱 ~OSSの選定からビルドまで~
  • Re: C#(Unity)でHTTP/3通信してみる その弐 ~Windowsでquicheのサンプルを動かしてみる~
  • Re: C#(Unity)でHTTP/3通信してみる その参 ~Unityから使ってみる~

前回の反省を踏まえ その参 までの作業を大部分終えて当記事を投稿しているので、今回は記事間隔が数カ月あくような事態は発生しない見込みです。
※その弐は明日 5/18 (日) に、その参は 5/23 (土) あたりに公開予定です

利用する OSS の条件

(Unity で使う関係上、前回に比べて条件を少し厳しくしています)

必須

  • HTTP/3 のサポート
  • Unity で利用したい
    • C# 製、もしくは筆者の慣れの問題から C/C++ 製がベター
      • ただし、 Unity での利用を想定しているので C# のバージョンに縛り有り
    • RUST の場合は C ラッパが用意されているものであればセーフに
  • Android, iOS のサポート
    • 自前でビルド通せば動くケースも多々あるが、結構しんどいので公式にサポートしてるかどうかを判断基準に
    • 過去の実績や方針等で今後サポートの可能性が高い場合はセーフに
  • アクティブに更新されているもの
    • RFC へ追いつかない雰囲気があるものは除外

できれば欲しい

  • パフォーマンスへの配慮
    • ゲーム(Unity)で使うことを目指しているのでメモリ周りの制御やスレッドでの動作サポートがデフォルトであるのが望ましい
    • 受信データの随時受け取り等メモリ周りの制御は特に重要
  • qlog への対応
  • HTTP/2 対応
    • フォールバック用にサポートしてると楽
  • 輻輳制御アルゴリズムに BBR が採用されている

不要

  • QUIC DATAGRAM2 への対応
    • まだ QUIC DATAGRAM に対応している OSS はほぼ無い為、今回の条件には含めない

OSS 選定 - 候補を選ぶ

半年前に比べて選択肢がかなり増えたので、一通り再確認していこうと思います。
基本的には QUIC WG base-drafts の Implementations に掲載されている OSS から選定していきますが、一部例外も有りです。

また、 Robin Marx さんが A Study of QUIC and HTTP/3 Implementation Diversity - submitted to EPIQ2020 という記事で QUIC を実装したライブラリを一部ピックアップして、アルゴリズムや性能比較をしてくださっているのでこれも参考にします。

それでは、いってみましょー。

Apache Traffic Server

Apache Traffic Server は Apache Software Foundation が開発を進めている高性能 HTTP キャッシュプロキシサーバ(クライアント実装も有り)です。
以下 公式ドキュメント より抜粋。

Apache Traffic Server™ はネットワークの縁で頻繁にアクセスされる情報をキャッシュすることでネットワークの効率とパフォーマンスを改善するハイパフォーマンスなウェブプロキシーキャッシュです。これはより速い配信と帯域使用量の削減を実現すると同時に、エンドユーザに物理的に近いコンテンツを提供します。Traffic Server は企業向けコンテンツ配信、インターネットサービスプロバイダ (ISP) 、バックボーンプロバイダ、そして大きなイントラネットが持つ既存の利用可能な帯域幅を最大化することによって改善するために設計されました。

パフォーマンスに寄せた実装になってそうなので惹かれますが、モバイル系に対応していない(反面、出自的に Linux 系には物凄い強いです)ので今回は採用を見送ります。

LiteSpeed QUIC (LSQUIC)

LSQUIC は Web サーバとして最近名を上げつつある LiteSpeed の開発を手掛ける LiteSpeed Technologies が主導する C 言語製の OSS です。
LiteSpeed Web サーバー、 LiteSpeed ADC 、 OpenLiteSpeed 等の LiteSpeed Technologies の製品・サービスにて利用されているようです。
対応プラットフォームは Linux, FreeBSD, MacOS, Windows とのこと。
サーバ系の利用を目的としたものなのでこちらも出自的に仕方ないですが、モバイル非対応の空気を感じるので今回は採用を見送ります。

Proxygen

Proxygen は Facebook により開発が進められている C++ 製の OSS です。
同様に Facebook が開発している QUIC を実装する mvfast を下層に持ちます。
現在はサーバ側をメインに実装が進んでいるようで、 GSO にも対応している等パフォーマンス的にかなり期待が持てそうです。
現状の対応プラットフォームは Ubuntu 18.04 と Mac OSX です。
将来的には Android, iOS へは対応しているので良さそうな雰囲気なのですが、 Windows をサポートしておらず、 C# へ繋ぐ開発を行うのが結構しんどそうです。
心惹かれるものがありますが今回は採用を見送ります。

Neqo

Neqo は Mozila が開発している Rust 製の OSS で、 Firefox に採用されています。
build.rs 見る感じ Windows, Andoroid に対応している(iOS はまだなさげ)のでいい感じなのですが、フル Rust 製(C ラッパが用意されてない)なので今回は採用を見送ります。

nghttp3

nghttp3 は前回の記事でも検討した C 製の OSS です。
CURL の実装である libcurl の下層(HTTP/2 部)に用いられている nghttp2 のメインコミッターである Tatsuhiro Tsujikawa さんが主導し実装が進められています。
QUIC レイヤーの実装は同じプロジェクト内で開発されている ngtcp2 に依存しています。
現状 Andoroid, iOS には対応していないようですが、過去の実績(nghttp2)から 将来的に対応する可能性は高めです。
条件にマッチしているので 候補1 とします。

picoquic

picoquic は Private Octopus が開発している C 言語製の OSS です。
HTTP 向け以外の QUIC 拡張を試すことを目的の一つとしている点が他の OSS と大きく異なっています。
picoquic をベースにした、 DNS over QUIC の実装である quicdoq も同時に開発しているようです。
SiDUCK3 にも現時点で既に対応していたり、中々意欲的なプロジェクトだと思います。
Visual Studio 2017 のプロジェクトが用意されていたりと開発もし易そうですが、残念ながら対応プラットフォームが Windows/Linux/MacOZ/FreeBSD とモバイル系には非対応です。
プロジェクトの目的から今後も Andoroid, iOS への対応は期待薄なので今回は採用を見送ります。

Quant

QUANT は NetApp により開発されている POSIX 及び IoT プラットフォーム向けに開発されている C11 製の OSS です。
QUIC レイヤーのみの提供で HTTP/3 には対応していない為今回は採用を見送ります。

quiche

quiche は CDN ベンダーの CloudFlare が主導して開発が進められている Rust 製の OSS です。
Rust API の上に被せる C 言語製のラッパも提供しているのも特徴で、 C/C++ のアプリケーションやライブラリからも簡単に呼び出すことができます。
現段階で Android, iOS のビルドについても標準でサポートしているのも強みです。
Windows の標準ビルド環境は整っていませんが、 前回の記事で採用した関係で Windows のビルドについては問題なしとします。
条件にマッチしているので 候補2 とします。

quicly

quicly は H2O HTTP server での利用を目的として開発されている C 製の OSS です。
CDN ベンダーの Fastly 所属である Kazuho Oku 氏と Jana Iyengar 氏が主導し開発が進められているようです。
quicly については Can QUIC match TCP’s computational efficiency? という記事が書かれる程パフォーマンスについて意識が割かれています。
ただし、非常に残念ながらモバイル系には非対応な為、今回は採用を見送ります。

Quinn

  • Language: Rust
  • Roles: library, client, server
  • HTTP/3 Support: No (将来的には対応予定)
  • License: MIT
  • Repository: quinn

Quinn は Dirkjan Ochtman 氏と Benjamin Saunders 氏によって開発されている Rust 製の OSS です。
現在 Linux/MacOS/Windows に対応しています。
モバイル非対応 & フル Rust 製(C ラッパが用意されてない)ので今回は採用を見送ります。

libcurl

libcurl は CURL の内部実装である C 言語製の OSS です。
libcurl 自体には QUIC, HTTP/3 層の実装は含まれておらず、他の OSS(quiche もしくは ngtcp2 のいずれかを選択)に依存しています。
今回紹介する OSS の中でも随一の歴史を誇るだけあり、 HTTP の機能が非常に豊富なのが強みです。
(ただし、その分内部実装も若干歴史を感じさせるものとなっており、オーバーヘッドは若干気になります)
HTTP3.md を見る限りだと現状の対応プラットフォームが謎(プラットフォームも quiche/ngtcp2 に依存?)ですが、過去の実績的には Android/iOS といったモバイルにも対応するのは間違いない安心感があります。
条件にマッチしているので 候補3 とします。

MsQuic

MsQuic は MicroSoft が開発をしている C 言語製の OSS です。
以下を目指して開発が進められています。

  • Windows カーネルの HTTP/3 スタックでの利用
  • ASP.NET Core の Kestrel 及び .Net の HttpClient での HTTP/3 のサポート
    • どちらも .Net 5 のプレビュー版にて実装中
    • Kestrel は QUIC 層も完備
  • MS365 や Windows SMB での活用

「受信処理を別スレッドで非同期に処理できる」、「受信データを一括ではなく都度処理できる」、「データコピーしない送信モードも完備」、「GRACEFUL シャットダウンも用意されている」等、現時点でも非常に高機能で心惹かれますが HTTP/3 には非対応 です。かなしい。
と言う訳で今回は採用を見送ります。

.Net 5

MsQuic 項でも触れたように、 .Net 5 において HTTP/3 のモジュールの実装が進められています。
しかし、残念ながら Unity は現状 .Net 5 に対応していない為、今回は採用を見送ります。
また、Unity Forums のやりとり によると「It is very unlikely that any .NET Core or .NET 5 support will land in 2020 LTS.」とのことなので、Unity 2020 の LTS で .Net 5 対応する可能性は低く、当面利用できないと捉えておくのが良さそうです。
将来的には.Net の Core class ライブラリの利用及び Mono の CoreCLR への置き換えを検討しているようなので、これに期待しましょう。
(CoreCLR は実験的なものだからリリースできるかあやしいとも書いているので過度の期待は厳禁かもしれません)

OSS 選定 - 利用する OSS の確定

長くなりましたが、候補が nghttp3, quiche, libcurl に絞られました。
前回の記事での検討時にもこの 3 つの OSS は挙がっているので、モバイルも含めての C# での HTTP/3 の実装については、なんだかんだで半年前とあまり状況は変わっていないと言えそうです。
(モバイルは HTTP/3 普及期に入ってから実装が進むと思われるので、これは自然の流れかなと思います)

この 3 ライブラリを できれば欲しい の条件 + α で再度検討してみます。

ライブラリ スレッド メモリ HTTP/2 qlog 輻輳制御
     nghttp3      × × NewReno
     quiche      × × NewReno
CUBIC
     libcurl      ×
  • - : 下層のライブラリの実装に依存
  • スレッド : 内部的にスレッドで処理を行うかどうか。非同期ソケットやイベントで動作するのとは別。スレッドセーフかどうかとも別
  • メモリ : 受信したデータを都度ライブラリから受け取って処理することができるか

上記の表ベースでは、 HTTP/2 が利用できる面で libcurl が一歩リードしています。
しかし、 libcurl の GitHub - Wiki によると ストリーム多重化 が現状未実装のようです。
多重化が使えないのは流石に HTTP/3 採用の意味が薄いので、 libcurl での HTTP/3 実装は時期尚早として今回は採用を見送ることにします。

残る nghttp3 と quiche は上記の条件ではほぼ拮抗しているようです。
先ほどの Robin Marx さんの記事 A Study of QUIC and HTTP/3 Implementation Diversity - submitted to EPIQ2020 でもそこまで優位な差があるようには見えません。
どちらにするか非常に悩ましいですが、以下の理由から今回は quiche を選択することに決定とします

  • quiche は現時点で Android/iOS 対応している
  • quiche は Hystart++4 に最近対応した
  • nghttp3 はサンプルがまだ整備されていない
    • nghttp3.h にあるリファレンスが丁寧なので、しっかり読めば問題なく使えそうではありますが、前回の記事で触ったことがある quiche に比べるとちょっと心理的なハードルがあります

補足 : 現状での Unity での QUIC, HTTP/3 実装方針例

今回の記事では quiche を使いますが、製品に利用するのであれば libcurl を使う方が HTTP 関連の色々な処理をしてくれて楽なのでお勧めです。
ただし、 libcurl の実装方針が HTTP/2 と HTTP/3 で同様と想定する(現状の実装ではそう見えます)と、パラメータ設定においてかなりの部分が隠蔽されてしまう為に、制御があまり効きません5
通常のゲームやアプリにおける HTTP/3 通信で QUIC 関連のパラメータをいじりたいケースは稀なのであまり気にしないで良いとは思いますが、利用する際は心にとめておいた方が良いと思います。

また、今までの内容を踏まえ、お勧めのモバイル環境を含む Unity での QUIC, HTTP/3 実装方針について以下にまとめてみました。
時期別(あくまで個人的な予想ベース)に分けてあるので参考にして頂ければと。

  • 今すぐ
    • QUIC を使いたい場合 → quiche もしくは nghcp2 で頑張ろう
    • HTTP/3 を使いたい場合** → quiche もしくは nghttp3 で頑張ろう
  • libcurl の対応がある程度固まって、 Unity が .Net 5 に対応するまでの間 (予想 : 2020 年後半くらい~)
    • QUIC を使いたい場合 → MsQuic を利用 (タイミング次第ではまだ利用が厳しい可能性あり)
    • HTTP/3 を使いたい場合** → libcurl を利用
  • Unity が .Net 5 に対応した後 (予想 : 2021 年後半くらい~)
    • QUIC を使いたい場合 → Kestrel を利用
    • HTTP/3 を使いたい場合 → HttpClient を利用

.Net 5 対応はよ……。

quiche をビルドするまでの道のりを確認

注意 : ここからの内容は前回の記事の内容を最新バージョンの quiche での作業に差し替えて、以下を書き換えたものです

  • BoringSSL を git submodule 経由で取得するように変更
  • 最新のバージョンに合わせて quiche のビルド手順を修正

それでは quiche をビルドしてみましょう。
リポジトリ : https://github.com/cloudflare/quiche
前述した通り、 quiche は RUST 製の OSS ですが、C 製のラッパ(quiche.h)も提供してくれています。
筆者は Rust はほとんど触ったことがないレベルなので、今回はこの C ラッパ層を呼び出せるような Windows の動的/静的ライブラリを作成します。
C# から直接 Rust 製の .dll の呼び出しも可能なので、慣れている人はこうしたラッパを作らずにそのまま Rust でビルドしても良いと思います。
(が、C# 層から使い易いようにどちらにせよ一段噛ませた方が良い印象はあります)

ちなみに、 C# から呼び出す段では .dll を作成する必要がありますが、 OSS の上にもう一段ラッパ層を作成する予定なので、quiche のビルドは .lib/.dll どちらでも問題ありません。
好みに合わせて作成できるように、当記事では両方の手順を記載しておきます。

quiche のビルド方法の確認

quiche のビルドには cargo build を用います。
cargo build は Rust のビルドシステム&パッケージマネージャである Cargo を使ったビルドコマンドです。

デフォルトの設定では libquiche.a しかビルドしてくれないようなので、 .lib や .dll をビルド可能なように自前で設定してあげる必要があります。
また、quiche は BoringSSL に依存しているので、こちらも同様に .lib/.dll を用意してあげる必要があります。

Calling quiche from C/C++
quiche exposes a thin C API on top of the Rust API that can be used to more easily integrate quiche into C/C++ applications (as well as in other languages that allow calling C APIs via some form of FFI). The C API follows the same design of the Rust one, modulo the constraints imposed by the C language itself.
When running cargo build, a static library called libquiche.a will be built automatically alongside the Rust one. This is fully stand-alone and can be linked directly into C/C++ applications.

BoringSSL のビルド準備

と言う訳で、まずは BoringSSL を準備しましょう。
quiche のリポジトリが BoringSSL を submodule として取り込んでいるので、まずは quiche を --recursive 付きで clone します。

$ git clone --recursive https://github.com/cloudflare/quiche

BoringSSL の Windows 版(.lib/.dll)ビルドには以下のモジュールが必要です。

  • CMake (必須)
  • Perl (必須)
  • NASM (必須)
  • C/C++ コンパイラ (必須)
  • Go (必須)
  • Ninja (推奨)

1つずつセットアップしていきましょう。

CMake

3.0 以上が必要です。
https://cmake.org/download/
から Windows 用のインストーラーをダウンロードして展開しましょう。
今回は CMake 3.15.3 を使用しています。

Perl

Perl の最新バージョンが必要です。
Windows では ActivePerl と MSYS Perl が利用できます。
StrawberryPerl も使えるようですが、 CMake と PATH の取り扱いで競合が起きるようで面倒とのことです。
ActivePerl は以下のサイトから入手可能です。
https://www.activestate.com/products/activeperl/
アカウント登録をするとリポジトリができるので、「Buildタブ」 ⇒ 「Windows 10 タブ」から好きな形式でダウンロードしましょう。
perl.png
インストーラーから入れると勝手にパスが設定されますが、環境変数 PERL_EXECUTABLE で指定しても OK です。
今回は ActivePerl 5.8 を使用しています。

NASM

BoringSSL は一部にアセンブリを使用しているようで、 Windows でビルドするには NASM が必要です。
NASM は Netwide Assembler の略で x86 系を対象としたアセンブラです。
https://www.nasm.us/ からダウンロードしてパスを通しましょう。
今回は NASM 2.14.02 を使用しています。

C/C++ コンパイラ

Windows ビルドでは Platform SDK 8.1 以降を含む MSVC 14(Visual Studio 2015) 以降がサポートされているようです。
GCC 4.8 移行でもいけるようですが、ドキュメントの表記が maybe なので MSVC 側を使う方が無難そうです。
CMake 時には MSVC の cl.exe へのパスを通してあげる必要がありますが、単体でパスを通すのではなく関連の環境変数をまとめせて設定してくれる vcvars64.bat 等を使いましょう。
参照 コマンドラインからMicrosoft C ++ツールセットを使用する(MSDN)
今回は Visual Studio 2019 を使用しています。

Go

最新の安定バージョンが必要です。
https://golang.org/dl/ 等からインストールしてパスを通しましょう。
パスとを通す代わりに、環境変数 GO_EXECUTABLE でもパス指定が可能です。
今回は Go 1.13 を使用しています。

Ninja

Ninja は CMake の置き換えを目指して作られた高速なビルドシステムです。
Windows 版 BoringSSL をビルドするには、 この Ninja を使うか CMake のみで実施するかの二つの選択肢があるようです。
しかし、 Windows 環境での CMake のみでのビルドはメンテされていないようなので、現状では Ninja を使うしかなさそうです(どっちにせよビルドが圧倒的に早いので Ninja の方が良いとは思いますが)。
と言う訳で https://github.com/ninja-build/ninja/releases から Ninja を落としてパスを通してください。
今回は Ninja 1.9.0 を使用しています。

BoringSSL のビルド

上記の準備が完了したら Ninja を使って BoringSSL のビルドを実行します。
手順はとても簡単です。

最初に clone した quiche のリポジトリの deps\boringssl に移動して以下のコマンドを叩いてください。

(必要に応じて MSVC 等へのパスを通す)
$ mkdir build
$ cd build
$ cmake -GNinja ..
$ ninja

何かしらにパスが通ってないと
cmake -GNinja ..
でエラーが出ますが、エラーメッセージが分かり易いので苦労しないと思います。
(vcvars64.bat 実行漏れでのパス設定忘れに注意)

成功すると以下の .lib ファイルができます。

build\crypto\crypto.lib
build\decrepit\decrepit.lib
build\ssl\ssl.lib

このうち crypto.libssl.lib を使用します。

ビルドオプション

リリースビルドしたい時には -DCMAKE_BUILD_TYPE=Release を指定します。

$ cmake -DCMAKE_BUILD_TYPE=Release -GNinja ..

.dll をビルドしたい時には -DBUILD_SHARED_LIBS=1 を cmake 時のオプションとして指定します。
※ quiche のビルドには .lib が要求されるので今回は不要です

$ cmake -DBUILD_SHARED_LIBS=1 -GNinja ..

また、OPENSSL_SMALL を定義するとコードサイズ等削れるようです(詳細は追っていないです)。
今回はお試し実装なので、この設定や使用する暗号スイートの制限等は無しでそのまま使います。

C ランタイムライブラリの指定

quiche のビルドは Rust の Cargo で行いますが、Cargo では MDd, MTd を指定することはできません。
また、MTのビルドも若干手間が掛かります。
BoringSSL の設定はデフォルトでは MD でのビルドなので、基本的にはそのまま MD で行くのが良さそうです。
何らかの事情で MT でビルドしたい場合には、CMakeLists.txt に以下の変更を加えることで実現可能です。

set(CompilerFlags
    CMAKE_CXX_FLAGS
    CMAKE_CXX_FLAGS_DEBUG
    CMAKE_CXX_FLAGS_RELEASE
    CMAKE_C_FLAGS
    CMAKE_C_FLAGS_DEBUG
    CMAKE_C_FLAGS_RELEASE)
foreach(CompilerFlag ${CompilerFlags})
    string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}")
endforeach()

project(BoringSSL NONE) の後ろにでも雑に付け加えましょう。
※Windoiws 以外でもビルドする場合は if(WIN32) 等で囲むこと

Ninja により生成された細かいビルドオプションを確認したい場合は cmake -GNinja .. 後に生成される build\build.ninja 内にあるので、そちらを参照しましょう。

quiche のビルド準備

いよいよ quiche のビルドに入ります。

Rust 環境のセットアップ

先ほども書いたように、 quiche のビルドには Rust のビルドシステム&パッケージマネージャである Cargo が必要です。
Cargo は Rust に付属されているので、まずは Rust をインストールします。
@euledge さんが Windows10でRustの開発環境を構築 をまとめてくださっているので、これを参考に環境を構築してください(詳細は割愛)。
現状 Rust のバージョンは 1.39 かそれ以降のものを利用する必要があるようです。

Cargo のコンフィグファイルを修正する

このリポジトリのカレントディレクトリに Cargo.toml という Cargo のコンフィグファイルがあるので、 .lib/.dll がビルドされるようにこのファイルの crate-type を変更します。

.lib を作りたい場合
crate-type = ["staticlib"]

.dll を作りたい場合
crate-type = ["cdylib"]

まとめて
crate-type = ["staticlib", "cdylib"]

BoringSSL のライブラリにパスを通す

環境変数 QUICHE_BSSL_PATH に先ほどビルドした BoringSSL のカレントディレクトリを指定してください。
更に、 BoringSSL でビルドした際に生成されるデフォルトのパスを quiche は見に行かないので、ディレクトリ構成を変更してあげる必要があります。

  • Debug 時
    • crypto.lib 関連ファイルを build\crypto\Debug に入れる
    • ssl.lib 関連ファイルを build\ssl\Debug に入れる
  • Release 時
    • crypto.lib 関連ファイルを build\crypto\RelWithDebInfo に入れる
    • ssl.lib 関連ファイルを build\ssl\RelWithDebInfo に入れる

上記の構成にするのが面倒な場合は BoringSSL 側のコンフィグを直すか、もしくは quiche 内に同梱されている src\build.rs の以下の内容を任意のパスに修正すると良いです。

        if cfg!(debug_assertions) {
            return format!("{}/Debug", lib);
        } else {
            return format!("{}/RelWithDebInfo", lib);
        }

quiche のビルド

お疲れさまでした。ここまでくればあとは Cargo を叩くだけです!

$ cargo build

ビルドが成功するとカレントディレクトリ直下にある target¥Debug フォルダ内に .lib/.dll が生成されます。
(適当に Visual Studio からリンクして呼び出せることまでは確認してますが割愛します)

Release ビルドしたい時は --release オプションを付けます。

$ cargo build --release

また、この手順で生成されるのはランタイムライブラリは MD、プラットフォームは x64 であることに注意してください(x86 ビルドの方法は未調査です)。
ログの詳細が欲しい場合は --verbose オプションを付けると若干詳しい内容が出てきます。

長くなりましたが、以上で Re: C#(Unity)でHTTP/3通信してみる その壱 ~OSSの選定からビルドまで~ の手順は完了です。
Next!! → Re: C#(Unity)でHTTP/3通信してみる その弐 ~Windowsでquicheのサンプルを動かしてみる~

おまけ: quiche を MT でビルドする

環境変数 RUSTFLAGS に MT であることを明示する以下のオプションを設定してから cargo build してください。

RUSTFLAGS=-C target-feature=+crt-static

参考 : https://doc.rust-lang.org/reference/linkage.html


  1. https://github.com/quicwg/base-drafts/blob/master/CONTRIBUTING.md 

  2. QUIC DATAGRAM は QUIC 上で信頼性の低いデータ通信を行う為の拡張仕様です。詳細は『くいっく』DATAGRAM編にて解説していますよ!(宣伝) 

  3. Simple Datagram Usability and Connectivity Kata の略で、QUIC DATAGRAM の接続性のテストに用いる仕様です。詳細は『くいっく』DATAGRAM編にて解説していますよ!(宣伝) 

  4. 参考 : TCP Slow Startを改善する HyStart++について - ASnoKaze blog 

  5. 参考 : 【Unity】Unite Tokyo 2019 「大量のアセットも怖くない!~HTTP/2による高速な通信の実装例~」講演と壇上では語られなかった6つのこと。 - SEGA TECH BLOG 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity x LeapMotionでもAssemblyDefinitionFilesを使いたい

Leap Motion

Leap Motionは独自のセンサとカメラ技術により、人の手をセンシングするデバイスです。
安価で入手しやすいハンドトラッキングツールとして、発売から5年以上たった今でも人気のデバイスです。

image.png

Leap Motion リープモーションLM-C01-JP : yodobashi.com(現在は取り扱いなし)

SDK

Leap Motionを使った開発をする上で、以前は公式サイトから開発環境に応じたSDKを入手する必要があったようですが、現在はGitHubからダウンロードすることが出来ます。

UnityのSDKも4.5.0が最近アップデートされるなど、VRなどの用途からかまだまだ開発が続いていきそうです。

Assembly Definition

さて、Unityにはディレクトリ単位でAssemblyを分ける機能があり、ビルド時間短縮などのために自分のプロジェクトと3rdのライブラリのAssemblyを分けるために、Assembly Definition File(asmdef)を定義することがよく行われます。

上の記事でも

ライブラリや、スクリプトを含んだアセットを公開する場合はadfを定義してアセンブリを分割するべきです。
むしろやってくださいおねがいします。

とあるように、開発者がUnity向けにライブラリ開発を行う場合はAssembly Definitionを考慮して開発することが望まれます。しかし、LeapMotionのUnity向けライブラリには2020/5/16日時点で

Assembly Definition Fileは定義されていません。

Assembly Definition Fileは定義されていません。

?

無いとどうなる

無くても問題ないでしょ?というのはもちろんそうです。LeapMotionの中の人たちもこのスタンスなのでしょう。
しかし、すでにAssembly Definitionを持った自分のプロジェクトにLeapMotionのSDKを突っ込みたくなった時問題になります。具体的には

  • LeapMotionの定義を使用するAssemblyのディレクトリ内部でSDKを展開する
    • つまり自プロジェクトと同じAssemblyに含めてしまう
  • 自分でSDKのAssembly Definitionを設定する

の二択を迫られます。前者の場合で回避できるなら良いですが、Assembly Definitionの利点を殺しています。また、今後LeapMotionを必要とするAssemblyすべてに配置する必要が出てきます。

自分で定義

私が定義したAssembly DefinitionをGitHubに公開しています。

使ってみたい方は ここからunitypackageをダウンロード し、自分のUnityプロジェクトにimportしてください。

UnityModules-4.5.0のデフォルトからディレクトリ構成を変えていなければ、そもまま同じディレクトリに.asmdefが配置されます。

余談ですが、UniVRMでは.asmdefのみのunitypackageを分けてリリースしているようです

注意

  • 現状 4.5.0 の Core.unitypackage にしか対応していません。
  • 将来LeapMotionが正式にAssembly Definitionに対応した場合、間違いなく競合します。

***

ここで「それでは良いLeapMotion+Assembly Definitionライフを」で終わってもよいですが、折角なので苦労話も載せておきます。

苦労話

さて、新しくAssembly Definitionを追加する場合、まず何をするでしょうか?
そう、とりあえず右クリックで[Create]->[Assembly Definition]から.asmdefを作りますね。

では、UnityModules-4.5.0の Assets/Plugins/LeapMotion/Core にLeapMotion.Core.asmdefを作ってみましょう。何が起こるでしょうか?ちなみにUnity のバージョンは2019.3.12です。

image.png

正解は603件のコンパイルエラーでした。

why?

LeapMotionは殊勝なことにSDKのテストをしっかり実装しており、テストコードもunitypackageに含めています。しかし、LeapMotion.Core.asmdefにて独自のAssemblyを定義したことにより、NUnit などのテストのための定義が Assets/Plugins/LeapMotion/Core/ から参照できなくなってしまったのが原因です。
すべての定義が Assembly-CSharp にある状態なら問題にならなかったのですが、Assembly Definitionにて Assets/Plugins/LeapMotion/Core 以下のディレクトリが独立したAssemblyになったため、NUnit など必要な定義を参照できる設定を.asmdefにしてやる必要があります。

how?

LeapMotion.Core.asmdefの設定だけを良い感じにすればよいのでしょうか?

答えは No です。

全ての Tests/ のディレクトリにテスト用のAssembly Definition設定をする必要があります。

しかもこれ、以前(Unity2018.x)ではチェックボックスで切り替えれたのですが、2019.2からなくなりました。

[Create]->[Testing]->[Tests Assembly Folder]で作ることはできますが、いくつもあるLeapMotionの Tests/ ディレクトリ全てにはとてもやってられません。

Unityの仕様上.asmdefが追加される度にコンパイルが走ってハングするので尚更です。

全ての Editor/ ディレクトリの.asmdefに platfrom=Editor を定義してやる必要があります。

Assembly Definition無しなら Editor/ という名のディレクトリは自動でEditor専用のディレクトリとして認識されますが、Assembly Definitionを追加した場合、Editor/ 内に platfrom=Editor になるように自分で.asmdefを設定しなくてはなりません。

なぜ[Create]->[Editor Assembly Definition]のような操作がないのでしょうね。こちらも1つ設定するたびにUnityがハングするのでとても手作業ではやってられません。

余談ですが、おそらく最短の platfrom=Editor 手動設定順は

  • [Any Platformのチェックを外す]
  • [Deselect all]
  • [Editorのチェックを入れる]
  • [Apply]

だと思います。

これキツくね?

私も手間がリターンに合わないと判断して一度は諦めました。しかし、できそうな手順を思いついたのでやってみました。キツかったですが詰んではなかったです。

.asmdefの自動生成

人間の温かみのある手作業で.asmdefを追加していくと毎回Unityがハングするので、Unityに気づかれないように.asmdefを一括で追加します。

LeapMotion/Core/ 以下にある Editor/Tests/ という名前のディレクトリを発見し、ディレクトリのパスに応じた名前の.asmdefを自動生成するエディタ拡張を実装しました。

例えば、 LeapMotion/Core/Scripts/Animation/Editor/ のディレクトリには LeapMotion.Core.Scripts.Animation.Editor.asmdef というAssembly Definition Fileが生成されます。

.asmdefの一括参照設定

.asmdefを追加しただけでは参照の設定がないのでコンパイルエラーは直りません。
人間の温かみのある手作業で.asmdefを一つ一つ編集するとそのたびにUnityがハングするので、LeapMotionとは別の個人開発でAssembly Definition Fileを同時に編集するエディタ拡張を作っていたので活用しました。

とはいえ、内部の依存関係はSDK開発者でない私にはわからないので、取り合えずは全ての.asmdefに LeapMotion.Core.asmdef への参照を追加し、以降はエラー内容を確認して対応しました。

流石はLeapMotion、Assembly Definitionこそ無かったものの破綻した依存関係にはなっておらず、苦労はしましたが.asmdefの設定に無事成功しました。これからは FrameImage クラスを使いたいAssemblyにだけ LeapMotion.Core.asmdef の参照を追加すればよいのです。

おわりに

UnityのAssembly Definitionは便利で強力なのでもっと広く使われてほしいのですが、今回のようなややこしい問題に直面するとなると、正直まだ難しいのかなと思ってしまいます。

しかし、ライブラリ作成者は別です。Scriptを含む場合は是非Assembly Definitionを設定してください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む