ホットスワップ可能なC ++モジュールを実装するにはどうすればよいですか?


39

速い反復時間はゲームを開発するための鍵であり、私の意見では、多くの機能を備えた派手なグラフィックスやエンジンよりもはるかに重要です。多くの小規模な開発者がスクリプト言語を選択するのも当然です。

ゲームを一時停止し、アセットとコードを変更し、続行して変更をすぐに有効にすることができるUnity 3Dの方法は、このために絶対に素晴らしいです。私の質問は、C ++ゲームエンジンに同様のシステムを実装している人はいますか?

いくつかの非常にハイエンドのエンジンが行うことを聞いたことがありますが、私は自家製のエンジンやゲームでそれを行う方法があるかどうかを調べることにもっと興味があります。

明らかにトレードオフがあり、ゲームが一時停止している間にコードを再コンパイルし、すべての状況またはすべてのプラットフォームでリロードして動作させることは想像できません。

しかし、おそらくAIプログラミングと単純なレベルのロジックモジュールが可能です。短期(または長期)のプロジェクトでそれをやりたいというわけではありませんが、興味がありました。

回答:


26

通常のC ++コードではできませんが、間違いなく実行できます。実行時に動的にリンクおよびリロードできるCスタイルライブラリを作成する必要があります。これを可能にするには、ライブラリは、リロード時にライブラリに提供できる不透明なポインタ内にすべての状態を含まなければなりません。

Timothy Farrarが彼のアプローチについて説明します。

「開発のために、コードはライブラリとしてコンパイルされ、小さなローダープログラムがライブラリのコピーを作成し、ライブラリコピーをロードしてプログラムを実行します。プログラムは起動時にすべてのデータを割り当て、単一のポインターを使用してデータを参照します。読み取り専用データ以外のグローバルは使用しません。プログラムの実行中に元のライブラリを再コンパイルできます。その後、キーを1回押すと、プログラムがローダーに戻り、その単一のデータポインターを返します。ローダーは新しいライブラリのコピーを作成し、コピーをロードし、データポインタを新しいコードに渡します。その後、エンジンは中断したところから続行します。」元のソースWebアーカイブから入手可能)


あなたが実際に質問に答えるので、私はこれをブレアの答えよりも好みます。
ジョナサンディキンソン

15

ホットスワップは、バイナリコードで解決するのが非常に難しい問題です。コードが個別のダイナミックリンクライブラリに配置されている場合でも、コードではメモリ内の関数への直接参照がないことを確認する必要があります(関数のアドレスは再コンパイルによって変更される可能性があるため)。これは基本的に、仮想関数を使用しないことを意味し、関数ポインターを使用するものはなんらかのディスパッチテーブルを介して使用します。

毛むくじゃらの、抑制的なもの。高速な反復が必要なコードにはスクリプト言語を使用することをお勧めします。

職場では、現在のコードベースはC ++とLuaの混合です。Luaも私たちのプロジェクトの小さな部分ではありません-それはほぼ50/50の分割です。Luaのオンザフライリロードを実装しました。これにより、コードの行を変更し、リロードして続行できます。実際、ゲームを再起動せずにLuaコードで発生するクラッシュバグを修正するためにこれを行うことができます!


3
もう1つの重要なポイントは、システムをスクリプトに保持するつもりがない場合でも、早い段階で反復する場合は、Luaで開始し、必要なときにC ++に移行することは完全に合理的であることです。余分なパフォーマンスと反復率が低下しています。ここでの明らかな欠点は、コードを移植しなければならないことですが、多くの場合、ロジックを正しくすることは難しい部分です。
ローガンキンケイド

15

(ユーモラスな精神的イメージ以外の何ものでもない場合は、「サルのパッチング」または「アヒルのパンチ」という用語について知りたいかもしれません。)

それはさておき、もしあなたの目標が「振る舞い」の変更のための反復時間を減らすことであるなら、そこにあなたを最大限に導くいくつかのアプローチを試してください。

