우리가 기획한 프로젝트에는 타이머(게이지바)가 있는데, 소켓 통신을 이용했더니 게임 화면을 내리거나 다른 탭을 이용하는 경우 다른 사용자들과 시간이 맞지 않는 경우가 있었다.
아래와 같은 이유로 발생한 것으로 추측된다.
1. 클라이언트 측 시간 동기화
- 네트워크 지연 : 웹소켓을 통해 서버와 클라이언트 간에 시간 정보를 주고받을 때 네트워크 지연이 발생할 수 있습니다. 이로 인해 각 클라이언트가 받는 시간 정보에 약간의 차이가 발생할 수 있습니다.
- 클라이언트 시스템 시간 : 각 클라이언트의 시스템 시간이 정확하게 동기화되어 있지 않을 수 있습니다. 이는 운영체제 설정, 하드웨어 성능 등 다양한 요인에 의해 발생할 수 있습니다.
2. 클라이언트 측 타이머 처리
JavaScript 타이머 : 웹 브라우저에서 타이머를 구현할 때 주로 JavaScript의 setTimeout이나 setInterval 함수를 사용합니다. 하지만 JavaScript 타이머는 정확성을 보장하지 않으며, 브라우저의 성능, 다른 스크립트 실행 등에 영향을 받을 수 있습니다.
리액트는 자바스크립트 환경에서 작동하고, 자바스크립트는 싱글 쓰레드이다.
싱글 쓰레드의 경우 기본적으로 연산량이 많은 작업을 하는 경우, 그 작업이 완료되어야 다른 작업을 수행할 수 있다.
setInterval, setTimeout 등과 같은 함수로 반복 로직을 구현한 경우 브라우저의 정책으로 쓰로틀링이 걸릴 수 있다.
그래서 메인 쓰레드가 아닌 별도의 쓰레드로 타이머의 시간이 동일하게 흘러갈 수 있도록 웹 워커(Web Worker)를 통해 백그라운드 쓰레드에서 타이머를 작동시키고자 한다.
*쓰로틀링
브라우저가 자체적으로 판단했을 때 비활성화된 탭(tab)에서 반복적으로 발생하는 이벤트(타이머 등)가 있다면 그 이벤트에 delay등을 줘서, 해당 이벤트를 무시하고 다른 곳에 메모리 자원을 쓰는 등 메모리를 효율적으로 관리하는 방법
웹 워커(Web Worker)?
웹 워커는 멀티 쓰레딩을 지원해 워커에서 작성된 스크립트는 메인 쓰레드에서 분기되어 독립된 쓰레드로 실행되기 때문에 메모리 자원을 효율적으로 사용이 가능하게 한다.
주의사항
메모리는 유한하다.
간단한 로직이라면 싱글 쓰레드로도 충분하지만 굳이 브라우저의 백그라운드 쓰레드를 사용하게 되면, 오히려 멀티 쓰레드이기 때문에 컨텍스트 스위칭(문맥 교환)이 자주 발생하면서 오버헤드 비용이 발생할 수 있고, 그렇게 되면 성능에 문제가 생길 수 있다.
Worker 내에서는 직접 DOM을 조작할 수 없고 window 객체의 기본 메서드나 속성 중 사용할 수 없는 것들이 존재한다.
적용
GSAP을 이용해 게이지바 애니메이션을 구현했기 때문에,
타이머는 웹 워커(webWorker)를 사용해 백그라운드에서 작동시키고,
렌더링은 메인 쓰레드에서 타이머에서 시간 정보를 받아 렌더링하도록 할 생각이다.
1. 워커 생성
const worker = useRef(new Worker(new URL('./worker.js', import.meta.url)));
구현을 하는 과정에서 경로를 제대로 해석하지 못하거나, 동작하지 않는 문제가 발생했다.
1. worker 파일에 접근할 수 없는 문제
let worker = new Worker('./worker.js')
위와 같은 형태로 worker에 접근하면 동작하지 않았다. Webpack을 사용하는 번들링 환경에서는 상대 경로가 제대로 해석되지 않을 수 있다.
let worker = new Worker(new URL('./worker.js', import.meta.url))
상대 경로 대신 절대 경로를 사용하여 웹 워커 스크립트 파일을 참조해야 한다.
2. worker가 간헐적으로 동작하지 않는 문제
onMessege, terminate 등 메서드를 사용할 때, 같은 로직인데도 불구하고 어떤 상황에서는 동작하고, 어떤 상황에서는 동작을 안하는 아이러니한 상황을 마주했다.
let worker = new Worker(new URL('./worker.js', import.meta.url))
위 방법으로 worker를 생성하면 컴포넌트가 업데이트될 때마다 새로운 worker가 생성된다. 즉, 기존 워커가 사라지고 새로운 워커가 생기니까 간헐적으로 작동하지 않았던 것이다.
let worker = useRef(new Worker(new URL('./worker.js', import.meta.url)))
그러므로 위와 같이 useRef로 worker 생성자를 감싸서 컴포넌트의 렌더링과 무관하게 동작하도록 한다.
2. 워커 파일 작성
// worker.js
self.onmessage = function (e) {
if (e.data.type === "game1") {
let timerTime = 0;
setTimeout(() => {
const intervalId = setInterval(() => {
postMessage(timerTime++);
if (timerTime === 31) {
clearInterval(intervalId);
postMessage("endRound");
}
}, 1000);
}, 9500)
}
postMessage를 통해 게이지바에 업데이트된 시간(타이머) 정보를 보낸다.
지정한 시간(30초)이 되면 라운드가 종료됐다는 메세지를 보내 후처리를 진행하도록했다.
'크래프톤 정글 - TIL' 카테고리의 다른 글
크래프톤 정글 키워드 복습하기(3) (2) | 2024.09.09 |
---|---|
크래프톤 정글 5기 TIL - 나만의 무기 만들기 7(상태 관리 - Zustand) (0) | 2024.08.06 |
크래프톤 정글 5기 TIL - 나만의 무기 만들기 6(Canvas - Laser pen) (0) | 2024.08.06 |
크래프톤 정글 5기 TIL - 나만의 무기 만들기 5(OpenVidu) (1) | 2024.07.12 |
크래프톤 정글 5기 TIL - 나만의 무기 만들기 4(WebRTC 구현 및 궁금증) (0) | 2024.07.07 |