Git for Windowsをコマンドラインからインストールする方法
概要
今回はWindowsのコマンドラインでGit for Windows (Git Bash) をインストールする方法を紹介します。
Git for WindowsはWindows上でGitやBashを使用する場合にインストールしておくと何かと役に立つパッケージですが、
GUI経由でのインストールは必要オプションの取捨選択が必要であったり、なによりインストール状態の再現性に乏しいところがあります。
コマンドライン経由のインストール方法を確立することで、再現性があり開発環境インフラ構築などにも応用させることができますためよかったらご活用ください!
動作環境
- Windows 10
- 筆者はPro版ですが、Homeでも問題ないと思います
注意事項
- 以下を試す場合は自己責任でお願いします!
- 以下の処理は管理者権限で実行しますが、当方は事故や故障などの一切の責任を負いません!
上記の件、ご留意ください
変更履歴
- 2022/05/17
- 「2022/05/16 23:00以前のExampleを試した場合は以下を実施をお願いします」を追加
- 「(任意)PATHの追加」に注意文を追記
- 2023/02/05
- 「install-git.ps1 を実行」を
PowerShell -ExecutionPolicy Bypass
を使ったコマンドに修正
- 「install-git.ps1 を実行」を
2022/05/16 23:00以前のExampleを試した方は以下を実施を推奨
環境変数の設定に使用していたコマンド ([Environment]::SetEnvironmentVariable
) に少し難点がありました。
WindowsInfrastructureExample
上のスクリプトは修正済みです。
もし、すでに試していてパスが通らなくなったり、コマンドが実行できなくなった方は以下の「余談、PATHのREG_SZ登録をREG_EXPAND_SZに切り替える方法」を試してみてください。
余談、PATHのREG_SZ登録をREG_EXPAND_SZに切り替える方法
詳細も上記記事に記載しています。
インストール方法
インストールオプションについて
- 以下のコマンドでのGit for Windowsのインストールオプションは筆者が普段使用しているオプションを使用
- 詳細は後述しますが、各自で
git-for-windows.inf
都合の良いように改変してください
- 詳細は後述しますが、各自で
Gitからps1ファイルをクローン
- ps1ファイル(PowerShellスクリプト)を以下のリポジトリからクローンする
PowerShellを管理者権限で開く
※開き方は好きなやり方で問題ありません
今回はWindows 10標準搭載のPowerShellを使用
Windows
キーを押して、「powershell」を検索- 「管理者として実行する」をクリック
PowerShellでクローンしたWindowsInfrastructureExample上に移動
> cd <path>\WindowsInfrastructureExample
Git-2.x.x-64-bit.exe を配置
コマンドでダウンロードする場合
> Invoke-WebRequest https://github.com/git-for-windows/git/releases/download/v2.36.1.windows.1/Git-2.36.1-64-bit.exe -OutFile Git-2.36.1-64-bit.exe
※インフラ構築のスクリプトを書く場合は、このコマンド自体をps1スクリプトに組み込んでも良い
ブラウザでダウンロードする場合
以下より Git-2.x.x-64-bit.exe
をダウンロードし、WindowsInfrastructureExample
フォルダ以下に配置
install-git.ps1 を実行
> PowerShell -ExecutionPolicy Bypass .\install-git.ps1
エラーログが出なければ問題なく Git for Windowsのインストール完了
インストールの確認 (コマンドラインの場合)
Dir 'C:\Program Files\Git\cmd\git.exe'
でエラーが出なければインストール完了
> Dir 'C:\Program Files\Git\bin\git.exe' ディレクトリ: C:\Program Files\Git\bin Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 2022/05/09 13:28 45584 git.exe
(任意)PATHの追加
(追記)ローカルPCの環境変数 PATH が変更されます!ご注意ください!
> .\set-git-path.ps1
以上でインストール及び設定は完了です。
スクリプト解説
InnoSetup
Git for WindowsはInnoSetupというフリーのWindows向けインストーラーが使用されています
パラメータの詳細は Setup Command Line Parameters で確認できます
/ALLUSERS
はその通りWindows上の全部のユーザーにインストール/VERYSILENT
はダイアログなどを抑制/Log xxx.log
でログ出力- 標準出力に直接ログを流し込めなかったため、一度ログファイルに落として最後に出力に流しています
/LOADINF
/LOADINF=git-for-windows.inf
オプションファイルの指定で、実質的にGit for Windowsのオプション指定方法になります
- Gitのcommit時の改行に
LF
を指定 (CRLFOption
) - Credential Managerの使用 (
UseCredentialManager
) - コマンドプロンプトでgitを使用可能にするかの指定 (
PathOption
) - etc...
手順で使用したファイルは git-for-windows.inf となります
後述の SAVEINF
からオプションファイルを作成できます。
自分はインストーラーのスクリプト (install.iss) から割り出してました。。。
余談、git-for-windows.inf の方針
- CLI特化
- UNIX系と互換性を意識
- OpenSSHの使用
- LFOnly指定など
- Credential Manager を有効化
- これがあるとGitHubとの連携が楽になります! (別記事でいずれ紹介します!)
- デフォルトブランチ名は
main
(昨今の流れを汲み取って) - git LFS を有効化
/SAVEINF
/LOADINF=git-for-windows_save.inf
※install-git.ps1
では非使用
今回は直接は使用していませんが、指定するとインストール時のオプションを確認可能です!
| Out-Null
コマンドの最後に | Out-Null
を入れることで、インストール完了までコマンド終了待ちになります。
PowerShellだとメジャーなテクニックのようです。
余談、今回の調査で躓いたところ
- PowerShellで変数に実行ファイルを仕込む場合は
& ./$PackageFile
のように記載&
なしで./$PackageFile
とするとエラーになる
Set-ExecutionPolicy
を実行しないと PowerShell上でps1ファイルが実行できない- オプションの探し方
Foo.exe /?
orFoo.exe /HELP
などで確認できる場合があるようです- Windowsのダイアログ出でるのでコピーできないなど面倒。。。
- 別ps1ファイルのfunctionをコールする方法
自分は元々UNIX系のエンジニアだったので、WindowsとPowerShellのコマンドライン化には結構手こずりました。
再現性のあるインフラの構築にはIaC (Infrastructure as Code) は欠かせないのですが、Windowsに慣れていないせいかほとんどPowerShell調査みたいになってしまいました。
雑感
前回の記事投稿から2ヶ月経ってしまいましたね。。。
しかも今回はXRと3DCGから程遠いCLIに関する記事に。
「3DCGとかUnityやる気あるの?」と思われた方もいらっしゃるかもしれませんが、ちゃんと普段は触ってます!
地味なことですが、再現性のある開発環境の構築方法は何かと便利だったりします。
また、Git for Windowsってインストールしておくと、UnityのPackage ManagerでGitHub経由のパッケージが取得しやすくなったり、
GitHubのHTTPS経由での連携が安定したりと、何気に非エンジニアの方にもメリットがあったりします!
さて、次回は「PowerShell7をコマンドラインからインストールする方法」にする予定です!
それでは~
【Unity, XR】Single Pass Instanced向けURP Render FeatureのTips 【2020.3, 2021.2】
- 概要
- 追記
- 略語
- 動作環境
- Tips Unity 2020
- 雑感
概要
UnityのUniversal Render Pipelineには Render Feature という描画処理を差し込む機能があります。
この Render Feature ですが、XRのSingle Pass Instanced 対応をする場合はクセがあります。
今回はそんなSingle Pass Instanced向けのRender FeatureのTipsを共有します。
追記
- 2022/03/27 「おまけ、IntermediateTextureMode.Alwaysの指定なしでも機能させる方法」項目を追加
略語
用語が長いので、以下では略語で紹介します。
用語 | 略語 |
---|---|
Universal Render Pipeline | URP |
Render Feature | RF |
Single Pass Instanced | SPI |
Render Texture | RT |
動作環境
- Unity 2020.3.30f1 + Universal RP 10.8.1
- Unity 2021.2.16f1 + Universal RP 12.1.6
Tips Unity 2020
ポストエフェクトでは cmd.Blit は使用不可で cmd.DrawMesh を使用
詳細は以下のドキュメントに記載されています。
※以下、上記ドキュメントを 「SPI Blit Example」と呼びます
NOTE: Do not use the cmd.Blit method in URP XR projects because that method has compatibility issues with the URP XR integration.
互換性の問題でSPIでは cmd.Blit
は正しく機能しないようで、
もし、ポストエフェクト的なことを実施したい場合は cmd.DrawMesh
+ _CameraColorTarget
から画面キャプチャを取得して Blit を実現します。
サンプルのColorBlitRenderFeature について
ちなみに SPI Blit Example の ColorBlitRenderFeature
のサンプルは描画の緑成分を抽出するポストエフェクトになっています。
↓
(2021.2以降) SwapBufferはSPIでは実質的に使用不可
Unity 2021.2 ではRFでポストエフェクトを実装しやすいようにSwapBufferという機能が追加されています。
残念ながらSwapBufferはSPIでは使用できません。。。(もし使用できる場合は教えていただけるとありがたいです!!)。
原因はSwapBufferの処理が隠蔽されていること、SwapBufferを使用するBlit
メソッドが cmd.Blit
を使用するためです。
※SwapBufferについては以下の動画をご参照ください。
画面キャプチャは不透明オブジェクトのみ
さて、ポストエフェクトと言いましたが、URP + SPIにおいては欠点があります。
不透明オブジェクトの描画結果しか取得できません
これは、シェーダーに渡されるテクスチャの名前 (_CameraOpaqueTexture
) の通り、
不透明オブジェクトのみ描画した画面キャプチャが渡されるためです。
↓
SwapBufferも使用できないため、残念ながらSPIでのポストエフェクトは透過オブジェクトを対象にできないなど制限が強いです。。。
完全な描画結果が取れない理由
SPIにおいて、スクリーン向けRTにアクセスする術がないためです。
ScriptableRenderer.GetCameraColorFrontBuffer
が internalメソッドのためアクセスできない- (2021.2以降)SwapBuffer用のRTにアクセスできない
Unity 2021.2以降でのポストエフェクトにはIntermediateTextureMode.Alwaysの指定が必要
Unity2021.2から IntermediateTextureMode
という設定項目が追加されました。
IntermediateTextureMode.Always
を指定しないとSPI Blit Exampleの ColorBlitRenderFeature
は機能しません!
名前の通り、中間テクスチャ(IntermediateTexture)を使用するかの指定になります。
IntermediateTextureMode.Alwaysの指定が必要な理由
指定していない場合は cmd.DrawMesh
の描画先が中間テクスチャではなくスクリーンのRTに直接描画されるのですが、
その後、FinalBlitPass
によってスクリーンのRTが上書きされてしまうためです。
以下のコードにIntermediateTextureMode
による条件分岐があります。
github.com
SPI Blit Example には、注意書きがないのでIntermediateTextureMode.Always
の指定を忘れると Unity 2021.2 では実質的に描画結果に現れません。
判明するまで数時間かかりました。。。一応Report投げときました。
ちなみに非SPI(Multi Pass) や NonXRではSwapBufferが使用可能なので問題になりません。
Unity 2020.3 以前での中間テクスチャ
Unity 2020.3 + Universal RP 10 では IntermediateTextureMode
は有りませんが、
実は RF が Forward Rendererに1つでも追加されている場合に、中間テクスチャが自動で有効になっていたようです。
以下のコードの rendererFeatures.Count != 0
という判定からわかります。
おまけ、IntermediateTextureMode.Alwaysの指定なしでも機能させる方法
※IntermediateTextureMode.Always
を指定する方が確実と思われますが、一応紹介します。
SPI Blit ExampleのColorBlitPass
を以下のようにrenderingData.cameraData.renderer.cameraColorTarget
を指定することで、
IntermediateTextureMode.Always
を指定していない場合でも機能させることができます。
--- a/Assets/ColorBlitPass.cs +++ b/Assets/ColorBlitPass.cs @@ -34,7 +34,7 @@ internal class ColorBlitPass : ScriptableRenderPass using (new ProfilingScope(cmd, m_ProfilingSampler)) { m_Material.SetFloat("_Intensity", m_Intensity); - cmd.SetRenderTarget(new RenderTargetIdentifier(m_CameraColorTarget, 0, CubemapFace.Unknown, -1)); + cmd.SetRenderTarget(new RenderTargetIdentifier(renderingData.cameraData.renderer.cameraColorTarget, 0, CubemapFace.Unknown, -1)); //The RenderingUtils.fullscreenMesh argument specifies that the mesh to draw is a quad. cmd.DrawMesh(RenderingUtils.fullscreenMesh, Matrix4x4.identity, m_Material); }
IntermediateTextureMode.Alwaysの指定なしでも機能する理由
renderingData.cameraData.renderer.cameraColorTarget
は基本的に中間テクスチャとなっているため- 以下のコードを読む限り
createColorTexture
がtrueの場合に中間テクスチャが描画先にになるのですが、スクリーン(非SceneView)向け描画は基本的にtrueになるみたいです
- 以下のコードを読む限り
createColorTexture == false
になる(中間テクスチャを描画先にしない)パターンのほうが珍しいですが、
SPIの場合で中間テクスチャを描画先にする場合は、renderingData.cameraData.renderer.cameraColorTarget
を使用するならIntermediateTextureMode.Always
を指定する方が確実だと思います。
URP + SPI で可能な表現の例
不透明オブジェクトの描画結果を使用したポストエフェクト
透明オブジェクトは対象にできませんが、不透明オブジェクトならポストエフェクトをかけることができます。
アルファブレンドによるフェードイン・フェードアウト
シェーダーのアルファブレンドは問題なく使用できるため、フェードイン・フェードアウトは実現できます。
Blend SrcAlpha OneMinusSrcAlpha ... half4 frag (Varyings input) : SV_Target { UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); return half4(0, 0, 0, saturate(_Intensity)); }
Single Pass Instanced向けURP RenderFeatureでアルファブレンドが機能することの確認 pic.twitter.com/RH3MDPJwq2
— すぎしー (@tsgcpp) 2022年3月22日
雑感
なんとか3月中に1つ記事が書けました! 本当はAddressablesに関する記事を書いていたんですが、検証の自動テストがGameCIで実現できず。。。
Unity2020でRFを使用すると中間テクスチャが必ず使用されるようになるのも知るきっかけになってよかったです(RF使用する場合のパフォーマンスの懸念自体は増えましたが)。
世間は卒業シーズン、新卒入社で新生活を始める人も多そうですね。 これからUnity、XR始める人にも興味を持っていただければ幸いです!
それでは~
【Moq】Moqはメソッドチェーンでまるごとモック化できるよ
メソッドチェーンのモック化
IFoo -> IBar -> IBaz のようにinterfaceのプロパティが連結されている場合、 以下の2つのモック化は同等になります。
// setup var fooMock = new Mock<IFoo>(); var barMock = new Mock<IBar>(); var bazMock = new Mock<IBaz>(); // when fooMock.Setup(m => m.Bar).Returns(barMock.Object); barMock.Setup(m => m.Baz).Returns(bazMock.Object); bazMock.Setup(m => m.Message).Returns("Hello Redundant"); // then IFoo target = fooMock.Object; Assert.That(target.Bar.Baz.Message, Is.EqualTo("Hello Redundant")); Assert.That(target.Bar.Baz, Is.Not.Null);
↓
// setup var fooMock = new Mock<IFoo>(); // when fooMock.Setup(m => m.Bar.Baz.Message).Returns("Hello Chain"); // then IFoo target = fooMock.Object; Assert.That(target.Bar.Baz.Message, Is.EqualTo("Hello Chain"));
上部(以後Aパターン)のようにわざわざ、IFoo
, IBar
, IBaz
のMockを作る必要はなく、
下部(以後Bパターン)の fooMock.Setup(m => m.Bar.Baz.Message).Returns("Hello Chain");
のようにIFoo
からメソッドチェーンでまとめてモック化してくれるという話でした。
何が起こっているの?
どうやらAパターンの宣言でBパターンと同等になるようです。
後で検証コードを記載しますが、
target.Bar
に Mock<IBar>.Object
, target.Bar.Baz
にMock<IBaz>.Object
のインスタンスが割り当てられていました。
Setupで指定しない場合はdefault
以下の用にSetup
を実施しない場合はdefault、つまりnullを返します。
// setup var fooMock = new Mock<IFoo>(); // when // fooMock.Setup(m => m.Bar.Baz.Message).Returns("Hello Chain"); // then IFoo target = fooMock.Object; Assert.That(() => target.Bar.Baz.Message, Throws.TypeOf<NullReferenceException>());
検証コード
- NUnitで検証
IFoo
,IBar
,IBaz
のinterfaceも定義
雑感
Moqを使ったことない同僚の方に、自分のコードを参考にMoqを使ってもらったら、
上記のようにメソッドチェーンでまるごとモック化する使い方をされていて発覚しました。
Moq使いだして2年以上、全然気づきませんでした。。。 先入観って怖いですね(笑)。
それでは~
【Unity, VSCode】Visual Studio Code Editor 1.2.5 への更新のススメ
概要
Visual Studio Code Editor 1.2.5 が 02/09 (日本だと2/10) に公開されたため、 1.2.4の問題と合わせて、1.2.5を紹介したいと思います。
今回はVSCode愛用者向けの記事となります。
対象Unity
- Unity 2020 or later
- Unity 2021 or later
1.2.4の問題
Windowsの場合に Package Manager経由で取り込んだ(Packages/
以下の)ソースコードへの参照が機能しないバグがあったこと!
Find All References
などでinterfaceや実装の確認もできず、コードのSuggestionも機能しないため、
VSCodeユーザーにとって、コーディングの効率を損なう問題がありました。。。
一応、1.2.3 にダウングレードすれば上記問題は回避はできましたが、
ただ、最新のUnity2020とUnity2021では標準で1.2.4がインストールされるため、煩わしさがありました。
ちなみにですが、1.2.4は 2021/09/01 にリリースされたので5ヶ月は放置されていた問題になります。。。
1.2.5のススメ
っということで結論として1.2.5へ更新しましょう!
ちゃんとWindowsで参照の問題が解決されていました。
パッケージ更新後は Preferences -> External Tools -> Regenerate project files を忘れずに!
ちなみに以下のプルリクが問題の修正箇所です。
この1行の修正を5ヶ月待ったことになります。。。
雑感
今Addressablesに関する記事を書いているんですが、 資料をまとめるのに時間がかかりそうなので、1つ軽めの記事を書いてみました。
多分、この記事のタイトル見て「さっさとRiderにしたら?」って思った方もいらっしゃるかもですね。
正直VSCodeに出会ってから、VSCodeの魅力に取りつかれて手放せなくなっています。
ちなみに僕のVSCodeを主な用途は以下です。
特にBlenderのアドオンの作りやすさは感動を覚えるレベルです!
もちろん、豊富なアドオンがあることも理由の一つですね。
そんなVSCodeですが、Unityでのバグが数ヶ月放置されていたのでやっと解消されてホッとしてます。
みなさんも、良いVSCodeライフを!
それでは~
【Unity】Transparentシェーダー レンダリングTips (RenderQueue編)
概要
UnityにおけるTransparentシェーダー(透過シェーダー)に関するTipsです。
Transparentシェーダーは3DCGにおいてよく使われる一方で昔から非常に悩ましい存在であったりします。
特に描画順の関係で思ったような絵にならなかったというのはよくあるパターンだと思います。
Unityでの透過オブジェクトを利用する場合の参考にしていただければと思います。
動作環境
- Windows 10
- Unity 2021.2.6f1
- Universal RP 12.1.2
検証対象のTransparentシェーダー
- Shader Graphを使用
_Color
プロパティのみを持ち、アルファも使用
Transparentシェーダーの特徴
アルファブレンドの場合、描画の順番で結果が異なる
デフォルトのブレンド設定であるアルファブレンド(Blending Mode が Alpha)の場合、オブジェクトの描画順で結果が異なります。
アルファ0.4の赤と緑のシェーダーを使ったオブジェクトを描画順を変えて見てみましょう。
上記のように描画順で描画結果が異なります。
Transparentシェーダーはアルファブレンドの場合に描画順で結果が変わるということを覚えておきましょう!
基本的なことですが、とても重要な性質になります。
アルファブレンドは描画順で結果が変わる理由
簡単に説明するとアルファブレンドの計算式のためです。
R * a + G * (1 - a) => 0.4R + 0.6G (緑 -> 赤の順番)
G * a + R * (1 - a) => 0.6R + 0.4G (赤 -> 緑の順番)
※上記はイメージのための計算式で実際は R * Ra + (G * Ga + Dest * (1 - Ga)) * (1 - Ra)
のような計算
加算ブレンドのみの場合、描画の順番に関わらず結果が同じ
加算ブレンド(Blending Mode が Additive)のマテリアル同士の場合は描画順に関わらず結果が同じになります。
加算ブレンドのみの場合ということに注意してください。
アルファの乗算色を単純に加算するため、計算の順番で結果が変わらないからです。
R * Ra + G * Ga(緑 -> 赤の順番) = G * Ga + R * Ra(赤 -> 緑の順番) * Ra: 赤のアルファ値, Ga: 緑のアルファ値
RenderQueueが2501以上の場合、ソートが発生
RenderQueue 2501以上かつ同一のRenderQueueの場合はカメラから遠いオブジェクトから近いオブジェクトの順番で描画する処理が発生します。
透明オブジェクトはカメラから遠いオブジェクトから近いオブジェクトの順番で描画したほうが結果が安定します。
Unityはデフォルトでソートを実施して、遠->近の順番でオブジェクトを描画してくれています。
UnityのTransparentシェーダー + BlendingMode AlphaでRenderQueueを変えたときの描画結果 pic.twitter.com/PjsRwgjHBz
— すぎしー (@tsgcpp) 2021年12月26日
※どちらもアルファは1.0
ポイントは以下です。
- RenderQueue 2501以上かつ同一のRenderQueueのマテリアルを持つRendererが複数ある場合はソートが発生
- カメラから見て遠いオブジェクトから近いオブジェクトの順番で描画
- ソートの基準位置はオブジェクトのTransformのposition (オブジェクトのピボット)
- 異なるRenderQueueのマテリアル間ではソートは発生しない
- 異なる場合はRenderQueueが小 -> 大の順番で描画
- RenderQueue 2500以下の場合はソートは発生しない
- 不透明オブジェクトはデプステストにより基本的に描画結果が安定するため
TransparencySortMode の説明にも以下のように記載されています。
By default, perspective cameras sort objects based on distance from camera position to the object center;
おまけ1: BlendingMode Additiveの場合
描画順に関わらず結果が一定になります。
BlendingMode Additive の場合。ずっと同じ pic.twitter.com/u7BRj6ph5I
— すぎしー (@tsgcpp) 2021年12月26日
※どちらもアルファは1.0
おまけ2: 赤がAlpha, 緑がAdditiveの場合
全体がAdditiveではない場合は結果が不安定になります。
マテリアルにBlendingMode Alphaが存在する場合はRenderQueueを意識する必要があります。
おまけ、赤がAlphaで緑がAdditiveの場合 pic.twitter.com/HQyGzn35lV
— すぎしー (@tsgcpp) 2021年12月26日
※どちらもアルファは1.0
デプス (深度) を通常は書き込まない
Transparentシェーダーはデプスを書き込まないように設定することが一般的です。
こちらも描画順で結果が不安定になるためです。
- Transparentシェーダーはデフォルト(
Depth Write: Auto
)の場合はデプスを書き込まない - 意図的に書き込むことは可能
ZWrite On
(Depth Write: ForceEnabled
) をシェーダー or マテリアルで指定
デプスを書き込まない場合と書き込む場合との違い
例えば以下のようにオブジェクトを配置した場合を見てみましょう。
- 赤Cubeと緑Sphereを重なるように配置
- 両マテリアルのアルファは0.4
- 両マテリアルのRenderQueueは3000
- 緑Sphereのほうがカメラに近い
- 遠い赤Cube -> 近い緑Sphereの順番で描画
デプスを書き込んだ場合、重なった部分の緑側が描画されていません。
これは赤 -> 緑と描画されて、赤側でデプスが書き込まれたため後続の緑側の深度テストによりピクセルシェーダーがスキップされたためです。
意図的にこちらの現象を利用することもありますが、基本的にはTransparentシェーダーはデプスを書き込まないのが一般的です。
Transparentシェーダーパフォーマンス
※本記事では詳しくは取り上げません。
ここまで読んだ方の中にはパフォーマンス面が気になった方もいらっしゃるかもしれません。
Transparentシェーダーはピクセルシェーダー処理が発生しやすい性質上、パフォーマンス面で注意が必要です。
簡単に言えばオーバードローという現象が確実に発生するようなものです。
安易にUIやパーティクルなどで使用すると描画負荷が一気に上がりますためご注意ください!
また、ソート処理も発生するためCPU側にも影響があると考えられます。
パフォーマンスに関しては別の機会に取り上げようと思います。
サンプルプロジェクト
雑感
今年最後の技術ブログになります。
今回は自分の復習も兼ねて3DCGっぽい記事にしてみました。
というかここ数ヶ月、3DCGっぽくない記事が多かったですね。。。
昔のUnityでは透明マテリアルはRenderQueue 2450だった気がしますが、いつの間にか3000になってましたね。
パフォーマンス面については別の機会に紹介しようとは思いますが、いつになることやら。。。
Unity 2021.2ではShader Graphに Allow Material Override
でBlending Modeを切り替えれたり、URPでVFXが使えたりと2020.3と比べて一気に使いやすさが上がっている気がしています。
そういえば、サンプルプロジェクトにTimelineでマテリアルをいじる簡易的なカスタムTrackも入れているので良かったら参考にどうぞ(Preview機能とか色々微妙ですが)。
来年もUnityを追求していきたいです!
それでは、良いお年を~
【Unity C#】 IReadOnlyListとアロケーション
- 概要
- 環境
- IReadOnlyList の アロケーション発生ポイント
- Unity Test Runnerによるアロケーション確認
- サンプルコード
- 参考
- 雑感
概要
今回はIReadOnlyList
と アロケーションについて深堀りしようと思います。
前回の記事でも述べましたが、IReadOnlyList
はアロケーションを回避したい場合は注意が必要なinterfaceになっていたりします。
そんなIReadOnlyList
のアロケーションについて調べたので共有します。
IReadOnlyList
については前回の記事で少し紹介していますので合わせてどうぞ!
環境
- Unity 2021.2.2f1
- Mono
- .Net Framework (API Comptibility Level)
IReadOnlyList の アロケーション発生ポイント
foreach使用時などのIEnumerable.GetEnumerator
List
をIReadOnlyList
に変換してforeach
に回すと使用するたびにアロケーションが発生します。
List<Foo> list = new List<Foo>(); foreach (var item in list) { } // アロケーションは発生しない IReadOnlyList<Foo> readonlyList = list; foreach (var item in readonlyList) { } // アロケーションが発生する
アロケーションの原因
List
の場合はEnumerable
(値型)のboxingが原因です。
public Enumerator GetEnumerator() { return new Enumerator(this); }
list.cs,569 より
List
のGetEnumerator
が返すEnumerable
は値型のため、List
を直接使用する場合はアロケーションは発生しません。
一方で、IReadOnlyList
に変換した場合のGetEnumerator
はIEnumerable
としてEnumerable
を返します。
IEnumerator<T> IEnumerable<T>.GetEnumerator() { return new Enumerator(this); }
list.cs,574 より
この戻り値のEnumerable
からIEnumerable
は値型から参照型への変換、つまりboxingが発生するためアロケーションが発生します。
アロケーションの回避方法
foreach
ではなく for
を使用すれば回避可能です。
for (int i = 0; i < readonlyList.Count; ++i) { var item = readonlyList[i]; }
IEnumerable
と異なりIReadOnlyList
はlist[i]
が配列の要素に直接アクセスする形となっています。
ListをIEnumerable
(or IReadOnlyCollection
)に変換してしまうとfor
が使用不可でGetEnumerator
によるアロケーションは避けられなくなりますが
IReadOnlyList
への変換であれば読込専用を保ちつつfor
が使用可能なため結果的にアロケーションの回避が可能です。
コーディングの煩わしさはありますが、メモリ的には優しくなります。
IEquatable<T>を実装しない値型(struct)でEquals
IEquatable<T>
を継承していない値型はEquals
の挙動によりアロケーションが発生するパターンが多いです。
IReadOnlyList
とは直接は関係ありませんが、後述するLinq.Enumerable.Contains
に関わってくるため紹介します。
public struct SimpleData { public int value; }
アロケーションの原因
C#の型に定義されているValueType.Equals(Object)
は引数にobject型を受け取ります。
つまり、このEqualsに値型を渡すと値型から参照型への変換でboxingが発生しアロケーションに繋がります。
アロケーションの回避方法
主に2つあります。
- structに
IEquatable<T>
を継承して実装Equals(T value)
で型指定となるためboxingが発生しない
IEqualityComparer<T>
を継承したオブジェクトを実装して使用Equals(T x, T y)
で型指定となるためboxingが発生しない
public struct EquatableData : IEquatable<EquatableData> { public int value; public bool Equals(EquatableData other) { return this.value == other.value; } ... }
IReadOnlyListでLinq.Enumerable.Contains
using System.Linq;
...
readonlyList.Contains(item);
IReadOnlyList
は残念ながらContains
メソッドを持っていません(List.Contains
は定義されている)。
拡張メソッドLinq.Enumerable.Contains
を使用することで同様の機能を使用できます。
しかし、Linq.Enumerable.Contains
はアロケーションが発生するパターンが多いです。
アロケーションの回避方法
Linq.Enumerable.Contains
のアロケーションは細かい話が多いため先に回避方法を紹介します。
これに関してはIReadOnlyList
向けのContains
を実装する必要がありました。
後述する「アロケーションの原因」を アロケーション回避の検証も含めUnitTestも作成しています。
余談ですが、汎用性を保つためwhere T : struct
のような制約は入れていません。
その代わりtypeof(T).IsValueType
による条件分岐が入っています。
アロケーションの原因
共変性変換のIReadOnlyListに対してLinq.Enumerable.Contains
「共変性を使用したIReadOnlyList」とは要するにList<Foo>
-> IReadOnlyList<IFoo>
みたいな変換のことです。
共変性については前回の記事を参照。
ポイントは 共変性を利用して変換したIReadOnlyList
にあります。
実は List<Foo>
-> IReadOnlyList<Foo>
のように不変(TをFoo
のまま変換)の場合はアロケーションは発生しません。
共変性変換の場合にアロケーションが発生する要因はLinq.Enumerable.Contains
の定義にあります。
public static bool Contains<TSource>(this IEnumerable<TSource> source, TSource value) { ICollection<TSource> collection = source as ICollection<TSource>; // ICollectionは不変性のため、IReadOnlyList<IFoo>へのキャストは不可のためnullを返す if (collection != null) return collection.Contains(value); return Contains<TSource>(source, value, null); } public static bool Contains<TSource>(this IEnumerable<TSource> source, TSource value, IEqualityComparer<TSource> comparer) { if (comparer == null) comparer = EqualityComparer<TSource>.Default; if (source == null) throw Error.ArgumentNull("source"); foreach (TSource element in source) // <- GetEnumerator使用によりboxingによるアロケーションが発生 if (comparer.Equals(element, value)) return true; return false; }
ICollection<T>
は共変性(out T
)を持たず不変性のため、List<Foo>
-> ICollection<IFoo>
は不可となります。
よって IReadOnlyList<IFoo>
-> ICollection<IFoo>
の変換も不可のため、上記Contains
のコードでsource as ICollection<TSource>
はnullを返します。
その後foreach
にたどり着き、「foreachなどIEnumerable
を必要とする処理」で述べたアロケーションが発生します。
逆にIReadOnlyList<Foo>
は不変性を満たすためICollection<Foo>
にキャストができforeach
に到達しないためアロケーションが発生しません。
なんともややこしい。。。
IEquatable<T>非継承のstructを型とするListでLinq.Enumerable.Contains
IEquatable<T>
非継承のstructをLinq.Enumerable.Contains
に使用した場合はアロケーションが発生します。
private static EqualityComparer<T> CreateComparer() { ... if (typeof(IEquatable<T>).IsAssignableFrom(t)) { return (EqualityComparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(GenericEqualityComparer<int>), t); } ... return new ObjectEqualityComparer<T>(); }
IEquatable<T>
継承の場合は型指定のEqualityComparer<T>
が生成されますが、
IEquatable<T>
非継承の場合はObjectEqualityComparer<T>
が使用されてboxingが発生するんですね。
IEqualityComparer<T>指定有りでLinq.Enumerable.Contains
Linq.Enumerable.Contains
はオーバーロードで第2引数にIEqualityComparer<T>
指定可能なメソッドがあります。
Equals
のboxingは回避できるんですが、残念ながらその後で使用されるforeach
のboxingは回避できません。。。
「アロケーションの回避方法」にて記載したコードには、IEqualityComparer<T>
指定可能なContains
も実装して記載しています。
Unity Test Runnerによるアロケーション確認
今回のアロケーション有無の検証ですがUnity Test Runnerを利用しました。
実装開始時はIs.AllocatingGCMemory()
とIs.Not.AllocatingGCMemory()
を使い分けていましたが、
再利用性(TestBaseクラスからの派生)と視認性のためにIs.Not.AllocatingGCMemory()
指定で統一しました。
以下の例だと✅でアロケーション発生無し、🚫でアロケーション発生有りという見方になります。
テストコード的にはおかしいですが、一旦視認性を重視しました(別の視認性が確保しやすい方法が思いついたら修正しておきます)。
また、1st, 2ndはインスタンスごとの初回と2回目でアロケーションの変化があるかを確認するために入れています。
List<Foo>
List<Foo> -> IReadOnlyList<Foo>
foreach
のみで発生
List<Foo> -> IReadOnlyList<IFoo>
foreach
および 共変性変換のためLinq.Enumerable.Contains
で発生
List<SimpleData> -> IReadOnlyList<SimpleData>
IEquatable
非実装のstruct
List<EquatableData> -> IReadOnlyList<EquatableData>
IEquatable
実装のstruct
サンプルコード
IReadOnlyList
向けContains
なども含む
追記
Package Manager対応版を用意しました。
参考
- Unity - Scripting API: AllocatingGCMemoryConstraint
- 【Unity】指定のコードがGCを発生させるかどうかをテストする(AllocatingGCMemory) - テラシュールブログ
- テストでのアロケーションの確認方法
雑感
世間ではFacebookからMetaへと社名変更、新しいOculusが発表、メタバースへの注目など、
XR界隈で目まぐるしい変化が来そうなときに、
「なんでXRとは程遠いC#の話してるの?」って言われそうですねw。
アプリケーションの楽しさに直接関連するものでは確かに有りませんが、
一方で快適なXRアプリケーションの実現において、過剰なアロケーションの回避や削減は大事な要素であると考えています。
もちろんUnityにおいて完全にアロケーションを避けることは難しいため、
こだわり過ぎず、避けれるなら避けるぐらいがちょうど良いのではと思います。
マニアックな話では有りましたが、少しでも面白いと思っていただけたなら幸いです。
それでは~
【Unity C#】 IReadOnlyListの紹介
概要
今回の主役は IReadOnlyList
です!
List
でもなく、IReadOnlyCollection
でもありません!
個人的には パフォーマンス と ポリモーフィズムを併せ持った良いinterfaceだと思っています。
しかし、アロケーションの発生を避けたいときにはなかなか注意が必要 な存在だったりします。。。
そんなIReadOnlyList
を紹介していきたいと思います。
ちなみにアロケーションについては別記事にする予定です。
IReadOnlyList について
注意事項
- ダウンキャスト (
readonlyList as List<>
など)は禁止という前提で説明- ダウンキャストを許した場合は読込専用が簡単に崩壊するため
追記
2022/02/25
- 「Unityでの使用例」で共変性を利用したアップキャストになるようにコード例を修正
2022/02/26
IReadOnlyListは読込専用のList
- 名前の通り 読込専用のList 向けinterface
- 標準配列(
Array
)やList
などが継承している
- 標準配列(
- 各要素の変更が不可になっており
list[0] = default
などはコンパイルエラーとなる - 主な利用用途は変更不可のListとして提供したいときなど
[Tooltip("ラベル一覧")] [SerializeField] private List<string> _labelList; // 読込専用としてラベル一覧をクラス外に提供 public IReadOnlyList<string> LabelList => _labelList;
C#標準の一次元配列はIReadOnlyListにキャスト可能
string[] strList = new string[] { "foo", "bar", "baz" }; IReadOnlyList<string> readonlyStrList = strList;
- 公式ドキュメントにも 「一次元配列は
IList<T>
とIEnumerable<T>
を実装している」と明記されているSingle-dimensional arrays also implement IList<T> and IEnumerable<T>.
IReadOnlyCollectionとの違いは index指定で要素にアクセスが可能なこと
list[i]
がList
同様に使用可能- そもそも
T this[int index] { get; }
の定義はIReadOnlyList
由来 IReadOnlyCollection
と異なりindexさえわかればO(1)
の計算量でアクセス可能
※個人的にパフォーマンス的観点で有効性を感じるところ
IReadOnlyListのTに値型を宣言すれば要素含め読込専用となる
[SerializeField] private List<Vector3> _vectorList; public IReadOnlyList<Vector3> VectorList => _vectorList;
- 上記のようにTが値型で
IReadOnlyList
に変換した場合は完全な読込専用として提供される
IReadOnlyListは参照型の要素を読込専用にはしない
IReadOnlyList
はあくまでList自体の変更を不可にしたもので、要素本体はT型に従う- 要素の方が
GameObject
の場合は引き続きnameやtransformを変更することは可能
- 要素の方が
IReadOnlyList<GameObject> readonlyObjectList = new List<GameObject> { ... }; readonlyObjectList[0].name = "Renamed_" + readonlyObjectList[0].name;
但し、後述する共変性を利用することで参照型の要素本体も読込専用としての提供も可能
IReadOnlyList<out T>のため共変性持つ
public interface IReadOnlyList<out T> : System.Collections.Generic.IEnumerable<out T>, System.Collections.Generic.IReadOnlyCollection<out T>
- 上記のように
out T
で宣言されているため、IReadOnlyList
は共変性がある
例えば以下のような参照型クラスFoo
、interface INameHolder
があったとする。
public interface INameHolder { string Name { get; } } public class Foo : INameHolder { public string Name { get; set; } = "Default Name"; }
上記Foo
使ったList<Foo>
は共変性を使って以下のような変換が可能。
IReadOnlyList<INameHolder> readonlyNameHolderList = new List<Foo> { ... };
// コンパイルエラー(INameHolderはget_Nameのみ提供のため) readonlyNameHolderList[1].Name = "Melon";
readonlyの継承型として提供することで参照型要素もreadonlyとして提供可能。
Unityでの使用例
MonoBehaviourやScriptableObjectを依存逆転の法則を当てはめて提供
- Unityとは疎結合にしたまま、Unityの機能を利用した読込専用データ群を配布に利用するなど
※以下はあくまで実装イメージ
[CreateAssetMenu(fileName = nameof(ScriptableObjectNameHolder), menuName = "ScriptableObjects/" + nameof(ScriptableObjectNameHolder), order = 1)] public sealed class ScriptableObjectNameHolder : ScriptableObject, INameHolder { [SerializeField] private string _name; // ScriptableObject側で定義された名前を読込専用として提供 public string Name => _name; }
以下の様に List<ScriptableObjectNameHolder>
から IReadOnlyList<INameHolder>
に変換し、Unityとは疎結合なインスタンス公開が可能。
[SerializeField] private List<ScriptableObjectNameHolder> _nameHolders; // 共変性を利用してUnityの存在を隠蔽し、読み込み専用として公開 public IReadOnlyList<INameHolder> NameHolders => _nameHolders;
インスタンスの公開はExtenjectやVContainerなどを利用することが多いですが詳細は割愛。
関連
本題記事
雑感
久々の投稿です。
最近、環境が変わって色々ドタバタしていました。
世間ではFacebookのMetaへ社名変更、新しいQuestが発表、メタバースへの注目など、
XR界隈で目まぐるしい変化が来そうなときに、
「なんでC#の話してるの?」って言われそうですねw。
IReadOnlyList
はUnityでの開発でも結構役立つことが多いと感じています。
DI, SOLID原則, テスタビリティ, etc...
ちなみに今回の記事は前座で本題は次回の「IReadOnlyListのアロケーション(関連にリンク)」だったりします!
なんでアロケーションなんかを調べたのかは次回にお話できればと思います。
それでは~