モバイル端末の発熱情報をUnreal Engine上で取得する

iOSとAndroid端末の発熱状況をUE上から参照する方法を調査してみました。

まず、iOS/Androidそれぞれのネイティブプラットフォームの発熱情報について軽く説明します。

# iOSの熱ステータス

参考元:
https://developer.apple.com/documentation/foundation/processinfo/thermalstate

* nominal : 通常範囲
* fair : やや上昇している
   * システムの対策：ファン開始、すぐに必要されないバックグラウンドタスクの停止、等
   * 推薦されたアプリの対策：バックグラウンドタスクの削減（例：バックグラウンドダウンロードの停止、DBインデックス更新の停止、等）
* serious : 高い
   * システムの対策：ファンがフルスピードで稼働、パフォーマンスに影響するリソース削減（例：I/O,CPU,GPUの使用の削減、60FPS→30FPSにカット）
   * 推薦されたアプリの対策：I/O,CPU,GPUのようなシステムリソースの使用の削減（例：グラフィック品質設定を低に変更、低FPSに変更、等）
* critical : 端末を冷やさないといけない状態で、パフォーマンスに大きく影響している。
   * システムの対策：ファンがフルスピードで稼働、パフォーマンスに影響するリソース削減を大幅に実行
   * 推薦されたアプリの対策：ユーザが最小限の操作が可能な状態までシステムリソースの使用率を削減

# Androidの熱ステータス

参考元：
https://source.android.com/docs/core/power/thermal-mitigation

* none (0) : スロットリングなし
* light (1) : 軽度の熱軽減策を実施。UXに影響なし
* moderate (2) : 中程度のスロットリング。フォアグラウンドのアクティビティに影響する
* severe (3) : 熱軽減策によってシステム容量を制限する必要がある。ディスプレイジャンクやオーディオジッターなどの副作用が発生
* critical (4) : プラットフォームは電力を節減するためのすべてのアクションを実行
* emergency (5) : プラットフォームの主要コンポーネントがシャットダウンされ、デバイスの機能が制限され、デバイスがシャットダウンする前の最後の警告
* shutdown (6) : 直ちにシャットダウン

# UEの熱ステータス

UEの熱ステータスは CoreDelegates で定義されている ETemperatureSeverity になります。
iOSスタイルに従っているようです。

```cpp:Engine\Source\Runtime\Core\Public\Misc\CoreDelegates.h
	enum class ETemperatureSeverity : uint8
	{
		Unknown,
		Good,
		Bad,
		Serious,
		Critical,

		NumSeverities,
	};
```

UEのエンジンコードですでに熱情報を取得する処理が存在しています。
ネイティブから通知された熱ステータスの値は以下のUE内部ステータスに変換されています：

|UE  |iOS  |Android  |
|---|---|---|
|Good  |nominal  |none (0), light (1)  |
|Bad  |fair  |moderate (2)  |
|Serious  |serious  |severe (3), critical (4)  |
|Critical  |critical  |emergency (5), shutdown (6)  |

iOS端末では、熱ステータスに変化がある度にログにもその情報が出力されているようです。
```cpp:Engine\Source\Runtime\ApplicationCore\Private\IOS\IOSAppDelegate.cpp:
-(void)temperatureChanged:(NSNotification *)notification
{
        UE_LOG(LogIOS, Display, TEXT("Temperature Changed: %s"), *Level);
}
```

# 発熱の対策を実装する
## C++で発熱情報を取得する場合

熱ステータスの変化は以下のデリゲートで検出することができます。
```cpp
FCoreDelegates::OnTemperatureChange
```

OnTemperatureChangeデリゲートを使えば、アプリ側で対策を実装することができます。
以下のサンプルコードでは、熱ステータスが serious／critical に上昇したら、グラフィック品質設定を最小限に下げるように実装します。

```cpp:Source\SampleGame\System\SampleGameInstance.h
#include "Misc/CoreDelegates.h"
virtual void OnTemperatureChange(FCoreDelegates::ETemperatureSeverity Severity);
```

```cpp:Source\SampleGame\System\SampleGameInstance.cpp:
void USampleGameInstance::Init()
{
    ...
    FCoreDelegates::OnTemperatureChange.AddUObject(this, &USampleGameInstance::OnTemperatureChange);
    ...
}
void USampleGameInstance::Shutdown()
{
    ...
	FCoreDelegates::OnTemperatureChange.RemoveAll(this);
    ...
}

void USampleGameInstance::OnTemperatureChange(FCoreDelegates::ETemperatureSeverity Severity)
{
	FString Level = TEXT("Unknown");
	Scalability::FQualityLevels Quality;

	switch (Severity)
	{
		case FCoreDelegates::ETemperatureSeverity::Good:
			Level = TEXT("Good");
			break;
		case FCoreDelegates::ETemperatureSeverity::Bad:
			Level = TEXT("Bad");
			break;
		case FCoreDelegates::ETemperatureSeverity::Serious:
			Level = TEXT("Serious");
            Quality.SetFromSingleQualityLevel(0);
			break;
		case FCoreDelegates::ETemperatureSeverity::Critical:
			Level = TEXT("Critical");
            Quality.SetFromSingleQualityLevel(0);
			break;
	}

	UE_LOG(LogSample, Log, TEXT("Temperature Changed: %s"), *Level);
	Scalability::SetQualityLevels(Quality, true);
}
```

あとは、たとえばオプション画面でユーザーがグラフィック品質を選択することが可能だったら、熱ステータスが Good に戻った時にユーザーが設定したグラフィック品質に変更する処理も追加するといいと思います。

## Blueprintで発熱情報を取得する場合

UApplicationLifecycleComponent の OnTemperatureChangeDelegate を利用すれば、Blueprintから簡単に熱ステータスの変化を検出することができます。

![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2980740/45b2cd7a-1a9a-5c41-f0ec-3917fe9caa3b.png)

こちらも、Good に戻った時に元々のグラフィック品質設定／ユーザーが設定したグラフィック品質に変更するノードを追加することが次の実装内容になります。

# 最後に

端末がすぐ熱くなったり、熱くなった時に処理落ちが起きたりすることはよくある話なので、発熱情報の変化を検出してログに出力し、熱が上昇している時の対策を検討するといいと思います。