(これは少し接線上に出ますが、私はそれが戻ると約束します!)

  • dataから始めて小さく始めます:境界(「レベル」など)でリロードしてから、OS機能を使用してファイル変更通知を取得するか、定期的にポーリングします。
  • (ボーナスポイントとより短いロード時間(繰り返し時間の減少)については、データベイキングを調べてください。)
  • スクリプトはデータであり、動作を繰り返すことができます。スクリプト言語を使用する場合、通知またはコンパイルされたスクリプトをリロードする機能があります。インタープリターをゲーム内のコンソール、ネットワークソケットなどにフックして、実行時の柔軟性を高めることもできます。
  • コードもデータである可能性があります。コンパイラは、オーバーレイ、共有ライブラリ、DLLなどをサポートします。そのため、手動または自動に関係なく、オーバーレイまたはDLLをアンロードおよび再ロードする「安全な」時間を選択できるようになりました。他の回答については、ここで詳しく説明します。これのいくつかの変形は、暗号署名の検証、NX(no-execute)ビット、または同様のセキュリティメカニズムを台無しにする可能性があることに注意してください。
  • 深い、バージョン管理された保存/読み込みシステムを検討してください。コードが変更された場合でも、状態を堅牢に保存および復元できる場合は、ゲームをシャットダウンして、まったく同じ時点で新しいロジックでゲームを再起動できます。言うよりも簡単ですが、それは実行可能です。また、命令を変更するためにメモリを突くよりも著しく簡単でポータブルです。
  • ゲームの構造と決定性によっては、記録と再生ができる場合があります。その記録が「ゲームコマンド」のすぐ上にある場合(カードゲームなど)、必要なレンダリングコードをすべて変更し、記録を再生して変更を確認できます。一部のゲームでは、これはいくつかの開始パラメーター(ランダムシードなど)を記録してからユーザーアクションを記録するのと同じくらい「簡単」です。一部の人にとってははるかに複雑です。
  • コンパイル時間短縮する努力をしてください。前述の保存/ロードまたは記録/再生システムと組み合わせて、またはオーバーレイやDLLと組み合わせても、これは他のどの製品よりもターンアラウンドを減少させる可能性があります。

これらのポイントの多くは、データまたはコードをリロードする方法がすべてわからなくても有益です。

逸話のサポート:

大規模なPC RTS(〜120人のチーム、ほとんどがC ++)には、少なくとも3つの目的で使用される非常に深い状態保存システムがありました。

  • 「浅い」保存がディスクではなくCRCエンジンに送られ、マルチプレイヤーゲームが10〜30フレームごとに1 CRCのロックステップシミュレーションを確実に維持できるようにしました。これにより、誰も不正行為を行わず、数フレーム後に非同期バグをキャッチしました。
  • マルチプレーヤーの同期解除バグが発生した場合、フレームごとに余分な深さの保存が実行され、CRCエンジンに再び供給されましたが、今回はCRCエンジンがバイトの小さいバッチごとに多くのCRCを生成します。このようにして、最後のフレーム内で状態のどの部分が発散し始めたかを正確に知ることができます。これを使用して、AMDプロセッサとIntelプロセッサの間に「デフォルトの浮動小数点モード」という厄介な違いを見つけました。
  • 通常の深度保存では、ユニットが再生していたアニメーションの正確なフレームは保存されないかもしれませんが、すべてのユニットの位置、ヘルスなどが取得され、ゲームプレイ中いつでも保存および再開できます。

それ以来、DSのC ++およびLuaカードゲームで確定的な記録/再生を使用しました。AI用に設計したAPI(C ++側)にフックし、ユーザーとAIのすべてのアクションを記録しました。ゲームでこの機能を使用して(プレーヤーにリプレイを提供するため)、問題を診断するために:クラッシュまたは奇妙な動作があった場合、保存ファイルを取得してデバッグビルドで再生するだけでした。

それ以来、オーバーレイを数回以上使用し、「このディレクトリを自動的にスパイダーし、新しいコンテンツをハンドヘルドにアップロードする」システムと組み合わせました。私たちがしなければならないのは、カットシーン/レベル/何でも残して戻ってくることです。新しいデータ(スプライト、レベルレイアウトなど)だけでなく、オーバーレイの新しいコードもロードされます。残念なことに、最近のハンドヘルドでは、コードを特別に扱うコピー防止機能とハッキング防止メカニズムにより、それはさらに難しくなっています。それでも、luaスクリプトに対してはそれを行っています。

最後になりましたが、命令オペコードに直接パッチを当てることで、少しアヒルのパンチを行うことができます(非常に小さな特定の状況では可能です)。ただし、これは、固定プラットフォームとコンパイラーを使用している場合に最適です。また、ほとんど維持できず、非常にバグが発生しやすく、すぐに達成できることも限られているため、ほとんどの場合、デバッグ中にコードを再ルーティングするためにのみ使用します。それはないけれども、あなたに急いで自分の命令セット・アーキテクチャについて多くの地獄を教えます。


