mino
Oyuncuların gezip sohbet ettiği, tarayıcıda ortak bir 2B alan. Node.js ve uWebSockets.js üzerinde gerçek zamanlı bir sunucu, aynı dünyayı Redis üzerinden paylaşan birden çok süreç ve WebGPU ile çizen bir istemci.
Her konumun sahibi sunucu: istemci yalnız yürümek istediği yönü gönderir; sunucu her oyuncuyu saniyede 20 kez hareket ettirir ve snapshot gönderir. Aynı makinede ya da farklı makinelerde çalışan birden çok sunucu süreci alanı Redis üzerinden paylaşır: her biri kendi oyuncularını simüle eder, snapshot’larını, sohbetini ve presence bilgisini yayınlar, diğerleri bunları değiştirmeden iletir. Wire protokolü FlatBuffers, yalnız binary. Tarayıcı istemcisi bütün oyuncuları WebGPU ile tek draw call’da, en yeni snapshot’ın yaklaşık 100 ms gerisinden, interpolasyonla çizer.
- Sunucusu ölen oyuncu başka bir sunucuda devam eder: resume bileti, session ve her shard’ın son snapshot’ı Redis’te. AWS’de, Cloudflare üzerinden, ilk makinedeki süreç öldürüldüğünde oyuncu socket’i kapandıktan 436 ms sonra ikinci makinede karşılandı, 473 ms’de yerindeydi.
- Sınırına gelen süreç, kendi işi yüzünden tick’leri gecikmeye başlayınca yeni oyuncuları “server busy” ile reddeder; bütün tick’lerin kaymasına izin vermez.
- Şimdilik misafir oyuncular; ilk mesajda identity-service access token’ı için yer var.
AWS’de ölçüldü
İki c7a.large makine, her birinde bir sunucu süreci, önlerinde TLS ile nginx ve aynı subnet’te bir yük üreteci. Her oyuncu bir ile üç saniyede bir yeni bir yöne yürür.
- iki c7a.large
- bir tick, 50 ms
snapshot aralığı p95, ms
oyuncu
| oyuncu | iki c7a.large |
|---|---|
| 100 | 51 |
| 250 | 51 |
| 500 | 52 |
| 1.000 | 58 |
| 1.500 | 104 |
| 2.000 | 237,5 |
1000 oyuncuda snapshot'lar zamanında ve her makine yaklaşık %30 dolu. 1500 ve 2000'de yük üreteci %70'in üstünde doluydu, bu iki adım temiz değil; 2500 ve 3000 geçersiz.
Tek makinede ölçüldü
Bir masaüstü, sunucu süreçleri çekirdeklere sabitlenmiş: bir süreç nerede duruyor ve neden.
- loopback TCP
- Unix socket
- tick hızı, saniyede 20
saniyede tick
oyuncu
| oyuncu | loopback TCP | Unix socket |
|---|---|---|
| 1.500 | 20,1 | 20 |
| 2.000 | 20,1 | 20,1 |
| 2.500 | 12,2 | 19,4 |
| 3.000 | 9,9 | 13,6 |
Unix socket'te bir frame sunucuya yaklaşık %30 daha ucuz; bir süreç saniyede 20 tick'te yaklaşık 2000 yerine 2500 oyuncu taşıyor. Bu masaüstünün sayıları, busy kuralı kapalı.
- Girişlerin maliyeti oyuncu sayısının karesiyle artıyordu: her join her shard’ı okuyor ve bütün oyuncu listesini encode ediyordu. Saniyede yaklaşık 280 gelen 2500 oyuncuyla değişiklikten önce 671 join zaman aşımına uğradı, sonra hiçbiri; bellek 297 MB’tan 142 MB’a indi.
- Maliyet byte başına değil, write başına. Bir istemcinin frame’leri tick başına tek write’ta birleşince iki süreç 1642 yerine 2000 oyuncunun hepsini, 0,97 yerine 0,65 çekirdekle aldı.
- Tek süreç ve tek çekirdekte yarım milyon boşta websocket: her biri süreçte yaklaşık 1,3 KB, kernel’de 2,7 ile 4 KB. Sınır gönderimde: hepsine bir mesaj göndermek bir çekirdeğin 5,7 ile 12 saniyesini alıyor.
- Tek alanda byte’lar oyuncu sayısının karesiyle artıyor, çünkü her oyuncu diğer her oyuncunun konumunu saniyede 20 kez alıyor. Birkaç binin üstünde sınır ağ; aşmanın yolu her oyuncuya yalnız görebildiğini göndermek.