코루틴

http://neue.cc/2014/12/18_499.html

StartCoroutine

Rx에서 코루틴을 스트림 소스로 만드는 방법은 MainThreadDispatcher.StartCoroutine 을 사용하는 것입니다.

IEnumerator MyCoroutine()
{
    ....
    yield return null;    
}

// Global StartCoroutine runner
MainThreadDispatcher.StartCoroutine(MyCoroutine);

코드에서와 같이 MonoBehaviour.StartCoroutine 과 다르지 않습니다.

namespace UniRx
{
    public sealed class MainThreadDispatcher : MonoBehaviour
    {
        /// static!
        new public static Coroutine StartCoroutine(IEnumerator routine)
       {
           ...
       }

MainThreadDispatcher.StartCoroutine 함수는 Coroutine 형을 반환합니다.

MainThreadDispatcher.StartCoroutine vs MonoBehaviour.StartCoroutine

MainThreadDispatcher.StartCoroutine 함수로 실행한 코루틴 함수는 MonoBehaviour 인스턴스의 메소드가 아닌 정적(static) 메소드라는 점이 차이점입니다.

FromCoroutine

FromCoroutine은 코루틴을 IObservable로 바꿀 때 사용합니다.

다음은 플레이어가 생존해 있는 동안에만 주어진 시간동안 작동하는 코루틴 함수를 처리하는 코드입니다.

private  void  Start ()
{
    ...

    // 플레이어가 생존 해있는 시간을 30 초 카운트 다운
    // 타이머의 현재 카운트 [초]이 통지되는
    Observable . FromCoroutine < int > ( observer  =>  CountDownCoroutine ( observer ,  30 ,  player ))
        . Subscribe ( count  =>  Debug.Log ( count ));
}

/// <summary>
/// 플레이어가 살아있는 동안에 만 카운트 다운 타이머
/// 플레이어가 죽은 경우 계산은 중지
/// </ summary>
IEnumerator  CountDownCoroutine ( IObserver < int >  observer, int  startTime, player  player )
{
    var  currentTime  =  startTime ;
    while  ( currentTime  >  0 )
    {
        if  ( player.IsAlive )
        {
            observer.OnNext ( currentTime);
            currentTime -= 1;
        }
        yield  return  new  WaitForSeconds ( 1 );
    }
    observer.OnCompleted ();
}

FromCoroutine 연산자로 스트림 소스를 만드는 경우는 이처럼 IObservable을 코루틴 쪽으로 넘겨 줄 수 있기 때문에 코루틴 내에서 OnNext의 메시지 발생을 필요한 조건에 맞춰서 처리할 수 있습니다.

그러면 FromCoroutineMainThreadDispatcher.StartCoroutine(IEnumerator)은 어떻게 구분해서 사용해야 할까요?

FromCoroutine/FromMicroCoroutine은 IObservable형을 반환합니다. 만약 Rx 연산자가 필요하거나 스트림에서 취소 이벤트를 처리할 필요가 있거나 혹은 원하는 값을 리턴하거나 에러 처리가 필요한 경우라면 FromCoroutine을 사용하길 바랍니다.

StartCoroutine/StartUpdateMicroCoroutine 함수는 단순히 지정한 코루틴 함수를 실행하는 역할만 합니다. MonoBehaviour의 StartCoroutine과 같다고 볼 수 있습니다.

MainThreadDispatcher는 싱글톤(singleton) MonoBehaviour이며 FromCoroutine/FromMicroCoroutine 에서도 코루틴 실행시에는 내부에서 StartCoroutine/StartUpdateMicroCoroutine 함수를 호출합니다.

StartAsCoroutine

StartAsCoroutine은 IObservable을 코루틴으로 바꿀 때에 사용합니다.

FromCoroutine은 코루틴을 IObservable로 바꿀 때, StartAsCoroutine은 IObservable을 코루틴으로 바꿀 때 사용합니다.

private  void  Start ()
{
    StartCoroutine ( CoroutineA ());
}

IEnumerator  CoroutineA ()
{
    // 코루틴의 실행 결과를 저장하는 변수
    var  result  =  0 ;

    //Observable.Range를 코 루틴으로 변환한다
    yield  return  Observable . Range ( 0 ,  10 ). StartAsCoroutine ( x  =>  result  =  x );

    Debug . Log ( "Result: " + result );
}

실행 결과는 아래와 같습니다.

Result: 9

StartAsCoroutine은 다음과 같은 특징이 있습니다.

