Hari ini, ketika Node.js sedang berjalan lancar, kita sudah dapat menggunakannya untuk melakukan segala macam hal. Beberapa waktu yang lalu, Host UP berpartisipasi dalam acara lagu Geek. Selama acara ini, kami bermaksud membuat game yang memungkinkan "orang-orang head-down" untuk berkomunikasi lebih banyak. Fungsi inti adalah interaksi multi-orang real-time dari konsep LAN Party. Perlombaan Geeks hanya 36 jam singkat yang menyedihkan, membutuhkan segalanya untuk menjadi cepat dan cepat. Di bawah premis seperti itu, persiapan awal tampaknya agak "alami". Solusi Aplikasi Cross-Platform Kami memilih Node-Webkit, yang cukup sederhana dan memenuhi persyaratan kami.
Menurut persyaratan, pengembangan kami dapat dilakukan secara terpisah sesuai dengan modul. Artikel ini secara khusus menjelaskan proses pengembangan spaceroom (kerangka kerja game multiplayer real-time kami), termasuk serangkaian eksplorasi dan upaya, serta solusi untuk beberapa pembatasan pada node.js dan platform webkit itu sendiri, dan proposal solusi.
Memulai
Pandangan ruang angkasa
Pada awalnya, desain spaceroom pasti didorong oleh permintaan. Kami berharap kerangka kerja ini dapat memberikan fungsi dasar berikut:
Dapat membedakan sekelompok pengguna berdasarkan kamar (atau saluran)
Dapat menerima instruksi dari pengguna di grup koleksi
Saat mencocokkan antara setiap klien, data game dapat disiarkan secara akurat sesuai dengan interval yang ditentukan
Dapat menghilangkan dampak latensi jaringan sebanyak mungkin
Tentu saja, pada tahap pengkodean selanjutnya, kami menyediakan ruang spaceroom dengan lebih banyak fungsi, termasuk berhenti dari permainan, menghasilkan angka acak yang konsisten antara setiap klien, dll. (Tentu saja, ini dapat diimplementasikan sendiri sesuai dengan persyaratan, dan tidak perlu menggunakan ruang angkasa, kerangka kerja yang berfungsi pada tingkat komunikasi, yang lebih merupakan tingkat komunikasi).
Lebah
Spaceroom dibagi menjadi dua bagian: ujung depan dan belakang. Pekerjaan yang diperlukan oleh server termasuk memelihara daftar kamar dan menyediakan fungsi membuat dan bergabung dengan ruangan. API Klien kami terlihat seperti ini:
spaceroom.connect (alamat, callback) Sambungkan ke server
spaceroom.createroom (callback) Buat kamar
spaceroom.ninroom (roomId) Bergabunglah dengan kamar
spaceroom.on (acara, callback) mendengarkan acara
...
Setelah klien terhubung ke server, ia menerima berbagai acara. Misalnya, pengguna di kamar dapat menerima acara di mana pemain baru bergabung, atau acara di mana permainan dimulai. Kami memberi klien "siklus hidup", yang akan berada di salah satu negara berikut kapan saja:
Anda bisa mendapatkan status klien saat ini melalui spaceroom.State.
Menggunakan kerangka kerja sisi server relatif sederhana. Jika Anda menggunakan file konfigurasi default, maka Anda dapat menjalankan kerangka kerja sisi server secara langsung. Kami memiliki persyaratan dasar: Kode server dapat berjalan langsung di klien tanpa perlu server terpisah. Pemain yang telah memainkan PS atau PSP harus tahu apa yang saya bicarakan. Tentu saja, juga sangat baik untuk dijalankan di server khusus.
Implementasi kode logis singkat di sini. Generasi spaceroom pertama menyelesaikan fungsi server soket, yang memelihara daftar kamar, termasuk status ruangan, dan komunikasi waktu permainan yang sesuai dengan setiap kamar (koleksi instruksi, siaran ember, dll.). Untuk implementasi tertentu, silakan merujuk ke kode sumber.
Algoritma sinkron
Jadi, bagaimana kita bisa membuat hal -hal yang ditampilkan di antara setiap klien yang konsisten secara real time?
Hal ini terdengar menarik. Pikirkan dengan cermat, apa yang kami butuhkan server untuk membantu kami mengirimkannya? Secara alami, Anda akan memikirkan apa yang dapat menyebabkan ketidakkonsistenan logis antara berbagai klien: instruksi pengguna. Karena kode yang berhubungan dengan logika game adalah sama, diberi kondisi yang sama, kode menjalankan hasil yang sama. Satu -satunya perbedaan adalah berbagai perintah pemain yang diterima selama pertandingan. Benar, kita perlu cara untuk menyinkronkan instruksi ini. Jika semua klien bisa mendapatkan instruksi yang sama, maka semua klien secara teoritis dapat memiliki hasil operasi yang sama.
Algoritma sinkronisasi game online adalah semua jenis skenario yang aneh dan berlaku. Spaceroom menggunakan algoritma sinkronisasi yang mirip dengan konsep penguncian bingkai. Kami membagi garis waktu menjadi interval satu per satu, dan setiap interval disebut ember. Bucket digunakan untuk memuat instruksi dan dikelola oleh sisi server. Pada akhir setiap periode waktu ember, server menyiarkan ember ke semua klien. Setelah klien mendapatkan ember, instruksi diambil dari itu, dan kemudian dieksekusi setelah verifikasi.
Untuk mengurangi dampak penundaan jaringan, setiap instruksi yang diterima oleh server dari klien akan dikirimkan ke ember yang sesuai sesuai dengan algoritma tertentu, dan langkah -langkah berikut secara khusus diikuti:
Biarkan order_start menjadi waktu perintah yang dibawa oleh perintah, dan t adalah waktu mulai dari ember tempat order_start berada.
Jika t + delay_time <= waktu mulai dari ember yang saat ini mengumpulkan instruksi, kirim perintah ke ember yang saat ini mengumpulkan instruksi, jika tidak lanjutkan langkah 3
Kirim instruksi ke ember yang sesuai dari T + Delay_Time
di mana delay_time adalah waktu tunda server yang disepakati, yang dapat diambil sebagai penundaan rata -rata antara klien. Nilai default di ruang angkasa adalah 80, dan nilai default panjang ember adalah 48. Pada akhir setiap periode waktu ember, server menyiarkan ember ini ke semua klien dan mulai menerima instruksi untuk ember berikutnya. Klien mengontrol kesalahan waktu dalam kisaran yang dapat diterima saat secara otomatis melakukan pencocokan dalam logika berdasarkan interval bucket yang diterima.
Ini berarti bahwa dalam keadaan normal, klien akan menerima ember yang dikirim dari server setiap 48ms. Ketika waktu ketika ember perlu diproses, klien akan menanganinya sesuai. Dengan asumsi bahwa klien FPS = 60, ia akan menerima ember setiap 3 frame atau lebih, dan logika akan diperbarui sesuai dengan ember ini. Jika ember belum diterima setelah waktu berakhir karena fluktuasi jaringan, klien berhenti logika game dan menunggu. Pada waktu dalam ember, logika dapat diperbarui menggunakan metode LERP.
Dalam kasus delay_time = 80, bucket_size = 48, salah satu instruksi akan ditunda oleh setidaknya 96ms eksekusi. Ubah dua parameter ini, misalnya, dalam kasus delay_time = 60, bucket_size = 32, salah satu instruksi akan ditunda setidaknya 64ms.
Kejadian berdarah yang disebabkan oleh timer
Melihat semuanya, kerangka kerja kami perlu memiliki timer yang akurat saat berjalan. Jalankan siaran ember di bawah interval tetap. Tentu saja, kami pertama kali berpikir untuk menggunakan setInterval (), tetapi detik berikutnya kami menyadari betapa tidak dapat diandalkannya ide ini: setinterval nakal () tampaknya memiliki kesalahan yang sangat serius. Dan yang sangat buruk adalah bahwa setiap kesalahan akan menumpuk, menyebabkan konsekuensi yang semakin serius.
Jadi kami segera berpikir untuk menggunakan setTimeout () untuk membuat logika kami secara kasar stabil di sekitar interval yang ditentukan dengan memperbaiki waktu kedatangan berikutnya secara dinamis. Misalnya, waktu ini setTimeout () adalah 5ms kurang dari yang diharapkan, jadi kami akan membiarkannya 5ms sebelumnya waktu berikutnya. Namun, hasil tes tidak memuaskan, dan ini tidak cukup elegan tidak peduli bagaimana Anda melihatnya.
Jadi kita perlu mengubah pemikiran kita. Apakah mungkin untuk membuat setTimeout () kedaluwarsa secepat mungkin, dan kemudian kami memeriksa apakah waktu saat ini telah mencapai waktu target. Misalnya, di loop kami, menggunakan SetTimeout (Callback, 1) untuk terus memeriksa waktu, yang sepertinya ide yang bagus.
Pengatur waktu yang mengecewakan
Kami segera menulis sepotong kode untuk menguji ide -ide kami, dan hasilnya mengecewakan. Dalam versi stabil node.js terbaru (v0.10.32) dan platform Windows, jalankan kode ini:
Salinan kode adalah sebagai berikut:
var sum = 0, count = 0;
function test () {
var sekarang = date.now ();
setTimeout (function () {
var diff = date.now () - sekarang;
jumlah += diff;
Count ++;
tes();
});
}
tes();
Setelah periode waktu tertentu, masukkan jumlah/hitung di konsol dan Anda dapat melihat hasilnya, mirip dengan:
Salinan kode adalah sebagai berikut:
> jumlah / jumlah
15.624555160142348
Apa?! Saya ingin interval 1ms, tetapi Anda memberi tahu saya bahwa interval rata -rata aktual adalah 15.625ms! Gambar ini terlalu indah. Kami melakukan tes yang sama pada Mac dan hasilnya adalah 1,4 ms. Jadi kami bingung: apa ini? Jika saya adalah penggemar Apple, saya mungkin menyimpulkan bahwa Windows terlalu sampah dan menyerah pada Windows. Untungnya, saya adalah seorang insinyur front-end yang ketat, jadi saya mulai terus memikirkan nomor ini.
Tunggu, mengapa nomor ini begitu akrab? Apakah angka ini terlalu mirip dengan interval timer maksimum di bawah Windows? Saya segera mengunduh clockres untuk pengujian, dan setelah menjalankan konsol, saya mendapatkan hasil berikut:
Salinan kode adalah sebagai berikut:
Interval pengatur waktu maksimum: 15.625 ms
Interval pengatur waktu minimum: 0,500 ms
Interval pengatur waktu saat ini: 1,001 ms
Tentu saja! Melihat manual Node.js, kita dapat melihat deskripsi penyelesaian seperti ini:
Penundaan aktual tergantung pada faktor eksternal seperti granularitas timer OS dan beban sistem.
Namun, hasil tes menunjukkan bahwa penundaan aktual ini adalah interval timer maksimum (perhatikan bahwa interval timer saat ini hanya 1,001 ms), yang tidak dapat diterima dalam kasus apa pun. Keingintahuan yang kuat mendorong kita untuk melihat melalui kode sumber Node.js untuk melihat kebenaran.
Bug di Node.js
Saya percaya bahwa sebagian besar dari Anda dan saya memiliki pemahaman tertentu tentang mekanisme loop node. Melihat kode sumber implementasi timer, kami dapat secara kasar memahami prinsip implementasi timer. Mari kita mulai dengan loop utama Loop Event:
Salinan kode adalah sebagai berikut:
while (r! = 0 && loop-> stop_flag == 0) {
/* Perbarui waktu global*/
uv_update_time (loop);
/* Periksa apakah timer kedaluwarsa dan jalankan panggilan balik timer yang sesuai*/
uv_process_timers (loop);
/* Hubungi panggilan balik idle jika tidak ada yang bisa dilakukan. */
if (loop-> pending_reqs_tail == null &&
loop-> endgame_handles == null) {
/* Mencegah loop acara keluar*/
uv_idle_invoke (loop);
}
uv_process_reqs (loop);
uv_process_endgames (loop);
uv_prepare_invoke (loop);
/* Kumpulkan acara IO*/
(*poll) (loop, loop-> 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)) &&
! (Mode & uv_run_nowait));
/* setimmediate () dll*/
uv_check_invoke (loop);
r = uv__loop_alive (loop);
if (mode & (uv_run_once | uv_run_nowait))
merusak;
}
Kode sumber fungsi uv_update_time adalah sebagai berikut: (https://github.com/joyent/libuv/blob/v0.10/src/win/timer.c)))
Salinan kode adalah sebagai berikut:
void uv_update_time (uv_loop_t* loop) {
/* Dapatkan waktu sistem saat ini*/
Dword ticks = getTickCount ();
/ * Asumsinya dibuat bahwa besar_integer.quadpart memiliki tipe yang sama */
/* loop-> waktu, yang kebetulan. Apakah ada cara untuk menegaskan ini? */
Large_integer* time = (besar_integer*) & loop-> waktu;
/* Jika timer telah dibungkus, tambahkan 1 ke DWORD order tinggi. */
/ * uv_poll harus memastikan bahwa timer tidak pernah bisa meluap lebih dari */
/* Sekali antara dua panggilan UV_UPDate_Time berikutnya. */
if (kutu <lima-> lowpart) {
waktu-> highpart += 1;
}
waktu-> lowpart = kutu;
}
Implementasi internal fungsi ini menggunakan fungsi getTickCount () dari Windows untuk mengatur waktu saat ini. Sederhananya, setelah memanggil fungsi setTimeout, setelah serangkaian perjuangan, timer internal-> jatuh tempo akan diatur ke waktu loop saat ini + batas waktu. Dalam loop event, pertama perbarui waktu loop saat ini melalui uv_update_time, dan kemudian periksa apakah timer kedaluwarsa di UV_PROCESS_TIMES. Jika demikian, masukkan dunia JavaScript. Setelah membaca seluruh artikel, loop acara kira -kira seperti proses ini:
Perbarui waktu global
Periksa timer. Jika timer kedaluwarsa, jalankan panggilan balik.
Periksa antrian Reqs dan jalankan permintaan menunggu
Masukkan fungsi jajak pendapat untuk mengumpulkan acara IO. Jika acara IO tiba, tambahkan fungsi pemrosesan yang sesuai ke antrian Reqs untuk dieksekusi di loop acara berikutnya. Di dalam fungsi jajak pendapat, metode sistem dipanggil untuk mengumpulkan acara IO. Metode ini akan menyebabkan proses memblokir sampai acara IO tiba atau waktu batas waktu yang ditetapkan tercapai. Ketika metode ini dipanggil, waktu batas waktu diatur ke waktu ketika timer terbaru berakhir. Ini berarti bahwa peristiwa IO dikumpulkan dengan memblokir dan waktu pemblokiran maksimum adalah waktu terakhir dari timer berikutnya.
Kode sumber dari salah satu fungsi jajak pendapat di bawah Windows:
Salinan kode adalah sebagai berikut:
static void uv_poll (uv_loop_t* loop, int blok) {
Byte dword, batas waktu;
Kunci ulong_ptr;
Tumpang tindih* tumpang tindih;
uv_req_t* req;
if (block) {
/* Mengambil waktu kedaluwarsa dari timer terbaru*/
timeout = uv_get_poll_timeout (loop);
} kalau tidak {
Timeout = 0;
}
GetQueuedCompetiontatus (loop-> IOCP,
& byte,
&kunci,
& tumpang tindih,
/* Paling banyak memblokir sampai timer berikutnya berakhir*/
timeout);
if (tumpang tindih) {
/ * Paket didequeued */
req = uv_overlapped_to_req (tumpang tindih);
/* Masukkan acara IO ke dalam antrian*/
uv_insert_pending_req (loop, req);
} lain jika (getlasterror ()! = wait_timeout) {
/ * Kesalahan serius */
uv_fatal_error (getlasterror (), "getqueuedCompetiontatus");
}
}
Mengikuti langkah -langkah di atas, dengan asumsi bahwa kami mengatur timer timer = 1ms, fungsi jajak pendapat akan memblokir paling banyak 1ms dan kemudian pulih setelah pemulihan (jika tidak ada peristiwa IO selama periode tersebut). Saat terus memasukkan loop loop acara, UV_UPDATE_TIME akan memperbarui waktu, dan kemudian UV_PROCESS_TIMERS akan menemukan bahwa timer kami kedaluwarsa dan menjalankan panggilan balik. Jadi analisis pendahuluan adalah bahwa baik UV_UPDATE_TIME memiliki masalah (waktu saat ini tidak diperbarui dengan benar), atau fungsi jajak pendapat menunggu 1ms dan kemudian pulih. Ada masalah dengan 1ms ini menunggu.
Melihat MSDN, kami secara mengejutkan menemukan deskripsi fungsi gettickcount:
Resolusi fungsi getTickCount terbatas pada resolusi timer sistem, yang biasanya dalam kisaran 10 juta detik hingga 16 juta detik.
Akurasi GettickCount sangat kasar! Asumsikan bahwa fungsi jajak pendapat dengan benar memblokir waktu 1ms, tetapi waktu berikutnya UV_UPDATE_TIME dieksekusi, waktu loop saat ini tidak diperbarui dengan benar! Jadi timer kami tidak dinilai sudah kedaluwarsa, jadi jajak pendapat menunggu 1ms lagi dan memasuki loop acara berikutnya. Sampai GetTickCount akhirnya diperbarui dengan benar (yang disebut 15.625ms diperbarui sekali), waktu loop saat ini diperbarui, dan timer kami dinilai kedaluwarsa di UV_PROCESS_TIMERS.
Minta bantuan Webkit
Kode sumber node.js ini sangat tidak berdaya: ia menggunakan fungsi waktu dengan presisi rendah dan tidak melakukan apa pun. Tetapi kami segera berpikir bahwa karena kami menggunakan Node-Webkit, selain pengaturan Node.js, kami juga memiliki setTimeout Chromium. Tulis kode uji dan gunakan browser atau node-webkit kami untuk menjalankan: http://marks.lrednight.com/test.html#1 (# diikuti oleh angka tersebut menunjukkan interval yang akan diukur). Hasilnya adalah sebagai berikut:
Menurut spesifikasi HTML5, hasil teoretis seharusnya bahwa 5 hasil pertama adalah 1ms, dan hasil selanjutnya adalah 4ms. Hasil yang ditampilkan dalam kasus uji dimulai dari ketiga kalinya, yang berarti bahwa data pada tabel harus secara teoritis 1 ms untuk tiga kali pertama, dan hasilnya setelah itu adalah 4ms. Hasilnya memiliki kesalahan tertentu, dan menurut peraturan, hasil teoritis terkecil yang bisa kita dapatkan adalah 4ms. Meskipun kami tidak puas, itu jelas jauh lebih memuaskan daripada hasil node.js. Tren Keingintahuan yang Kuat Mari kita lihat kode sumber Chromium untuk melihat bagaimana itu diimplementasikan. (https://chromium.googlesource.com/chromium/src.git/+/38.0.2125.101/base/time/time_win.cc)
Pertama, Chromium menggunakan fungsi timegettime () dalam menentukan waktu loop saat ini. Dengan melihat MSDN, Anda dapat menemukan bahwa keakuratan fungsi ini dipengaruhi oleh interval timer sistem saat ini. Pada mesin uji kami, secara teoritis 1,001 ms yang disebutkan di atas. Namun, secara default, interval timer adalah nilai maksimumnya (15.625ms pada mesin uji), kecuali jika aplikasi memodifikasi interval timer global.
Jika Anda mengikuti berita di industri TI, Anda harus melihat berita seperti itu. Tampaknya kromium kita telah mengatur interval pengatur waktu sangat kecil! Sepertinya kita tidak perlu khawatir tentang interval timer sistem? Jangan terlalu senang terlalu dini, perbaikan seperti itu memberi kami pukulan. Faktanya, masalah ini telah diperbaiki di Chrome 38. Haruskah kita menggunakan memperbaiki node-webkit sebelumnya? Ini jelas tidak cukup elegan dan mencegah kita menggunakan versi kromium yang lebih berkinerja.
Melihat lebih jauh pada kode sumber kromium, kita dapat menemukan bahwa ketika ada pengatur waktu dan batas waktu batas waktu <32ms, kromium akan mengubah interval pengatur waktu global sistem untuk mencapai pengatur waktu dengan akurasi kurang dari 15,625ms. (Lihat kode sumber) Saat memulai timer, sesuatu yang disebut HighResolutionTimermanager akan diaktifkan. Kelas ini akan memanggil fungsi EnableHighResolutionTimer berdasarkan jenis daya perangkat saat ini. Secara khusus, ketika perangkat saat ini menggunakan baterai, ia akan menghubungi enableHighResolutionTimer (false), dan true akan dilewati saat menggunakan daya. Implementasi fungsi EnableHighResolutionTimer adalah sebagai berikut:
Salinan kode adalah sebagai berikut:
void time :: enableHighresolutionTimer (bool enable) {
base :: autolock lock (g_high_res_lock.get ());
if (g_high_res_timer_enabled == enable)
kembali;
g_high_res_timer_enabled = enable;
if (! g_high_res_timer_count)
kembali;
// Sejak g_high_res_timer_count! = 0, A ActiveHighResolutionTimer (true)
// dipanggil yang disebut timebeginperiod dengan g_high_res_timer_enabled
// dengan nilai yang merupakan kebalikan dari | Enable |. Dengan informasi itu kami
// Hubungi TimeEndiod dengan nilai yang sama yang digunakan dalam timebeginperiod dan
// Oleh karena itu, batalkan efek periode.
if (aktifkan) {
TimeEndIoD (KMinTimerInterVallowResMS);
timebeginperiod (kmintimerintervalhighresms);
} kalau tidak {
TimeEndiod (KMintimerInterValHighResMS);
timebeginperiod (kmintimerintervallowresms);
}
}
Di mana, kMinTimerInterVallowResMS = 4 dan KMinTimerInvalHighResMS = 1. Timebeginperiod dan TimeEndiod adalah fungsi yang disediakan oleh Windows untuk memodifikasi interval timer sistem. Artinya, ketika menghubungkan ke catu daya, interval pengatur waktu terkecil yang bisa kita dapatkan adalah 1ms, saat menggunakan baterai, itu adalah 4ms. Karena loop kami terus -menerus memanggil SetTimeout, sesuai dengan spesifikasi W3C, interval minimum juga 4ms, jadi saya merasa lega, ini memiliki sedikit dampak pada kami.
Masalah presisi lainnya
Kembali ke awal, kami menemukan bahwa hasil tes menunjukkan bahwa interval setTimeout tidak stabil pada 4ms, tetapi berfluktuasi terus menerus. Http://marks.lrednight.com/test.html#48 Hasil tes juga menunjukkan bahwa interval tersebut dipukuli antara 48ms dan 49ms. Alasannya adalah bahwa dalam lingkaran kromium dan node.js, keakuratan panggilan fungsi Windows menunggu acara IO dipengaruhi oleh pengatur waktu sistem saat ini. Implementasi logika game membutuhkan fungsi requestanimationFrame (terus-menerus memperbarui kanvas), yang dapat membantu kami mengatur interval timer ke setidaknya kmintimerintervallowresms (karena membutuhkan timer 16ms, yang memicu persyaratan timer presisi tinggi). Ketika mesin uji menggunakan daya, interval timer sistem adalah 1ms, sehingga hasil tes memiliki kesalahan ± 1ms. Jika komputer Anda tidak mengubah interval timer sistem dan jalankan tes #48 di atas, MAX dapat mencapai 48+16 = 64ms.
Menggunakan implementasi SetTimeout Chromium, kami dapat mengontrol kesalahan SetTimeout (FN, 1) hingga sekitar 4ms, sedangkan kesalahan SetTimeout (FN, 48) dapat mengontrol kesalahan SetTimeout (FN, 48) menjadi sekitar 1ms. Jadi, kami memiliki cetak biru baru di pikiran kami, yang membuat kode kami terlihat seperti ini:
Salinan kode adalah sebagai berikut:
/ * Dapatkan dekorasi interval maks */
var dekorasi = getmaxintervaldeviation (bucketsize); // BucketSsize = 48, Deviation = 2;
fungsi gameloop () {
var sekarang = date.now ();
if (sebelumnyabucket + bucketsize <= now) {
sebelumnyabucket = sekarang;
dologic ();
}
if (sebelumnyabucket + bucketsize - date.now ()> dekorasi) {
// tunggu 46ms. Penundaan aktual kurang dari 48ms.
setTimeout (gameloop, bucketsize - desain);
} kalau tidak {
// sibuk menunggu. Gunakan setimmediate alih -alih proses.nexttick karena yang pertama tidak memblokir acara IO.
setimmediate (gameloop);
}
}
Kode di atas memungkinkan kami menunggu waktu dengan kesalahan kurang dari bucket_size (definisi bucket_size) alih -alih menyamai bucket_size secara langsung. Bahkan jika kesalahan maksimum terjadi dalam penundaan 46 ms, sesuai dengan teori di atas, interval aktual kurang dari 48 ms. Sisa waktu kami menggunakan metode tunggu sibuk untuk memastikan Gameloop kami dieksekusi dalam interval dengan presisi yang cukup.
Sementara kami memecahkan masalah sampai batas tertentu dengan kromium, ini jelas tidak cukup elegan.
Ingat permintaan awal kami? Kode sisi server kami harus dapat berjalan langsung di komputer dengan lingkungan Node.js tanpa klien Node-Webkit. Jika Anda menjalankan kode di atas secara langsung, nilai definisi setidaknya 16 ms, yang berarti bahwa dalam setiap 48ms, kami harus menunggu 16 ms. Tingkat penggunaan CPU naik.
Kejutan yang tidak terduga
Itu sangat menjengkelkan. Tidak ada yang memperhatikan bug besar di Node.js? Jawabannya benar -benar membuat kita sangat gembira. Bug ini telah diperbaiki dalam versi V.0.11.3. Anda juga dapat melihat hasil yang dimodifikasi dengan secara langsung melihat cabang master dari kode libuv. Pendekatan spesifiknya adalah menambahkan batas waktu ke waktu loop saat ini setelah fungsi jajak pendapat menunggu selesai. Dengan cara ini, bahkan jika GetTickCount tidak bereaksi, kami masih menambahkan waktu tunggu ini setelah jajak pendapat sedang menunggu. Jadi timer dapat kedaluwarsa dengan lancar.
Dengan kata lain, masalah yang telah bekerja keras untuk waktu yang lama telah diselesaikan dalam V.0.11.3. Namun, upaya kami tidak sia -sia. Karena bahkan jika fungsi getTickCount dihilangkan, fungsi jajak pendapat itu sendiri dipengaruhi oleh timer sistem. Salah satu solusi adalah menulis plugin Node.js untuk mengubah interval timer sistem.
Namun, pengaturan awal untuk game kami kali ini tidak bebas server. Setelah klien membuat kamar, itu menjadi server. Kode server dapat berjalan di lingkungan Node-Webkit, sehingga prioritas masalah timer pada sistem Windows bukan yang tertinggi. Mengikuti solusi yang kami berikan di atas, hasilnya cukup untuk memuaskan kami.
akhir
Setelah menyelesaikan masalah pengatur waktu, implementasi kerangka kerja kami pada dasarnya tidak akan lagi terhambat. Kami memberikan dukungan WebSocket (di lingkungan HTML5 murni), dan menyesuaikan protokol komunikasi untuk mencapai dukungan soket kinerja yang lebih tinggi (di lingkungan node-webkit). Tentu saja, fungsi spaceroom relatif sederhana pada awalnya, tetapi ketika permintaan diusulkan dan waktu meningkat, kami secara bertahap meningkatkan kerangka kerja ini.
Misalnya, ketika kami menemukan bahwa ketika kami perlu menghasilkan angka acak yang konsisten dalam permainan kami, kami menambahkan fungsi ini ke ruang spaceroom. Di awal permainan, spaceroom akan mendistribusikan biji nomor acak. Spaceroom klien menyediakan metode untuk menggunakan keacakan MD5 untuk menghasilkan angka acak dengan bantuan biji angka acak.
Tampaknya cukup lega. Saya juga belajar banyak dalam proses penulisan kerangka kerja seperti itu. Jika Anda tertarik dengan ruang angkasa, Anda juga dapat berpartisipasi di dalamnya. Saya percaya spaceroom akan menggunakan tinju dan kakinya di lebih banyak tempat.