1 puan yazan GN⁺ 2024-11-24 | Henüz yorum yok. | WhatsApp'ta paylaş
  • 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.

Henüz yorum yok.