오늘날 Node.js가 본격적으로 진행되면 이미 모든 종류의 작업을 수행하는 데 사용할 수 있습니다. 얼마 전, UP 호스트는 Geek Song 이벤트에 참여했습니다. 이 행사에서 우리는 "헤드 다운 사람들"이 더 많은 의사 소통을 할 수있는 게임을 만들려고합니다. 핵심 기능은 LAN 파티 개념의 실시간 다중 상호 작용입니다. 괴짜 경주는 불쌍한 짧은 36 시간이었으며 모든 것이 빠르고 빠르도록 요구했습니다. 그러한 전제 하에서, 초기 준비는 약간 "자연"인 것 같습니다. 크로스 플랫폼 애플리케이션 솔루션 우리는 노드-웨브 키트를 선택했는데, 이는 충분히 간단하고 요구 사항을 충족합니다.
요구 사항에 따라 우리의 개발은 모듈에 따라 별도로 수행 될 수 있습니다. 이 기사는 일련의 탐색 및 시도, Node.js 및 WebKit 플랫폼 자체의 일부 제한에 대한 솔루션 및 솔루션 제안을 포함하여 우주선 (실시간 멀티 플레이어 게임 프레임 워크)을 개발하는 프로세스를 구체적으로 설명합니다.
시작하기
우주선 눈
처음에는 우주 오우의 디자인이 확실히 수요 중심이었습니다. 우리는이 프레임 워크가 다음과 같은 기본 기능을 제공 할 수 있기를 바랍니다.
객실 (또는 채널)에 따라 사용자 그룹을 구별 할 수 있습니다.
컬렉션 그룹의 사용자로부터 지침을받을 수 있습니다.
각 클라이언트간에 일치하면 지정된 간격에 따라 게임 데이터를 정확하게 방송 할 수 있습니다.
네트워크 대기 시간의 영향을 가능한 한 많이 제거 할 수 있습니다.
물론, 코딩의 후반 단계에서, 우리는 우주선에 게임 일시 정지, 각 클라이언트간에 일관된 임의의 숫자를 생성하는 등 더 많은 기능을 제공했습니다 (물론 요구 사항에 따라 스스로 구현할 수 있으며 커뮤니케이션 수준에서 작동하는 프레임 워크 인 Spaceroom을 사용할 필요는 없습니다).
아피스
우주선은 두 부분으로 나뉩니다 : 프론트 엔드와 백엔드. 서버에서 요구하는 작업에는 객실 목록을 유지하고 객실 생성 및 가입 기능을 제공하는 것이 포함됩니다. 우리의 클라이언트 API는 다음과 같습니다.
spaceroom.connect (주소, 콜백) 서버에 연결합니다
우주선 .createroom (콜백) 방을 만듭니다
우주식. 조인 룸 (룸메이트) 방에 가입하십시오
Spaceroom.on (이벤트, 콜백)은 이벤트를 듣습니다
...
클라이언트가 서버에 연결하면 다양한 이벤트가 수신됩니다. 예를 들어, 방의 사용자는 새 플레이어가 합류하는 이벤트 또는 게임이 시작되는 이벤트를받을 수 있습니다. 우리는 고객에게 "수명주기"를 제공하며, 언제든지 다음 주 중 하나에있을 것입니다.
Spaceroom.state를 통해 클라이언트의 현재 상태를 얻을 수 있습니다.
서버 측 프레임 워크를 사용하는 것은 비교적 간단합니다. 기본 구성 파일을 사용하는 경우 서버 측 프레임 워크를 직접 실행할 수 있습니다. 기본 요구 사항이 있습니다. 서버 코드는 별도의 서버없이 클라이언트에서 직접 실행할 수 있습니다. PS 또는 PSP를 연기 한 플레이어는 내가 무슨 말을하는지 알아야합니다. 물론 특수 서버에서 실행하는 것이 좋습니다.
논리 코드의 구현은 여기에서 간단합니다. 1 세대의 우주선은 소켓 서버의 기능을 완료했으며, 이는 객실 상태와 각 객실에 해당하는 게임 시간 통신 (명령 수집, 버킷 브로드 캐스트 등)을 포함한 객실 목록을 유지하는 소켓 서버의 기능을 완성했습니다. 특정 구현은 소스 코드를 참조하십시오.
동기식 알고리즘
그렇다면 각 클라이언트간에 표시되는 것을 실시간으로 일관성있게 만들 수 있습니까?
이것은 흥미로운 것 같습니다. 신중하게 생각해보십시오. 서버는 무엇을 제공하는 데 도움이됩니까? 당연히, 당신은 다양한 클라이언트들 사이에 논리적 불일치를 일으킬 수있는 것을 생각할 것입니다 : 사용자 지침. 게임 로직을 다루는 코드는 동일하기 때문에 동일한 조건이 주어지면 코드는 동일한 결과를 실행합니다. 유일한 차이점은 게임 중에받은 다양한 플레이어 명령입니다. 그렇습니다. 이러한 지침을 동기화하는 방법이 필요합니다. 모든 클라이언트가 동일한 지시를받을 수 있다면 모든 고객이 이론적으로 동일한 작업 결과를 가질 수 있습니다.
온라인 게임의 동기화 알고리즘은 모든 종류의 이상하고 적용 가능한 시나리오입니다. 우주선은 프레임 잠금 개념과 유사한 동기화 알고리즘을 사용합니다. 타임 라인을 간격으로 하나씩 나누고 각 간격을 버킷이라고합니다. 버킷은 지침을로드하는 데 사용되며 서버 측에서 유지 관리됩니다. 각 버킷 기간이 끝나면 서버는 버킷을 모든 클라이언트에게 방송합니다. 클라이언트가 버킷을받은 후에는 지침이 물통에서 검색 된 다음 확인 후 실행됩니다.
네트워크 지연의 영향을 줄이기 위해 클라이언트로부터 서버가받은 각 명령어는 특정 알고리즘에 따라 해당 버킷으로 전달되며 다음 단계는 특별히 다음과 같습니다.
Order_start를 명령에 의해 운반하는 명령의 시간이되고 T는 Order_Start가있는 버킷의 시작 시간입니다.
t + delay_time <= 현재 명령어를 수집하는 버킷의 시작 시간 인 경우 현재 명령을 수집하는 버킷에 명령을 전달하십시오. 그렇지 않으면 3 단계를 계속하십시오.
T + Delay_Time의 해당 버킷에 명령을 전달하십시오.
여기서 Delay_time은 합의 된 서버 지연 시간이며 클라이언트 간의 평균 지연으로 취할 수 있습니다. 우주선의 기본값은 80이고 버킷 길이의 기본값은 48입니다. 각 버킷 기간이 끝나면 서버는이 버킷을 모든 클라이언트에 방송하고 다음 버킷에 대한 지침을 수신하기 시작합니다. 클라이언트는 수신 된 버킷 간격을 기반으로 로직에서 일치하는 것을 자동으로 수행 할 때 허용 가능한 범위 내에서 시간 오류를 제어합니다.
이는 정상적인 상황에서 클라이언트가 48ms마다 서버에서 보낸 버킷을 받게된다는 것을 의미합니다. 버킷을 처리 해야하는 시간이 있으면 클라이언트가 이에 따라 처리합니다. 클라이언트 FPS = 60이라고 가정하면 3 프레임마다 버킷을 받고이 버킷에 따라 논리가 업데이트됩니다. 네트워크 변동으로 인해 시간이 만료 된 후에 버킷이 접수되지 않은 경우 클라이언트는 게임 논리를 일시 중지하고 기다립니다. 버킷 내에서 한 번에 LERP 방법을 사용하여 로직을 업데이트 할 수 있습니다.
Delay_Time = 80의 경우 Bucket_Size = 48의 경우 적어도 96ms 실행으로 명령이 지연됩니다. 예를 들어, Delay_Time = 60의 경우 Bucet_Size = 32의 경우이 두 매개 변수를 변경하십시오. 두 가지 명령이 최소 64ms로 지연됩니다.
타이머로 인한 피의 사건
모든 것을 살펴보면 프레임 워크가 실행될 때 정확한 타이머가 필요합니다. 고정 간격으로 버킷의 방송을 실행하십시오. 물론, 우리는 먼저 setInterval ()을 사용하는 것을 생각했지만 다음 순간에 우리는이 아이디어가 얼마나 신뢰할 수 없는지 깨달았습니다. Naughty setInterval ()은 매우 심각한 오류가있는 것 같습니다. 그리고 그렇게 나쁜 것은 모든 오류가 축적되어 점점 더 심각한 결과를 초래한다는 것입니다.
그래서 우리는 다음 도착 시간을 동적으로 수정하여 지정된 간격에서 논리를 대략 안정적으로 만들기 위해 settimeout ()를 사용하는 것을 즉시 생각했습니다. 예를 들어,이 시간 Settimeout ()는 예상보다 5ms가 적으므로 다음에 5ms 미리 미리 알려 드리겠습니다. 그러나 테스트 결과는 만족스럽지 않으며, 어떻게 보더라도 우아하지 않습니다.
그래서 우리는 생각을 바꿔야합니다. settimeout ()가 가능한 빨리 만료 된 다음 현재 시간이 대상 시간에 도달했는지 확인할 수 있습니까? 예를 들어, 루프에서 Settimeout (콜백, 1)을 사용하여 시간을 계속 확인하십시오. 좋은 생각처럼 보입니다.
실망스러운 타이머
우리는 즉시 아이디어를 테스트하기 위해 코드를 작성했으며 결과는 실망 스러웠습니다. 현재 최신 Node.js 안정 버전 (v0.10.32) 및 Windows 플랫폼 에서이 코드를 실행하십시오.
코드 사본은 다음과 같습니다.
var sum = 0, count = 0;
기능 test () {
var now = date.now ();
settimeout (function () {
var diff = date.now () - 지금;
sum += diff;
카운트 ++;
시험();
});
}
시험();
일정 시간이 지나면 콘솔에 합계/수를 입력하면 다음과 유사한 결과를 볼 수 있습니다.
코드 사본은 다음과 같습니다.
> 합계 / 수
15.624555160142348
무엇?! 1ms 간격을 원하지만 실제 평균 간격은 15.625ms라고 말합니다! 이 사진은 단순히 너무 아름답습니다. 우리는 Mac에서 동일한 테스트를 수행했으며 결과는 1.4ms였습니다. 그래서 우리는 혼란 스러웠습니다.이게 대체 뭐야? 내가 Apple 팬이라면 Windows가 너무 쓰레기가되어 Windows를 포기했다고 결론을 내릴 수 있습니다. 다행히도 저는 엄격한 프론트 엔드 엔지니어 였으므로이 숫자에 대해 계속 생각하기 시작했습니다.
잠깐,이 숫자가 왜 그렇게 친숙합니까? 이 숫자가 Windows의 최대 타이머 간격과 너무 비슷합니까? 나는 즉시 테스트를 위해 Clockres를 다운로드했고, 콘솔을 실행 한 후 다음 결과를 얻었습니다.
코드 사본은 다음과 같습니다.
최대 타이머 간격 : 15.625ms
최소 타이머 간격 : 0.500ms
현재 타이머 간격 : 1.001ms
물론! Node.js 매뉴얼을 살펴보면 다음과 같은 Settimeout에 대한 설명을 볼 수 있습니다.
실제 지연은 OS 타이머 세분성 및 시스템 부하와 같은 외부 요인에 따라 다릅니다.
그러나 테스트 결과는이 실제 지연이 최대 타이머 간격임을 보여줍니다 (시스템의 현재 타이머 간격은 1.001ms에 불과 함). 강한 호기심은 우리가 Node.js의 소스 코드를 살펴보고 진실을 엿볼 수있게합니다.
node.js의 버그
나는 대부분의 사람들과 나는 node.js의 짝수 루프 메커니즘에 대한 확실한 이해를 가지고 있다고 믿는다. 타이머 구현의 소스 코드를 살펴보면 타이머의 구현 원리를 대략 이해할 수 있습니다. 이벤트 루프의 기본 루프부터 시작하겠습니다.
코드 사본은 다음과 같습니다.
while (r! = 0 && loop-> stop_flag == 0) {
/* 글로벌 시간 업데이트*/
uv_update_time (루프);
/* 타이머가 해당 타이머 콜백을 만료하고 실행하는지 확인*/
uv_process_timers (루프);
/* 할 일이 없으면 유휴 콜백을 호출하십시오. */
if (loop-> pending_reqs_tail == null &&
loop-> endgame_handles == null) {
/* 이벤트 루프가 종료되는 것을 방지*/
uv_idle_invoke (루프);
}
uv_process_reqs (루프);
uv_process_endgames (루프);
uv_prepare_invoke (루프);
/* IO 이벤트 수집*/
(*poll) (루프, 루프-> idle_handles == null &&
loop-> pending_reqs_tail == null &&
loop-> endgame_handles == null &&
! loop-> stop_flag &&
(loop-> active_handles> 0 ||
! ngx_queue_empty (& loop-> active_reqs) &&
! (모드 & uv_run_nowait));
/* setimmediate () 등*/
uv_check_invoke (루프);
r = uv__loop_alive (루프);
if (mode & (uv_run_once | uv_run_nowait)))
부서지다;
}
UV_UPDATE_TIME 함수의 소스 코드는 다음과 같습니다.
코드 사본은 다음과 같습니다.
void uv_update_time (uv_loop_t* loop) {
/* 현재 시스템 시간을 얻으십시오*/
dword ticks = getTickCount ();
/ * guge_integer.quadpart가 동일한 유형을 가지고 있다고 가정합니다 */
/* loop-> 시간. 이것을 주장하는 방법이 있습니까? */
large_integer* time = (barge_integer*) & loop-> time;
/* 타이머가 래핑 한 경우 1을 고차 DWORD에 추가하십시오. */
/ * uv_poll
/* 두 후속 UV_UPDATE_TIME 호출 사이에서 한 번. */
if (ticks <time-> lowpart) {
시간-> 하이 파트 += 1;
}
시간-> 로우 파트 = 진드기;
}
이 기능의 내부 구현은 Windows의 getTickCount () 함수를 사용하여 현재 시간을 설정합니다. 간단히 말하면, Settimeout 함수를 호출 한 후 일련의 투쟁 후에 내부 타이머-> 기한이 현재 루프 시간 + 시간 초과로 설정됩니다. 이벤트 루프에서 먼저 UV_UPDATE_TIME을 통한 전류 루프 시간을 업데이트 한 다음 타이머가 uv_process_times에서 만료되는지 확인하십시오. 그렇다면 JavaScript의 세계에 들어갑니다. 전체 기사를 읽은 후 이벤트 루프는이 과정과 거의 비슷합니다.
글로벌 시간을 업데이트하십시오
타이머를 확인하십시오. 타이머가 만료되면 콜백을 실행하십시오.
REQS 대기열을 확인하고 대기 요청을 실행하십시오
IO 이벤트를 수집하려면 폴링 기능을 입력하십시오. IO 이벤트가 도착하면 다음 이벤트 루프에서 실행을 위해 해당 처리 기능을 REQS 큐에 추가하십시오. 폴링 기능 내에서 IO 이벤트를 수집하기위한 시스템 방법이 호출됩니다. 이 방법으로 인해 IO 이벤트가 도착하거나 설정 시간 초과 시간에 도달 할 때까지 프로세스가 차단됩니다. 이 메소드가 호출되면 시간 초과 시간은 가장 최근 타이머가 만료되는 시간으로 설정됩니다. 이는 IO 이벤트가 차단하여 수집되고 최대 차단 시간이 다음 타이머의 마지막 시간임을 의미합니다.
Windows에서 설문 조사 기능 중 하나의 소스 코드 :
코드 사본은 다음과 같습니다.
static void uv_poll (uv_loop_t* loop, int block) {
dword 바이트, 타임 아웃;
ulong_ptr 키;
겹쳐진* 중첩;
uv_req_t* req;
if (블록) {
/* 가장 최근 타이머의 만료 시간을 꺼내십시오*/
타임 아웃 = uv_get_poll_timeout (loop);
} 또 다른 {
시간 초과 = 0;
}
getqueuedCompletionStatus (loop-> iocp,
& 바이트,
&열쇠,
& 중첩,
/* 다음 타이머가 만료 될 때까지 대부분의 차단*/
시간 초과);
if (중첩) {
/ * 패키지가 탈출되었습니다 */
req = uv_overlapped_to_req (중첩);
/* IO 이벤트 삽입 대기열*/
uv_insert_pending_req (loop, req);
} else if (getLasterror ()! = Wait_Timeout) {
/ * 심각한 오류 */
uv_fatal_error (getLasterror (), "getqueuedCompletionStatus");
}
}
위의 단계에 따라 타임 아웃 = 1ms 타이머를 설정한다고 가정하면 폴링 함수는 최대 1ms를 차단 한 다음 복구 후 복구됩니다 (기간 동안 IO 이벤트가없는 경우). 이벤트 루프 루프를 계속 입력하면 UV_UPDATE_TIME은 시간을 업데이트 한 다음 UV_Process_Timers는 타이머가 만료되어 콜백을 실행한다는 것을 알게됩니다. 따라서 예비 분석은 UV_UPDATE_TIME에 문제가 있거나 (현재 시간이 올바르게 업데이트되지 않음) 폴링 함수가 1ms를 기다린 다음 복구된다는 것입니다. 이 1ms 대기에는 문제가 있습니다.
MSDN을 살펴보면 놀랍게도 GetTickCount 기능에 대한 설명을 발견했습니다.
GetTickCount 함수의 해상도는 시스템 타이머의 해상도로 제한되며, 이는 일반적으로 1 천만 초에서 1,600 만 초입니다.
GetTickCount의 정확도는 너무 거칠다! 폴링 기능이 1ms의 시간을 올바르게 차단한다고 가정하지만 다음에 uv_update_time이 실행될 때 현재 루프 시간이 올바르게 업데이트되지 않습니다! 그래서 우리의 타이머는 만료 된 것으로 판단되지 않았으므로 여론 조사는 또 다른 1ms를 기다렸다가 다음 이벤트 루프에 들어갔다. getTickCount가 최종적으로 올바르게 업데이트 될 때까지 (소위 15.625ms가 한 번 업데이트 됨) 현재 루프의 현재 시간이 업데이트되고 타이머가 UV_Process_Timers에서 만료되는 것으로 판단됩니다.
WebKit에게 도움을 요청하십시오
Node.js 의이 소스 코드는 매우 무력합니다. 그는 정밀도가 낮은 시간 기능을 사용했으며 아무것도하지 않았습니다. 그러나 우리는 Node.js의 Settimeout 외에 Node-Webkit을 사용하기 때문에 Chromium의 Settimeout도 있다고 생각했습니다. 테스트 코드를 작성하고 브라우저 또는 노드 webkit을 사용하여 실행하십시오 (http://marks.lrednight.com/test.html#1) (# 다음에 측정 할 간격을 나타냅니다). 결과는 다음과 같습니다.
HTML5 사양에 따라 이론적 결과는 처음 5 개의 결과가 1ms이고 다음 결과는 4ms입니다. 테스트 케이스에 표시된 결과는 세 번째부터 시작됩니다. 이는 테이블의 데이터가 이론적으로 처음 세 번 1ms 여야하며 그 이후 결과는 4ms입니다. 결과에는 특정 오류가 있으며 규정에 따르면, 우리가 얻을 수있는 가장 작은 이론적 결과는 4ms입니다. 우리는 만족하지는 않지만 Node.js의 결과보다 훨씬 더 만족 스럽습니다. 강력한 호기심 추세는 크롬의 소스 코드를 살펴보고 그것이 어떻게 구현되는지 알아 보겠습니다. (https://chromium.googlesource.com/chromium/src.git/bit/38.0.2125.101/base/time/time_win.cc)
먼저 Chromium은 루프의 현재 시간을 결정할 때 timegettime () 함수를 사용합니다. MSDN을 보면이 기능의 정확도가 시스템의 현재 타이머 간격에 영향을 받는다는 것을 알 수 있습니다. 테스트 머신에서는 이론적으로 위에서 언급 한 1.001ms입니다. 그러나 기본적으로 응용 프로그램이 글로벌 타이머 간격을 수정하지 않는 한 타이머 간격은 최대 값 (테스트 시스템의 15.625ms)입니다.
IT 업계에서 뉴스를 따르면 그러한 뉴스를 보았을 것입니다. 우리의 크롬은 타이머 간격을 매우 작게 설정 한 것 같습니다! 시스템 타이머 간격에 대해 걱정할 필요가없는 것 같습니다. 너무 일찍 행복하지 마십시오. 그러한 수리는 우리에게 타격을주었습니다. 실제로이 문제는 Chrome 38에서 수정되었습니다. 이전 Node-Webkit을 수정해야합니까? 이것은 분명히 우아하지 않으며보다 성능이 뛰어난 크롬 버전을 사용하지 못하게합니다.
Chromium 소스 코드를 더 자세히 살펴보면 타임 아웃 <32ms의 타이머와 시간 초과가있을 때 Chromium은 시스템의 글로벌 타이머 간격을 변경하여 15.625ms 미만의 정확도로 타이머를 달성 할 수 있습니다. (소스 코드보기) 타이머를 시작하면 HighresolutiontimerManager라는 것을 활성화합니다. 이 클래스는 현재 장치의 전원 유형을 기반으로 enableHighresolutiontimer 함수를 호출합니다. 구체적으로, 현재 장치가 배터리를 사용하면 EnableHighresolutiontimer (False)를 호출하며 전원을 사용할 때는 TRUE가 전달됩니다. enableHighresolutiontimer 함수의 구현은 다음과 같습니다.
코드 사본은 다음과 같습니다.
void time :: enablehighresolutiontimer (bool enable) {
Base :: Autolock Lock (g_high_res_lock.get ());
if (g_high_res_timer_enabled == enable)
반품;
g_high_res_timer_enabled = enable;
if (! g_high_res_timer_count)
반품;
// g_high_res_timer_count! = 0이므로 activatehighresolutiontimer (true).
// G_HIGH_RES_TIMER_ENABLED를 사용하여 TimeBeginPeriod라고 불렀습니다
// | enable | 그 정보로 우리는
// TimeBeginPeriod에서 사용 된 것과 동일한 값으로 TimeEndperiod를 호출하고
// 따라서 기간 효과를 취소합니다.
if (enable) {
TimeEndperiod (kmintimerintervallowResms);
TimeBeginPeriod (kmintimerintervalhighresms);
} 또 다른 {
TimeEndperiod (kmintimerintervalhighresms);
TimeBeginPeriod (KmintimerIntervallowResms);
}
}
여기서 KmintimerintervallowResms = 4 및 kmintimerintervalhighresm = 1. TimeBeginPeriod 및 TimeEndperiod는 시스템 타이머 간격을 수정하기 위해 Windows가 제공하는 함수입니다. 즉, 전원 공급 장치에 연결할 때 가장 작은 타이머 간격은 1ms이며 배터리를 사용할 때는 4ms입니다. 우리의 루프는 Settimeout을 지속적으로 호출하기 때문에 W3C 사양에 따라 최소 간격도 4ms이므로 완화 된 느낌이 들기 때문에 이것은 우리에게 거의 영향을 미치지 않습니다.
또 다른 정밀 문제
처음으로 돌아가서, 테스트 결과는 설정 타임 아웃 간격이 4ms에서 안정적이지는 않지만 지속적으로 변동하고 있음을 발견했습니다. http://marks.lrednight.com/test.html#48 테스트 결과는 또한 간격이 48ms에서 49ms 사이에 상승하고 있음을 보여줍니다. 그 이유는 Chromium 및 Node.js의 이벤트 루프에서 IO 이벤트를 기다리는 Windows 함수 호출의 정확도가 현재 시스템의 타이머의 영향을 받기 때문입니다. 게임 로직을 구현하려면 requestAnimationFrame 함수 (캔버스를 지속적으로 업데이트)가 필요합니다. 이는 타이머 간격을 최소한 KmintimerIntervallowResms로 설정하는 데 도움이 될 수 있습니다 (16ms 타이머가 필요하기 때문에 고정식 타이머의 요구 사항을 유발합니다). 테스트 머신이 전원을 사용하면 시스템 타이머 간격은 1ms이므로 테스트 결과의 오류는 ± 1ms입니다. 컴퓨터가 시스템 타이머 간격을 변경하지 않고 위의 #48 테스트를 실행하지 않으면 Max가 48+16 = 64ms에 도달 할 수 있습니다.
Chromium의 Settimeout 구현을 사용하여 Settimeout (FN, 1)에서 약 4ms의 오류를 제어 할 수 있으며 Settimeout (FN, 48)의 오류는 Settimeout (FN, 48)에서 약 1ms의 오차를 제어 할 수 있습니다. 그래서 우리는 우리의 마음에 새로운 청사진을 가지고있어 코드가 다음과 같이 보이게합니다.
코드 사본은 다음과 같습니다.
/ * 최대 간격 장식 */
var 장식 = getMaxInterValdEviation (BucketSize); // BucketSsize = 48, 편차 = 2;
함수 gameloop () {
var now = date.now ();
if (previousbucket + bucketsize <= now) {
previousbucket = 지금;
dologic ();
}
if (previousbucket + bucketsize -date.now ()> 장식) {
// 46ms를 기다립니다. 실제 지연은 48ms 미만입니다.
settimeout (Gameloop, Bucketsize- 디자인);
} 또 다른 {
// 바쁘다. 전자가 IO 이벤트를 차단하지 않기 때문에 process.nexttick 대신 setimmediate를 사용하십시오.
setimmediate (gameloop);
}
}
위의 코드를 사용하면 Bucket_Size를 직접 동일하게하는 대신 Bucket_Size (Buct_Size 정의)보다 적은 오류가있는 시간을 기다릴 수 있습니다. 상기 이론에 따르면 최대 오차가 46ms 지연에서 발생하더라도 실제 간격은 48ms 미만입니다. 나머지 시간은 바쁜 대기 방법을 사용하여 Gameloop가 충분한 정밀한 간격으로 실행되도록합니다.
우리는 크롬으로 어느 정도 문제를 해결했지만, 이것은 분명히 우아하지 않습니다.
우리의 초기 요청을 기억하십니까? 서버 측 코드는 Node-Webkit 클라이언트가없는 Node.js 환경을 가진 컴퓨터에서 직접 실행할 수 있어야합니다. 위의 코드를 직접 실행하면 정의 값은 최소 16ms이므로 각 48ms에서 16ms를 기다려야합니다. CPU 사용률이 상승했습니다.
예상치 못한 놀라움
너무 성가시다. Node.js에서 그런 큰 버그를 보지 못했습니까? 그 대답은 실제로 우리를 너무 기뻐하게 만듭니다. 이 버그는 V.0.11.3 버전으로 수정되었습니다. Libuv 코드의 마스터 브랜치를 직접 보면 수정 된 결과를 볼 수 있습니다. 특정 접근법은 폴링 기능이 완료되기를 기다린 후 루프의 현재 시간에 시간 초과를 추가하는 것입니다. 이런 식으로, GetTickCount가 반응하지 않더라도 여론 조사가 기다린 후에도 여전히 대기 시간을 추가했습니다. 따라서 타이머가 부드럽게 만료 될 수 있습니다.
다시 말해, 오랫동안 열심히 일한 문제는 v.0.11.3에서 해결되었습니다. 그러나 우리의 노력은 헛되지 않았습니다. getTickCount 함수가 제거 되더라도 폴링 함수 자체는 시스템 타이머의 영향을받습니다. 한 가지 해결책은 Node.js 플러그인을 작성하여 시스템 타이머 간격을 변경하는 것입니다.
그러나 이번 게임의 초기 설정은 서버가 없습니다. 클라이언트가 방을 만들면 서버가됩니다. 서버 코드는 Node-Webkit 환경에서 실행될 수 있으므로 Windows 시스템에서 타이머 문제의 우선 순위가 가장 높지 않습니다. 위에서 준 해결책에 따라 결과는 우리를 만족시키기에 충분합니다.
종결
타이머 문제를 해결 한 후 프레임 워크 구현은 기본적으로 더 이상 방해받지 않습니다. 우리는 WebSocket 지원 (순수한 HTML5 환경)을 제공하고 통신 프로토콜을 사용자 정의하여 고위성 소켓 지원 (노드-웨브 키트 환경)을 달성합니다. 물론, 우주 실의 기능은 처음에는 비교적 간단했지만 수요가 제안되고 시간이 증가함에 따라 우리는이 프레임 워크를 점차 개선하고 있습니다.
예를 들어, 게임에서 일관된 랜덤 숫자를 생성해야 할 때이 기능을 우주식에 추가했습니다. 게임이 시작될 때 우주선은 임의의 숫자 씨앗을 배포합니다. 클라이언트의 우주선은 MD5의 임의성을 사용하여 임의의 숫자 씨앗을 사용하여 난수를 생성하는 방법을 제공합니다.
그것은 꽤 안심된 것 같습니다. 나는 또한 그러한 프레임 워크를 작성하는 과정에서 많은 것을 배웠습니다. 우주선에 관심이 있으시면 참여할 수도 있습니다. 나는 우주 정리가 더 많은 곳에서 주먹과 발을 사용할 것이라고 믿는다.