Deterministik oyunlar, kayan nokta belirsizliği karşısında nasıl mümkün olabilir?


52

Ağa bağlı bir RTS gibi bir oyun yapmak için, oyunu tamamen belirleyici hale getirmeyi öneren bazı cevaplar gördüm; o zaman sadece kullanıcıların eylemlerini birbirine aktarmanız ve bir sonraki kare oluşturulmadan önce herkesin girişini "kilitlemek" için görüntülenenleri biraz beklemeniz gerekir. O zaman ünitenin pozisyonları, sağlık vb. Gibi şeylerin ağ üzerinden sürekli güncellenmesi gerekmez, çünkü her oyuncunun simülasyonu tamamen aynı olacaktır. Ayrıca tekrar yapmak için önerilen aynı şeyi duydum.

Ancak, kayan nokta hesaplamaları makineler arasında ve hatta aynı programdaki aynı programın farklı derlemeleri arasında belirleyici olmadığından , bu gerçekten mümkün mü? Bu gerçeğin , oyun boyunca dalgalanan oyuncular (veya tekrarlar) arasında küçük farklara neden olmasını nasıl önleriz ?

Bazı insanların kayan nokta sayılarından tamamen uzak intdurmayı ve bir kesir oranını temsil etmeyi kullanmayı önerdiklerini duydum , ancak bu bana pratik gelmiyor - peki örneğin bir açının kosinüsünü almam gerekiyorsa? Bütün bir matematik kütüphanesini tekrar yazmam gerekiyor mu?

Genel olarak, söyleyebildiğim kadarıyla bu konuda C ++ ile tamamen aynı sorunları yaşayan C # ile ilgilendiğime dikkat edin.


5
Bu, sorunuzun cevabı değil, ancak RTS ağ problemini ele almak için, herhangi bir sapmayı düzeltmek için zaman zaman birim pozisyonlarını ve sağlığı senkronize etmenizi tavsiye ederim. Tam olarak hangi bilgilerin senkronize edilmesi gerektiği oyuna bağlı olacaktır; Durum efektleri gibi şeyleri senkronize etmek için uğraşmayarak bant genişliği kullanımını optimize edebilirsiniz.
12'de atıyor

@jhocking Yine de muhtemelen en iyi cevap budur.
Jonathan Connell

starcraft II infacti sadece her zaman tam olarak senkronize edildi, arkadaşlarım ile oyun tekrarına baktım, her bilgisayar yan yana ve orada, özellikle de yüksek gecikmeyle (1 sn) farkların (anlamlı olduğu). 6/7 haritanın benim ekranımda uzak
mesafeye

Yanıtlar:


15

Kayan noktalar deterministik mi?

Birkaç yıl önce yaptığınız aynı kilit mimarisini kullanarak bir RTS yazmak istediğimde bu konuyu okudum.

Donanım kayan noktaları hakkındaki sonuçlarım:

  • Aynı yerel montaj kodu, kayar nokta bayrakları ve derleyici ayarları konusunda dikkatli olmanız koşuluyla belirleyicidir.
  • Bir sarmalayıcı kitaplığı kullanarak farklı derleyiciler arasında deterministik C / C ++ derlemeleri olduğunu iddia eden bir açık kaynaklı RTS projesi vardı. Bu iddiayı doğrulamadım. (Doğru hatırlıyorsam, STREFLOPkütüphaneyle ilgiliydi )
  • .Net JIT’e biraz izin verilir. Özellikle, gerekenden daha yüksek doğruluk kullanmasına izin verilir. Ayrıca x86 ve AMD64'te farklı komut kümeleri kullanır (bence x86'da x87 kullanır, AMD64 ise davranışları dışkı için farklı olan bazı SSE komutlarını kullanır).
  • Karmaşık talimatlar (trigonometrik fonksiyon, üsteller, logaritmalar dahil) özellikle sorunludur.

