Home 코루틴 사용
Post
Cancel

코루틴 사용

머리

다들 코루틴 좋아하시나요?

저는 비 동기가너무좋아게임출시까지업데이트문을한번도사용하지않은적이존재해병님 이라고 불릴뻔한 정도로 비동기와 이벤트를 좋아합니다. (농담입니다. 그리고 어짜피 코루틴은 정통 비동기도 아닙니다)

몸통

캐싱은 쿨하게 패스하겠습니다. 알죠? 네 감사합니다.

타임아웃

코루틴을 사용할때 개인적으로 불편한 것 중 하나는 TimeOut 개념이 없다는 것입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/// <param name="onComplete">boolean : complete by condition?</param>
public static IEnumerator WaitforTimeWhileCondition(MonoBehaviour runner, float time, Func<bool> condition, Action<bool> onComplete = null)
{
    bool passed = false;
    bool timeOver = false;
    var timeRoutine = runner.StartCoroutine(WaitForTime(time, () => { passed = true; timeOver = true; }));
    var conditionRoutine = runner.StartCoroutine(WaitWhileCondition(condition, () => passed = true));
    yield return new WaitUntil(() => passed);

    onComplete?.Invoke(!timeOver);

    yield break;
}

private static IEnumerator WaitForTime(float time, Action onComplete)
{
    yield return YieldInstructionCache.WaitForSeconds(time);
    onComplete?.Invoke();
}
private static IEnumerator WaitWhileCondition(Func<bool> condition, Action onComplete)
{
    yield return new WaitWhile(condition);
    onComplete?.Invoke();
}

그러면 만들어줍니다. (timeout이니 until이 더 나았을까요?)

컨디션 검사와 시간 검사를 논블록으로 콜하고 콜백으로 변수를 수정할 수 있게한 후,

해당 변수를 기준으로 대기합니다.

랩핑

두번째는 맨날

1
2
3
4
5
6
7
8
9
private Coroutine routine;
...
void foo()
{
    if(routine != null)
        StopCoroutine(routine);
				
    routine = StartCoroutine(Work())
}

이런거 작성하기 귀찮습니다. 다 아는 형식내용인데 굳이?

그래서 대충 묶어서 처리할 수 있게 해줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public class CoroutineWrapper
{
    private class CoroutineRunner : MonoSingleton<CoroutineRunner> { }

    public CoroutineWrapper() => Runner = CoroutineRunner.Instance;
    public CoroutineWrapper(MonoBehaviour runner) => Runner = runner;

    //true : isCompleted, false : aborted
    public event Action<bool> OnCompleteOnce;

    public MonoBehaviour Runner { get; private set; }
    public Coroutine Routine { get; private set; }

    public bool IsPlaying { get => Routine != null; }

    private IEnumerator Target;

    public CoroutineWrapper Start(IEnumerator target)
    {
        Target = target;
        Runner.StartCoroutine(RunTarget());

        return this;
    }

    /// <summary>
    /// if routine already running, routine will stop and restart
    /// </summary>
    public CoroutineWrapper StartUnique(IEnumerator target)
    {
        if (Routine != null)
            Stop();

        return Start(target);
    }

    /// <summary>
    /// When Routine end, call once
    /// </summary>
    public CoroutineWrapper SetOnComplete(Action<bool> onComplete)
    {
        OnCompleteOnce += onComplete;

        return this;
    }

    private IEnumerator RunTarget()
    {
        this.Routine = Runner.StartCoroutine(Target);

        yield return Routine;

        Routine = null;

        OnCompleteOnce?.Invoke(true);
        OnCompleteOnce = null;
    }

    public void Stop()
    {
        if (Routine != null && Runner != null)
        {
            Runner.StopCoroutine(Routine);
            Routine = null;

            OnCompleteOnce?.Invoke(false);
            OnCompleteOnce = null;
        }
    }
}

물론 좀 문제가 있긴한데, 예를들어 start a - start b - stop 시 a는 멈출수 없음 등..

이건 사용 당시 IEnumerator 와 래퍼의 1:1 매칭 원칙이라 이라 그냥 냅뒀습니다.

모노 싱글톤 객체 두기

위 코드에서 싱글톤 클래스 CoroutineRunner : MonoSingleton<… 부분입니다.

비-모노 클래스들에서 코루틴을 돌릴때 (SDK, 네트워크 등) 저 monobehavior를 통해

코루틴을 돌릴 수 있어서, 메인쓰레드에서 접근해야하는 메시지큐잉 같은 것도

비-모노에서 알아서 할 수 있습니다. (모노를 들고있어!)

싱글톤이라 언제나 존재함을 보장받고.

누가 StopAllCoroutine 호출할 걱정도 안해도 되고요.

하나 집에 두면 나쁘지 않습니다 후후…

수동으로 돌리기

에디터코드에서는 돌지않다보니..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
IEnumerator currentRoutine;
    
void Init()
    => EditorApplication.update += EditorUpdate;

void EditorUpdate()
    => currentRoutine?.MoveNext();
		
void Foo()
{
    currentRoutine = GenerateCubes();

    IEnumerator GenerateCubes()
    {
		    //...
    }
}

이런식으로 에디터 타임에 수동?으로 돌릴 수 있습니다.

노가다 줄이기 - n초까지 가는 0→1 진행형

1
2
3
4
5
6
7
8
9
10
11
12
public static IEnumerator Easy(float runtime, Action<float> onUpdate)
{
    float t = 0;
    while (t < runtime)
    {
        onUpdate?.Invoke(t / runtime);
        t += Time.deltaTime;
        yield return null;
    }

    onUpdate?.Invoke(1);
}

이런형태의 코드 참많이 작성합니다. 그래서 뭉쳤습니다.

1
2
3
4
5
var move = new CoroutineWrapper();
move.StartUnique(Easy(1, progress =>
{
    transform.position = Vector3.Lerp(Vector3.left, Vector3.right, progress);
}));

이렇게 사용합니다. (더 이쁘게 할 순 없을까요?)

꼬리

너무 생각해보면 당연한 것들이라.. 쓰는게 맞나 싶긴한데 흠…

UniRx 쓰세요. 물론 이 글에선 언급하지 않습니다.

This post is licensed under CC BY 4.0 by the author.
Contents