私は自分のゲームエンジンに取り組んでおり、現在マネージャーを設計しています。メモリ管理については、コンストラクタとデストラクタを使用するよりもInit()
、CleanUp()
関数を使用する方が良いことを読みました。
これらの関数がどのように機能し、どのようにエンジンに実装できるかを確認するために、C ++コードの例を探していました。どのように機能しInit()
、どのようにしCleanUp()
てエンジンに実装できますか?
私は自分のゲームエンジンに取り組んでおり、現在マネージャーを設計しています。メモリ管理については、コンストラクタとデストラクタを使用するよりもInit()
、CleanUp()
関数を使用する方が良いことを読みました。
これらの関数がどのように機能し、どのようにエンジンに実装できるかを確認するために、C ++コードの例を探していました。どのように機能しInit()
、どのようにしCleanUp()
てエンジンに実装できますか?
回答:
実際には非常に単純です。
あなたの設定を行うコンストラクターを持つ代わりに、
// c-family pseudo-code
public class Thing {
public Thing (a, b, c, d) { this.x = a; this.y = b; /* ... */ }
}
...すべてではほとんどあるいは何行うあなたのコンストラクタを持っている、と呼ばれる方法の書き込み.init
や.initialize
、あなたのコンストラクタは、通常どうなるのかだろう。
public class Thing {
public Thing () {}
public void initialize (a, b, c, d) {
this.x = a; /*...*/
}
}
だから今のように行く代わりに:
Thing thing = new Thing(1, 2, 3, 4);
行ってもいい:
Thing thing = new Thing();
thing.doSomething();
thing.bind_events(evt_1, evt_2);
thing.initialize(1, 2, 3, 4);
ここでの利点は、システムで依存性注入/制御の反転をより簡単に使用できることです。
言う代わりに
public class Soldier {
private Weapon weapon;
public Soldier (name, x, y) {
this.weapon = new Weapon();
}
}
あなたは兵士を構築し、彼に武器を渡す装備メソッドを彼に与えることができ、それからコンストラクタ関数の残りのすべてを呼び出します。
したがって、今度は、ある兵士がピストルを持ち、別の兵士がライフルを持ち、別の兵士がショットガンを持っている敵をサブクラス化する代わりに、それが唯一の違いです。
Soldier soldier1 = new Soldier(),
soldier2 = new Soldier(),
soldier3 = new Soldier();
soldier1.equip(new Pistol());
soldier2.equip(new Rifle());
soldier3.equip(new Shotgun());
soldier1.initialize("Bob", 32, 48);
soldier2.initialize("Doug", 57, 200);
soldier3.initialize("Mike", 92, 30);
破壊についても同様です。特別なニーズがある場合(イベントリスナーの削除、配列からのインスタンスの削除/操作している構造体など)は、手動で呼び出して、発生したプログラムのいつどこで発生しているかを正確に把握します。
編集
Kryotanが指摘したように、これは元の投稿の"How"に答えるものですが、 "Why"をうまく実行していません。
上記の答えでおそらくわかるように、以下の間に大きな違いはないかもしれません。
var myObj = new Object();
myObj.setPrecondition(1);
myObj.setOtherPrecondition(2);
myObj.init();
そして書く
var myObj = new Object(1,2);
より大きなコンストラクター関数があるだけです。
15または20の事前条件を持つオブジェクトに対して行われるべき議論があります。これにより、コンストラクターが非常に扱いにくくなり、インターフェースにそれらを引き出すことにより、物事をより見やすく覚えやすくなります。 、インスタンス化がどのように機能するかを1レベル上のレベルで確認できます。
オブジェクトのオプション構成は、これに対する自然な拡張です。オプションで、オブジェクトを実行する前に、インターフェースで値を設定します。
JSにはこのアイデアの優れたショートカットがいくつかあります。これは、より強く型付けされたcのような言語では場違いに見えるだけです。
とは言っても、コンストラクターで長い引数リストを扱っている場合、オブジェクトが大きすぎて、あまりにも多くのことを行う可能性があります。繰り返しますが、これは個人的な好みの問題であり、例外は広範囲に存在しますが、オブジェクトに20のオブジェクトを渡す場合は、小さいオブジェクトを作成することにより、そのオブジェクトの処理を減らす方法を見つける可能性があります。
より適切な理由、そして広く適用できる理由の1つは、オブジェクトの初期化が、現在持っていない非同期データに依存していることです。
オブジェクトが必要であることはわかっているので、とにかくオブジェクトを作成しますが、正しく機能させるには、サーバーまたは現在ロードする必要がある別のファイルからのデータが必要です。
繰り返しになりますが、必要なデータを巨大なinitに渡すか、インターフェースを構築するかは、オブジェクトのインターフェースやシステムの設計にとって重要であるのと同じくらい、概念にとってそれほど重要ではありません...
しかし、オブジェクトの構築に関しては、次のようなことをするかもしれません:
var obj_w_async_dependencies = new Object();
async_loader.load(obj_w_async_dependencies.async_data, obj_w_async_dependencies);
async_loader
ファイル名やリソース名などを渡されて、そのリソースをロードする可能性があります-サウンドファイルや画像データをロードしたり、保存されたキャラクターの統計をロードしたりするかもしれません...
...そしてそのデータをにフィードバックしobj_w_async_dependencies.init(result);
ます。
この種のダイナミックはウェブアプリで頻繁に見られます。
より高いレベルのアプリケーションの場合、必ずしもオブジェクトの構築に含まれているとは限りません。たとえば、ギャラリーはすぐに読み込まれて初期化され、次にストリーミング中に写真を表示する可能性があります。これは、非同期初期化ではありませんが、より頻繁に見られる場所です。 JavaScriptライブラリ。
1つのモジュールが別のモジュールに依存している可能性があるため、そのモジュールの初期化は、依存関係のロードが完了するまで延期される可能性があります。
このゲーム固有のインスタンスについては、実際のGame
クラスを検討してください。
なぜ、.start
または.run
コンストラクタで呼び出せないのですか?
リソースをロードする必要があります-残りのすべてはほぼ定義されており、問題ありませんが、データベース接続なしで、またはテクスチャー、モデル、サウンド、またはレベルなしでゲームを実行しようとすると、それはうまくいきません。特に興味深いゲーム...
... Game
つまり、「先に進む」メソッドに、より興味深い名前を付ける.init
(または逆に、初期化をさらに分解して、ロードを分離する)ことを除いて、典型的なの違いは何ですか?ロードされたものをセットアップし、すべてがセットアップされたときにプログラムを実行します)。
.init
が、おそらく、多分、ありません。エルゴ、有効なケース。
InitとCleanUpの方が優れているとおっしゃっていても、その理由もお分かりでしょう。彼らの主張を正当化しない記事は読む価値はありません。
初期化関数とシャットダウン関数を別々にすると、呼び出す順序を選択できるため、システムのセットアップと破棄が容易になります。一方、コンストラクターはオブジェクトの作成時に正確に呼び出され、デストラクタはオブジェクトが破棄されるときに呼び出されます。2つのオブジェクト間に複雑な依存関係がある場合、それらが設定される前に両方のオブジェクトが存在する必要があることがよくあります。
一部の言語には、参照カウントとガベージコレクションによってオブジェクトがいつ破棄されるかを知ることが困難になるため、信頼できるデストラクタがありません。これらの言語では、ほとんど常にシャットダウン/クリーンアップメソッドが必要であり、対称性のためにinitメソッドを追加したいものもあります。
一番の理由は、プールを許可することです。
InitとCleanUpがある場合は、オブジェクトが削除されたときにCleanUpを呼び出して、オブジェクトを同じタイプのオブジェクトのスタック(「プール」)にプッシュできます。
次に、新しいオブジェクトが必要なときはいつでも、プールから1つのオブジェクトをポップすることができます。または、プールが空の場合は、新しいオブジェクトを作成する必要があります。次に、このオブジェクトでInitを呼び出します。
良い戦略は、ゲームが「良い」数のオブジェクトで始まる前にプールを事前に満たすことです。そのため、ゲーム中にプールされたオブジェクトを作成する必要はありません。
一方、「new」を使用し、不要になったオブジェクトの参照を停止すると、いつか再収集する必要のあるガベージが作成されます。この回収は、JavaScriptなどのシングルスレッド言語では特に悪いことです。ガベージコレクターは、不要になったオブジェクトのメモリを回収する必要があると評価すると、すべてのコードを停止します。ゲームが数ミリ秒間ハングし、プレイ体験が台無しになります。
-あなたはすでに理解しています-:すべてのオブジェクトをプールした場合、再収集は発生しないため、ランダムなスローダウンは発生しません。
また、メモリを割り当てて新しいオブジェクトを初期化するよりも、プールからのオブジェクトで初期化を呼び出す方がはるかに高速です。
ただし、オブジェクトの作成はパフォーマンスのボトルネックにならないことが多いため、速度の向上はそれほど重要ではありません...必死のゲーム、パーティクルエンジン、または計算に集中的に2D / 3Dベクトルを使用する物理エンジンなどのいくつかの例外があります。ここでは、プールを使用することにより、速度とガベージの作成の両方が大幅に改善されています。
Rq:Init()がすべてをリセットする場合は、プールされたオブジェクトのCleanUpメソッドが不要な場合があります。
編集:この投稿に返信することで、Javascriptでのプールについて私が作成した小さな記事をまとめる気になった。
興味がある場合は、ここで見つけることができます。http:
//gamealchemist.wordpress.com/
あなたの質問は逆転しました...歴史的に言えば、より適切な質問は:
なぜ、 構築と初期化が混同されているのか、つまり、これらのステップを個別に行わないのはなぜですか?確かにこれはSoCに反しますか?
C ++の場合、RAIIの目的は、リソースの獲得と解放がオブジェクトの存続期間に直接結び付けられることです。これにより、リソースの解放が保証されます。そうですか?一部。スタックベースの/自動変数のコンテキストで100%満たされます。関連するスコープを離れると、自動的にデストラクタが呼び出され、これらの変数が解放されます(つまり、修飾子automatic
)。ただし、ヒープ変数のdelete
場合、デストラクタを実行するために明示的に呼び出す必要があるため、この非常に便利なパターンは悲しいことに壊れてしまいます。そうしないと、RAIIが解決しようとする試みに噛み付いてしまいます。ヒープに割り当てられた変数のコンテキストでは、C ++はC(delete
vsfree()
)構築を初期化と融合させる一方で、次の点で悪影響があります。
Cでのゲーム/シミュレーション用のオブジェクトシステムの構築は、C ++およびそれ以降の古典的なOO言語の前提をより深く理解することで、RAIIやその他のOO中心のパターンの制限に多大な光を当てることを強くお勧めします(C ++はCで構築されたOOシステムとして始まったことを思い出してください)。