2 つの言葉: 時期尚早の最適化。
パフォーマンスが心配です。しかし、Minecraft のクローンを作りたいと考えると、これは、ゲームの世界を 3 次元配列でうまく表現できることを意味します。これらへのアクセスは、前述のすべてのプログラミング言語でかなり高速です。ゲーム ロジックの実行には、何百万もの配列エントリにアクセスするよりもはるかに時間がかかるはずです。では、最小限の作業バージョンを作成する前に、とにかく計算時間の大部分を必要としない部分を最適化する必要はありません。
ゲームの世界を表す Java インターフェースまたは Scala トレイトを作成したい場合があります。ゲーム世界のブロックのコンテンツを取得して保存するメソッドを提供します。後で一括メソッドを追加して、パフォーマンスをさらに最適化することもできます。たとえば、特定の立方体のすべてのブロックが空かどうかをチェックしたり、木のブロックの数を数えたり、それらの線に沿ったものです。ただし、最初は、これらのメソッドを除外するか、抽象メソッドを繰り返し呼び出すことに依存する単純な実装を作成することをお勧めします。後で最適化できます。
次に、実際に 3 次元配列を使用する、そのインターフェースの非常に単純な Java/Scala 実装を提供できます。代替案は、キーが座標で、値がブロックの状態であるマップです。利点は、ゲーム世界のサイズに実際の制限がなく、空のブロックがメモリを消費しないことです (空のブロックを含む座標の場合、マップにエントリはありません)。不利な点は明らかにパフォーマンスです。
その時点で、メモリを消費しすぎる場合は、データをより密にパックすることを検討してください。ビットセットを使用できます。その段階に到達したら、JNI を使用して、C または C++ で記述されたコードを JVM に挿入することは実際には理にかなっています。したがって、ゲーム ロジックは Java/Scala に保持し、メモリ パッキングとルックアップは C で行います。
コードのネイティブ部分の Java/Scala および C/C++ バージョンを作成できる共通の「スクリプト」ソースを作成しても意味がありません。ネイティブの C/C++ 関数は、Java に直接変換できない最適化に大きく依存します。「純粋な Java/Scala モード」でサーバーを起動する場合、つまり JNI 関数を使用しない場合は、前の手順で作成した他のクラスを使用するだけです。少し遅いかもしれませんが、純粋な JVM バイト コードです。そして、それらをシンプルに保っているので、乱暴に拡張したり、新しいバグを導入したりする必要があるという危険はありません。少なくとも、クロスプログラミング言語のコード ジェネレーターを作成または適応させるオーバーヘッドは、特に Java/Scala の実装が非常に単純な場合は、2 つの別個のコード ベースを保持するよりもはるかに大きくなります。
もちろん、ビット パッキングではここまでしかできません。ゲーム ワールドのいくつかの部分 (特に地表より上) はほぼ完全に空であり、他の部分には同じ種類のブロックで満たされた巨大なエリア (ほぼ石だけで構成されている地下エリアなど) が含まれていることに注意してください。それだけの冗長性を備えた巨大なメモリ構造を維持することは、実際にはメモリの無駄です。そのため、ゲーム ワールドをツリーに詰め込むことを検討することになるでしょう。各ノードはゲーム ワールドの大きな立方体領域を表し、子供たちはそれをさらに分割して、1 つの特定のゲーム ワールド座標の内容を記述する葉にします。1 つのノードに同じコンテンツ タイプの子しかない場合、子を格納する必要はありません。この時点でツリーを切り取り、ノードに「これ以上見る必要はありません。とても良いです!- もちろん、ツリーを変更可能にしておく必要があります。1 つのノードだけで表される世界の退屈な部分が変化した場合は、そのノードを分割して 2 つ以上の子に分割する必要があります。後でまた簡単になったら、また子供たちと一緒に木を切ることができます。
この時点で気付くかもしれないことの 1 つは、メモリのパッキングとアクセスの最適化は、ここではもはや実際の問題ではありません。このようなツリーは、ストレージ メソッドとルックアップ メソッドにネイティブ関数を使用しても合理的に最適化することはできません。そのような最適化から、たとえば 10% 以上を得ることができれば、これは非常にありそうもないことであり、非常に印象的です。(おそらく、これは、Java/Scala の対応物が適切に最適化されていなかったことを意味している可能性があります。) このような最小限の速度向上は、それに投入する必要がある膨大な追加の労力を正当化するものではありません。代わりに、より優れた CPU をマシンに搭載し、節約した時間をアイスクリームを食べたり、Dr. House を見たり、ゲームをさらに強化してプレイヤーにとってより面白く魅力的なものにしたりして楽しんでください。製品を本当に改善する価値のあるものを作成することによって.
しかし、これはまだそうではありません。私の記憶が正しければ、マインクラフトの世界の初期状態は手続き的に生成されます。フラクタル アルゴリズムを使用すると、瞬く間に複雑で自然な無限の領域を実際に作成できます。したがって、ゲーム ワールドのコンテンツを事前に計算して巨大なデータ構造に格納する代わりに、ワールド生成手順をルックアップ メソッドとして使用することをお勧めします。メモリから座標のコンテンツをルックアップする代わりに、アルゴリズム。このようにして、世界の初期状態を 4 バイト (アルゴリズムのシード値) に完全に格納できます。
もちろん、世界が常にこの状態にとどまるわけではありません。プレイヤー (または他の何か) が世界を変えるとき、これは保存する必要があるものです。したがって、ワールドのシード値とそれに加えられた変更のみを保存します。座標の内容を検索するときはいつでも、変更されたタイル ストレージで検索してみてください。そこにあるときは、その情報を使用してください。存在しない場合は、デフォルトで手続き型ワールド生成アルゴリズムを使用します。これにより、メモリ消費量が大幅に減少します。また、世界への変更は比較的小さく、巨大な空の領域が含まれているため、これらの変更を迅速かつ効率的に格納するデータ構造を作成するのは比較的簡単です。繰り返しますが、このためにネイティブ コードを記述しても、パフォーマンスが大幅に向上することはなく、努力する価値はありません。
ただし、他にも最適化できるものがあります。手続き型ワールド生成アルゴリズムです。これは、C または C++ で作成する必要がある主要なコンポーネントの 1 つです。比較的小さく、多くのコードは必要ありませんが、数学集約的であり、非常に頻繁に呼び出されます。したがって、それを適切に最適化し、それから小さな JNI ライブラリを作成してください。これにより、努力する価値のあるパフォーマンスが大幅に向上します。(もちろん、最初に Java/Scala の実装を行いたいと思うかもしれません。それがすでに十分に速い場合は、JNI の問題に取り掛かる必要はありません。)
ワールド生成手順がまだ遅すぎる場合は、キャッシュを実装できます。キャッシュは、JVM が遅延しているときに、プレイヤーの周囲の一部をプリエンプティブに生成することもできます。
この開発プロセスをいくつかのアイデアの繰り返しとして説明しましたが、前のアイデアよりも優れたものがあります。最初の段階で、最適化された C/C++ コードのライブラリを書き始めているイメージ。時間の無駄だったでしょう。後の段階ですべてを捨てることができたでしょう。C で記述された、ビット パッキングを使用する効率的な配列ストレージは優れた機能ですが、世界をバイナリ空間で分割されたツリーに再編成する場合は、まったく役に立ちません。
だから、無理しないでください。Java/Scala だけで最小限の動作をする (まだ遅くて最適化されていない) バージョンを作成できない場合、C/C++ またはクロスコンパイル スクリプト言語で最適化されたバージョンを作成することもできません。最初に単純なバージョンを実行してから、パフォーマンス テストを実行し、本当に必要な場合にのみ最適化します。最初に最適化のコンセプトを作ることからプロジェクトを始めないでください。この種の最適化は、あなたが取り組む最後のものであるべきです。