Unity가 지원하는 모든 플랫폼에 동일한 스크립팅 API와 경험을 제공하고자 노력하고 있습니다. 하지만, 몇몇 플랫폼에는 각각 고유한 제약이 있습니다. 이러한 제약을 이해하고 크로스 플랫폼을 지원하기 위해, 각각의 제약이 어떤 플랫폼과 스크립팅 백엔드에 적용되는지가 아래 표에 설명되어 있습니다.
플랫폼 | 스크립팅 백엔드 | 제약 |
---|---|---|
스탠드얼론 | Mono | None |
WebGL | IL2CPP | AOT 컴파일, 스레드 없음 |
iOS | IL2CPP | AOT 컴파일 |
Android | Mono | None |
Android | IL2CPP | AOT 컴파일 |
Samsung TV | Mono | AOT 컴파일 |
Tizen | Mono | AOT 컴파일 |
XBox 360 | Mono | AOT 컴파일 |
XBox One | Mono | AOT 컴파일 |
XBox One | IL2CPP | AOT 컴파일 |
WiiU | Mono | AOT 컴파일 |
PS3 | Mono | AOT 컴파일 |
PS Vita | Mono | AOT 컴파일 |
PS Vita | IL2CPP | AOT 컴파일 |
PS4 | Mono | AOT 컴파일 |
PS4 | IL2CPP | AOT 컴파일 |
Windows Store | .NET | .NET 코어 클래스 라이브러리 서브셋 사용 |
Windows Store | IL2CPP | AOT 컴파일 |
몇몇 플랫폼은 런타임 코드 생성을 지원하지 않습니다. 따라서 타겟 디바이스에서 JIT(just-in-time) 컴파일에 의존하는 일부 관리되는 코드는 작동하지 않습니다. 이 경우 관리되는 코드를 AOT(ahead-of-time) 방식으로 컴파일해야 합니다. 많은 경우 이러한 방식의 차이는 없지만, 몇몇 경우에서는 AOT 플랫폼을 사용할 때 몇 가지 고려해야할 사항이 있습니다.
AOT 플랫폼은 System.Reflection.Emit 네임스페이스의 메서드를 구현할 수 없습니다. 다만, 컴파일러가 리플렉션을 통해 사용된 코드가 런타임 시 존재해야 한다고 추측할 수 있는 경우에 한해 System.Reflection의 나머지 부분을 사용할 수 있습니다.
AOT 플랫폼은 리플렉션을 사용하므로 직렬화와 역직렬화 문제가 생길 수 있습니다. 타입 또는 메서드가 리플렉션을 통해 직렬화 또는 역직렬화의 일부로 사용되는 경우, AOT 컴파일러는 해당 타입이나 메서드에 대해 코드가 생성될 필요가 있다는 것을 감지할 수 없습니다.
일반 메서드는 디바이스에서 실제로 실행되기 위해 개발자가 작성한 코드를 컴파일러가 추가 작업을 통해 확장시켜야 합니다. 예를 들어, int나 double이 있는 경우 List에 대한 다른 코드를 필요로 합니다. 동작이 컴파일 도중이 아닌 런타임 시점에 결정되는 가상 메서드가 존재하는 경우, 컴파일러는 소스 코드에서는 명백하지 않더라도 런타임 코드 생성을 쉽게 요구할 수 있게 됩니다.
아래의 코드를 살펴보겠습니다. 이 코드는 JIT 플랫폼에서는 의도한 대로 작동하여, 콘솔에 “Message value: Zero”를 한 번 출력합니다.
using UnityEngine;
using System;
public class AOTProblemExample : MonoBehaviour, IReceiver {
public enum AnyEnum {
Zero,
One,
}
void Start() {
// Subtle trigger: The type of manager *must* be
// IManager, not Manager, to trigger the AOT problem.
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 컴파일러는 AnyEnum의 T와 일반 메서드 OnMessage에 대한 코드를 생성해야 함을 인지하지 못합니다. 따라서 컴파일러는 이 메서드를 무시하게 됩니다. 메서드가 호출되어도 런타임이 호출할 올바른 코드를 찾지 못하므로 오류 메시지를 출력합니다.
이러한 AOT 문제를 해결하려면 강제로 컴파일러가 올바른 코드를 생성하게 하면 됩니다. 예를 들어, AOTProblemExample 클래스에 아래와 같은 메서드를 추가할 수 있습니다.
public void UsedOnlyForAOTCodeGeneration() {
// IL2CPP needs only this line.
OnMessage(AnyEnum.Zero);
// Mono also needs this line. Note that we are
// calling directly on the Manager, not the IManager interface.
new Manager().SendMessage(null, AnyEnum.Zero);
// Include an exception so we can be sure to know if this method is ever called.
throw new InvalidOperationException("This method is used for AOT code generation only. Do not call it at runtime.");
}
컴파일러가 AnyEnum의 T와 OnMessage에 대한 명시적 호출을 받으면, 런타임이 실행할 올바른 코드를 생성합니다. UsedOnlyForAOTCodeGeneration 메서드는 호출되기 위한 목적이 아닌, 컴파일러가 감지하기 위한 목적입니다.
몇몇 플랫폼은 스레드를 지원하지 않으므로, System.Threading 네임스페이스를 사용하는 관리되는 코드는 런타임 도중 작동하지 않습니다. 또한, .NET 클래스 라이브러리의 일부분은 스레드에 암시적으로 의존합니다. 자주 사용되는 예제로는 System.Timers.Timer 클래스가 있는데, 이 클래스는 스레드 지원에 의존합니다.