기본 콘텐츠로 건너뛰기

RxJS 에서 requestAnimationFrame 을 사용하려면?

http://jsbin.com/qosigic/edit?html,js,output

소스는 이쪽.

게임과 같은 분야는 타이밍 처리가 매우 중요하기 때문에 UI를 갱신하는 무한 루프를 놓고 delta 시간을 얻어와서 처리하는 로직을 많이 쓰는데
이걸 Javascript 에서 할 땐 전통적으로 setTimeout 을 걸어놓은 함수를 재귀하는 식으로 썼는데 문제는 setTimeout(setInterval도 마찬가지)이 굉장히 부정확한 타이밍을 가지고 있었고 이를 보완하기 위해 requestAnimationFrame 이라는 것을 만들어 사용하기 시작했다.
문제는 역시 지원하지 않는 몇몇 브라우저(ex. IE < 10)들 때문에 안타깝게도 RxJS 5.x 의 defaultScheduler에선 setTimeout(https://github.com/Reactive-Extensions/RxJS/blob/6dac0365ad22f87a92197fc4dcee70e72a11ddbb/src/modular/scheduler/defaultscheduler.js#L119)을 쓰고 있는데
물론 권장하는 방식은 https://github.com/Reactive-Extensions/RxJS/tree/8fa95ac884181fb6cbff8ce7c1d669ffb190f5e4/examples/crop 의 예처럼 RequestAnimationFrameScheduler 를 사용하는 것이지만

RxLua 처럼 기본으로 타이머가 없고 외부패키지에 의존하는 환경도 있고해서
기본 RxJS 환경에서 직접 만들어보기로 했다.

Scope 을 위해 일단 function으로 감싸보았다.
(function() {
  var canvas=document.getElementById('canvas1');
  var ctx = canvas.getContext('2d');
  var start = 0;
  var step = function(timestamp) {
    if (!start) start = timestamp;
    var progress = timestamp - start;
    ctx.clearRect(0,0,canvas.width,canvas.height);
    ctx.fillText("legacy: "+progress.toFixed(2), 10,10);
    window.requestAnimationFrame(step);
  }
  window.requestAnimationFrame(step);
})();
이게 아마 보통 requestAnimationFrame 을 사용하는 패턴일 것이다.
그러면 Rx 에서 subscribe 일때 처리해야하는 부분은 어디일까?
실제로 UI를 갱신하는 ctx 부분일 것이다.
(function() {
 var canvas=document.getElementById('canvas1');
 var ctx = canvas.getContext('2d');
 var start = 0;
 var step = function(timestamp) {
   if (!start) start = timestamp;
   var progress = timestamp - start;
   /* 여기부터 */
   ctx.clearRect(0,0,canvas.width,canvas.height);
   ctx.fillText("legacy: "+progress.toFixed(2), 10,10);
   /* 여기까지 */
   window.requestAnimationFrame(step);
 }
 window.requestAnimationFrame(step);
})();
이 부분을 분리해보자.
가장 간단한 방법은 역시 Subject 로 만드는 방법이겠다.
stepSubject.subscribe(progress=>{
  ctx.clearRect(0,0,canvas.width,canvas.height);
  ctx.fillText("Rx Subjeect: "+progress.toFixed(2), 10,10);
});
이렇게 실제로 progess 가 넘어오는 것을 받는 subscribe 를 만들고
stepSubject 를 생성한 뒤 루프 안에서 stepSubject.next 에서 progress 를 건내주도록 하자.
(function() {
  var canvas=document.getElementById('canvas2');
  var ctx = canvas.getContext('2d');
  var stepSubject = new Rx.Subject();
  var start = 0;
  var step = function(timestamp) {
    if (!start) start = timestamp;
    var progress = timestamp - start;
    stepSubject.next(progress);
    window.requestAnimationFrame(step);
  }
  window.requestAnimationFrame(step);
  stepSubject.subscribe(progress=>{
    ctx.clearRect(0,0,canvas.width,canvas.height);
    ctx.fillText("Rx Subjeect: "+progress.toFixed(2), 10,10);
  });
})();
간단하다. 별로 크게 건들지 않고 분리를 했다.

scope을 생각했을 때 반복하는 step function이 Observable 안에 있으면 좋겠다 싶은 생각이 든다.
start 와 step을 Observable.create 안에 넣고 .next 로 쏘아보자. subscribe는 동일하다.
(function() {
  var canvas=document.getElementById('canvas3');
  var ctx = canvas.getContext('2d');
  var stepObservable = Rx.Observable.create(observer=>{
    var start = 0;
    var step = function(timestamp) {
      if (!start) start = timestamp;
      var progress = timestamp - start;
      observer.next(progress);
      window.requestAnimationFrame(step);
    }
    window.requestAnimationFrame(step);
  });
  stepObservable.subscribe(progress=>{
    ctx.clearRect(0,0,canvas.width,canvas.height);
    ctx.fillText("Rx Observable: "+progress.toFixed(2), 10,10);
  });
})();
이렇게 옮기면 Rx.Observable 안에서 requestAnimationFrame 을 제어할 수 있다.
개인적으로는 이런 상황이라면 start 와 step 의 Observable 안에 scope 을 가지고 있는 후자를 더 추천한다.

댓글

이 블로그의 인기 게시물

세상 간단한 https(+secured websocket): Caddy

nginx, apache2 같은 걸로 매번 certbot 연동을 통해 https 하는게 지겨워서 알아보니 Caddy라는게 있더라. 설치법은 강하게 크기 위해 알아서 해본다. # cat /etc/caddy/Caddyfile your.shitty.site:8123 {   proxy / localhost:9123 {     websocket     transparent   } } 이건 외부에서 8123으로 들어오는 걸 내부적으로 9123로 맞춰서 https 맞춰주는 reverse proxy. 만일 port를 생략하면 # cat /etc/caddy/Caddyfile your.shitty.site {   proxy / localhost:9123 {     websocket     transparent   } } 기본포트인 443으로 되어 https://your.shitty.site 로 접속이 된다. 요새 말썽이던 cloudflare 버리고 netlify domain이랑 caddy를 쓰니까 앓던 이가 빠진 느낌. 이 조합은 당분간 계속 써야겠다 싶다.

저지연 오디오 생성 LLVC 사용기

https://github.com/KoeAI/LLVC 써봄 CUDA 11.8 버전 쓰고 있어 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 VC 없어서 설치 https://wikidocs.net/168848 참조 FAIRSEQ 관련 오류 https://github.com/facebookresearch/fairseq/issues/2908 pip install editdistance  설치 No CUDA runtime is found, using CUDA_HOME='C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8' 오류 멀쩡하게 있는데 안됨. pip install torch==2.1.0+cu118 torchvision==0.16.0+cu118 torchaudio==2.1.0+cu118 --extra-index-url https://download.pytorch.org/whl/cu118 다시 설치 set DISTUTILS_USE_SDK=1 이게 문젠가 설정하고 재도전 Using cached ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl (118 kB) Building wheels for collected packages: fairseq   Building wheel for fairseq (pyproject.toml) ... error   error: subprocess-exited-with-error   × Building wheel for fairseq (pyproject.toml) did not run successfully.   │ exit code: 1   ╰─> [1829 lines of output]       No CUDA runtime is found, usi...