浮動小数点の非決定論に直面して決定論的ゲームはどのように可能ですか?


52

ネットワーク化されたRTSのようなゲームを作成するために、ここでゲームを完全に決定論的にすることを提案する多くの回答を見ました。次に、ユーザーのアクションを相互に転送するだけで、次のフレームがレンダリングされる前にすべてのユーザーの入力を「ロック」するために、表示されているものを少し遅らせるだけで済みます。その場合、ユニットの位置、ヘルスなどのようなものは、ネットワーク上で常に更新される必要はありません。すべてのプレイヤーのシミュレーションはまったく同じだからです。また、リプレイを行うために提案された同じことを聞いたことがあります。

ただし、浮動小数点の計算はマシン間、または同じマシン上の同じプログラムの異なるコンパイル間でも非決定的であるため、これは本当に可能ですか?その事実が、ゲーム全体に波及するプレイヤー(またはリプレイ)間の小さな違いを引き起こすのをどのように防ぐのでしょうか?

一部の人々は、浮動小数点数を完全に避けint、分数の商を表すために使用することを提案することを聞いたことがありますが、それは私には実用的ではありません-たとえば、角度の余弦を取る必要がある場合はどうなりますか?数学ライブラリ全体を真剣に書き直す必要がありますか?

私は主にC#に興味があることに注意してください。私が知る限り、この点ではC ++とまったく同じ問題があります。


5
これはあなたの質問に対する答えではありませんが、RTSネットワークの問題に対処するために、ドリフトを修正するために、ユニットの位置と状態を時折同期することをお勧めします。どの情報を同期する必要があるかは、ゲームによって異なります。ステータス効果のようなものを同期することに煩わされることなく、帯域幅の使用を最適化できます。
11

@jhockingそれでも、おそらくそれが最良の答えです。
ジョナサンコネル

starcraft II infactiは常に正確に同期しますが、各コンピューターの友人とゲームプレイのリプレイを見ました。私はいくつかのユニットを持っていましたが、6/7のマップが自分のスクリーンに関して私の画面上で遠く離れている
-GameDeveloper

回答:


15

浮動小数点は決定的ですか?

数年前、あなたと同じロックステップアーキテクチャを使用してRTSを作成したかったときに、この問題について多くのことを読みました。

ハードウェア浮動小数点に関する私の結論は次のとおりです。

  • 浮動小数点フラグとコンパイラー設定に注意すれば、同じネイティブアセンブリコードが決定的である可能性が高くなります。
  • ラッパーライブラリを使用してさまざまなコンパイラで決定論的なC / C ++コンパイルを取得したと主張する1つのオープンソースRTSプロジェクトがありました。私はその主張を確認しませんでした。(正しく思い出せば、それはSTREFLOP図書館に関するものでした)
  • .net JITにはかなりの余裕があります。特に、必要以上に高い精度を使用することが許可されています。また、x86とAMD64で異なる命令セットを使用します(x86ではx87を使用すると思いますが、AMD64では動作がdenormsで異なるSSE命令を使用します)。
  • 複雑な命令(三角関数、指数、対数を含む)は特に問題があります。

私は、.netの組み込み浮動小数点型を決定論的に使用することは不可能であると結論付けました。

考えられる回避策

したがって、回避策が必要でした。私は考慮した:

  1. FixedPoint32C#で実装します。これはそれほど難しいことではありませんが(実装が半分終わっています)、値の範囲が非常に狭いため、使用するのが面倒です。オーバーフローや精度の低下を防ぐため、常に注意する必要があります。最終的に、整数を直接使用するよりも簡単ではないことがわかりました。
  2. FixedPoint64C#で実装します。これはかなり難しいと思いました。一部の操作では、128ビットの中間整数が有用です。しかし、.netはそのようなタイプを提供していません。
  3. 1つのプラットフォームで決定的である数学演算にネイティブコードを使用します。すべての数学演算でデリゲート呼び出しのオーバーヘッドを引き起こします。クロスプラットフォームを実行する機能を失います。
  4. を使用しDecimalます。ただし、処理速度が遅く、大量のメモリを消費し、例外を簡単にスローします(0による除算、オーバーフロー)。経済的な用途には非常に便利ですが、ゲームには適していません。
  5. カスタム32ビット浮動小数点を実装します。最初はかなり難しかった。BitScanReverse組み込み関数の欠如は、これを実装するときにいくつかの迷惑を引き起こします。

私のソフトフロート

