Game Development, 게임개발/배경지식

Reactive Programming이란, Rx란?

게임이 더 좋아 2022. 1. 17. 14:52
반응형
728x170

 

반응형 프로그래밍이라고 직역되는 말인데 

이렇게 해야 성능을 제대로 이끌어낼 수 있다고 사람들이 말하곤 하는데 정확히 무엇인지 모르는게 다수다.

알아보자

 


 

우선 깔고 가야할 것이 있다.

우리가 지금까지 배운 개발 방식은 전혀 반응형 프로그래밍 방식이 아니다.

우리는 절차적, 함수형, 객체지향 등의 방식을 통해서 프로그래밍을 배웠다.

하지만 진짜 알기 위해선 새로운 모드를 우리의 머리에 추가시켜야 한다.

바로 Reactive 모드이다.

 

 

?? 아니 Reactive가 뭔지 알아야 모드를 만들든지 말든지 하지...?

 

줄여서 말하면

 

Reactive programming 은 비동기 데이터 스트림으로 프로그래밍을 하는 것이다.

 

 

???아는 단어로 설명해줘야지.. 새로운 단어가 나온 것 같은데??

 

비동기 데이터 스트림이 뭔데??

 

우리는 알게 모르게 비동기 방식을 사용하고 있다.

 

예를 들어 클릭 이벤트는 당연히 비동기 이벤트다.

Event를 관찰,Observe할 수 있고 side effect(이벤트가 일어나면 수행되는 작업)를 줄 수 있다.

 

Reactive는 이러한 것을 조금 확장시켜서 말한 것 뿐이다.

 

그러니까.. 비동기 이벤트들이 비동기 데이터 스트림이 된다는 말인가??

 

맞다. 정확하다.

이벤트뿐만 아니라 데이터 스트림으로 만들 수 있는 것은 모조리 Rx형으로 프로그래밍이 가능하다.

Rx에 쓰이는 스트림은 가볍고 만들기 쉽다.

변수가 될 수도 있고, 사용자 입력이 될 수도 있고, 캐시, 인스턴스 등등 어떤 것들도 스트림이 될 수 있다.

 

예를들어, 트위터 피드를 클릭 이벤트와 같은 데이터 스트림이라고 정의하고

우리는 해당 스트림에 listen 할 수 있고, 적절하게 react 할 수 있다.

다른 말로 Observe 하고 Subscribe 할 수 있다는 말이다.

-> 관찰하고 이에 대한 작업을 수행

 

근데.. 스트림을 한꺼번에 여러개 만들면 어떻게 관리하는데..?

뭐든지 만들 수 있으면 엄청 많이 만들어질거 아냐..?

 

그래서 우리는 스트림들을 조합하고, 만들고, 필터링할 수 있는 오퍼레이터, Operator 들을 이용할 수 있다.

 

스트림은 아주 유기적이면서 유연성을 가지고 있다.

그래서 스트림은 다른 스트림의 input이 될 수 있고

복수의 스트림들도 다른 스트림의 input이 될 수 있고 두 개의 스트림을 합칠수도 있다.

합친 스트림을 필터링 시켜서, 우리가 원하는 이벤트들만 있는 스트림으로 만들 수도 있다.

스트림에 있는 데이터 값들을 새로운 스트림으로 map 시킬 수도 있다. (Select)

 

오.. 그럼 모든 데이터들을 스트림으로 만들자!!

-> 항상 그것이 좋다고 모두 그렇게 만드려는 것은 흔하게 하는 실수이다.

 

** 그럴거면 기계어로 프로그래밍하는게 제일 성능 좋은데 왜 High-Level Language로 짜냐?

 

프로그래밍은 사람이 짜기가 쉬워야 하며 사람이 이해하기도 쉬워야 하고 그것이 성능이 좋아야 한다.

우리는 성능만 추구해서도 안되며 가독성만 추구해서도 개발속도만 추구해서도 안된다.

적당히 Trade-off를 찾아야 한다.

그렇다면 우리가 Rx를 배워야 한다면 저 3가지 중 무엇인가 개선된다는 말이겠지??

 


 

아무튼 그렇다면 예시로 알아보자

버튼 클릭의 스트림을 보자.

버튼 클릭을 스트림으로 만든 것이다.

 

