2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MemoryPackでUniRxのReactive〇〇をシリアライズする

Last updated at Posted at 2023-02-01

はじめに

MemoryPack で UniRx の Reactive〇〇 のような ジェネリック型 をシリアライズする方法を備忘録として残しておきます。
UniRxの ReactiveProperty, ReactiveCollection, ReactiveDictionary を例に出しますが、基本的にはどのジェネリック型にも対応できると思います。他のライブラリのクラスなど、自分では変更できないようなクラス向けです

環境は以下の通りです。

  • Unity2021.3.17
  • MemoryPack1.9.12

本記事の内容の実装とテストはこちら https://github.com/euglenach/MemoryPackSerializeRxTest

Zennに他にも色々なクラスをまとめてます(https://zenn.dev/euglenach/scraps/324467a4a4cfdd)

MemoryPackとは

MemoryPackのGithubneuecc先生のブログを見ることをオススメします。

ジェネリック型の外部クラスをシリアライズする

ReactiveProperty

MemoryPackのReadmeの serialize-external-types にしたがってシリアライズ対応していきます。

ラッパーとフォーマッターのコード全文
ReactivePropertyFormatter.cs
#nullable enable
using MemoryPack;
using UniRx;

[MemoryPackable]
public readonly partial struct SerializableReactiveProperty<T>
{
    [MemoryPackIgnore] public readonly ReactiveProperty<T> ReactiveProperty;
    [MemoryPackInclude] private T Value => ReactiveProperty.Value;

    [MemoryPackConstructor]
    private SerializableReactiveProperty(T value)
    {
        ReactiveProperty = new(value);
    }

    public SerializableReactiveProperty(ReactiveProperty<T> reactiveProperty)
    {
        this.ReactiveProperty = reactiveProperty;
    }
}

public class ReactivePropertyFormatter<T> : MemoryPackFormatter<ReactiveProperty<T>>
{
    public override void Serialize(ref MemoryPackWriter writer, ref ReactiveProperty<T>? value)
    {
        if (value == null)
        {
            writer.WriteNullObjectHeader();
            return;
        }

        writer.WritePackable(new SerializableReactiveProperty<T>(value));
    }

    public override void Deserialize(ref MemoryPackReader reader, ref ReactiveProperty<T>? value)
    {
        if (reader.PeekIsNull())
        {
            value = null;
            reader.Advance(1);
            return;
        }
    
        var wrapped = reader.ReadPackable<SerializableReactiveProperty<T>>();
        value = wrapped.ReactiveProperty;
    }
}

内部にReactivePropertyを持ち、ReactiveProperty.Value だけシリアライズ対象にする MemoryPackableな構造体 を定義しました。カスタムフォーマッターではそれをもとにシリアライズさせます。

ReactiveCollection

フォーマッターのコード全文
ReactiveCollectionFormatter.cs
#nullable enable
using MemoryPack;
using UniRx;

public class ReactiveCollectionFormatter<TElement> : MemoryPackFormatter<ReactiveCollection<TElement>>
{
    public override void Serialize(ref MemoryPackWriter writer, ref ReactiveCollection<TElement>? value)
    {
        if (value == null)
        {
            writer.WriteNullCollectionHeader();
            return;
        }

        var formatter = writer.GetFormatter<TElement?>();

        writer.WriteCollectionHeader(value.Count);
        foreach (var item in value)
        {
            var v = item;
            formatter.Serialize(ref writer, ref v);
        }
    }

    public override void Deserialize(ref MemoryPackReader reader, ref ReactiveCollection<TElement>? value)
    {
        if (!reader.TryReadCollectionHeader(out var length))
        {
            value = default;
            return;
        }

        var formatter = reader.GetFormatter<TElement?>();

        var collection = new ReactiveCollection<TElement>();
        for (var i = 0; i < length; i++)
        {
            TElement? v = default;
            formatter.Deserialize(ref reader, ref v);
            collection.Add(v);
        }

        value = collection;
    }
}

ReactiveCollection は GenericCollectionFormatter の実装を丸パクリしています。

ReactiveDictionary

フォーマッターのコード全文
ReactiveDictionaryFormatter.cs
#nullable enable
using MemoryPack.Formatters;
using UniRx;

public class ReactiveDictionaryFormatter<TKey, TValue> : GenericDictionaryFormatterBase<ReactiveDictionary<TKey, TValue>,TKey, TValue>
    where TKey : notnull
{
    protected override ReactiveDictionary<TKey, TValue> CreateDictionary()
    {
        return new();
    }
}

ReadtiveDictionary のような IDictionary<TKey, TValue>GenericDictionaryFormatterBaseクラス を継承したカスタムフォーマッターを作ると実装がとても楽です。

他にも ISet<T>向けの GenericSetFormatterBase があります。 (なぜ GenericCollectionFormatterBase は無いのだろうか...)

MemoryPackFormatterProviderに登録する

これらジェネリック型は MemoryPackFormatterProvider.RegisterGenericType() で登録します。

MemoryPackFormatterProvider.RegisterGenericType(typeof(ReactiveProperty<>), typeof(ReactivePropertyFormatter<>));
MemoryPackFormatterProvider.RegisterGenericType(typeof(ReactiveCollection<>), typeof(ReactiveCollectionFormatter<>));
MemoryPackFormatterProvider.RegisterGenericType(typeof(ReactiveDictionary<,>), typeof(ReactiveDictionaryFormatter<,>));

第1引数にシリアライズ対象の型第2引数にフォーマッターの型 を渡します。

IL2CPPで死ぬかも

RegisterGenericTypeIL2CPP環境だと、コンパイル時に必要な型を認識できない可能性があるようです。
なので愚直に使う型を全て列挙する必要があります。

IL2CPPならこっち
MemoryPackFormatterProvider.Register(new ReactivePropertyFormatter<string>());
MemoryPackFormatterProvider.Register(new ReactivePropertyFormatter<int>());
MemoryPackFormatterProvider.Register(new ReactivePropertyFormatter<float>());
MemoryPackFormatterProvider.RegisterCollection<ReactiveCollection<int>, int>();
MemoryPackFormatterProvider.RegisterCollection<ReactiveCollection<float>, float>();
MemoryPackFormatterProvider.RegisterCollection<ReactiveCollection<string>, string>();

// etc...

おわりに

ここ1週間くらい MemoryPack をちょこちょこと触っていました。
MemoryPack は割と新しいライブラリで情報があまりありません。その中で手探りで見つけた方法なので MemoryPackの作法として正しくない 可能性があります。間違い等ありましたらコメントやTwitterで連絡いただけると幸いです。

2
2
0

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
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?