グローバル状態
1) これは物事を進める良い方法ですか? 「グローバル」な Gamestate クラスを使用することをお勧めしますか? それについてもっと良い方法はありますか?
ゲームの場合、再利用可能なコードとは対照的に、グローバルな状態で十分だと思います。ゲームステート ポインターを渡すのを避けて、代わりに実際にグローバル変数にすることもできます。
同期
2) 私の意図は、Gamestate のゲッター/セッターにミューテックスを持たせることでした。ただし、ロックされている間はオブジェクトを返すことができないため、読み取りには機能しません。つまり、同期をゲッターの外部に配置する必要があります。 /setters を作成し、ミューテックスを公開します。それはまた、さまざまなリソースすべてに対して大量のミューテックスが必要になることも意味します。この問題を解決する最もエレガントな方法は何ですか?
これをトランザクションの観点から考えてみます。単一の状態変更を独自のミューテックス ロック コードにラップすると、パフォーマンスに影響を与えるだけでなく、コードが 1 つの状態要素を取得し、その要素に対して何らかの計算を実行し、後で値を設定する場合に、実際には正しくない動作につながる可能性があります。間の同じ要素。そのため、Gamestate とのすべてのやり取りがいくつかの場所にまとめて発生するように構造化しようとしLogicManager
ました。Renderer
その相互作用の間、スレッドは状態のミューテックスを保持する必要があります。
ミューテックスの使用を強制したい場合は、少なくとも 2 つのクラスを持つ構成を作成できます。GameStateData
それらを と と呼びましょうGameStateAccess
。GameStateData
すべての状態が含まれますが、パブリック アクセスは提供されません。GameStateAccess
の友人にGameStateData
なり、その個人データへのアクセスを提供します。のコンストラクターはGameStateAccess
、への参照またはポインターを取り、GameStateData
そのデータのミューテックスをロックします。デストラクタはミューテックスを解放します。そうすれば、状態を操作するコードは、GameStateAccess
オブジェクトがスコープ内にあるブロックとして単純に記述されます。
ただし、まだ抜け穴があります。このGameStateAccess
クラスから返されるオブジェクトが可変オブジェクトへのポインターまたは参照である場合、このセットアップでは、コードがそのようなポインターをミューテックスによって保護されているスコープ外に持ち出すことはできません。これを防ぐには、書き方に注意するかGameStateAccess
、スコープ外になったらクリアできるカスタム ポインターのようなテンプレート クラスを使用するか、参照ではなく値でのみ渡すようにします。
例
C++11 を使用すると、上記のロック管理のアイデアは次のように実装できます。
class GameStateData {
private:
std::mutex _mtx;
int _val;
friend class GameStateAccess;
};
GameStateData global_state;
class GameStateAccess {
private:
GameStateData& _data;
std::lock_guard<std::mutex> _lock;
public:
GameStateAccess(GameStateData& data)
: _data(data), _lock(data._mtx) {}
int getValue() const { return _data._val; }
void setValue(int val) { _data._val = val; }
};
void LogicManager::performStateUpdate {
int valueIncrement = computeValueIncrement(); // No lock for this computation
{ GameStateAccess gs(global_state); // Lock will be held during this scope
int oldValue = gs.getValue();
int newValue = oldValue + valueIncrement;
gs.setValue(newValue); // still in the same transaction
} // free lock on global state
cleanup(); // No lock held here either
}
ループ終了インジケータ
3) すべてのスレッドが「bool run」にアクセスして、ループを続行するかどうかを確認しています。
while(gs->run){
....
}
EventManager で終了メッセージを受信すると、run が false に設定されます。その変数をまったく同期する必要がありますか? 揮発性に設定しますか?
このアプリケーションでは、揮発性ではあるが同期されていない変数で問題ありません。コンパイラがその値をキャッシュするコードを生成しないようにするために、それを揮発性として宣言する必要があります。これにより、別のスレッドによる変更が隠されます。
std::atomic
別の方法として、これに変数を使用することもできます。
ポインター間接オーバーヘッド
4) 常にポインタを逆参照することなどは、パフォーマンスに影響を与えますか? たとえばgs->objects->entitylist.at(2)->move();
、これらすべてを実行する->
と.
、大幅な速度低下が発生しますか?
それは代替案に依存します。多くの場合、コンパイラーは、gs->objects->entitylist.at(2)
繰り返し使用される場合、上記のコードなどの値を保持することができ、何度も何度も計算する必要はありません。一般に、このすべてのポインターの間接化によるパフォーマンスの低下は小さな問題だと考えていますが、それを確実に判断するのは困難です。