LoginSignup
3

More than 3 years have passed since last update.

posted at

updated at

MacのUnityでMessagePack-CSharpのAOT Code Generationが動かない場合の対処

MessagePackってmacで使えないんでしょ? やだー!

MessagePack-CSharp
存在はけっこう前から知っていたのですが、UnityIL2CPP環境下だと事前コードジェネレート1が必要で、それがwindows環境でしか動かない……とのことだったので敬遠していました。

MessagePack for C# v2によるC#における最新のI/Oパイプライン最適化

ところがv2が出てなんかmacでも動くようになった? っぽい? ということなので試してみました。

実際動いたやり方はここここに書いてあります。
おま環疑惑がだいぶ濃厚なので、そこのところを念頭に置いてお読みください。

事前説明

MessagePackのバージョンは以下のとおりです。
2.0.335

データクラス

このデータクラスをSerialize/Deserializeするのが今回の目標です。
こんな感じのディレクトリに配置してあります。
スクリーンショット 2020-01-29 0.58.35.png

LineData.cs
using MessagePack;
using UnityEngine;

[MessagePackObject]
public class LineData
{
    [Key(0)]
    public int Index;

    [Key(1)]
    public string ID;

    [Key(2)]
    public Vector3[] Pos;

    [Key(3)]
    public Quaternion[] Rot;


    public override string ToString()
    {
        return $"{nameof(Index)}: {Index}, {nameof(ID)}: {ID}, {nameof(Pos)}: {Pos}, {nameof(Rot)}: {Rot}";
    }
}

テストコード

testCode
[SerializeField]
private Text _text;

private void Start()
{
    StaticCompositeResolver.Instance.Register(
        StandardResolver.Instance,
        UnityBlitResolver.Instance,
        GeneratedResolver.Instance // 今回事前生成しようとしているResolver
    );

    var option = MessagePackSerializerOptions.Standard.WithResolver(StaticCompositeResolver.Instance);
    MessagePackSerializer.DefaultOptions = option;

    var before = new LineData();
    before.ID = Guid.NewGuid().ToString();
    before.Index = 1;
    before.Pos = Enumerable.Range(0, 10).Select(i => UnityEngine.Random.insideUnitSphere).ToArray();
    before.Rot = Enumerable.Range(0, 10).Select(i => UnityEngine.Random.rotation).ToArray();

    Debug.Log($"before = {before.ToString()}");

    var binary = MessagePackSerializer.Serialize(before);

    var after = MessagePackSerializer.Deserialize<LineData>(binary);

    Debug.Log($"after = {after.ToString()}");

    _text.text = before.ToString() + Environment.NewLine + after.ToString();
}

事前生成なしでもエディタでは動きますが、Androidとかにビルドすると動きません。

みちすじ

初回起動

Window -> MessagePack -> CodeGenerator

スクリーンショット 2020-01-28 21.04.00.png
ねえぞ! って言われます。
確かに.Net Core 3入れた覚えはないのでリンクの通りにインストールします。
Terminalからバージョンチェック。2

バージョン確認
dotnet --version
3.1.101

入ってます。というわけでもう一回起動。
スクリーンショット 2020-01-28 21.04.00.png
ねえぞ! って言われます。

ある!!!!!!

Assets/Scripts/MessagePack/Unity/MessagePackWindow.cs
メッセージを生成しているのはこのスクリプトです。
ねえぞ! って言ってきてるのはこのあたり。

MessagePackWindow_259~270
public static async Task<(bool found, string version)> FindDotnetAsync()
{
    try
    {
        var version = await InvokeProcessStartAsync("dotnet", "--version");
        return (true, version);
    }
    catch
    {
        return (false, null);
    }
}

InvokeProcessStartAsyncの中身を見る限り、普通にTerminaldotnet --versionと入力したときと同じ結果を返してくる……はず。再起動してみても変化はありません。
ProcessStartInfoFileNameとかWorkingDirectoryとかがうまくいってないっぽいですが、シェルにぜんぜん詳しくないのでわかりません。

気分転換

いったんUnityを離れてリポジトリのReadMeの手順を進めます。MessagePack.Generatorをインストール。
dotnet tool install --global MessagePack.Generator

