Unityでシグナルハンドリングする
はじめに
本記事は QualiArts Advent Calendar 2023 21日目の記事です。
株式会社QualiArtsでUnityエンジニアをしている吉田です。
本投稿はUnityでシグナルハンドリングをするという、あまり需要がないであろうニッチな内容ですw。
やりたいこと
Unityアプリに送信されたシグナル(SIGTERM)を検知(ハンドリング)して、非同期処理実行後にアプリを終了させたい!
シグナルとは
ChatGPT(linuxのシグナルについて 説明)
Linuxのシグナルはプロセス間通信の一種で、特定のイベントを通知するために使われます。例えば、SIGTERMは終了要求、SIGKILLは強制終了、SIGSTOPは一時停止を指示します。プロセスはシグナルを受けてデフォルトの動作を行うか、特定の関数(シグナルハンドラ)を実行します。ただし、SIGKILLとSIGSTOPは無視や捕捉が不可能です。
Unityアプリにシグナル(SIGTERM)を送信する方法
ターミナルからkillコマンドを使用してシグナル送信できます!
// 対象アプリのPIDを確認
ps -a
94447 ttys013 0:05.53 ./hogehoge.app/Contents/MacOS/hogehoge
// killコマンドでシグナル(SIGTERM)送信
kill 94447
検証環境
- 作業マシン:macOS 12.7
- Unity2022.3.14f
- xcode 14.2
Unityでシグナル(SIGTERM)をハンドリングする方法
Unityでシグナルのハンドリング方法はこの記事 Handle linux signals in Unity3D に大変わかりやすい実装付きの説明がありました。
下記native plugin(sighandler.c)を使用すれば シグナル(SIGTERM)ハンドリングできる!
ありがとう。Sebastien!
#include <signal.h>
void (*c)(int);
void sig_callback(int n){
c(n);
}
void OnTerm(void (*handler)(int)){
c = handler;
signal(SIGTERM, sig_callback);
}
このように使用すれば,シグナル(SIGTERM)ハンドリングしてアプリ終了できる。
using System.Collections;
using UnityEngine;
public class HandleTerm : MonoBehaviour {
//This is the important part
public delegate void handlerDelegate();
[DllImport ("sighandler")]
private static extern void OnTerm(handlerDelegate handler);
void ExitHandler(){
print("onExit");
//We must call Application.Quit() manually :
// The default built-in handler is cancelled.
Application.Quit();
}
//Then it's just normal code
// Use this for initialization
void Start () {
//Register handler on initialization
OnTerm(new handlerDelegate(this.ExitHandler));
}
// Update is called once per frame
void Update () {
}
void OnApplicationQuit(){
//This doesn't get called under normal circumstances when app get a SIGTERM.
print("Application Quit");
}
}
native pluginをmacOS用にビルドする
Handle linux signals in Unity3D に掲載してあるnative pluginのビルド方法はLinux用なのでmacOS用にビルドして使用します。
// macOSターミナルでsighandler.bundleを作成
gcc -c -fPIC -arch arm64 -arch x86_64 sighandler.c
gcc -shared -arch arm64 -arch x86_64 sighandler.o -o sighandler.bundle
シグナル(SIGTERM)を検知して、アプリ終了前に非同期処理を実行させる!
新規プロジェクト(テンプレート3Dコアにしましたが、なんでもいいです)を作成し
下記DemoApp.csを追加、Pluginsフォルダにシグナルハンドリング用sighandler.bundleを配置して、macOS用アプリをビルドします。
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using UnityEngine;
public static class DemoApp
{
// originalから少し修正
// publicの必要はないのでprivateに変更
// sighandler.cの実装的には引数あったほうがよいので修正
private delegate void handlerDelegate(int sig);
[DllImport ("sighandler")]
private static extern void OnTerm(handlerDelegate handler);
// RuntimeInitializeLoadTypeは適当
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
private static void RuntimeInitializeOnLoadMethod()
{
// シグナル(SIGTERM)を検知できるようにハンドラーを登録する
OnTerm(SigTermHandler);
}
// シグナル(SIGTERM)発生時に呼び出すハンドラー
// IL2CPPでも動作するようにstaticにしてMonoPInvokeCallbackAttributeをつける
[AOT.MonoPInvokeCallbackAttribute(typeof(handlerDelegate))]
private static void SigTermHandler(int sig)
{
// 終了処理を開始
var _ = QuitAsync();
}
private static async Task QuitAsync()
{
// 10秒待機
Debug.Log("wait for 10sec");
await Task.Delay(10000);
// 終了させる
Debug.Log("Application.Quit");
Application.Quit();
}
}
ビルド設定を変更
デフォルトビルド設定だとフルスクリーン起動します。
作業しずらいのでFullscreenModeをWindowedに, Screenサイズも小さめに設定を変更したほうが良いです。
ビルドしたアプリをテストする
ビルドしたアプリを起動させる
// ターミナルからビルドしたアプリの起動
// 起動引数に"-logFile -"を追加し、ログ出力を標準出力に出力させる
./unity-signal.app/Contents/MacOS/unity-signal -logFile -
killコマンドでシグナル(SIGTERM)を送信
// 起動したアプリのPIDを確認
ps -a
:
94517 ttys013 0:05.53 ./unity-signal.app/Contents/MacOS/unity-signal -logFile -
:
// killコマンドでシグナル(SIGTERM)を送信
kill 94517
アプリを起動したターミナルに"wait for 10sec"が表示され アプリが終了すれば成功!
:
- Finished resetting the current domain, in 0.001 seconds
UnloadTime: 0.699459 ms
wait for 10sec
Application.Quit
Thread 0x17141b000 may have been prematurely finalized
Setting up 5 worker threads for Enlighten.
:
最後に
ニッチな内容でしたが、なにかの役に立てれば幸いです。
過去記事になりますが、QualiArts engineer blogでは「IDOLY PRIDE」におけるマスターデータについても投稿していますので興味ありましたら参照してみてください。