StackOverflowの投稿に触発され、ソフトウェアに32ビット浮動小数点型を実装し始めたばかりで、結果は有望です。

  • メモリ表現はIEEE floatとバイナリ互換であるため、キャストをグラフィックコードに出力するときにキャストを再解釈できます。
  • SubNorm、無限大、NaNをサポートしています。
  • 正確な結果はIEEEの結果と同じではありませんが、通常はゲームには関係ありません。この種のコードでは、結果がすべてのユーザーで同じであることが重要であり、最後の桁まで正確であることは重要ではありません。
  • パフォーマンスはまともです。些細なテストでは、220-260MFLOPSをfloat加算/乗算(2.66GHz i3のシングルスレッド)と比較して、約75MFLOPSを実行できることが示されました。私の現在のテストは非常に初歩的なものなので、誰かが.netの優れた浮動小数点ベンチマークを持っているなら、それらを私に送ってください。
  • 丸めを改善できます。現在、切り捨てられますが、これはほぼゼロへの丸めに相当します。
  • まだ非常に不完全です。現在、除算、キャスト、および複雑な数学演算がありません。

誰かがテストに貢献したりコードを改善したい場合は、私に連絡するか、githubでプルリクエストを発行してください。https://github.com/CodesInChaos/SoftFloat

不確定性のその他の原因

.netには不確定性の他のソースもあります。

  • を反復するDictionary<TKey,TValue>HashSet<T>、要素を未定義の順序で返します。
  • object.GetHashCode() 実行ごとに異なります。
  • 組み込みRandomクラスの実装は指定されていません。独自のものを使用してください。
  • 単純ロックを使用したマルチスレッドは、並べ替えと異なる結果をもたらします。スレッドを正しく使用するように非常に注意してください。
  • ときWeakReferencesが失うGCはいつでも実行することができるので、彼らのターゲットが決定不能です。

23

この質問に対する答えは、投稿したリンクからです。具体的には、Gas Powered Gamesからの引用を読む必要があります。

私はGas Powered Gamesで働いており、浮動小数点演算は決定論的であることを直接伝えることができます。同じ命令セットとコンパイラが必要なだけで、もちろんユーザーのプロセッサはIEEE754標準に準拠しています。これには、PCと360のすべてのお客様が含まれます。DemiGod、Supreme Commander 1および2を実行するエンジンは、IEEE754標準に依存しています。おそらく市場にある他のすべてのRTSピアツーピアゲームは言うまでもないでしょう。

そして、その下にこれがあります:

リプレイをコントローラー入力として保存する場合、異なるCPUアーキテクチャー、コンパイラー、または最適化設定を備えたマシンで再生することはできません。MotoGPでは、これは保存されたリプレイをXboxとPC間で共有できないことを意味していました。

確定的なゲームは、同一にコンパイルされたファイルを使用し、IEEE規格に準拠したシステムで実行する場合にのみ確定的です。クロスプラットフォーム同期ネットワークシミュレーションまたはリプレイはできません。


2
前述したように、私は主にC#に興味があります。JITが実際のコンパイルを行うため、同じマシンで同じ実行可能ファイルを実行している場合でも、「同じ最適化設定でコンパイルされる」ことを保証できません。
BlueRaja-ダニーPflughoeft

2
時々、丸めモードはfpuで異なって設定されます。一部のアーキテクチャでは、fpuには計算用の精度よりも大きな内部レジスタがあります...特定の数学関数を実装する方法を指定します。これにより、アーキテクチャの違いが明らかになる場合があります。
スティーブン

4
@ 3nixiosそのようなセットアップでは、ゲームは決定論的ではありません。固定のタイムステップを持つゲームのみが決定論的です。単一のマシンでシミュレーションを実行するとき、すでに何かが決定論的ではないと主張しています。
AttackingHobo

4
@ 3nixios、「タイムステップが固定されていても、タイムステップが常に固定されていることを保証するものは何もないと述べていました。」あなたは完全に間違っています。固定タイムステップのポイントは常に更新の各目盛りに同じデルタ時間を持つことである
AttackingHobo

3
@ 3nixios固定タイムステップを使用すると、開発者は許可しないため、タイムステップが確実に修正されます。固定タイムステップを使用する場合、ラグをどのように補正する必要があるかを誤って解釈しています。各ゲームの更新は同じ更新時間を使用する必要があります。そのため、ピアの接続が遅れた場合、ピアが逃した個々の更新を計算する必要があります。
キーブルブロックス

3

