DOTween을 async/await로 & 대응 취소

2019/01/22 붙여넣어야 할 테스트 코드 오류, 수정됨


작업 환경


Unity 2018.3

구상적 독자 목표


이미 아시는 분이에요.
CancellationToken이 뭔지 이미 알고 계신 분.

개요


DOTween을 직렬로 실행하려면 일반적으로 DOTween이 제공하는 Sequence를 사용하죠?
이것이 바로 사회의 상식이다
하지만 난 없어도 async/await에서 해보고 싶어
async/await는 남자의 꿈이 아니라...
그럼 DOTween의 Tween에 대응하는 async/await를 시도해 볼까요?

이것은 몇 번째 부친 보도입니까?


무슨 말을 하고 싶은지 알아요.
DOTween async에서 조금만 검색해보면 몇 개 있어요.
[Unity] DOTween에서 async/await 사용
http://baba-s.hatenablog.com/entry/2018/05/08/085900
Unity async/await로 비동기식 쓰기
https://qiita.com/unity_ganbaru/items/b0d837ef1baea5b8bd21
유닛 2018.3에서 갓 시작한 아기와 고추냉이.
간단한 기사부터 시작할 필요가 있어요.
그리고 상술한 페이지의 묘사 방법과 약간 다르기 때문에 상술한 보도 방법은 취소될 때의 문제가 있다
저의 이 보도의 존재를 용서해 주십시오

async/await에 어떻게 대처해야 좋을까요?


뭐냐면요.
    public static 目的の型のAwaiter GetAwaiter(this 目的の型 self){ return new 目的の型のAwaiter(); }
이러한 느낌의 확장 방법을 준비하여'목적형 Awaiter'를 정의하였다.
ICritical Notify Commpletion 인터페이스를 추가하면 다음과 같은 방법이 해결됩니다.
    public bool IsCompleted;

    public void GetResult();

    public void OnCompleted(System.Action continuation);

    public void UnsafeOnCompleted(System.Action continuation);
그러면 그 타입으로 Async/await 할 수 있을 것 같아요.
이거 진짜 쉽다.

실제로 DOTween은 async/await에 대응하지 않습니다.

public static class MyDOTweenExtention
{
    // TweenのAwaiter
    public struct TweenAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion
    {
        Tween tween;

        public TweenAwaiter(Tween tween) => this.tween = tween;

        // 最初にすでに終わってるのか終わってないのかの判定のために呼び出されるメソッドらしい
        public bool IsCompleted => tween.IsComplete();

        // Tweenは値を返さないので特に処理がいらないと思う
        public void GetResult() { }

        // このAwaiterの処理が終わったらcontinuationを呼び出してほしいって感じのメソッドらしい
        public void OnCompleted(System.Action continuation) => tween.OnKill(() => continuation());

        // OnCompletedと同じでいいっぽい?
        public void UnsafeOnCompleted(System.Action continuation) => tween.OnKill(() => continuation());
    }

    // Tweenに対する拡張メソッド
    public static TweenAwaiter GetAwaiter(this Tween self)
    {
        return new TweenAwaiter(self);
    }
}
그래, 둥!
이걸 파일에 저장하면 Tween에 async/await를 할 수 있습니다!

동작 확인


이러면 진짜 움직여요?
다음 테스트 코드를 시도해 봤죠.
public class TestTween : MonoBehaviour
{
    async void Start()
    {
        await transform.DOMove(new Vector3(0, 2, 0), 5);
        await transform.DOMove(new Vector3(2, 4, 0), 5);
    }
}
간단하게 5초 정도 걸려서 위로 이동한 다음에 5초 정도 걸려요.

그리고 다 했어!
이거, 정말 간단해!

잠깐만!취소


