Untuk membuat game seperti jaringan RTS, saya telah melihat sejumlah jawaban di sini menyarankan untuk membuat game sepenuhnya deterministik; maka Anda hanya perlu mentransfer tindakan pengguna satu sama lain, dan ketinggalan apa yang ditampilkan sedikit untuk "mengunci" input semua orang sebelum bingkai berikutnya diberikan. Maka hal-hal seperti posisi unit, kesehatan, dll. Tidak perlu terus diperbarui melalui jaringan, karena simulasi setiap pemain akan persis sama. Saya juga mendengar hal yang sama yang disarankan untuk membuat replay.
Namun, karena perhitungan floating-point adalah non-deterministik antar mesin, atau bahkan antara kompilasi berbeda dari program yang sama pada mesin yang sama, apakah ini benar-benar mungkin dilakukan? Bagaimana kita mencegah fakta itu menyebabkan perbedaan kecil antara pemain (atau tayangan ulang) yang bergejolak di sepanjang permainan ?
Saya pernah mendengar beberapa orang menyarankan menghindari angka floating-point sama sekali dan menggunakan int
untuk mewakili hasil bagi sebagian, tetapi itu tidak terdengar praktis bagi saya - bagaimana jika saya perlu, misalnya, mengambil kosinus sudut? Apakah saya serius perlu menulis ulang seluruh perpustakaan matematika?
Perhatikan bahwa saya terutama tertarik pada C #, yang sejauh yang saya tahu, memiliki masalah yang sama persis seperti C ++ dalam hal ini.
sumber
Jawaban:
Apakah floating-point deterministik?
Saya melakukan banyak membaca tentang masalah ini beberapa tahun yang lalu ketika saya ingin menulis RTS menggunakan arsitektur berbaris yang sama yang Anda lakukan.
Kesimpulan saya tentang floating-point perangkat keras adalah:
STREFLOP
perpustakaan)Saya menyimpulkan bahwa tidak mungkin untuk menggunakan tipe floating in built in .net secara deterministik.
Kemungkinan Solusi
Karena itu saya membutuhkan penyelesaian. Saya mempertimbangkan:
FixedPoint32
dalam C #. Meskipun ini tidak terlalu sulit (saya memiliki implementasi setengah jadi) rentang nilai yang sangat kecil membuatnya mengganggu untuk digunakan. Anda harus berhati-hati setiap saat sehingga Anda tidak meluap, atau kehilangan terlalu banyak presisi. Pada akhirnya saya menemukan ini tidak lebih mudah daripada menggunakan bilangan bulat secara langsung.FixedPoint64
dalam C #. Saya menemukan ini agak sulit dilakukan. Untuk beberapa operasi, bilangan bulat menengah 128bit akan bermanfaat. Tapi .net tidak menawarkan tipe seperti itu.Decimal
. Tetapi lambat, membutuhkan banyak memori dan dengan mudah melempar pengecualian (pembagian 0, meluap). Ini sangat bagus untuk penggunaan finansial, tetapi tidak cocok untuk game.SoftFloat saya
Terinspirasi oleh posting Anda di StackOverflow , saya baru saja mulai menerapkan tipe floating-point 32 bit dalam perangkat lunak dan hasilnya menjanjikan.
float
penambahan / perkalian (Utas tunggal pada i66 2,66GHz ). Jika ada yang memiliki tolok ukur floating point yang baik untuk .net, silakan kirim ke saya, karena tes saya saat ini sangat sederhana.Jika ada yang ingin berkontribusi tes atau meningkatkan kode, cukup hubungi saya, atau mengeluarkan permintaan tarik di github. https://github.com/CodesInChaos/SoftFloat
Sumber ketidakpastian lainnya
Ada juga sumber ketidakpastian lainnya di .net.
Dictionary<TKey,TValue>
atauHashSet<T>
mengembalikan elemen dalam urutan yang tidak ditentukan.object.GetHashCode()
berbeda dari menjalankan ke menjalankan.Random
kelas bawaan tidak ditentukan, gunakan milik Anda sendiri.WeakReference
kehilangan target mereka tidak pasti karena GC dapat berjalan kapan saja.sumber
Jawaban untuk pertanyaan ini berasal dari tautan yang Anda poskan. Khususnya Anda harus membaca kutipan dari Game Bertenaga Gas:
Dan kemudian di bawah yang itu adalah ini:
Permainan deterministik hanya akan bersifat deterministik saat menggunakan file yang dikompilasi secara identik dan berjalan pada sistem yang mematuhi standar IEEE. Simulasi atau replay jaringan yang disinkronkan lintas platform tidak akan mungkin.
sumber
Gunakan aritmatika titik tetap. Atau pilih server yang otoritatif dan sesekali sinkronkan status permainan - itulah yang dilakukan MMORTS. (Setidaknya, Elements of War bekerja seperti ini. Itu ditulis dalam C # juga.) Dengan cara ini, kesalahan tidak memiliki kesempatan untuk menumpuk.
sumber
Sunting: Tautan ke kelas titik tetap (Pembeli Waspadalah! - Saya belum menggunakannya ...)
Anda selalu bisa kembali ke aritmatika titik tetap . Seseorang (membuat rts, tidak kurang) telah melakukan pekerjaan kaki pada stackoverflow .
Anda akan membayar penalti kinerja, tetapi ini mungkin atau mungkin tidak menjadi masalah, karena .net tidak akan terutama tampil di sini karena tidak akan menggunakan instruksi simd. Tolok Ukur!
NB Rupanya seseorang di intel tampaknya memiliki solusi untuk memungkinkan Anda menggunakan pustaka primitif kinerja intel dari c #. Ini dapat membantu membuat vektor kode titik tetap untuk mengimbangi kinerja yang lebih lambat.
sumber
decimal
akan bekerja dengan baik untuk itu juga; tetapi, ini masih memiliki masalah yang sama dengan menggunakanint
- bagaimana cara saya memicu dengannya?Saya telah mengerjakan sejumlah judul besar.
Jawaban singkat: Mungkin saja jika Anda sangat keras, tetapi mungkin tidak sepadan. Kecuali jika Anda menggunakan arsitektur tetap (baca: konsol) itu rewel, rapuh dan dilengkapi dengan sejumlah masalah sekunder seperti terlambat bergabung.
Jika Anda membaca beberapa artikel yang disebutkan, Anda akan mencatat bahwa sementara Anda dapat mengatur mode CPU ada beberapa kasus aneh seperti ketika driver cetak mengganti mode CPU karena berada di ruang alamat yang sama. Saya punya kasus di mana aplikasi dikunci bingkai ke perangkat eksternal, tetapi komponen yang salah menyebabkan CPU melambat karena panas dan melaporkan secara berbeda di pagi dan sore hari, membuatnya berlaku mesin yang berbeda setelah makan siang.
Perbedaan platform ini ada pada silikon, bukan perangkat lunak, jadi untuk menjawab pertanyaan Anda C # juga terpengaruh. Instruksi seperti gabungan multiply-add (FMA) vs ADD + MUL mengubah hasilnya karena hanya dibulatkan secara internal satu kali, bukan dua kali. C memberi Anda lebih banyak kontrol untuk memaksa CPU melakukan apa yang Anda inginkan pada dasarnya dengan mengecualikan operasi seperti FMA untuk menjadikan hal-hal "standar" - tetapi dengan mengorbankan kecepatan. Secara intrinsik tampaknya paling cenderung berbeda. Pada satu proyek saya harus menggali buku acos tables berusia 150 tahun untuk mendapatkan nilai perbandingan untuk menentukan CPU mana yang "benar". Banyak CPU menggunakan perkiraan polinomial untuk fungsi trigonometri tetapi tidak selalu dengan koefisien yang sama.
Rekomendasi saya:
Terlepas dari apakah Anda melakukan langkah-kunci integer atau menyinkronkan, menangani mekanik game inti secara terpisah dari presentasi. Jadikan permainan ini akurat, tetapi jangan khawatir tentang akurasi di lapisan presentasi. Juga ingat Anda tidak perlu mengirim semua data dunia jaringan pada kecepatan bingkai yang sama. Anda dapat memprioritaskan pesan Anda. Jika simulasi Anda 99,999% cocok, Anda tidak perlu mengirim sesering untuk tetap berpasangan. (Mengesampingkan pencegahan.)
Ada artikel yang bagus tentang mesin Sumber yang menjelaskan satu cara untuk pergi tentang sinkronisasi: https://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking
Ingat, jika Anda tertarik untuk terlambat bergabung, Anda harus menggigit peluru dan tetap menyinkronkan.
sumber
Ya, C # memiliki masalah yang sama dengan C ++. Tetapi juga memiliki lebih banyak.
Misalnya, ambil pernyataan ini dari Shawn Hawgraves:
"Cukup mudah" untuk memastikan bahwa ini terjadi di C ++. Namun dalam C # , itu akan menjadi jauh lebih sulit untuk dihadapi. Ini berkat JIT.
Menurut Anda apa yang akan terjadi jika penerjemah menjalankan kode Anda ditafsirkan sekali, tetapi kemudian JIT melakukannya untuk yang kedua kalinya? Atau mungkin itu menafsirkannya dua kali pada mesin orang lain, tetapi JIT setelah itu?
JIT tidak deterministik, karena Anda memiliki sedikit kendali atas itu. Ini adalah salah satu hal yang Anda menyerah untuk menggunakan CLR.
Dan Tuhan membantu Anda jika satu orang menggunakan .NET 4.0 untuk menjalankan permainan Anda, sementara orang lain menggunakan Mono CLR (menggunakan perpustakaan .NET tentu saja). Bahkan .NET 4.0 vs. .NET 5.0 dapat berbeda. Anda hanya perlu lebih banyak kontrol atas detail level rendah dari platform untuk menjamin hal semacam ini.
Anda harus bisa lolos dengan matematika fixed-point. Tapi itu saja.
sumber
int
- bagaimana saya melakukan trigonometri dengan itu?Saya menyadari setelah menulis jawaban ini bahwa itu sebenarnya tidak menjawab pertanyaan, yang secara khusus tentang nondeterminisme floating-point. Tapi mungkin ini berguna baginya jika dia akan membuat game berjaringan dengan cara ini.
Bersamaan dengan berbagi input yang Anda siarkan kepada semua pemain, akan sangat berguna untuk membuat dan menyiarkan checksum dari status permainan penting, seperti posisi pemain, kesehatan, dll. Saat memproses input, nyatakan bahwa checksum status permainan untuk semua pemain jarak jauh dalam sinkronisasi. Anda dijamin tidak memiliki sinkronisasi (OOS) bug untuk diperbaiki dan ini akan membuatnya lebih mudah - Anda akan memiliki pemberitahuan sebelumnya bahwa ada yang tidak beres (yang akan membantu Anda mengetahui langkah-langkah reproduksi), dan Anda harus dapat menambahkan lebih banyak status permainan masuk dalam kode yang dicurigai untuk memungkinkan Anda untuk menandai apa pun yang menyebabkan OOS.
sumber
Saya pikir ide dari blog yang ditautkan masih memerlukan sinkronisasi berkala agar dapat hidup - saya telah melihat cukup banyak bug di game RTS jaringan yang tidak menggunakan pendekatan itu.
Jaringan bersifat lossy, lambat, latensi, dan bahkan mungkin mencemari data Anda. "Floating point determinism", (yang terdengar buzzwordy cukup membuat saya skeptis) adalah yang paling tidak Anda khawatirkan dalam kenyataan ... khususnya jika Anda menggunakan langkah waktu yang tetap. dengan langkah waktu variabel Anda perlu interpolasi antara langkah waktu tetap untuk menghindari masalah determinisme juga. Saya pikir ini biasanya apa yang dimaksud dengan perilaku "floating point" non-deterministik - hanya saja langkah-langkah waktu variabel menyebabkan integrasi berbeda - tidak ada hubungannya dengan perpustakaan matematika atau fungsi tingkat rendah.
Sinkronisasi adalah kuncinya.
sumber
Anda membuatnya deterministik. Untuk contoh yang bagus, lihat Dungeon Siege GDC Presentation tentang bagaimana mereka membuat lokasi di dunia menjadi jaringan.
Juga ingat bahwa determinisme juga berlaku untuk peristiwa 'acak'!
sumber