스트림이라는 것은 흐름(Stream)이라는 것인데

감이 오지만 역시 시간의 흐름에 따라 데이터가 움직이는 것을 말한다.

즉, 스트림은 시간 순으로 진행중인 이벤트들을 나열한 것이다.

-> Event라고 설명하기도 하고 데이터라고도 말하기도 한다.

 

스트림은 (타입을 갖고 있는) 값, 에러(Error), 완료(Completed), 신호(OnNext), 

세가지 종류의 이벤트를 발생시킬 수 있다.

-> 값을 발행한다고도 한다.

 

프로그램마다 다르지만

여기서 "완료"는 창이나 이 버튼을 포함하고 있는 화면이 닫혀지는 순간이라고 하자.

-> 당연히 창이 닫히면 버튼이 눌리지 못한다.

 

우리는 각 이벤트(값, 에러, 완료)가 발생할때 실행되어질 함수들을 각각 정의함으로써

이 발생한 이벤트들을 비동기적으로만 포착할 수 있다.

-> 여기서 비동기적이란 것은 함수의 종료를 알 수 없다는 말과 비슷하다.

 

아래 글을 잘 읽어보자.

[CS Interview] - (A)Synchronous 그리고 (Non)-Blocking

 

대부분은 에러와 완료 이벤트는 생략하고, 값을 내보내는 이벤트만 포착하는 함수를 정의할 때가 많다.

 

스트림을 "listening"하는 것은 subscribing(구독) 이라고 부른다.

 

우리가 정의한 함수들은 observer들이다.

스트림은 observed 되는 대상(subject)이다.

-> observable이라고도 한다.

 

이것은 옵저버 패턴과 같다.

[Game Developer, 게임개발자/디자인패턴] - Observer Pattern, 관찰자, 감시자 패턴 [디자인패턴]

 

관찰자 목록을 관리하면서 해당 이벤트가 일어나면 Notify해주고

Notified된 관찰자들은 해당 작업을 실행한다.

정확히 Rx가 동작하는 원리와 같다.

 


 

버튼 클릭이 스트림으로 되는 것을 봤다면

클릭 이벤트 스트림을 변경시켜보자

예를 들어서 버튼이 몇번이나 클릭되었는지를 의미하는 계수(counter) 스트림을 만들어보자.

 

일반적으 Reactive library에는

스트림에 붙일 수 있는 많은 오퍼레이터(map, filter, scan 등 )들을 갖고 있다.

 

clickStream.map(f)처럼 그 함수들 중 하나를 호출하게 되면

클릭 스트림에 기초해서 새로운 스트림 을 반환한다.

 

이것은 기존의 클릭 스트림을 전혀 건드리지 않는다.

다만 그것을 기반으로 새로운 스트림을 만드는 것이다.

** 이런 속성은 불변성(immutability) 이라고 말하는데  Reactive 스트림과 잘 어울리는 속성이다.

덕분에 우리는 clickStream.map(f).scan(g)처럼 함수 체이닝도 할수 있게 된다.

-> 원래 있던 것이 변형되는 것이 아니기 때문에 체이닝을 통해서 스트림이 함수를 거쳐 무엇이든 될 수 있다.

 

 

counterStream이 만들어지는 과정

 

 

?? map이랑 scan이 무슨 역할을 하는지 난 모르는데??

몰라도 된다. 그냥 보면 된다.

 

map(f) 오퍼레이터는 각각 발생하는 값들을, 우리가 정의한 함수 f에 통과시킨 후,

이 새로운 값들을 가지는 새로운 스트림을 만들어내는 것이다. 

 

예를 들어보자면, 우리는 각각의 클릭을 1로 매핑 시켰다. 

scan(g) 함수에서는 스트림에서 이전 값들을 모두 합쳐서

x = g(accumulated, current) 라는 새로운 값을 만든다. 

** g는 단순히 더하는 함수를 의미한다고 하자.

 

이후, counterStream은 클릭이 발생할때마다

총 클릭 횟수를 발생시키도록 만들었다.

 

음.. 그게 끝??

뭔가 이해가 잘 안되는디?

 

Reactive의 진짜 힘을 보기 위해, 이번엔 "더블 클릭" 이벤트 스트림을 만들어보자.

