ジェネリック関数
スクリプトのシリアライゼーション

スクリプトの制限

Unityがサポートしているすべてのプラットフォームで、同じ API が使用できるように努力しています。ですが、いくつかのプラットフォームでは、固有の制限があります。これらの制限を理解して、クロスプラットフォームのコードが書けるようになるために、以下に各プラットフォームに適用される制限とスクリプティングバックエンドについて説明します。

プラットフォーム スクリプティングバックエンド 制限
Standalone Mono なし
WebGL IL2CPP 事前コンパイルスレッド不可
iOS IL2CPP 事前コンパイル
Android Mono なし
Android IL2CPP 事前コンパイル
Tizen Mono 事前コンパイル
XBox One Mono 事前コンパイル
XBox One IL2CPP 事前コンパイル
WiiU Mono 事前コンパイル
PS Vita Mono 事前コンパイル
PS Vita IL2CPP 事前コンパイル
PS4 Mono 事前コンパイル
PS4 IL2CPP 事前コンパイル
ユニバーサル Windows プラットフォーム .NET .NET Core クラスライブラリサブセットを使用
ユニバーサル Windows プラットフォーム IL2CPP 事前コンパイル

事前コンパイル

プラットフォームの中には、実行時のコード生成ができないものもあります。そのため、そのようなデバイスで実行時 (Just-In-Time、JIT) コンパイルをおこなうと失敗します。代わりに、すべてのコードを事前 (Ahead-Of-Time、AOT) にコンパイルする必要があります。しばしば、この差異はあまり重要でないこともあります。しかし、ある特定の場合では、AOT コンパイルを必要とするプラットフォームには、追加の配慮が必要なことがあります。

System.Reflection.Emit

事前 (AOT) コンパイルが必要なプラットフォームには、System.Reflection.Emit 名前空間のメソッドを実装することができません。それ以外の System.Reflection は、使用可能なことに気を付けてください。ただし、リフレクションを通して使用されるコードが実行時に必要であることをコンパイラーが検知する必要があります。

シリアライズ

事前 (AOT) コンパイルが必要なプラットフォームでは、リフレクションの使用が原因でシリアライズと非シリアライズで問題が発生することがあります。シリアライズと非シリアライズの一部として、型とメソッドをリフレクション経由でのみ使用できる場合、事前コンパイラーは型とメソッドのためにコードが生成される必要があることを検知することができません。

ジェネリックな仮想メソッド

ジェネリックメソッドでは、開発者が書いたコードを拡張して、デバイスで実際に実行できるようなコードにするコンパイラーによる追加作業が必要です。例えば、intdoubleをもつListでは、異なるコードが必要です。仮想メソッドが存在する場合、挙動はコンパイル時よりもはむしろ実行時に決定されます。そのため、ソースコードから明白にされないコードがある場合、コンパイラーによって、簡単に適所で実行時コード生成が要求されます。

以下のようなコードがあるとします。実行時 (JIT) コンパイルのプラットフォームでは、意図した通りに動きます (「Message value: Zero」がコンソールに 1 回出力されます)。

using UnityEngine;
using System;

public class AOTProblemExample : MonoBehaviour, IReceiver {
    public enum AnyEnum {
        Zero,
        One,
    }

    void Start() {
        // 微細なトリガ:  AOT の問題をトリガするには
        //マネージャータイプは Manager でなく IManager でなくてはならない。 
        IManager manager = new Manager();
        manager.SendMessage(this, AnyEnum.Zero);
    }

    public void OnMessage<T>(T value) {
        Debug.LogFormat("Message value: {0}", value);
    }
}

public class Manager : IManager {
    public void SendMessage<T>(IReceiver target, T value) {
        target.OnMessage(value);
    }
}

public interface IReceiver {
    void OnMessage<T>(T value);
}

public interface IManager {
    void SendMessage<T>(IReceiver target, T value);
}

このコードを IL2CPP スクリプティングバックエンドの事前 (AOT) コンパイルのプラットフォームで実行すると、以下の例外が発生します。

ExecutionEngineException: Attempting to call method 'AOTProblemExample::OnMessage<AOTProblemExample+AnyEnum>' for which no ahead of time (AOT) code was generated.
  at Manager.SendMessage[T] (IReceiver target, .T value) [0x00000] in <filename unknown>:0 
  at AOTProblemExample.Start () [0x00000] in <filename unknown>:0

同様に、Mono スクリプティングバックエンドでは、以下のような類似の例外が発生します。


  ExecutionEngineException: Attempting to JIT compile method 'Manager:SendMessage<AOTProblemExample/AnyEnum> (IReceiver,AOTProblemExample/AnyEnum)' while running with --aot-only.
    at AOTProblemExample.Start () [0x00000] in <filename unknown>:0

事前 (AOT) コンパイラーは、AnyEnumT を持つジェネリックメソッド OnMessage のコードを生成する必要があることを察知しません。そのため、このメソッドを飛ばしてそのまま継続します。そのメソッドが呼び出されると、ランタイムコンパイラーは実行できる適当なコードを見つけることができず、このエラーメッセージを出力して失敗します。

このような AOT 問題を避けるために、強制的に適当なコードをコンパイラーから生成させることができます。そのためには、以下のようなコードを AOTProblemExample クラスに加えます。

public void UsedOnlyForAOTCodeGeneration() {
    // IL2CPP には以下のラインのみ必要です
    OnMessage(AnyEnum.Zero);

    // Mono はこのラインも必要です。 IManager インターフェースでなく、
    // 直接 Manager で呼び出していることに注意してください。
    new Manager().SendMessage(null, AnyEnum.Zero);

    // 例外も加えます。そうすれば、このメソッドが呼び出されれば、確実に検知できます。
    throw new InvalidOperationException("This method is used for AOT code generation only. Do not call it at runtime.");
}

コンパイラーが AnyEnumT を持つジェネリックメソッド OnMessage を明白に呼び出すと、ランタイムに適切なコードが生成され実行されます。UsedOnlyForAOTCodeGeneration メソッドは呼び出される必要はありません。ただ、コンパイラーが見るように存在しさえすればよいです。

スレッド不可

プラットフォームの中には、スレッドの使用をサポートしないものもあります。そのような場合、System.Threading 名前空間を使用するすべてのコードは、実行時に失敗します。また、.NET クラスのライブラリには、暗にスレッドに依存しているものもあります。よく使われる例は System.Timers.Timer クラスで、スレッドのサポートに依存しています。


ジェネリック関数
スクリプトのシリアライゼーション