Yapılı kayan nokta türlerini .net'te deterministical olarak kullanmanın imkansız olduğuna karar verdim.

Olası Geçici Çözümler

Bu yüzden geçici çözümlere ihtiyacım vardı. Düşündüm:

  1. Uygulamak FixedPoint32C #. Bu çok zor olmasa da (uygulamalarımın yarısını bitirdim) çok küçük bir değer aralığı kullanımı can sıkıcı hale getiriyor. Her zaman dikkatli olmalısınız, böylece ne taşmazsınız ne de çok fazla hassasiyet kaybedersiniz. Sonunda bunu doğrudan tam sayıları kullanmaktan daha kolay bulmuyorum.
  2. Uygulamak FixedPoint64C #. Bunu yapmak zor buldum. Bazı işlemler için 128bit'lik orta tamsayılar yararlı olacaktır. Ancak .net böyle bir tür sunmuyor.
  3. Tek platformda deterministic olan matematik işlemleri için yerel kodu kullanın. Her matematik işleminde bir delege çağrısının yükünü üstlenir. Çapraz platform çalıştırma yeteneğini kaybeder.
  4. Kullanın Decimal. Ancak yavaştır, çok fazla hafıza alır ve kolayca istisnalar atar (0'a bölünme, taşma). Finansal kullanım için çok güzel, ama oyunlar için uygun değil.
  5. Özel bir 32 bit kayan nokta uygulayın. İlk başta oldukça zor geliyordu. Bir BitScanReverse içsel eksikliği, bunu uygularken birkaç sıkıntıya neden olur.

SoftFloat'ım

StackOverflow'taki yazınızdan esinlenerek , yazılımda 32 bit kayar nokta tipini uygulamaya başladım ve sonuçlar umut verici.

  • Bellek gösterimi IEEE yüzdürücüler ile ikili olarak uyumludur, bu yüzden bunları grafik koduna gönderirken cast'ı yeniden yorumlayabilirim.
  • SubNorms, sonsuzluk ve NaN'leri destekler.
  • Kesin sonuçlar, IEEE sonuçlarıyla aynı değildir, ancak bu genellikle oyunlar için önemli değildir. Bu tür bir kodda, sonucun son kullanıcılar için doğru olması değil, tüm kullanıcılar için aynı olması önemlidir.
  • Performans iyi. Önemsiz bir test, floatekleme / çarpma için 220-260MFLOPS ile karşılaştırıldığında yaklaşık 75MFLOPS yapabileceğini gösterdi (2.66GHz i3'te tek iplik). Herhangi biri .net için iyi bir kayan nokta kriterine sahipse, lütfen şu anki testim çok basit olduğu için bana gönderin.
  • Yuvarlama geliştirilebilir. Şu anda, kabaca sıfıra yuvarlamaya karşılık gelen kesiliyor.
  • Hala çok eksik. Şu anda bölünme, yayın ve karmaşık matematik işlemleri eksik.

Herhangi biri testlere katkıda bulunmak veya kodu geliştirmek istiyorsanız, sadece benimle iletişime geçin veya github ile ilgili bir istek çekin. https://github.com/CodesInChaos/SoftFloat

Diğer indeterminizm kaynakları

Net'te başka indeterminizm kaynakları da var.

  • yinelenen Dictionary<TKey,TValue>veya a HashSet<T>öğelerini tanımsız bir düzende döndürür.
  • object.GetHashCode() koşmadan koşuya değişir.
  • Dahili Randomsınıfın uygulaması belirtilmemiş, kendinizinkini kullanın.
  • Saf kilitleme ile çoklu kullanım yeniden sıralama ve farklı sonuçlara yol açar. Dişleri doğru kullanmaya çok dikkat edin.
  • Kaybettiği zaman WeakReferencehedefleri belirsizdir, çünkü GC herhangi bir zamanda çalışabilir.

23

Bu sorunun cevabı gönderdiğiniz linkten. Özellikle Gas Powered Games'ten alıntıyı okumalısınız:

Gas Powered Games'te çalışıyorum ve ilk elden kayan nokta matematiğinin deterministik olduğunu söyleyebilirim. Aynı komut setine ve derleyiciye ihtiyacınız var ve tabii ki kullanıcının işlemcisi tüm PC ve 360 ​​müşterilerimizi içeren IEEE754 standardına bağlı. DemiGod, Yüksek Komutan 1 ve 2'yi çalıştıran motor, IEEE754 standardına dayanıyor. Muhtemelen diğer tüm RTS pazardaki akran oyunlarına eşlerinizden bahsetmiyorum bile.

Ve sonra bunun da altında:

Tekrarları kontrolör girişi olarak saklarsanız, farklı CPU mimarilerine, derleyicilere veya optimizasyon ayarlarına sahip makinelerde oynatılamazlar. MotoGP'de bu, Xbox ve PC arasında kaydedilen tekrarları paylaşamayacağımız anlamına geliyordu.

Deterministik bir oyun ancak aynı şekilde derlenmiş dosyaları kullanırken ve IEEE standartlarına uygun sistemler üzerinde çalışırken belirleyici olacaktır. Çapraz platform senkronize ağ simülasyonları veya tekrarlamaları mümkün olmayacaktır.


2
Bahsettiğim gibi, öncelikle C # ile ilgileniyorum; JIT fiili derlemeyi yaptığından, aynı makinede aynı çalıştırılabilir çalıştırırken bile "aynı optimizasyon ayarlarıyla derlendiğini" garanti edemiyorum !
BlueRaja - Danny Pflughoeft

2
Bazen yuvarlama modu fpu'da farklı bir şekilde ayarlanmış, bazı mimarilerde fpu hesaplamalar için hassasiyetten daha büyük bir dahili sicile sahipken ... IEEE754 FP işlemlerinin çalışması konusunda boşluk bırakmıyor, değil t Mimari açıdan farklılıkları ortaya çıkarabilecek herhangi bir matematiksel fonksiyonun nasıl uygulanması gerektiğini belirtir.
Stephen,

4
@ 3nixios Bu tür bir kurulumla oyun belirleyici değildir. Sadece sabit bir zaman aşımına sahip oyunlar belirleyici olabilir. Tek bir makinede bir simülasyonu geri çalıştırırken zaten belirleyici olmayan bir şeyi savunuyorsunuz.
AttackingHobo

4
@ 3nixios, "Sabit bir zaman çizelgesinde bile hiçbir şeyin zaman çizelgesinin her zaman sabit kalmasını sağlayacağını belirtmemiştim." Tamamen yanılıyorsun. Sabit bir timestep noktası her zaman güncellenmesi her kene için aynı delta zaman var olmaktır
AttackingHobo

3
@ 3nixios Sabit bir zaman çizelgesi kullanmak, zaman çizelgesinin sabitlenmesini sağlar, çünkü geliştiricilerin aksi izin vermediğini. Sabit bir zaman adımı kullanırken gecikmenin nasıl telafi edilmesi gerektiğini yanlış yorumluyorsunuz. Her oyun güncellemesi gerekir aynı güncelleme zamanı kullanın; bu nedenle, bir eş bağlantısı koptuğunda, kaçırdığı her güncellemeyi yine de hesaplaması gerekir.
Keeblebrox

3

Sabit nokta aritmetik kullanın. Veya yetkili bir sunucu seçin ve arada sırada oyun durumunu senkronize etmesini sağlayın - MMORTS böyle yapar. (En azından Savaş Elemanı böyle çalışır. C # da yazılmıştır.) Bu şekilde hataların birikme şansı yoktur.


Evet, bunu bir müşteri sunucusu yapabilir ve müşteri tarafı tahmini ve ekstrapolasyonunun baş ağrılarıyla başa çıkabilirim. Bu soru daha kolay olması gereken eşler arası mimarilerle ilgili ...
BlueRaja - Danny Pflughoeft

Eh, "tüm istemciler aynı kodu çalıştırın" senaryosunda tahmin yapmanıza gerek yoktur. Sunucunun rolü yalnızca eşitleme yetkisi olmaktır.
Nevermind

Sunucu / istemci ağa bağlı bir oyun yapmanın yalnızca bir yoludur. (Özellikle C ve C ya da Starcraft gibi örneğin. RTS oyunları için) başka popüler yöntem olup, orada eşler arası olan bir bir yetkili sunucusu. Bunun mümkün olması için , tüm hesaplamalar tüm müşteriler arasında tamamen belirleyici ve tutarlı olmalıdır - bu yüzden sorumu.
BlueRaja - Danny Pflughoeft

Tamamen kesinlikle p2p'yi hiçbir sunucu / yükseltilmiş istemciyle yapma şansınız yok. Kesinlikle yapmak zorundaysanız - sonra sabit nokta kullanın.
Nevermind

3

Düzenleme: Sabit puan sınıfına bir bağlantı (Alıcı dikkat! - Ben daha önce kullanmadım ...)

Her zaman sabit nokta aritmetik geri düşebiliriz . Birisi (bir rts yapan, daha az olmayan) bacağını stackoverflow üzerinde gerçekleştirdi .

Bir performans cezası ödeyeceksiniz, ancak bu bir sorun olabilir veya olmayabilir, çünkü .net özellikle basit talimatlar kullanmayacağından dolayı performans göstermeyecektir. Benchmark!

NB Anlaşılan birileri intel Eğer c # dan istihbarat performans ilkel kütüphane kullanmasına izin vermek bir çözüm gibi görünüyor. Bu, daha yavaş performansı telafi etmek için sabit nokta kodunun vektörleştirilmesine yardımcı olabilir.


decimalbunun için de iyi çalışırdı; Ancak, bu hala kullanmakla aynı problemleri var int- onunla nasıl tetiklerim?
BlueRaja - Danny Pflughoeft

1
En kolay yol arama tablosu oluşturmak ve uygulamanın yanına göndermek. Kesinlik bir sorunsa değer arasında enterpolasyon yapabilirsiniz.
LukeN

Alternatif olarak, trig aramalarını hayali numaralarla veya kuaterniyonlarla değiştirebilirsiniz (3B ise). Bu mükemmel bir açıklama
LukeN 13:11

Bu da yardımcı olabilir.
LukeN

Eskiden çalıştığım şirkette, sabit puanlı matematiği kullanan bir ultrason makinesi yaptık, bu yüzden kesinlikle sizin için işe yarayacak.
LukeN

3

Birkaç büyük başlık üzerinde çalıştım.

Kısa cevap: Delicesine titizseniz, muhtemelen buna değmezseniz mümkündür. Sabit bir mimaride (okuma: konsol) olmadıkça titiz, kırılgan ve geç katılım gibi ikincil problemlerle birlikte gelir.

Belirtilen makalelerden bazılarını okursanız, CPU modunu ayarlayabildiğiniz sırada, bir yazıcı sürücüsünün CPU modunu değiştirdiği zamanlar gibi, aynı adres alanında olduğu gibi tuhaf durumlar olduğunu unutmayın. Bir uygulamanın harici bir cihaza çerçeveli olarak kilitlendiği bir durum vardı, ancak hatalı bir bileşen işlemcinin sabah ve öğleden sonra farklı bir makineye dönüşmesi nedeniyle sabah ve öğleden sonra farklı şekilde raporlanmasına ve rapor vermesine neden oldu.

Bu platform farklılıkları yazılımda değil silikondadır, bu nedenle C # sorunuzu yanıtlamak da etkilenir. Sigortalı çoklu toplama (FMA) - ADD + MUL gibi talimatlar sonucu değiştirir, çünkü dahili olarak iki kez yerine sadece bir kez yuvarlanır. C, işlemcileri “standart” hale getirmek için FMA gibi işlemleri hariç tutup temelde istediğinizi yapmaya zorlamak için daha fazla kontrol sağlar - ancak hız pahasına. İçsel farklılıklar en eğilimli gibi görünüyor. Bir projede, hangi işlemcinin "doğru" olduğunu belirlemek için değerler elde etmek için 150 yıllık bir acos tablo kitabı kazmam gerekiyordu. Çoğu CPU, trig fonksiyonları için her zaman aynı katsayılarla değil, polinom yaklaşımlarını kullanır.

Benim önerim:

Tamsayı kilitleme adımı veya senkronizasyonu yapmanıza bakılmaksızın, çekirdek oyun mekaniğini sunumdan ayrı olarak ele alın. Oyunun doğru oynamasını sağlayın, ancak sunum katmanındaki doğruluk konusunda endişelenmeyin. Ayrıca, ağa bağlı tüm dünya verilerini aynı kare hızında göndermeniz gerekmediğini unutmayın. Mesajlarınıza öncelik verebilirsiniz. Simülasyonunuz% 99,999 eşleşirse, eşleşmek için sık sık iletmeniz gerekmez. (Hile önleme bir yana.)

Source motoru hakkında senkronizasyona gitmenin bir yolunu anlatan güzel bir makale var: https://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking

Unutmayın, geç katılmakla ilgileniyorsanız, mermiyi ısırmanız ve yine de eşitlemeniz gerekir.


1

Genel olarak, söyleyebildiğim kadarıyla bu konuda C ++ ile tamamen aynı sorunları yaşayan C # ile ilgilendiğime dikkat edin.

Evet, C #, C ++ ile aynı sorunları yaşıyor. Ama aynı zamanda çok daha fazlası var.

Örneğin, bu açıklamayı Shawn Hawgraves'tan alın:

Tekrarları kontrolör girişi olarak saklarsanız, farklı CPU mimarilerine, derleyicilere veya optimizasyon ayarlarına sahip makinelerde oynatılamazlar.

Bunun C ++ 'da olmasını sağlamak "yeterince kolaydır". In C # , ancak, bununla başa çıkmak çok daha zor olacak. Bu JIT'ye teşekkürler.

Tercüman, kodunuzu bir kez yorumlandığında koyarsa ne olacağını düşünüyorsunuz, ama sonra ikinci kez JIT yaptı mı? Ya da belki başka birinin makinesinde iki kez yorum yapar, ama JIT bundan sonra mı?

JIT deterministik değildir, çünkü onun üzerinde çok az kontrolünüz var. Bu, CLR'yi kullanmaktan vazgeçtiğiniz şeylerden biridir.

Ve eğer bir kişi oyununuzu çalıştırmak için .NET 4.0 kullanıyorsa, bir başkası Mono CLR (elbette. NET kütüphanelerini kullanarak) kullanıyorsa, Tanrı size yardım eder. .NET 4.0 ve .NET 5.0 bile farklı olabilir. Bu tür bir şeyi garanti altına almak için platformun düşük seviyeli detayları üzerinde daha fazla kontrole ihtiyacınız var .

Sabit noktalı matematikten kurtulabilmelisin. Ama bu konuda.


Matematik dışında, başka ne farklı olabilir? Muhtemelen, "giriş" eylemleri bir rts mantıksal eylemler olarak gönderiliyor. Örn: "a" ünitesini "b" konumuna getirin. Rasgele sayı üretme ile ilgili bir problem görebiliyorum, ancak bunun açıkça bir simülasyon girişi olarak gönderilmesi gerekiyor.
Luken

@LukeN: Sorun şu ki, Shawn'ın pozisyonu, derleyici farklılıklarının kayan nokta matematiği üzerinde gerçek etkileri olabileceğini şiddetle tavsiye ediyor. Benden daha fazlasını biliyordu; Ben sadece onun uyarısını JIT ve C # derleme / yorumlama dünyasına aktarıyorum.
Nicol Bolas,

C # operatörün aşırı yüklenmesine sahiptir ve ayrıca sabit nokta matematik için yerleşik bir türe sahiptir . Bununla birlikte, bunun bir problem kullanmasıyla aynı problemleri var int- onunla nasıl tetiklenirim?
BlueRaja - Danny Pflughoeft

1
> Tercüman kodunuzu çalıştırdıysa> bir kez yorumlandığında ne olacağını düşünüyorsunuz, peki ikinci kez JIT yaptınız mı? Veya belki de> başka birinin makinesinde iki kez yorumluyor, ama JIT bundan sonra mı? 1. CLR kodu yürütmeden önce her zaman atlanır. 2. .net tabii ki yüzmek için IEEE 754 kullanıyor. > JIT deterministik değildir, çünkü onun üzerinde çok az kontrole sahipsin. Sonuç, yanlış yanlış ifadelerin oldukça zayıf bir nedenidir. > Ve eğer bir kişi oyununuzu çalıştırmak için .NET 4.0 kullanıyorsa,> bir başkası Mono CLR'yi kullanırken (> elbette. NET kütüphanelerini kullanarak). Hatta .NET
Romanenkov Andrey,

@Romanenkov: "Yanlış yanlış ifadelerin sonucu oldukça zayıf." Lütfen, hangi ifadelerin yanlış olduğunu ve neden düzeltilebileceklerini söyle.
Nicol Bolas,

1

Bu cevabı yazdıktan sonra, özellikle kayan nokta belirsizliğiyle ilgili olan soruyu aslında yanıtlamadığını fark ettim. Ama belki de bu şekilde ağ bağlantılı bir oyun yapacaksa, bu ona yardımcı olur.

Tüm oyunculara yayın yaptığınız giriş paylaşımının yanı sıra, oyuncu pozisyonları, sağlık vb. Gibi önemli bir oyun durumu sağlama toplamı oluşturmak ve yayınlamak çok yararlı olabilir. tüm uzak oyuncular senkronize durumda. Senkronize edilmemiş (OOS) hataların giderileceği garanti edilir ve bu daha kolay hale gelir - bir şeyin yanlış gittiğini önceden fark edeceksiniz (bu, çoğaltma adımlarını bulmanıza yardımcı olacak) ve daha fazlasını ekleyebilmeniz gerekir. OOS'a neyin yol açtığını bulmak için oyun durumu, şüpheli kodda oturum açıyor.


0

Bence blogdan gelen fikir hala geçerli olmak için periyodik senkronizasyona ihtiyaç duyuyor - ağ bağlantılı RTS oyunlarında bu yaklaşımı benimseyen yeterince hata gördüm.

Ağlar kayıp, yavaş, gecikme süresine sahip ve verilerinizi kirletebilir. “Kayan nokta determinizmi” (ki bu beni kuşku uyandıracak kadar soluk kuş gibi geliyor) gerçekte endişelerinizin en küçüğüdür… özellikle sabit bir zaman adımı kullanıyorsanız. değişken zaman adımlarında, determinizm sorunlarını da önlemek için sabit zaman adımlarının arasına girmeniz gerekecektir. Ben genellikle bunun deterministik olmayan "kayan nokta" davranışıyla kastedildiğini düşünüyorum - sadece değişken zaman adımlarının entegrasyonların farklılaşmasına neden olduğu - matematik kütüphaneleri veya düşük seviye fonksiyonlarla ilgisi yok.

Senkronizasyon olsa da anahtardır.


-2

Onları deterministik yapıyorsun. Harika bir örnek için, dünyadaki yerleri nasıl bağlanabilir hale getirdiğine dair Dungeon Siege GDC Sunumuna bakın .

Ayrıca determinizmin 'rastgele' olaylara da uygulandığını unutmayın!


1
Bu, OP'nin kayan nokta ile nasıl yapılacağını bilmek istediği şey.
Komünist Ördek
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.