はじめに
今回はUniTask-Supplement
というUniTask
のCancellationToken
を渡す記述をより簡単にしてくれるライブラリについて紹介をしたいと思います。
導入方法
PackageManager
のAdd package from git url...
を選択し、以下の文字列を打ち込みます。
https://github.com/su10/UniTask-Supplement.git#upm
OpenUPM
にも対応しているので、そちらのやり方がいい場合はGitHub
をチェックしてください。
github.com
使い方
基本
Cysharp/UniTask
ではCancellationToken
を名前付き引数として渡さなければならなかったものを、省略できるようになります。
var cancellationToken = this.GetCancellationTokenOnDestroy(); // 以下コメントアウトされている書き方がUniTask Supplementを利用しない書き方 //await UniTask.DelayFrame(1, cancellationToken: cancellationToken); await UniTask.DelayFrame(1, cancellationToken); //await UniTask.Delay(1, cancellationToken: cancellationToken); await UniTask.Delay(1, cancellationToken); //await UniTask.Delay(TimeSpan.FromMilliseconds(1), cancellationToken: cancellationToken); await UniTask.Delay(TimeSpan.FromMilliseconds(1), cancellationToken); //await UniTask.WaitUntil(() => true, cancellationToken: cancellationToken); await UniTask.WaitUntil(() => true, cancellationToken); //await UniTask.WaitWhile(() => false, cancellationToken: cancellationToken); await UniTask.WaitWhile(() => false, cancellationToken); //await UniTask.WaitUntilValueChanged(transform, x => x.position, cancellationToken: cancellationToken); await UniTask.WaitUntilValueChanged(transform, x => x.transform, cancellationToken);
実装としてはCancellationToken = default
のようにデフォルト値が設定されていないオーバーロードを増やしています。
ちゃんとAssembly Definition Reference
を利用してUniTask
(後で紹介するがUniTask.DoTween
も)のアセンブリにコードを入れてくれていますね。助かります。
新しく実装されたメソッド
UniTask.DelayMilliseconds
・UniTask.DelaySecond
というメソッドが新しく追加されました。
// UniTask.DelayMillisecondsはUniTaskSupplementの中に実装されている(Cysharp/UniTaskには実装されていない) await UniTask.DelayMilliseconds(1, cancellationToken: cancellationToken); await UniTask.DelayMilliseconds(1, cancellationToken); // UniTask.DelaySecondはUniTaskSupplementの中に実装されている(Cysharp/UniTaskには実装されていない) await UniTask.DelaySeconds(1, cancellationToken: cancellationToken); await UniTask.DelaySeconds(1, cancellationToken);
またシンボル定義を行うことによって、これらのメソッドを利用させないようにすることもできます。
// UniTask.DelayMillisecondsを消す場合
UNITASK_SUPPLEMENT_DISABLE_DELAY_MILLISECONDS
// UniTask.DelaySecondsを消す場合
UNITASK_SUPPLEMENT_DISABLE_DELAY_SECONDS
Unity
でプロジェクト全体にシンボル定義を行う場合はProjectSettings/Player/OtherSettings/Script Compilation/Scripting DefineSymbols
に記述してあげればOKです。
またアセンブリ単位で定義したい場合はAssembly Definition File
のVersion Defines
で記述してあげます。
【Unity】asmdefのVersion Definesを利用して特定のパッケージ(の特定バージョン)がある場合にのみシンボル定義を行う - はなちるのマイノート
WhenAnyでCancellationTokenを渡す
WhenAny
にもCanellationToken
を渡せるようになります。
private async void Start() { var token = this.GetCancellationTokenOnDestroy(); // 非同期メソッドの返り値の型(UniTask<int>とUniTask<int>)が同じ場合 var (winArgumentIndex, result) = await UniTask.WhenAny<int>( cancel => HogeAsync(cancel), cancel => HogeAsync(cancel), token ); // 非同期メソッドの返り値の型(UniTask<int>とUniTask<bool>)が異なる場合 var (winArgumentIndex2, result1, result2) = await UniTask.WhenAny( cancel => HogeAsync(cancel), cancel => FugaAsync(cancel), token ); } private async UniTask<int> HogeAsync(CancellationToken token) { await UniTask.DelayMilliseconds(10, token); return 1; } private async UniTask<bool> FugaAsync(CancellationToken token) { await UniTask.DelayMilliseconds(10, token); return true; }
ただし挙動についてちゃんと理解しながら利用した方が良いでしょう。
どんな挙動をするのか実装を見てみます。
public static async UniTask<(int winArgumentIndex, T result)> WhenAny<T>( Func<CancellationToken, UniTask<T>> taskFunc1, Func<CancellationToken, UniTask<T>> taskFunc2, CancellationToken cancellationToken ) { var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); (int winArgumentIndex, T result) result = default; try { result = await WhenAny<T>( taskFunc1(cts.Token), taskFunc2(cts.Token) ); cts.Cancel(); } catch (OperationCanceledException ex) when (ex.CancellationToken == cts.Token) { if (cancellationToken.IsCancellationRequested) { throw new OperationCanceledException(ex.Message, ex, cancellationToken); } throw; } finally { cts.Dispose(); } return result; }
UniTask-Supplement/UniTask.WhenAny.Generated.cs at main · su10/UniTask-Supplement · GitHub
エラーが発生しなければ以下のステップを踏みます。
- 引数の
CancelltionToken
を紐づけながらCancellationTokenSource
を生成 - 引数の
UniTask<T>
を生成したCancellationTokenSource.Token
を渡しながら実行 - エラーなく終了すれば
CancellationTokenSource.Cancell
を呼び出す
つまり引数で渡したUniTask
達は、どれか一つのUniTask
が終われば他もキャンセルされるようになっています。(ちゃんとUniTask
の中で止まるように書いてあればであるが)
使わないのに処理が続いているなんてことが起こらないようになるので、助かる機能だと思います。
またキャンセル周りの実装について、以下の記事が参考になりました。
neue.cc
DoTween関係
UniTask.DoTween
の機能を拡張する機能も実装されています。
UniTask
のDoTween
の機能を有効にするには少し作業が必要でした。
【Unity】DoTweenでUniTaskを対応させてawaitできるようにする - はなちるのマイノート
具体的にはUNITASK_DOTWEEN_SUPPORT
をシンボル定義する必要があったわけですが、UniTask-Supplement
の機能を有効にする場合は代わりにUNITASK_SUPPLEMENT_DOTWEEN_SUPPORT
を定義します。
TweenerCore<float, float, FloatOptions> tween = DOTween.To( () => 0f, x => Debug.Log(x), 1f, 1f ); CancellationToken cancellationToken = this.GetCancellationTokenOnDestroy(); // UniTask await tween.ToUniTask(cancellationToken: cancellationToken); await tween.AwaitForComplete(cancellationToken: cancellationToken); await tween.AwaitForPause(cancellationToken: cancellationToken); await tween.AwaitForPlay(cancellationToken: cancellationToken); await tween.AwaitForRewind(cancellationToken: cancellationToken); await tween.AwaitForStepComplete(cancellationToken: cancellationToken); // with UniTask Supplement await tween.ToUniTask(cancellationToken); await tween.AwaitForComplete(cancellationToken); await tween.AwaitForPause(cancellationToken); await tween.AwaitForPlay(cancellationToken); await tween.AwaitForRewind(cancellationToken); await tween.AwaitForStepComplete(cancellationToken);
実装としてCysharp/UniTask
に同梱されていたDOTweenAsyncExtensions.cs
がまるっとUniTask-Supplement
に含まれており、完全上位互換になっているので安心してください。
新しく追加された機能はDoTweenAsyncExtensions.Supplement.cs
の中に入っています。
#if UNITASK_SUPPLEMENT_DOTWEEN_SUPPORT using System.Threading; using DG.Tweening; namespace Cysharp.Threading.Tasks { public static partial class DOTweenAsyncExtensions { private const TweenCancelBehaviour DefaultTweenCancelBehaviour = #if UNITASK_SUPPLEMENT_DOTWEEN_SUPPORT_USE_ORIGINAL_DEFAULT_TWEEN_CANCEL_BEHAVIOUR TweenCancelBehaviour.Kill; #else TweenCancelBehaviour.KillAndCancelAwait; #endif public static UniTask ToUniTask(this Tween tween, CancellationToken cancellationToken) { return ToUniTask(tween, DefaultTweenCancelBehaviour, cancellationToken); } public static UniTask AwaitForComplete(this Tween tween, CancellationToken cancellationToken) { return AwaitForComplete(tween, DefaultTweenCancelBehaviour, cancellationToken); } public static UniTask AwaitForPause(this Tween tween, CancellationToken cancellationToken) { return AwaitForPause(tween, DefaultTweenCancelBehaviour, cancellationToken); } public static UniTask AwaitForPlay(this Tween tween, CancellationToken cancellationToken) { return AwaitForPlay(tween, DefaultTweenCancelBehaviour, cancellationToken); } public static UniTask AwaitForRewind(this Tween tween, CancellationToken cancellationToken) { return AwaitForRewind(tween, DefaultTweenCancelBehaviour, cancellationToken); } public static UniTask AwaitForStepComplete(this Tween tween, CancellationToken cancellationToken) { return AwaitForStepComplete(tween, DefaultTweenCancelBehaviour, cancellationToken); } } } #endif
UniTask-Supplement/DOTweenAsyncExtensions.Supplement.cs at main · su10/UniTask-Supplement · GitHub
またDoTweenAsyncExtensions.Supplement.cs
ではTweenCancelBehaviour
なるものがUniTask.DoTween
のデフォルトのものと異なっています。
private const TweenCancelBehaviour DefaultTweenCancelBehaviour = #if UNITASK_SUPPLEMENT_DOTWEEN_SUPPORT_USE_ORIGINAL_DEFAULT_TWEEN_CANCEL_BEHAVIOUR TweenCancelBehaviour.Kill; #else TweenCancelBehaviour.KillAndCancelAwait; #endif
こちらをデフォルトと同じ挙動にしたい場合はUNITASK_SUPPLEMENT_DOTWEEN_SUPPORT_USE_ORIGINAL_DEFAULT_TWEEN_CANCEL_BEHAVIOUR
を定義してとのことです。
GitHub - su10/UniTask-Supplement: Supplemental codes for UniTask.
具体的にどう異なるかは以下のコードの箇所が関係しそうです。
void OnUpdate() { originalUpdateAction?.Invoke(); if (!cancellationToken.IsCancellationRequested) { return; } switch (this.cancelBehaviour) { case TweenCancelBehaviour.Kill: default: this.tween.Kill(false); break; case TweenCancelBehaviour.KillAndCancelAwait: this.canceled = true; this.tween.Kill(false); break; // 以下省略 } }