이 코드가 끝까지 실행되면 문제 없어요.
하지만 첫 번째 await에서 이 게임 Object가 죽으면 골치 아파요!
게임Object가 죽어도 이 async 처리는 계속 작동합니다!
코르크 느낌으로 Async/await를 사용하면...
이 게임Object가 죽어도 계속 처리할 문제!
Unity의 게임 층에서 프로그램 설계를 취소할 수 있는 것은 비교적 필요한 것이다!
취소된 대응은 반드시 주의해야 합니다!
...
단, async/await를 취소하면'CancellationToken'사용 가능
이 Awaiter는 도대체 언제 CancellationToken을 내면 좋을까요?

CancellationToken에 해당하는 Awaiter를 쓰세요!

public struct TweenAwaiter : ICriticalNotifyCompletion
{
    Tween tween;
    CancellationToken cancellationToken;

    public TweenAwaiter(Tween tween, CancellationToken cancellationToken)
    {
        this.tween = tween;
        this.cancellationToken = cancellationToken;
    }

    public bool IsCompleted => !tween.IsPlaying();

    public void GetResult() => cancellationToken.ThrowIfCancellationRequested();

    public void OnCompleted(Action continuation) => UnsafeOnCompleted(continuation);

    public void UnsafeOnCompleted(Action continuation)
    {
        CancellationTokenRegistration regist = new CancellationTokenRegistration();
        var tween = this.tween;

        // Tweenが死んだら続きを実行
        tween.OnKill(() =>
        {
            regist.Dispose(); // CancellationTokenRegistrationを破棄する
            continuation(); // 続きを実行
        });

        // tokenが発火したらTweenをKillする
        regist = cancellationToken.Register(()=>{
            tween.Kill(true);
        });
    }

    public TweenAwaiter GetAwaiter() => this;
}

public static class TweenAwaiterEx
{
    // TweenにToAwaiter拡張メソッドを追加
    public static TweenAwaiter ToAwaiter(this Tween self, CancellationToken cancellationToken = default)
    {
        return new TweenAwaiter(self, cancellationToken);
    }
}

다 했어!
DOTween은 Kill의 호출 설정을 설정할 수 있습니다. 이렇게 하면 성공할 때 처리할 수 있습니다
CanncellationToken.Register로 로그인 취소 시 처리!

테스트 코드

public class TestTween : MonoBehaviour
{
    CancellationTokenSource source;

    void Start()
    {
        // CancellationTokenSourceを準備
        source = new CancellationTokenSource();
        Go(source.Token).Forget();
    }

    private void OnDestroy()
    {
        // CancellationTokenSourceを破棄する
        source.Cancel();
        source.Dispose();
    }

    async UniTask Go(CancellationToken token)
    {
        try
        {
            // 上に移動、ToAwaiter拡張メソッドにCancellationTokenを渡す
            await transform.DOMove(new Vector3(0, 5, 0), 3).ToAwaiter(token);
            Debug.Log("Step1");

            // 右斜め上に移動
            await transform.DOMove(new Vector3(5, 5, 0), 3).ToAwaiter(token);
            Debug.Log("Step2");
        }
        finally
        {
            Debug.Log("Finish");
        }
    }
}
그러면 게임Object가 DOTween의 await에서 죽어도 괜찮아!
실제로 tween 이동 중 Unity Editor의 등급 제도에서 강제로 삭제하려 한다면
Finish 문자열이 콘솔에 즉시 출력됩니다!
너무 좋아요!

그러나 DOWeen의 직렬 실행을 위한 실제 응용은 DOTween의 Sequence를 사용하는 것이 좋다
DOTween이라면 그런 면에서 재미가 많아요.

끝맺다


나는 갓난아이이기 때문에 여러 번 말했다
나는 이 페이지가 있는지 없는지 전혀 장담할 수 없다
만약 틀렸다면, 엄격하게 평론하고 나에게 정답을 알려주세요!

라이선스


MIT License 또는 Apache2.0 License를 좋아하는 사람은 소스 코드 라이센스를 사용합니다.

좋은 웹페이지 즐겨찾기