  • OnCompleted를 호출할 때까지 yield return null을 계속 반복한다.
  • StartAsCoroutine에 전달 된 함수는 OnCompleted를 호출할 때에 한 번만 실행되고 마지막 OnNext 값이 전달된다.

StartAsCoroutine을 잘 사용하면 비동기 처리를 포함하는 복잡한 처리를 Task의 await 처리와 같이 동기화 처리처럼 다룰 수 있습니다.

/// 시간이 소요되는 처리.
bool  HeavyTask ()
{
    // 무거운 처리를하는
    Thread . Sleep ( 3000 );

    // 랜덤으로 true / false를 반환
    var  random  =  new  System . Random ();
    return  random . Next ()  %  2  ==  0 ;
}

private  IEnumerator  HeavyTaskCoroutine ()
{
    // 실행 결과의 반환값
    bool  result  =  false ;

    // Observable.Start 사용시 다른 스레드에서 HeavyTask 작업을 수행.
    yield  return  Observable . Start (() => HeavyTask ()). StartAsCoroutine ( x  =>  result  =  x );

    if  ( result )
        Debug . Log ( "Success" );
    else
        Debug . Log ( "Failure" );
}

Start() 연산자는 멀티쓰레딩으로 비동기 처리를 하는 연산자로 HeavyTask는 다른 쓰레드에서 처리를 하게 됩니다. 이 IObservable을 StartAsCoroutine 연산자를 사용해서 코루틴으로 변경함으로써 이 쓰레드의 처리를 완료하기 전까지 yield return null을 반환하게 됩니다. HeavyTask에서 처리를 완료하면 OnCompleted 메시지가 전달되고 이 때 랜덤으로 선택한 true나 false 반환값이 result에 전달됩니다.

5.3 버전부터는 ToYieldInstruction으로 사용할 수도 있습니다.

yield return Observable.Start(()=>HeavyTask).ToYieldInstruction(); // 5.3 이후 버전

에디터에서의 사용

MonoBehaviour의 코루틴은 Unity 에디터 스크립트에서 사용할 수 없지만 FromCoroutine 연산자는 Unity 에디터 스크립트에서 사용하는 것이 가능합니다.

UniRx는 FromCoroutine을 실행하면 내부에서 MainThreadDispatcher.SendStartCoroutine 사용해서 작동하기 때문에 Editor에서 실행할 수 있습니다.

아래 코드는 에디터 윈도우를 실행하면 OnEnable 함수에서 코루틴 함수를 실행하고 30초후 라벨에 "completed!" 라는 문자열을 출력하는 예제입니다.

using UnityEngine;
using UnityEditor;
using System.Collections;
using UniRx;

public class CoroutineWindow : EditorWindow
{
    static float deltaTime;
    static bool isCompleted;

    [MenuItem ("Tools/CoroutineTest")]
    static void Open ()
    {
        GetWindow<CoroutineWindow> ();
    }

    static void OpenFromCLI ()
    {
        Observable.FromCoroutine (Update).Subscribe (_ => {
            Debug.Log ("complete:" + deltaTime);
            EditorApplication.Exit (0);
        });
    }

    void OnEnable ()
    {
        Debug.Log ("init");
        Observable.FromCoroutine (Update).Subscribe (_ => isCompleted = true);
    }

    void OnDestroy ()
    {
        Debug.Log ("end");
        deltaTime = 0f;
        isCompleted = false;
    }

    void OnGUI ()
    {
        GUILayout.Label ("deltaTime:" + deltaTime);
        if (isCompleted) {
            GUILayout.Label ("completed!");
        }
    }

    static IEnumerator Update ()
    {
        while (30f > deltaTime) {
            deltaTime += Time.fixedDeltaTime;
            yield return null;
        }
    }
}

위의 코드에서처럼 에디터 코드에서의 FromCoroutine 사용도 동일합니다.

정리

UniRx를 사용해서 코루틴을 실행하면 다음과 같은 특징이 있습니다.

  • 다양한 것으로 합성할 수 있다.
  • 다양한 반환값의 사용이 가능하다.
  • 실해 도중 취소가 쉽다.
  • 에디터에서 코루틴의 사용이 가능하다.
// 인자가 없는 코루틴 함수
IEnumerator CoroutineA ( )
{
    Debug.Log ( "a start" ) ;
     yield  return  new WaitForSeconds ( 1 ) ;
    Debug.Log ( "a end" ) ;
 }

// 이런 식으로 사용할
Observable.FromCoroutine ( CoroutineA ).Subscribe ( _ => Debug.Log ( "complete" ) ) ;

// IObservable를 인자로 가지는 코루틴 함수
IEnumerator CoroutineB ( IObserver < int > observer )
{
    observer.OnNext ( 100 ) ;
     yield  return  new WaitForSeconds ( 2 ) ;
    observer.OnNext ( 200 ) ;
    observer.OnCompleted ( ) ;
 }

// 합성
var coroutineA = Observable.FromCoroutine ( CoroutineA ) ;
 var coroutineB = Observable.FromCoroutine < int > ( observer => CoroutineB ( observer ) ) ;

// A가 끝난 후 B를 시작. B Subscribe시 100이 전달, 2초후 200이 전달.
var subscription = coroutineA.SelectMany ( coroutineB ).Subscribe ( x => Debug.Log ( x ) ) ;

// Subscribe 반환에서 Dispose를 호출하면 취소 가능
// subscription.Dispose ();

results matching ""

    No results matching ""