아니 더 나아가서 우리는 새 스트림이 더블클릭을 받듯이 삼중클릭도 받도록 취급하던가

아니면 아예 일반화해서, 다중 클릭을 받도록 하고싶다고 해보자.

 

우선 그것을 Rx가 아닌 방법으로 한다고 생각해보자.

1초안에 3번 누르면 3중 클릭인가..?

그렇다면 0.1초만에 3번 누르면 0.9초는 기다린 후에 삼중 클릭에 대한 작업을 해야하나..?

아 복잡한데..?

 

역시나 전통적으로 배운  명령(imperative)와 상태(stateful)를 서술하는 방식으로는 만들기가 힘들다.

하지만 Reactive에선 간단하다

사실, 그 로직은 4줄짜리 코드다.

 

하지만 그 코드가 나오기 위해서 우리는 그림부터 그려보자.

초심자건 전문가건, 스트림을 이해하고 만드는 가장 좋은 방법은

도표(다이어그램, 그림)를 생각한 후 작성하는 것이다.

 

 

회색 상자는 한 스트림을 다른 스트림으로 바꾸는 함수를 의미한다.

 

1. buffer(clickStream.throttle(250ms))

첫번째는 이벤트가 발생하면 최초의 발생 이후 250ms 동안의 클릭을 리스트에 모아놓을 것이다.

그 리스트가 스트림으로 다시 변한다.

 

2.map

다음은 이 스트림에 map()을 적용시켜서, 각각의 리스트를 그 리스트의 길이를 가진 정수 값으로 mapping 시킨다.

-> 250ms 구간으로 나누어진 정수 스트림이 되겠지??

 

3.filter

마지막으로, 우리는 filter(x >= 2) 함수를 사용하여서  그 스트림 중  정수 1 값인 아이들을 걸러내면 된다.

남은 스트림으로 무엇인가 작업을 진행하면 된다.

 

원래는 마지막 스트림에 subscribe를 함으로써(스트림을 listen하는 작업)

우리가 원하는대로 반응하게 만든다.

-> 여기서는 더블클릭 했을 때 폴더가 열린다거나 뭐.. 그런 것이다.

 

 

음... 근데 더블클릭할 때 편해지는 것은 알겠어..

근데 어차피 어렵다뿐이지 전통적인 방법으로도 구현할 수 있잖아??

새로운 기술 배우는게 더 손해같은데??

 

아니다.(단호)

 

Reactive Programming은 코드의 추상화 레벨을 올려줌으로써

로직을 규정하는 이벤트들 간의 상호 관계에만 집중할 수 있게 해준다.

더이상 세부 구현을 어떻게 하나 계속 끄적끄적거리고 있을 필요가 없게 된다는 것이다.

 

Reactive Programming 으로 짠 코드는 매우 간결해진다.

-> 가독성, 개발속도, 성능 3가지 다 잡을 수 있다.

 

특히 MVP(R) 패턴을 적용했을 때 빛을 발한다.

(M) 데이터 이벤트들이  (V) 많은 UI 이벤트들과 연계하여

상호작용하고 있는 최근의 웹앱이나 모바일앱들에게 현저한 효과를 보여주고 있다.

 

예전에는 웹 페이지들의 상호작용이 단순하게 긴 폼을 백엔드에 보내고 프론트엔드에선 단순한 렌더링을 수행하는 게 전부였다.

하지만 지금의 앱들은 더욱 진화하여 실시간 작업을 수행하고 있다.

한 폼 필드의 값을 수정하면 백엔드에서 자동으로 저장이 되어버리는 것이 바로 그것이다.

페이스북이라면 내가 어떤 컨텐츠에 "좋아요"를 누르면, 다른 연결되어 있는 유저에게 실시간으로 반영될 수 있다.

 

우리는 사용자에게 상호작용 경험을 제공하는 거의 모든 종류의 실시간 이벤트들이 넘쳐나고 있고

그것에 대한 적절한 반응을 보여줘야한다.

그것을 위한 적절하게 다룰 도구가 필요하며, Reactive Programming은 바로 우리가 해야할 일이다.

 

 


참고 링크

https://gist.github.com/staltz/868e7e9bc2a7b8c1f754

반응형
그리드형