- Hızlı FPS ortamlarında geç gelen durum bilgisinin değeri düşük olduğu için, Quake 3 gecikmeyi azaltmak adına UDP/IP merkezli bir tasarımı tercih eder
- NetChannel, kayıp yaşanabilen UDP üzerinde iletişimi soyutlar ve sunucu, istemci başına snapshot kayıtları ile yalnızca gerekli durum farklarını yeniden hesaplar
- Sunucu, Master Gamestate, son 32 gamestate ve dummy gamestate’i birlikte kullanarak tam güncelleme ile delta güncellemesini aynı prosedüre dönüştürür
- İstemciden ACK gelmezse sunucu, son onaylanan snapshot ile mevcut durumu karşılaştırıp kaçırılan değişiklikler ile yeni değişiklikleri tek bir mesaja koyar
- C’de yerleşik introspection olmasa da
netField_t ve makrolarla alan farkları bulunur; NetChannel da yönlendirici parçalanmasını önlemek için 1400 baytlık ön bölme kullanır
UDP/IP varsayımı üzerine kurulu ağ modeli
- Quake 3’ün ağ modeli, motorun en zarif bölümlerinden biri olarak değerlendirilir; düşük seviyede iletişim, ilk kez Quake World’de ortaya çıkan NetChannel modülüyle soyutlanır
- Hızlı oyunlarda ilk iletimde kaçırılan bilgi kısa süre içinde zaten eski bilgiye dönüştüğü için, yeniden göndermektense en güncel durumu göndermek daha avantajlıdır
- Bu nedenle motorda TCP/IP’ye dair hiçbir iz yoktur ve güvenilir iletimin oluşturduğu gecikmenin kabul edilemeyeceği düşünülür
- Ağ yığınına birbirini dışlayan iki katman eklenir
- Önceden paylaşılan anahtar kullanan şifreleme
- Önceden hesaplanmış Huffman anahtarı kullanan sıkıştırma
- Sunucu, UDP datagram boyutunu küçültürken güvensizliği de telafi eder
- Snapshot kayıtlarıyla delta paketleri üretir
- Bellek introspection yaklaşımıyla yalnızca değişen alanları bulup yollar
Sunucu ve istemcinin rolleri
- İstemci tarafındaki akış basittir
- Her karede sunucuya komut gönderir
- Sunucudan gamestate güncellemeleri alır
- Sunucu, her istemciye Master Gamestate yayarken kaybolan UDP paketlerini de hesaba katmak zorundadır
- Temel mekanizma üç bileşenden oluşur
- Master Gamestate: Genel olarak geçerli oyun durumudur; istemci komutları NetChannel üzerinden gelir,
event_tye dönüştürülür ve ardından sunucuda oyun durumunu değiştirir
- İstemci başına son 32 gamestate: Ağ üzerinden gönderilen durumlar döngüsel bir dizide tutulur ve bunlara snapshot denir
- dummy gamestate: Tüm alanları 0 olan durumdur; önceki durum olmadığında delta üretimi için referans olarak kullanılır
- Sunucu, bu üç bileşenle NetChannel’a verilecek güncelleme mesajlarını oluşturur
- İstemci başına çok sayıda gamestate tutulması gerektiğinden bellek kullanımı yüksektir
- Ölçüm olarak 4 oyuncu için 8MB kullanılır
Snapshot’larla tam güncelleme ve kısmi güncelleme üretmek
- Örnek, Client1’e güncelleme gönderilen bir durumda Client2’nin
pos[X], pos[Y], pos[Z], health olmak üzere dört alandan oluşan durumunu kullanır
- İletişim UDP/IP üzerinden yapılır ve internette mesajlar sık sık kaybolabilir
-
Birinci sunucu karesi
- Sunucu, tüm istemcilerden aldığı güncellemeleri Master Gamestate’e yansıttıktan sonra durumu Client1’e yayar
- Ağ modülü her seferinde aynı prosedürü izler
- Master Gamestate’i istemci kaydındaki bir sonraki slota kopyalar
- Kopyalanan snapshot’ı başka bir snapshot ile karşılaştırır
- İlk güncellemede Client1 kaydında geçerli snapshot olmadığından dummy snapshot ile karşılaştırma yapılır
- dummy snapshot’ın tüm alanları 0 olduğu için sonuç tam güncelleme olur
- Her alanın önüne, değişip değişmediğini gösteren bir bit işaretçisi eklenir
- Örnekte tam güncelleme 132 bit kullanır
- Biçim
[1 A_on32bits 1 B_on32bits 1 B_on32bits 1 C_on32bits] şeklindedir
-
İkinci sunucu karesi
- Sonraki karede Client2, Y ekseninde hareket eder ve
pos[1] değeri E olur
- Client1 önceki güncellemeyi aldığını ACK ile bildirdiği için Snapshot1 ACK durumuna geçer
- Sunucu, Master Gamestate’i kayıttaki bir sonraki slota kopyalayarak Snapshot2’yi oluşturur ve bunu geçerli Snapshot1 ile karşılaştırır
- Sonuç olarak yalnızca değişen
pos[1] = E ağ üzerinden gönderilir
- Her alana bit işaretçisi eklendiği için bu kısmi güncelleme 36 bit kullanır
- Biçim
[0 1 32bitsNewValue 0 0] şeklindedir
-
Üçüncü sunucu karesi
- Sonraki karede Client2 hasar alır ve
health = H olur
- Client1 son güncellemeyi ACK etmez
- Sunucunun UDP paketi kaybolmuş olabilir ya da istemcinin ACK’i kaybolmuş olabilir
- Her iki durumda da ilgili snapshot kullanılamaz
- Sunucu, Master Gamestate’i bir sonraki slota kopyalayarak Snapshot3’ü oluşturur ve bunu en son ACK alınan Snapshot1 ile karşılaştırır
- Gönderilen mesaj kısmi güncellemedir; önceki değişiklik
pos[1] = E ile yeni değişiklik health = H birlikte yer alır
- Snapshot1 çok eskiyse ve artık kullanılamıyorsa motor yeniden dummy snapshot temel alınarak tam güncelleme gönderir
Aynı prosedürle kaybı telafi etme yöntemi
- Snapshot sisteminin sadeliği, aynı algoritmanın iki işi otomatik olarak yapmasından gelir
- Tam güncelleme veya kısmi güncelleme üretimi
- Alınmamış eski bilgi ile yeni bilginin tek mesajda yeniden gönderimi
- Kayıp UDP paketleri ayrı ve karmaşık bir akışla ele alınmaz; bunun yerine son ACK alınan snapshot ile mevcut Master Gamestate arasındaki fark hesaplanarak telafi sağlanır
- Önceki durum yoksa ya da kullanılamıyorsa, kurtarma için dummy snapshot temel alınarak tüm durum gönderilir
C’de alan farklarını bulma yöntemi
- Quake 3, C dilinde introspection olmamasına rağmen her alanın konumunu
netField_t dizisi ve önişlemci yönergeleriyle önceden tanımlar
netField_t; alan adı, offset ve bit sayısını içerir
NETF(x) makrosu, stringizing operator ile entityState_t üzerindeki offset hesabını kullanarak alan bilgisinin daha kısa yazılmasını sağlar
- Örnek yapı aşağıdaki gibidir
typedef struct { char *name; int offset; int bits; } netField_t;
// using the stringizing operator to save typing...
#define NETF(x) #x,(int)&((entityState_t*)0)->x
netField_t entityStateFields[] = {
{ NETF(pos.trTime), 32 },
{ NETF(pos.trBase[0]), 0 },
{ NETF(pos.trBase[1]), 0 },
...
}
- Tam uygulama MSG_WriteDeltaEntity içinde görülebilir
- Quake 3, karşılaştırılan şeyin anlamını yorumlamaz;
entityStateFields içindeki index, offset ve size değerlerini izleyerek farkları ağ üzerinden gönderir
Neden önceden 1400 bayta bölüyor
- NetChannel modülü, UDP datagram’ın azami boyutu 65507 bayt olmasına rağmen mesajları 1400 baytlık parçalara böler
- İlgili kod Netchan_Transmit içinde yer alır
- Ağların çoğunda MTU 1500 bayt olduğundan, 1400 bayta bölme tercihi internet yolunda yönlendiricilerin paketi parçalamamasını sağlamak içindir
- Yönlendirici parçalanmasından kaçınmanın iki nedeni vardır
- Paket ağa girerken yönlendirici, onu parçalara ayırırken paketi bekletmek zorunda kalır
- Paket ağdan çıkarken maliyetli yeniden birleştirme için datagram’ın tüm parçalarının beklenmesi gerekir
Mutlaka teslim edilmesi gereken mesajlar
- Snapshot sistemi, ağda kaybolan UDP datagramlarını telafi eder; ancak bazı mesaj ve komutların mutlaka teslim edilmesi gerekir
- Oyuncunun oyundan çıkması ya da sunucunun istemciden yeni bir seviye yüklemesini istemesi buna örnektir
- Bu garantiyi NetChannel soyutlar
İlgili okumalar
Henüz yorum yok.