6

モジュールをダイナミックリンクライブラリ(またはUNIXの共有ライブラリ)として実装し、ライブラリから関数を動的にロードするためにdlopen()およびdlsym()を使用して、実行時にモジュールをホットスワップすることができます。

Windowsの場合、同等のものはLoadLibraryとGetProcAddressです。

これはCのメソッドであり、C ++で使用するといくつかの落とし穴があります。これについては、こちらで読むことができます


そのガイドは、ホットスワップ可能なライブラリを作成するための鍵です、ありがとう
-JqueryToAddNumbers

4

Visual Studio C ++を使用している場合、実際には特定の状況下でコードを一時停止して再コンパイルできます。Visual Studioは、編集と続行をサポートしています。デバッガーでゲームにアタッチし、ブレークポイントで停止させ、ブレークポイントの後にコードを変更します。保存してから続行する場合、Visual Studioはコードの再コンパイル、実行中の実行可能ファイルへの再挿入、および続行を試みます。すべてがうまくいけば、行った変更は実行中のゲームにライブで適用され、コンパイル、ビルド、テストのサイクル全体を実行する必要はありません。ただし、以下はこれが機能しないようにします。

  1. コールバック関数の変更は正しく機能しません。E&Cは、新しいコードチャンクを追加し、新しいテーブルを指すように呼び出しテーブルを変更することで機能します。コールバックや関数ポインターを使用するものについては、変更されていない古い呼び出しを引き続き実行します。これを修正するには、静的関数を呼び出すラッパーコールバック関数を使用します。
  2. ヘッダーファイルの変更はほとんど機能しません。これは、実際の関数呼び出しを変更するために設計されています。
  3. さまざまな言語構造により、不思議なことに失敗します。私の個人的な経験から、enumのような事前宣言はしばしばこれを行います。

編集と続行を使用して、UIの繰り返しなどの速度を劇的に改善しました。何も見えないように誤って2つのボックスの描画順序を入れ替えた以外は、完全にコードで構築された作業UIがあるとします。2行のコードをライブで変更することにより、簡単なUI修正を確認するための20分のコンパイル/ビルド/テストサイクルを節約できます。

これは運用環境の完全なソリューションではありません。可能な限り多くのロジックをデータファイルに移動し、それらのデータファイルを再読み込み可能にすることが最善のソリューションであることがわかりました。


どのコンパイルサイクルに20分かかりますか?!?私はむしろ自分自身を撮影したい
JqueryToAddNumbers

3

他の人が言ったように、それは難しい問題であり、C ++を動的にリンクします。しかし、それは解決された問題です-あなたはCOMまたは長年にわたってそれに適用されてきたマーケティング名の1つを聞いたかもしれません:ActiveX。

COMは、それを使用して機能を公開するC ++コンポーネントを実装するのに多大な労力を費やす可能性があるため、開発者の観点からは少し悪い名前を持っています(ただし、ATL(ActiveXテンプレートライブラリ)を使用すると簡単になります)。消費者の観点から見ると、たとえば、ExcelスプレッドシートをWord文書に埋め込んだり、VisioダイアグラムをExcelスプレッドシートに埋め込んだりするアプリケーションが、かなり昔にクラッシュする傾向があったため、悪い名前になっています。そして、それは同じ問題に帰着します-マイクロソフトが提供するすべてのガイダンスがあったとしても、COM / ActiveX / OLEを正しくするのは困難でした。

COMの技術自体が本質的に悪いものではないことを強調します。まず、DirectXはCOMインターフェイスを使用してその機能を公開し、ActiveXコントロールを使用してInternet Explorerを組み込む多数のアプリケーションと同様に十分に機能します。次に、C ++コードを動的にリンクする最も簡単な方法の1つです。COMインターフェースは、本質的に純粋な仮想クラスです。CORBAのようなIDLがありますが、特に定義するインターフェイスがプロジェクト内でのみ使用される場合は、強制的に使用する必要はありません。

Windows用に作成していない場合、COMを検討する価値がないとは思わないでください。Mozillaは、C ++コードをコンポーネント化する方法が必要だったため、コードベース(Firefoxブラウザーで使用)で再実装しました。


2

ここに、ゲームプレイコード用のランタイムコンパイルC ++の実装があります。また、同じことを行う独自のゲームエンジンが少なくとも1つあることを知っています。トリッキーですが、実行可能です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.