北陽の側域センサーを利用するにあたり、データ取得部分をUnityのMainThreadから切り離して取得することで、fpsを向上させるコードを書きました。外部装置と連携する際に、Threadingして並列処理でデータを取得したいケースはよくあるパターンかなと思いますが、UniTaskを利用すれば手軽に書くことができます。
その際に二点ほど気を付けるところがあります。
- 外から見えるデータを書き込むときはロックする
- ThreadのMain・ThreadPoolを切り替える
この2点を踏まえて、Sample Codeをみてください。
省略型のSample Code
using ... // 省略
public class Sensor : MonoBehaviour
{
// 今回はセンサーで距離が取れる想定で、
// このクラスの外側からはこのDistancesを使って距離情報にアクセスしてもらう
public List<long> Distances => distances;
private List<long> distances = new ();
// その他は省略
private object lookObj = new ();
private CancellationTokenSource updateDataCanceler;
void Start()
{
// 外部機器に接続するところ、場合によって異なるので無視して良い
Open();
updateDataCanceler = new CancellationTokenSource();
// データ取得するためのスレッド開始、ここではまだMainThread
UpdateDataOnThread().Forget();
}
private void OnDestroy()
{
// UnityEditorなどで停止するときに合わせてスレッドを停止させる
updateDataCanceler.Cancel();
// 外部機器との接続を閉じるところ、場合によって異なるので無視してよい
Close();
}
// 外部機器との・・・略!
private void Open() { ... }
private void Close() { ... }
// データ更新処理をループで回し続けるメソッド
// cancelation tokenでキャンセルされまで実行し続ける
private async UniTask UpdateDataOnThread()
{
// 注意①:下記章を参照
while (!updateDataCanceler.IsCancellationRequested)
{
// 切り替え:この処理の後からUnityのメインスレッド外
await UniTask.SwitchToThreadPool();
// データ更新する実処理
UpdateData();
// 切り替え:この処理の後からUnityのメインスレッド内
await UniTask.SwitchToMainThread();
}
}
private List<long> temp = new ();
private void UpdateData()
{
try
{
var data = urg.ReadLine();
// まずはこのクラス内でしかアクセスできないtempを更新する
if (!SCIP_Reader.MD(data, ref timeStamp, ref temp)) return;
if (temp.Count == 0) return;
// 外からdistancesがアクセスされている可能性があるのでlockして更新する
lock (lookObj)
{
distances = temp.Select(t => t).ToList();
}
}
catch (Exception ex)
{
Debug.Log(ex.Message);
}
}
}
注意①
ループ内でMainThreadとThreadPoolとのスイッチングをしないと、Unityの実行を止めたときに必ずUnityが固まってしまいました。おそらくUnityの停止処理からのキャンセルと、ThreadPool内の処理の連携がうまくいっていないと推測し、性能的に特に影響がなかったので、ループ内で都度Threadingを切り替えることで回避するようにしています。
前提
- Unity 2021.3.11f1
- UniTask 2.3.3
- 北陽センサー UBG-04LX-F01