##動かなくなったので共有
2.3系(?)ではでUnionの仕様が変わったらしく、下記は使えなくなりました。
具体的に言うと、UnionがInterfaceか抽象クラスにしか対応しなくなったので、
下記の手法を使おうとすると、Unityエディタでの動作確認時点でException発生するようになります。
うまく対応させる方法があるのかは現在模索中です。
##前提
このライブラリの応用?的な使い方の話をしています。
現時点の最新版、2.2.85で動作確認済み。
https://github.com/neuecc/MessagePack-CSharp
Unityエディタでは動いてたのに、IL2CPP対応(iOSとかWebGLビルドだと強制)で
FormatterNotRegisteredException 出てキッツい人向けお役立ち情報。
##やりたいこと
変数部分をGenericにして、要処で使いわけたいパターンがあるとする。
テストコードとしてはこういう感じ。
public class TestClass
{
[MessagePackObject(keyAsPropertyName: true)]
public class TargetClass<T>
{
public T param = default;
public TargetClass(T param)
{
this.param = param;
}
}
// Tには [MessagePackObject] が付いた別クラスのインスタンスが来る想定
public void ExecuteTest<T>(T parameter)
{
var bytes = MessagePackSerializer.Serialize(new TargetClass<T>(parameter));
MessagePackSerializer.Deserialize<TargetClass<T>>(bytes);
}
}
Unityで通常実行すると普通に通るんだけども、何も考えずIL2CPP設定でビルドすると通らない。
IL2CPP環境では動的コード生成が行えないので、事前にGeneratedResolverを生成しておく必要がある。
事前生成のやり方は、公式ReadMeのAOT Code Generation (support for Unity/Xamarin)の項目で説明されている。
https://github.com/neuecc/MessagePack-CSharp#aot-code-generation-support-for-unityxamarin
問題は、AOTコード生成をやっても上のコードはまだ通らない。
なので、もうひと手間を加える必要があるぞ、というのが今回の記事。
##解決方法
[MessagePackObject(keyAsPropertyName: true)]
[MessagePack.Union(0, typeof(TargetClass<GenericClass_0>))]
[MessagePack.Union(1, typeof(TargetClass<GenericClass_1>))]
[MessagePack.Union(2, typeof(TargetClass<GenericClass_2>))]// 対応させる必要クラス分、Unionをこの要領で追加すること
public class TargetClass<T>
{
public T param = default;
public TargetClass(T param)
{
this.param = param;
}
}
Union機能を使うことで解決ができます。
https://github.com/neuecc/MessagePack-CSharp#union
__Unionを追加した後に、先述のAOT Code Generationの作業を(再)実行する必要がある__ので注意してください。
AOT Code Generationで生成したコード内を見ると、対応できてそうかは目視確認できます。
// 前略
internal static class GeneratedResolverGetFormatterHelper
{
private static readonly global::System.Collections.Generic.Dictionary<Type, int> lookup;
static GeneratedResolverGetFormatterHelper()
{
lookup = new global::System.Collections.Generic.Dictionary<Type, int>(193)
{
// 中略
{ typeof(global::TestClass.TargetClass<global::GenericClass_0>), 30 },
{ typeof(global::TestClass.TargetClass<global::GenericClass_1>), 31 },
{ typeof(global::TestClass.TargetClass<global::GenericClass_2>), 32 },
// 後略
こういう感じで、TargetClass<T> 項目の反映がなされていれば多分OKです。
###追記(2021/01/26)
一応ですが、これはバッドノウハウに相当する可能性があります。
Unionを追加した状態でAOT Code Generation手順を忘れると
UnityエディタでDynamicUnionResolver(非IL2CPP環境で、対応コードを自動生成してくれるやつ)が
Union can only be interface or abstract class.
というエラーを吐きます。
エラー表示通り、本来Unionはinterfaceとabstractクラスだけを対応するつもりのものっぽいですね。
後々の仕様変更で死ぬ可能性はあるので、その際はまた別途解決策を模索したほうがよさそうです。
Unityエディタで暫定回避をするための対応としては、下記みたいな方法があるんじゃないでしょうか。
- Union部分を
#if ENABLE_IL2CPP
とか#if !UNITY_EDITOR
で括って、発生を回避する - 逆に何もせず、エラーを都度出して見落とさないようにし、AOTコード生成を都度行う運用でカバー
###追記2(2021/01/27)
今思ったんだけどこれ、GenericじゃなくてAbstractクラスで良いのでは???