固定小数点演算を使用します。または、権限のあるサーバーを選択して、時々ゲームの状態を同期させます-これがMMORTSの動作です。(少なくとも、Elements of Warはこのように機能します。C#でも記述されています。)このように、エラーが蓄積する可能性はありません。


はい、私はそれをクライアントサーバーにして、クライアント側の予測と外挿の頭痛に対処できました。この質問は、ピアツーピアアーキテクチャに関するもので、これはより簡単になるはずです ...
BlueRaja-ダニーPflughoeft

まあ、「すべてのクライアントが同じコードを実行する」シナリオで予測を行う必要はありません。サーバーの役割は、同期の権限のみです。
ネヴァーマインド

サーバー/クライアントは、ネットワークゲームを作成する1つの方法にすぎません。別の一般的な方法(特にC&CやStarcraftなどのRTSゲームなど)は、ピアツーピアで、権限のあるサーバーはありません。それを可能にするためには、すべての計算が完全に決定論的であり、すべてのクライアントで一貫している必要があります-したがって、私の質問です。
BlueRaja-ダニーPflughoeft

さて、サーバー/昇格クライアントなしでゲームを厳密にp2pにすることを絶対に持っているわけではありません。ただし、どうしても必要な場合は、固定小数点を使用してください。
ネヴァーマインド

3

編集:固定小数点クラスへのリンク(バイヤーは注意してください!-私はそれを使用していません...)

常に固定小数点演算に頼ることができます。だれか(rtsを作成します)は、すでにstackoverflowのレッグ作業を行っています。

パフォーマンスのペナルティはかかりますが、これは問題になる場合もあれば、そうでない場合もあります。これは、.netがsimd命令を使用しないため、ここでは特にパフォーマンスが良くないためです。基準!

NBどうやら、Intelの誰かが、c#のIntelパフォーマンスプリミティブライブラリを使用できるようにするソリューションを持っているようです。これは、パフォーマンスの低下を補うために固定小数点コードをベクトル化するのに役立ちます。


decimalその場合もうまくいくでしょう。しかし、これにはまだ使用と同じ問題がありますint-どのようにそれをトリガーしますか?
BlueRaja-ダニーPflughoeft

1
最も簡単な方法は、ルックアップテーブルを作成し、アプリケーションと一緒に送信することです。精度に問題がある場合は、値の間を補間できます。
ルーク

または、トリガー呼び出しを虚数または四元数(3Dの場合)に置き換えることができる場合があります。これは素晴らしい説明です
ルーク

これも役立ちます。
ルーク

私が働いていた会社では、固定小数点演算を使用して超音波装置を構築したので、間違いなく機能します。
ルーク

3

私はいくつかの大きなタイトルに取り組んできました。

簡単な答え:あなたがめちゃくちゃ厳しい場合は可能ですが、おそらく価値はありません。固定アーキテクチャ(コンソール:を参照)を使用している場合を除き、細かく壊れやすく、レイトジョインなどの二次的な問題が多数あります。

上記の記事を読むと、CPUモードを設定できますが、同じアドレス空間にあるためにプリンタードライバーがCPUモードを切り替えるなどの奇妙なケースがあることに注意してください。アプリケーションが外部デバイスにフレームロックされているケースがありましたが、コンポーネントが故障しているため、CPUが熱によりスロットルし、午前と午後でレポートが異なるため、昼食後は別のマシンになりました。

これらのプラットフォームの違いはソフトウェアではなくシリコンにあるため、質問に答えるためにC#も影響を受けます。融合乗算加算(FMA)対ADD + MULなどの命令は、内部的に2回ではなく1回だけ丸めるので、結果を変更します。Cを使用すると、FMAなどの操作を除外して速度を犠牲にして「標準」にすることで、基本的に必要な処理をCPUに強制するための制御が強化されます。組み込み関数は、最も異なる傾向があるようです。あるプロジェクトでは、150年前のacosテーブルの本を掘り下げて、比較用の値を取得して、どのCPUが「正しい」かを判断する必要がありました。多くのCPUは、トリガー関数に多項式近似を使用しますが、常に同じ係数ではありません。

私の推薦:

整数ロックステップまたは同期のどちらを使用するかに関係なく、プレゼンテーションとは別にコアゲームの仕組みを処理します。ゲームのプレイを正確にしますが、プレゼンテーションレイヤーの正確さについて心配する必要はありません。また、ネットワーク化されたすべてのワールドデータを同じフレームレートで送信する必要はありません。メッセージに優先順位を付けることができます。シミュレーションが99.999%一致している場合、ペアを維持するために頻繁に送信する必要はありません。(チート防止は別として。)

同期についての1つの方法を説明するソースエンジンに関する素晴らしい記事がありますhttps : //developer.valvesoftware.com/wiki/Source_Multiplayer_Networking

遅延結合に興味がある場合は、とにかく弾丸を噛んで同期する必要があることを忘れないでください。


1

私は主にC#に興味があることに注意してください。私が知る限り、この点ではC ++とまったく同じ問題があります。

はい、C#にはC ++と同じ問題があります。しかし、それはもっとたくさんあります。

たとえば、Shawn Hawgravesの次のステートメントをご覧ください。

リプレイをコントローラー入力として保存する場合、異なるCPUアーキテクチャー、コンパイラー、または最適化設定を備えたマシンで再生することはできません。

これがC ++で確実に行われるようにするのは「十分に簡単」です。ではC# 、しかし、それは多くの困難に対処することになるだろう。これはJITのおかげです。

インタプリタがコードを1回解釈して実行した後、2回目にJITした場合はどうなると思いますか?または、他の誰かのマシンで2回解釈しますが、その後JITで解釈しますか?

JITを制御できないため、JITは決定論的ではありません。これは、CLRの使用をあきらめることの1つです。

1人が.NET 4.0を使用してゲームを実行し、他の誰かがMono CLRを使用している場合(もちろん.NETライブラリを使用している場合)、神はあなたを助けます。.NET 4.0と.NET 5.0でも異なる場合があります。この種のことを保証するには、プラットフォームの低レベルの詳細をさらに制御する必要があります。

固定小数点演算で逃げることができるはずです。しかし、それはそれについてです。


数学は別として、他に何が違うのでしょうか?おそらく、「入力」アクションは、rtsの論理アクションとして送信されています。たとえば、ユニット「a」を位置「b」に移動します。乱数生成に問題があることはわかりますが、それは明らかにシミュレーション入力としても送信する必要があります。
ルーク

@LukeN:問題は、コンパイラの違いが浮動小数点演算に実際の影響を与える可能性があることをショーンの立場が強く示唆していることです。彼は私よりも多くを知っているだろう。私は単に彼の警告をJITとC#のコンパイル/解釈の領域に外挿しています。
ニコルボラス

C#には演算子のオーバーロードがあり、固定小数点演算用の組み込み型もあります。ただし、これにはanを使用した場合と同じ問題がありintます。どうすればトリガーを使用できますか?
BlueRaja-ダニーPflughoeft

1
>インタープリターがコードを1回実行し、その後2回JITした場合、どうなると思いますか?それとも>他の誰かのマシンで2回解釈しますが、その後はJITで解釈しますか?1. CLRコードは、実行前に常にジッターされます。2. .netは、もちろんフロートにIEEE 754を使用します。> JITは決定論的ではありません。JITを制御することはほとんどできないからです。あなたの結論は、偽の虚偽の陳述の非常に弱い原因です。>そして、ある人が.NET 4.0を使用してゲームを実行し、他の人がMono CLRを使用している場合(もちろん.NETライブラリを使用している場合)、神はあなたを助けます。Even .NET
ロマネンコフアンドレイ

@ロマネンコフ:「あなたの結論は、偽りの虚偽の陳述の非常に弱い原因です。」どのステートメントが間違っているのか、そしてその理由を教えてください。そうすれば修正できます。
ニコルボラス

1

この答えを書いた後、実際には質問に答えていないことに気付きました。これは特に浮動小数点非決定性に関するものでした。しかし、この方法でネットワーク化されたゲームを作成するのであれば、とにかくこれは彼に役立つでしょう。

すべてのプレーヤーにブロードキャストしている入力の共有に加えて、プレーヤーの位置、健康などの重要なゲーム状態のチェックサムを作成してブロードキャストすると非常に便利です。入力を処理するとき、ゲーム状態のチェックサムがすべてのリモートプレーヤーは同期しています。修正する同期(OOS)バグがあることが保証されており、これにより簡単になります-何かが間違っているという事前の通知があり(これにより、再生手順がわかりやすくなります)、さらに追加できるはずです疑わしいコードでのゲーム状態のロギングにより、OOSの原因となっているものをすべてまとめることができます。


0

リンクされているブログのアイデアを実行するには、まだ定期的な同期が必要だと思います-ネットワークRTSゲームで、そのアプローチをとらない十分なバグを見てきました。

ネットワークは損失が多く、遅く、待ち時間があり、データを汚染する可能性さえあります。「浮動小数点の決定論」(これは私を懐疑的にさせるほど不器用なように聞こえます)は、実際にはあなたの心配の最小です...特に固定時間ステップを使用する場合。可変時間ステップでは、確定時間の問題も回避するために、固定時間ステップ間を補間する必要があります。これは通常、非決定的な「浮動小数点」動作が意味するものだと思います-可変時間ステップが積分を発散させるだけで、数学ライブラリや低レベル関数とは関係ありません。

ただし、同期が重要です。


-2

それらを確定的にします。すばらしい例については、Dungeon Siege GDCプレゼンテーションを参照して、世界中の場所をネットワーク化した方法を確認してください。

また、決定論は「ランダム」イベントにも適用されることを忘れないでください!


1
これは、OPが浮動小数点を使用して行う方法を知りたいものです。
共産主義のカモ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.