UniRx(2)-UpdateAsObservable의 정확한 뜻은? 쓰는 이유는?

Yeons·2023년 1월 29일
0
post-thumbnail

이제 코드를 뜯어살펴보면서, 모르는 것을 알아갈 예정이다.
UpdateAsObservable()은 정확히 뭔 뜻일까?
(천천히 개념 빌드업을 해서, UpdateAsObservable()의 개념은 글을 읽다보면, 맨 아래에서 이해되도록 하였다)

UpdateAsObservable()

public Text MyText;

void Start()
{
    //매프레임 마우스 클릭 이벤트를 관찰하는 스트림을 정의 한다.
    var clickStream = UpdateAsObservable()
        .Where(_ => Input.GetMouseButtonDown(0));

    //이 스트림에 0.3초 간 흘러들어오는 (마우스클릭) 이벤트를 모은다.(Buffer)
    clickStream.Buffer(clickStream.Throttle(TimeSpan.FromMilliseconds(300)))
      .Where(x => x.Count >= 2)             //(마우스클릭) 이벤트가 2회 이상 발생한 경우만 필터링
      .SubscribeToText(MyText, x =>         //위 조건을 충족한 경우 GUI의 MyText 컴포넌트에 정보 출력
      string.Format("Double Clicked! Click Count = {0}", x.Count);
 }
  1. 위에서 UpdateAsObservable 의 Where 오퍼레이터를 통해 마우스 클릭이 발생한 프레임 들을 골라서 스트림으로 만들어 준다.
  2. 1번에서 생성되어 흘러온 스트림은 Buffer 에서 흐름이 멈춘 채 차곡차곡 누적된다.
  3. 2번의 Buffer 는 개방 조건이 만족될 때까지 스트림을 흘려보내지 않고 누적시키는데 이 코드의 개방 조건은 Throttle(0.3초) 이다.
  4. 즉, 밸브의 역할을 하는 Throttle 오퍼레이터가 클릭 이벤트 발생 후 0.3초 동안 새로운 클릭 이벤트가 없으면 통지를 해주는데 그때 Buffer가 모은 이벤트의 갯수를 체크해서 2개가 넘었다면 더블클릭으로 판정하게 된다.
  5. 샘플에 의하면 Throttle 은 3번 발생했고 그중 첫번째와 세번째는 각각 3번, 2번의 클릭이 기록되어 더블클릭으로 판정 되며 두번째 Throttle 이벤트는 1번의 클릭이므로 Subscribe 를 통지하지 않는다.

개념이 정확하지 않은 키워드 : 스트림, buffer
곧바로 검색해보았다 -> 스트림과 버퍼

buffer과 스트림

먼저 스트림과 파일의 기본적인 차이부터 집고 넘어가자.
(이해하기 쉽게 단순화해서!)
파일은 mp3 처럼 4분 30초짜리 음원 데이터 전체가 기록되어 있다.
따라서 고정되어 있는 크기의 데이터를 처음부터 끝까지 읽어서 처리할 수 있다.
하지만 스트림은 파일과는 다르다.
스트림이란 계속해서 흘러 들어오는 데이터를 의미한다.
즉, 마이크에 대고 하는 이야기나, 비디오 카메라로 찍는 동영상 처럼 입력장치를 통해 계속해서 들어오는 데이터의 흐름을 의미한다.

따라서 실제 데이터가 입력되기 전에는 어떤 데이터가 들어올지 알 수 없으며, 실시간으로 들어오는 데이터를 그때 그때 읽고 처리해야 한다.

"스트림(stream)"은
마이크와 같은 입력장치나 서버로 부터 들어오는 일련의 데이터 흐름을 읽어 들이면서, 스피커와 같은 출력장치나 동영상플레이어로 데이터의 흐름을 내보내는 역할을 하는 "객체"라고 볼 수 있다.

스트림이 있기 전까지는 데이터 전체가 다운로드 되어야만 처리가 가능했다. 스트림이 가능해지면서 동영상 스트리밍, 오디오 스트리밍 등의 서비스가 가능해진 것이다.

스트림의 문제점 : 딜레이

혹은 유튜브 처럼 대용량의 데이터를 서버를 통해 받아서 재생하는 동영상의 경우, 데이터를 모두 다운로드 받아서 재생하려면 긴 딜레이가 발생하게 된다.

이를 막기 위해서는
먼저 들어오는 데이터부터 조금씩 메모리의 임시공간에 저장하여 재생시켜 주어야 한다.

컴퓨터는 이 데이터들을 chunk(데이터를 처리하는 임의의 크기) 단위로 쪼개서 임시 공간(메모리: RAM의 일부 영역)에 저장해두고 처리를 시작한다.

스트림은 이런 식으로 데이터를 chunk 단위로 임시공간(메모리)에 저장하고, 처리하고, 비우기를 반복한다.

이렇게 하면 어머어마하게 큰 데이터도 조금씩 조금씩 처리할 수 있는 매커니즘이 완성된다.

이게 바로 컴퓨터가 스트림을 처리하는 방식이다.
그리고 처리가 완료되면 임시 공간을 비우고, 다음 데이터가 들어오길 기다린다.

그런데 컴퓨터는 계속해서 흘러 들어오는 데이터들을 정확히 어떤 방식으로 처리할까?

여기서 바로 버퍼(Buffer)가 출현한다.

해결 방법 : Buffer

일반적으로는 스트림 데이터를 조금씩 읽고, 저장하고, 처리하고, 비우기를 반복하는 메모리 공간이다.
이런 행위를 버퍼링이라고 하며, 임시로 데이터를 저장하는 메모리 공간 혹은 데이터 자체를 버퍼라고 한다.
(일반적으로 RAM메모리의 일부공간이 버퍼를 위해 사용된다.)
즉, Buffer란 특정 용도로 활용하기 위해 임시로 저장하는 Binary 형태의 데이터 저장 객체이다.

스트림을 위해 데이터를 저장해두는 임시공간이 바로 버퍼(buffer)이고, 버퍼가 작동하는 방식을 버퍼링(buffering)이라고 한다.

따라서 버퍼의 크기는 chunk(데이터 처리 단위)와 같은 수준으로 설정되는게 일반적이다.

다시 코드로 돌아오자

throttle의 개념

public Text MyText;

void Start()
{
    //매프레임 마우스 클릭 이벤트를 관찰하는 스트림을 정의 한다.
    var clickStream = UpdateAsObservable()
        .Where(_ => Input.GetMouseButtonDown(0));

    //이 스트림에 0.3초 간 흘러들어오는 (마우스클릭) 이벤트를 모은다.(Buffer)
    clickStream.Buffer(clickStream.Throttle(TimeSpan.FromMilliseconds(300)))
      .Where(x => x.Count >= 2)             //(마우스클릭) 이벤트가 2회 이상 발생한 경우만 필터링
      .SubscribeToText(MyText, x =>         //위 조건을 충족한 경우 GUI의 MyText 컴포넌트에 정보 출력
      string.Format("Double Clicked! Click Count = {0}", x.Count);
 }
  1. 위에서 UpdateAsObservable 의 Where 오퍼레이터를 통해 마우스 클릭이 발생한 프레임 들을 골라서 스트림으로 만들어 준다.
  2. 1번에서 생성되어 흘러온 스트림은 Buffer 에서 흐름이 멈춘 채 차곡차곡 누적된다.
  3. 2번의 Buffer 는 개방 조건이 만족될 때까지 스트림을 흘려보내지 않고 누적시키는데 이 코드의 개방 조건은 Throttle(0.3초) 이다.
  4. 즉, 밸브의 역할을 하는 Throttle 오퍼레이터가 클릭 이벤트 발생 후 0.3초 동안 새로운 클릭 이벤트가 없으면 통지를 해주는데 그때 Buffer가 모은 이벤트의 갯수를 체크해서 2개가 넘었다면 더블클릭으로 판정하게 된다.
  • Buffer란?
    옵저버블에서 방출하는 이벤트를 번들로 한번에 묶어서 묶음으로 방출하는 메모리
  • timeSpan
    어느정도의 일정시간의 간격 타임을 줄것인지에 대해 timeSpan을 받습니다. 해당 timeSpan이후 묶여진 이벤트 묶음이 방출됩니다.
    즉 이 timeSpan이란것은 항목을 수집하는 시간을 뜻합니다.
  • throttle : 조절판
    스로틀(throttle)은 유체 흐름이 압축이나 차단에 의해 통제되는 구조를 말한다. 프로그래밍에서 Throttle 는 여러번 발생하는 이벤트를 일정 시간 동안, 한번만 실행 되도록 만드는 개념이다.
  • SubscribeToText
    인자로 넘겨주는 Text 컴포넌트에 람다식으로 지정한 문자열을 자동으로 셋팅해 주는 기능을 제공한다.

throttle vs debounce

Throttle 와 Debounce 는 자주 사용 되는 이벤트나 함수 들의 실행되는 빈도를 줄여서, 성능 상의 유리함을 가져오기 위한 개념이다.

자주 사용되는 간단한 예로는 자동 완성이 있다.
keyboard 가 한자씩 입력될 때마다, api 로 데이터를 가져오게 되면, 사용자의 의도와 무관한 요청 이 자주 발생되는데, 이를 줄이기 위해, 입력이 끝난후나, 입력되는 중간 중간 200ms 마다 api 값을 가져온다면, 성능에서 매우 유리해 진다.

Throttle 는 입력 주기를 방해하지 않고, 일정 시간 동안의 입력을 모와서, 한번씩 출력을 제한한다
Debounce 는 입력 주기가 끝나면, 출력한다.
위 예제에서 Throttle 는 200ms 마다 api 값을 가져오는 것이고, Debounce 는 입력이 끝난후 api 값을 가져오는 것이다.

결론 : Rx의 핵심 작동원리

  1. 스트림(Stream) 을 정의한다

  2. 오퍼레이터(Operator) 로 스트림의 데이터를 가공한다

  3. 가공된 데이터를 통지 (Subscribe) 받아서 우리의 로직에 태운다.

즉, 이벤트를 받으면 이걸 어떻게 가공하고 사용할지를 작성하는게 아니라 이벤트를 받기 전에 어떤 이벤트만 받고 싶은지를 작성하는 것이다.

참고 사이트

Observer 패턴

UniRx 뿐만 아니라 리액티브 프로그래밍(Reactive Programming 이하 Rx) 를 이해하는 핵심 포인트 중 하나는 Observer 패턴을 이해하는 것이다.

Observer 는 관찰자라는 뜻이며 Observable (관찰 가능한) 이라는 개념의 객체를 사용한다.

Observable 객체 생성 방법

Observable 객체를 생성한다는 것은 스트림에 흘려 보내는 값 즉, 프로그램 내에서 발생하는 각종 이벤트 들의 관찰을 시작한다는 뜻이다.
그런 의미에서 Observable을 Rx의 스트림(Stream) 과 동일한 개념으로 생각할 수도 있지만 정확하게 말하면 스트림은 이벤트 메세지가 발행되어 최종 목적지인 Subscribe 에 도달하는 순간 까지 처리되는 일련의 흐름이며 Observable 은 이벤트 메세지를 발행할 수 있는 하나의 스트림을 시작시키고 스트림 내에서 일어나는 모든 일을 관찰 할 수 있는 외부 객체라는 말이 옳을 것이다.

  1. Subject 시리즈를 사용
  2. ReactiveProperty 시리즈를 사용
  3. 팩토리 메소드 시리즈를 사용
  4. UniRx.Triggers 시리즈를 사용
  5. Coroutine 을 변환하여 사용
  6. UGUI 이벤트를 변환하여 사용
  7. 그외 UniRx 가 제공하는 기능들 ...

데이터 스트림에 이어, Observer의 얄팍한 개념까지 볼 수 있었다. 다음 시간에는 IObserver & IObservable 인터페이스에 대해 공부해야겠다.

profile
공부중

0개의 댓글