入ってます。でなんかdotnet toolsをPATHに追加しろとか言ってくるので素直に表示されるコマンドをコピー→実行していきます。今回もTerminalから存在チェック。

CodeGenerator
dotnet-mpc
argument list:
-i, -input: Input path of analyze csproj or directory, if input multiple csproj split with ','.
-o, -output: Output file path(.cs) or directory(multiple generate file).
-c, -conditionalSymbol: [default=null]Conditional compiler symbols, split with ','.
-r, -resolverName: [default=GeneratedResolver]Set resolver name.
-n, -namespace: [default=MessagePack]Set namespace root name.
-m, -useMapMode: [default=False]Force use map mode serialization.
-ms, -multipleIfDirectiveOutputSymbols: [default=null]Generate #if-- files by symbols, split with ','.

います。でもどうせねえぞって言われるんでしょ。知ってる。

コマンドラインで実行してみる

/Users/XXXXXX/.dotnet/tools/dotnet-mpc
実態っぽいのはここのパスにあります。なのでAssets直下に移動して次のコマンドを打ってみます。
/Users/XXXXXX/.dotnet/tools/dotnet-mpc -i Scripts/Entity -o Scripts/Entity/Generated/MessagePackGenerated.cs

スクリーンショット 2020-01-29 1.03.22.png

こんな感じで生成されました。
MessagePackGenerated.csにはusing UnityEngineが足りなくてエラーが出ていますが、それさえ補填してあげればビルドは通ります。
ビルドすると実機でも動作します。

エディタ拡張

こんな感じのメソッドを作ってみました。とりあえず自分の環境では動いてます。
既存のエディタ拡張をいろいろコメントアウトするとか、必要なとこだけコピペして新しく作るとかしてこのメソッドに食わせれば動く……はず。
using UnityEngineが足りなくてエラー出てるのは相変わらずなのでこわいですが。

GenerateResolverAsync
public static Task<string> GenerateResolverAsync(MpcArgument argument)
{
    var psi = new ProcessStartInfo()
    {
        CreateNoWindow = true,
        WindowStyle = ProcessWindowStyle.Hidden,
        StandardOutputEncoding = Encoding.UTF8,
        StandardErrorEncoding = Encoding.UTF8,
        RedirectStandardOutput = true,
        RedirectStandardError = true,
        UseShellExecute = false,
        FileName = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".dotnet/tools/dotnet-mpc"),
        Arguments = argument.ToString(),
        WorkingDirectory = Application.dataPath
    };

    Process p;
    try
    {
        p = Process.Start(psi);
    }
    catch (Exception ex)
    {
        return Task.FromException<string>(ex);
    }

    var tcs = new TaskCompletionSource<string>();
    p.EnableRaisingEvents = true;
    p.Exited += (object sender, System.EventArgs e) =>
    {
        var data = p.StandardOutput.ReadToEnd();
        p.Dispose();
        p = null;

        tcs.TrySetResult(data);
    };

    return tcs.Task;
}

まとめ

macでもなんとかならないことはないっぽい。です。
windowsでは試していませんが、あっさり動くのではないでしょうか。
ぶっちゃけSerialize/Deserializeするクラスを頻繁に変更することってそうないと思うので、必要なときだけwindowsで生成してgitで共有してもらったほうが早いと思います。

ちなみにOSアップデートしてない、まだbashのmacでも同様でした。とはいえどちらも自分が使っているやつなので、他の人が使ってるmacだと動くかも。
あと、ぱっと見た感じIssuesに似たような問題は上がってませんでした。おま環なのでは疑惑。

おしまい。

参考

MessagePack-Csharp と MagicOnion のコード生成を自動化する


  1. コンパイル時にコードを生成しておく、のが大好きです。もっと言うなら通信とかするならProtoBufみたいにお約束を作っておいたほうがみんなしあわせなのでは? くらいに思っています。でも.proto書くのめんどくせえ! 

  2. なんか知らないうちにbashじゃなくてzshになってる。macのOSアップデートでそうなった? らしい? なぞ。 

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
3