- 投稿日:2020-05-16T22:49:05+09:00
UnityのWebGLのPluginの機能を呼び出したら何か知らないけど404になった話
前置き
- Unity5系のプロジェクトを2018系に移行した際の話
(結論Unityのverは関係なさそうだった)- twitterのtweetAssetsを自作してて、新しいゲームにimportしてみたが、何故かWebGLビルドでのみつぶやけなくなった(404になる)
- いくつかのゲームにimportした時は特に問題なく動作していた……
- WebGLのデバッグの仕方とか分からず絶望した
事象
error_messageFailed to load resource: the server responded with a status of 404 (Not Found)
- どうにもリソースが見つからない模様。plugin呼び出しのタイミングだったので、pluginが見つからない感じ?
解決方法
- 正しい対処かは不明だが、plugin(.jslib)のguidを発行し直せば解消した!
- 発行し直しは以下のような感じ。(.metaを削除して.metaを作り直すのはダメだった)
- 該当plugin(.jslib)を複製
- 複製元を削除
- 複製したファイルを複製元の名前でリネーム
- guidを発行し直して、ビルドし直して起動して、エラーになってた部分の機能を呼び出すと直ってた!
解決までの試行錯誤
- とりあえず
Google先生
- そもそも何で発生しているかが意味不明な状況。とりあえずそれっぽいキーワードでGoogle先生に聞いてみるも、良い感じのものが見つからなかった……
Chromeのデベロッパーツール
でlogを確認
- そもそもデバッグのやり方が分からなかったが、Chromeで
表示 > 開発/管理 > デベロッパーツール
でデベロッパーツールを開き、ConsoleよりUnity側で出力したlogを確認することができた.jslib
の出力設定- Unityのverを変えて試してみた
- 大丈夫なprojectが今回のverよりも低い2018系だったので、そのverに合わせて実行とかビルドしてみた
- 結果はダメだったりOKだったりと規則性がなく、よく分からない状態に……
- 試しに同じ処理を書いたpluginを追加して実行
- 新しく追加したpluginは正常に呼び出された!
- importし直しても結果は変わらずだった……
- あれ、作り直せばいける?
- 複製&リネームで行けた!
- そのままどんどん原因狭めていって、guidに行き着きました……
感想
- 慣れないplatformだと調査にも時間かかる……
- 投稿日:2020-05-16T21:30:57+09:00
Unity Assembly definition Files を利用したより強固なレイヤ定義
はじめに
よく実装は「疎結合にすべき」といわれます。
そのためにレイヤードアーキテクチャに腐敗防止層を設けたり、依存性逆転の原則を適用したりして、3rdパーティのライブラリの変更から自身のコードを守ることがよく行われています。Assembly definition
Unityでは Assembly definitionによりアセンブリ空間を定義でき、ディレクトリ構成を基にアセンブリ空間を分離することができます。
レイヤ定義を
namespace
任せにするだけでなく、アセンブリ空間さえ分離してやることでより他の開発者にレイヤ構成を遵守させることができそうです。アセンブリ空間分離のお試しプロジェクトを作成しました。
以下で、レイヤ分離の意図を解説します。
詳細は知らない
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.asmdef は DependenceeImplement.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
さて、では
Main
にDependencee
クラスのインスタンスを渡しているのは何者でしょうか?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.asmdef と DependenceeImplement.asmdef の両方を参照します。依存性注入を行う特性上、多くの asmdef を参照するようになると思います。
そしてシーン上に
Main
,DependenceIntermediary
,Zenject.SceneContext
を配置することで、SceneContext
によりIDependencee
とDependencee
の間の依存関係が解決され、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によるレイヤ分けに興味が出てきた方は是非こちらもご覧ください
- 投稿日:2020-05-16T17:18:02+09:00
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.csusing 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()); } }
- 投稿日:2020-05-16T16:03:08+09:00
シンプルなImageEffect その9。ディゾルブ(dissolve)
TwitterにアップしたディゾルブImageEffectのシェーダコードを貼っておきます。
今日のシンプルなImageEffect。Dissolve(ディゾルブ)
— MIYAKE (@ScreenPocket) May 15, 2020
よくあるくり抜きだけだと味気ないので、境界部分の色を指定出来るようにした。
(加算、Lerp合成の2種)
テクスチャと色設定をいじれば、トランジションで色々使えるかと。 pic.twitter.com/Q1iQ2aaiH6ディゾルブのコードはググればいくらでもありますが、
せっかくなので境界線に色付けができるシェーダコードを用意しました。DissolveBorderColor.shaderShader "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.shaderShader "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 } } }
- 投稿日:2020-05-16T14:13:34+09:00
左手座標系と右手座標系の壁を超えよう。
はじめに
左手座標系における回転(コンピュータビジョン系や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を見てほしいですが、要は、回転行列を角軸等価ベクトル(回転ベクトルとは少し異なります)する話です。文献自体は右手座標系での算出方法を述べています。が上記の流れでこのまま使えます。
まとめ
上記を使えば、
右手座標系のクォータニオンを右手座標系の回転行列にして、左手座標系の回転行列にしたい
といったこともできます!
あくまでメモなので間違っている点等あればご遠慮なくコメントいただければと思います。
- 投稿日:2020-05-16T13:40:13+09:00
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 が採用されている
- 今回はサーバではなくクライアント向けのライブラリの作成ですが、最近はプレイヤーからの配信やリプレイ等クライアントからのアップロードも増えてきたので重要視してみます
- 参考 : IETF QUICの実装に使われている輻輳制御のアルゴリズムを調べてみました。(2019/12/6時点) - neko--suki’s blog
不要
- 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
- Language: C++
- Roles: Server, Client
- HTTP/3 Support: Yes
- License: Apache License Version 2.0
- Repository: https://github.com/apache/trafficserver
Apache Traffic Server は Apache Software Foundation が開発を進めている高性能 HTTP キャッシュプロキシサーバ(クライアント実装も有り)です。
以下 公式ドキュメント より抜粋。Apache Traffic Server™ はネットワークの縁で頻繁にアクセスされる情報をキャッシュすることでネットワークの効率とパフォーマンスを改善するハイパフォーマンスなウェブプロキシーキャッシュです。これはより速い配信と帯域使用量の削減を実現すると同時に、エンドユーザに物理的に近いコンテンツを提供します。Traffic Server は企業向けコンテンツ配信、インターネットサービスプロバイダ (ISP) 、バックボーンプロバイダ、そして大きなイントラネットが持つ既存の利用可能な帯域幅を最大化することによって改善するために設計されました。
パフォーマンスに寄せた実装になってそうなので惹かれますが、モバイル系に対応していない(反面、出自的に Linux 系には物凄い強いです)ので今回は採用を見送ります。
LiteSpeed QUIC (LSQUIC)
- Language: C
- Roles: Client, Server, Library
- HTTP/3 Support: Yes
- License: MIT
- Repository: https://github.com/litespeedtech/lsquic
LSQUIC は Web サーバとして最近名を上げつつある LiteSpeed の開発を手掛ける LiteSpeed Technologies が主導する C 言語製の OSS です。
LiteSpeed Web サーバー、 LiteSpeed ADC 、 OpenLiteSpeed 等の LiteSpeed Technologies の製品・サービスにて利用されているようです。
対応プラットフォームは Linux, FreeBSD, MacOS, Windows とのこと。
サーバ系の利用を目的としたものなのでこちらも出自的に仕方ないですが、モバイル非対応の空気を感じるので今回は採用を見送ります。Proxygen
- Language: C++
- Roles: client, server, library
- HTTP/3 Support: Yes
- License: MIT
- Repository: https://github.com/facebook/proxygen
Proxygen は Facebook により開発が進められている C++ 製の OSS です。
同様に Facebook が開発している QUIC を実装する mvfast を下層に持ちます。
現在はサーバ側をメインに実装が進んでいるようで、 GSO にも対応している等パフォーマンス的にかなり期待が持てそうです。
現状の対応プラットフォームは Ubuntu 18.04 と Mac OSX です。
将来的には Android, iOS へは対応しているので良さそうな雰囲気なのですが、 Windows をサポートしておらず、 C# へ繋ぐ開発を行うのが結構しんどそうです。
心惹かれるものがありますが今回は採用を見送ります。Neqo
- Language: Rust
- Roles: library, client, server (server needs work)
- HTTP/3 Support: Yes
- License: MIT
- Repository: https://github.com/mozilla/neqo
Neqo は Mozila が開発している Rust 製の OSS で、 Firefox に採用されています。
build.rs 見る感じ Windows, Andoroid に対応している(iOS はまだなさげ)のでいい感じなのですが、フル Rust 製(C ラッパが用意されてない)なので今回は採用を見送ります。nghttp3
- Language: C
- Roles: client, library, server
- HTTP/3 Support: Yes
- License: MIT
- Repository: https://github.com/ngtcp2/nghttp3
nghttp3 は前回の記事でも検討した C 製の OSS です。
CURL の実装である libcurl の下層(HTTP/2 部)に用いられている nghttp2 のメインコミッターである Tatsuhiro Tsujikawa さんが主導し実装が進められています。
QUIC レイヤーの実装は同じプロジェクト内で開発されている ngtcp2 に依存しています。
現状 Andoroid, iOS には対応していないようですが、過去の実績(nghttp2)から 将来的に対応する可能性は高めです。
条件にマッチしているので 候補1 とします。picoquic
- Language: C
- Roles: library and test tools, test client, test server
- HTTP/3 Support: Yes
- License: MIT
- Repository: https://github.com/private-octopus/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
- Language: C11
- Roles: client, library, server
- HTTP/3 Support: No
- License: BSD
- Repository: https://github.com/NTAP/quant
QUANT は NetApp により開発されている POSIX 及び IoT プラットフォーム向けに開発されている C11 製の OSS です。
QUIC レイヤーのみの提供で HTTP/3 には対応していない為今回は採用を見送ります。quiche
- Language: Rust
- Roles: library, client, server
- HTTP/3 Support: Yes
- License: BSD 2-Clause "Simplified" License
- Repository: https://github.com/cloudflare/quiche
quiche は CDN ベンダーの CloudFlare が主導して開発が進められている Rust 製の OSS です。
Rust API の上に被せる C 言語製のラッパも提供しているのも特徴で、 C/C++ のアプリケーションやライブラリからも簡単に呼び出すことができます。
現段階で Android, iOS のビルドについても標準でサポートしているのも強みです。
Windows の標準ビルド環境は整っていませんが、 前回の記事で採用した関係で Windows のビルドについては問題なしとします。
条件にマッチしているので 候補2 とします。quicly
- Language: C
- Roles: client and server
- HTTP/3 Support: Yes
- License: MIT
- Repository: https://github.com/h2o/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
- Language: C
- Roles: client
- HTTP/3 Support: Yes
- License: MIT
- Repository: https://github.com/curl/curl
libcurl は CURL の内部実装である C 言語製の OSS です。
libcurl 自体には QUIC, HTTP/3 層の実装は含まれておらず、他の OSS(quiche もしくは ngtcp2 のいずれかを選択)に依存しています。
今回紹介する OSS の中でも随一の歴史を誇るだけあり、 HTTP の機能が非常に豊富なのが強みです。
(ただし、その分内部実装も若干歴史を感じさせるものとなっており、オーバーヘッドは若干気になります)
HTTP3.md を見る限りだと現状の対応プラットフォームが謎(プラットフォームも quiche/ngtcp2 に依存?)ですが、過去の実績的には Android/iOS といったモバイルにも対応するのは間違いない安心感があります。
条件にマッチしているので 候補3 とします。MsQuic
- Language: C
- Roles: client, server
- HTTP/3 Support: No
- License: MIT
- Repository: https://github.com/microsoft/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
- Language: C#
- Roles: client, server
- HTTP/3 Support: Yes
- License: MIT
- Repository: https://github.com/dotnet/runtime
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
CUBIClibcurl × 〇 〇 〇 -
- - : 下層のライブラリの実装に依存
- スレッド : 内部的にスレッドで処理を行うかどうか。非同期ソケットやイベントで動作するのとは別。スレッドセーフかどうかとも別
- メモリ : 受信したデータを都度ライブラリから受け取って処理することができるか
上記の表ベースでは、 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/quicheBoringSSL の 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_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.lib
とssl.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
https://github.com/quicwg/base-drafts/blob/master/CONTRIBUTING.md ↩
QUIC DATAGRAM は QUIC 上で信頼性の低いデータ通信を行う為の拡張仕様です。詳細は『くいっく』DATAGRAM編にて解説していますよ!(宣伝) ↩
Simple Datagram Usability and Connectivity Kata の略で、QUIC DATAGRAM の接続性のテストに用いる仕様です。詳細は『くいっく』DATAGRAM編にて解説していますよ!(宣伝) ↩
参考 : 【Unity】Unite Tokyo 2019 「大量のアセットも怖くない!~HTTP/2による高速な通信の実装例~」講演と壇上では語られなかった6つのこと。 - SEGA TECH BLOG ↩
- 投稿日:2020-05-16T03:02:41+09:00
Unity x LeapMotionでもAssemblyDefinitionFilesを使いたい
Leap Motion
Leap Motionは独自のセンサとカメラ技術により、人の手をセンシングするデバイスです。
安価で入手しやすいハンドトラッキングツールとして、発売から5年以上たった今でも人気のデバイスです。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です。
正解は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からなくなりました。
2019.2 だとテスト関連の asmdef を参照させないとダメかな? #UniteTokyo #RoomD
— もんりぃ先生 (@monry) September 26, 2019[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の設定に無事成功しました。これからは
Frame
やImage
クラスを使いたいAssemblyにだけ LeapMotion.Core.asmdef の参照を追加すればよいのです。おわりに
UnityのAssembly Definitionは便利で強力なのでもっと広く使われてほしいのですが、今回のようなややこしい問題に直面するとなると、正直まだ難しいのかなと思ってしまいます。
しかし、ライブラリ作成者は別です。Scriptを含む場合は是非Assembly Definitionを設定してください。