Skip to content

브라우저에서 오픈 월드 만들기, 2부: Worker 물리와 입력 지연에 대한 두려움

Oleg Sidorkin, Cinevva CTO 겸 공동 창업자

처음 오셨나요? 시리즈 가이드를 보세요. spike가 무엇인지 설명하고 모든 파트를 링크해 둡니다.

브라우저 멀티플레이어를 충분히 오래 만들다 보면 결국 이 논쟁을 만나게 됩니다.

"물리를 worker에 두면 아키텍처가 깔끔하다. 물리를 메인 스레드에 두면 더 안전하게 느껴진다."

둘 다 맞을 수 있습니다. 정말 중요한 건 실제 입력에서의 조작감과 지연입니다.

Spike 2는 의견이 아니라 측정으로 그 질문에 답하기 위해 만들었습니다.

새 탭에서 Spike 2 열기 ↗ · 소스 보기

우리는 Spike 1의 지형을 재사용하고 Rapier를 전용 module worker에 통합했습니다. 입력 상태를 매 프레임 worker로 보내고, 시뮬레이션은 그쪽에서 한 스텝 진행했으며, 권위 있는 위치를 렌더러로 다시 받아왔습니다.

핵심 지표는 입력부터 눈에 보이는 움직임까지의 지연, 그리고 물리 스텝 타이밍이었습니다. 또한 일반 이동, 스프린트 버스트, 점프 케이던스에서 지터가 생기는지도 지켜봤습니다.

결과는 예상보다 좋았습니다. 우리의 메시지 형태와 케이던스에서는 worker 경계가 지연을 좌우하지 않았습니다. 조작은 여전히 즉각적으로 느껴졌고, 그것이 플레이어가 신경 쓰는 유일한 부분이었습니다.

이 단계에서 한 가지 어려움은 해석의 위험이었습니다. 성공적인 결과가 나오면, 팀은 종종 지나치게 일반화해서 아키텍처 질문이 영원히 끝났다고 가정합니다. 그렇지 않습니다. 우리는 구체적인 시나리오 하나와 하드웨어 프로필 하나만 검증했을 뿐입니다. 이후의 spike들은 GPU와 스트리밍 압력이 바뀌었을 때 여전히 가정을 다시 점검해야 했습니다.

이 spike는 또한 프로세스를 한 단계 끌어올려 주었습니다. 우리는 인터랙티브 spike에서 기본으로 HUD에 타이밍 텔레메트리를 노출하기 시작했습니다. 그 덕분에 팀 대화가 "뭔가 이상하다"에서 "이 경로가 1.2 ms를 더했다"로 바뀌었습니다.

3부에서는 나중에 값비싼 사고를 막아준, 덜 화려한 실험들을 다룹니다. 브로드캐스트 부하, 모바일 제약, 행동 생성 신뢰성입니다.

이 장에서 언급된 기술

Rapier. Rust로 작성되어 브라우저 사용을 위해 WebAssembly로 컴파일되는 물리 엔진입니다. 리지드 바디, 콜라이더, 조인트, 캐릭터 컨트롤러, 레이캐스팅을 네이티브 대비 2-3배 성능으로 처리합니다. 오픈 월드에서 Rapier는 플레이어 캐릭터 컨트롤러(지형 위 걷기, 계단 오르기, 경사면 미끄러지기), 오브젝트 충돌, 상호작용을 위한 레이캐스팅, 트리거 볼륨을 제공합니다. Rapier 문서물리에 관한 브라우저 3D 기술 가이드를 보세요.

Web Workers. 메인 스레드와 별개로 JavaScript(또는 Wasm)를 실행하는 브라우저 스레드입니다. 물리 시뮬레이션을 worker에 두면 무거운 world.step() 호출이 렌더링을 막지 않습니다. 메인 스레드는 매 프레임 postMessage로 입력 상태를 worker에 보내고 권위 있는 위치를 돌려받습니다. 지연 비용은 두 번의 메시지 홉입니다(데스크톱에서 각각 약 0.1-0.5 ms). 이점은 렌더 스레드가 충돌 감지에서 절대 멈추지 않는다는 것입니다. Transferable 객체(ArrayBuffer 전송)는 큰 위치 배열의 복사 오버헤드를 없앱니다.

WebAssembly (Wasm). 브라우저에서 네이티브에 가까운 속도로 실행되는 바이너리 명령 형식입니다. Rapier, Havok, Recast 모두 Wasm으로 컴파일됩니다. Rapier-Wasm의 물리 스텝은 수백 개의 바디에 대해 보통 0.5-2 ms인데, 같은 작업을 JavaScript로 하면 5-15 ms입니다. Wasm 모듈은 JavaScript 글루 코드와 함께 .wasm 파일로 가져옵니다. WebAssembly 명세를 보세요.

입력부터 화면까지의 지연. 키 입력과 그 결과로 화면에 나타나는 시각적 변화 사이의 시간입니다. 움직임이 "즉각적"으로 느껴지려면 이 값이 약 80 ms 아래로 유지되어야 합니다. worker 물리 구성에서 이 사슬은 누적됩니다.

L=tinput+2tmsg+tstep+trender+tvsync

keydown 이벤트(메인 스레드), worker로 갔다 오는 postMessage(2tmsg), 물리 스텝, 렌더러가 새 위치를 적용하는 단계, 그다음 다음 vsync입니다. 각 항은 그 자체로는 작지만(데스크톱에서 메시지 홉당 약 0.1-0.5 ms) 쌓입니다. 그래서 아키텍처 다이어그램을 믿는 대신 L을 직접 측정했습니다.


12부 중 2부.
이전: 1부 - 우리는 그것을 부수려는 시도부터 시작했다
다음: 3부 - 우리를 구한 화려하지 않은 spike들
시리즈 가이드: /ko/blog/2026-02-25-open-world-browser-series-guide