しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

【失敗】Unity の Roslyn で MemoryPack の属性記述を自動生成したい

※ これは 2023/10/05 時点の Unity 2023.1.16f1 の情報です

最新版では動作が異なる可能性がありますのでご注意ください

前回で Unity の Roslyn を利用したコード生成をやりやすくしたので、今度は MemoryPack 用の属性記述を自動生成してみたい

コード生成先のプロジェクトは下記のような手順で MemoryPack のパッケージ設定をしておく

コード生成プロジェクトの CodeGenerator.cs のコード出力部分

下記のところを

        private void CreateProperties(GeneratorExecutionContext context, SyntaxReceiver receiver, Compilation compilation)
        {
            foreach (var classSyntax in receiver.TargetClasses)
            {
                var model = compilation.GetSemanticModel(classSyntax.SyntaxTree);
                var symbol = model.GetDeclaredSymbol(classSyntax);
                var name = symbol.Name;
                var properties = symbol.GetAttributes()
                    .Where(x => x.AttributeClass.Name == "SimplePropertyAttribute")
                    .Select(x => (Name: x.ConstructorArguments[0].Value.ToString(), Comment: x.ConstructorArguments[1].Value.ToString()))
                    .ToArray();

                var sb = new CodeBuilder(true);
                sb.AppendLine("using System.Collections.Generic;");
                sb.AppendLine();
                using (sb.BeginBlock($"public partial class {name}"))
                {
                    foreach (var property in properties)
                    {
                        var publicName = char.ToUpper(property.Name[0]) + property.Name.Substring(1);
                        var localName = char.ToLower(property.Name[0]) + property.Name.Substring(1);

                        sb.AppendLine();
                        sb.AppendSummary(property.Comment);
                        using (sb.BeginBlock($"public string {publicName}"))
                        {
                            sb.AppendLine($"get => this.{localName};");
                            using (sb.BeginBlock("set"))
                            {
                                using (sb.BeginBlock($"if (this.{localName} != value)"))
                                {
                                    sb.AppendLine($"this.{localName} = value;");
                                }
                            }
                        }
                        sb.AppendLine($"private string {localName} = null;");
                    }
                }

                context.AddSource($"{name}.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8));
            }
        }

次のように変更

        private void CreateProperties(GeneratorExecutionContext context, SyntaxReceiver receiver, Compilation compilation)
        {
            foreach (var classSyntax in receiver.TargetClasses)
            {
                var model = compilation.GetSemanticModel(classSyntax.SyntaxTree);
                var symbol = model.GetDeclaredSymbol(classSyntax);
                var name = symbol.Name;
                var properties = symbol.GetAttributes()
                    .Where(x => x.AttributeClass.Name == "SimplePropertyAttribute")
                    .Select(x => (Name: x.ConstructorArguments[0].Value.ToString(), Comment: x.ConstructorArguments[1].Value.ToString()))
                    .ToArray();

                var sb = new CodeBuilder(true);
                sb.AppendLine("using MemoryPack;");
                sb.AppendLine("using System.Collections.Generic;");
                sb.AppendLine();
                sb.AppendLine("[MemoryPackable]");
                using (sb.BeginBlock($"public partial class {name}"))
                {
                    foreach (var property in properties)
                    {
                        var publicName = char.ToUpper(property.Name[0]) + property.Name.Substring(1);
                        var localName = char.ToLower(property.Name[0]) + property.Name.Substring(1);

                        sb.AppendLine();
                        sb.AppendSummary(property.Comment);
                        sb.AppendLine("[MemoryPackIgnore]");
                        using (sb.BeginBlock($"public string {publicName}"))
                        {
                            sb.AppendLine($"get => this.{localName};");
                            using (sb.BeginBlock("set"))
                            {
                                using (sb.BeginBlock($"if (this.{localName} != value)"))
                                {
                                    sb.AppendLine($"this.{localName} = value;");
                                }
                            }
                        }
                        sb.AppendLine("[MemoryPackInclude]");
                        sb.AppendLine($"private string {localName} = null;");
                    }
                }

                context.AddSource($"{name}.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8));
            }
        }

ところどころに MemoryPack 用の属性記述を挿入してみた

いつものように VSCode のソリューションエクスプローラーの右クリックメニューから「リビルド」実行

出力先プロジェクトを開いている UnityEditor に戻り、下記のような Data.cs を追加

[Sample.SimpleProperty("Foo", "Fooプロパティ")]
[Sample.SimpleProperty("Bar", "Barプロパティ")]
public partial class Data
{
    public Data()
    {
    }
}

Test.cs は下記のように変更

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public partial class Test : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        var data = new Data()
        {
            Foo = "Foo",
            Bar = "Bar",
        };
        var input = MemoryPack.MemoryPackSerializer.Serialize(data);
        var output = MemoryPack.MemoryPackSerializer.Deserialize<Data>(input);

        UnityEngine.Debug.Log($"---------------> {output.Foo}/{output.Bar}");
    }
}

[Ctrl] + [R] で再コンパイル

この Test.cs を Hierarchy 上の適当な GameObject にアタッチした上で実行すると⋯

エラー!⋯シリアライズに失敗した?!