確認したバージョンは UE5.3.2です。
はじめに
プロジェクト設定を色々といじった後、アプリをパッケージして.exeを起動すると、Max UObject Count is invalid. It must be a number that is greater than 0.と表示されて.exeが終了してしまう状況に陥ってしまった。
原因はちょっと罠っぽいものだったので、備忘録として残しておく。
状況は:
- エディタ上で実行した場合は問題なく動く。
- そのアプリをパッケージしてできた.exeファイルを起動すると、上記のダイアログが表示される。ダイアログ右下の OKボタンを押すと、アプリが終了する。(アプリを正しく起動する手段がない)
- 当時、Garbage Collectionに関する試行錯誤のため、プロジェクト設定をあれこれいじっていた。
原因
まずは結論から。
プロジェクト設定の各項目は「現在表示されている値と異なる値を書き込む」ことで、.iniファイルに即座に反映される。
例えば、今回問題となっている「Maximum number of UObjects that can exist in cooked game」を、0 → 1 → 0と書き換えると、.iniファイルの内容は以下のように変化する。
※当該.iniファイルはプロジェクトディレクトリの Confing/DefaultEngine.ini
1)初期状態。当該パラメータを書き換える前 (値は0のまま)
[/Script/Engine.GarbageCollectionSettings]
2)当該パラメータを 0 → 1 に書き換えた後
[/Script/Engine.GarbageCollectionSettings]
gc.MaxObjectsInGame=1
3)当該パラメータを 1 → 0 に戻した後
[/Script/Engine.GarbageCollectionSettings]
gc.MaxObjectsInGame=0
つまり:
- プロジェクト設定でいう「Maximum number of UObjects that can exist in cooked game」は、.iniファイルでは「gc.MaxObjectsInGame」である。
- 何もしなければ DefaultEngine.iniには
gc.MaxObjectsInGame
に関する項目は無いはずだった。 - いじった本人は「元の値に戻した」つもりだったのに、DefaultEngine.iniには元々無かったはずの
gc.MaxObjectsInGame=0
が書き込まれてしまった。 -
gc.MaxObjectsInGame=0
はパッケージしたアプリ専用のパラメータのため、パッケージ版でしか問題が発現しなかった。 -
gc.MaxObjectsInGame
の値は 0であってはならないため(本来、何らかの大きな数値を書くべきである)、.exe起動時に Fatalエラーダイアログが起動した。
…という現象が起きてしまったのである。
解決方法
元の状態に戻してやれば解決する。
つまり、テキストエディタで DefaultEngine.iniからgc.MaxObjectsInGame=0
の行を削除すればよい。
※パッケージを作り直す必要はある。
深掘り
ここから先は、この問題について深掘りした内容を書いていく。
gc.MaxObjectsInGameのデフォルト値はいくつか?
プロジェクト設定「Maximum number of UObjects that can exist in cooked game (=gc.MaxObjectsInGame)」のデフォルト値がゼロであることから、「パッケージしたアプリではメモリが許す限り無限」だと連想してしまう。
だが、gc.MaxObjectsInGame
の実際の初期値は 2 * 1024 * 1024 (=2,097,152)である。
これはエンジンソースの InstallDir\Engine\Source\Runtime\CoreUObject\Private\UObject\UObjectBase.cpp の984行目、UObjectBaseInit()
関数で指定されている。
エンジンソースをコピペするのは憚られるので、ダミーコードで流れを示す。
void UObjectBaseInit()
{
int32 MaxUObjects = 2 * 1024 * 1024; // Default to ~2M UObjects
if (/*クックされたデータ*/)
{
// Maximum number of UObjects in cooked game
GConfig->GetInt(TEXT("/Script/Engine.GarbageCollectionSettings"), TEXT("gc.MaxObjectsInGame"), MaxUObjects, GEngineIni);
}
else
{
// Maximum number of UObjects in the editor
GConfig->GetInt(TEXT("/Script/Engine.GarbageCollectionSettings"), TEXT("gc.MaxObjectsInEditor"), MaxUObjects, GEngineIni);
}
GUObjectArray.AllocateObjectPool(MaxUObjects);
}
- まずローカル変数
MaxUObjects
が 2 * 1024 * 1024 (=2,097,152)で初期化される。 - クックされたデータが要求されている場合、つまりパッケージ版のアプリの場合は、if の中に入る。その場合 .iniファイルから
gc.MaxObjectsInGame
の値が取得される。-
GConfing->GetInt()
は .iniファイルにそのパラメータの記述がない場合は何もしない。初期状態では、DefaultEngine.iniをはじめとするどのEngine.iniファイルにもgc.MaxObjectsInGame
の記述はない。したがって、ローカル変数MaxUObjects
は 2 * 1024 * 1024のままである。 - もし、DefaultEngine.iniなどのどれかのEngine.iniファイルに
gc.MaxObjectsInGame
の記述があれば、GConfing->GetInt()
はその値を取得する。今回私がハマったように、手動でゼロを書き込んでしまっていた場合、ローカル変数MaxUObjects
はゼロになり、そのあと呼び出されるGUObjectArray.AllocateObjectPool()
から、冒頭に載せたスクショのような Fatalエラーのダイアログが表示され、アプリは終了する。
-
なお、エディタから起動した場合は、elseの中に入る。
この場合、gc.MaxObjectsInEditor
の値がローカル変数MaxUObjects
に入る。
ちなみに、gc.MaxObjectsInEditor
は InstallDir\Engine\Config\BaseEngine.iniにデフォルト値が定義されており、その値は 25,165,824である。
[/Script/Engine.GarbageCollectionSettings]
gc.MaxObjectsInEditor=25165824
この値は、プロジェクト設定の Engine - Garbage Collectionの Optimizationカテゴリにある「Maximum number of UObjects that can exist in editor」に相当する。
gc.MaxObjectsInGameもしくはgc.MaxObjectsInEditorの値を超えるとどうなるか
これを実験するのは比較的たやすい。UObjctを大量に生成(NewObject)するだけで発現させられる。
この場合、次のようなダイアログが表示され、OKボタンを押すとアプリが終了する。
つまり、gc.MaxObjectsInGame
やgc.MaxObjectsInEditor
の値を超えるとアプリは強制終了することになる。
上記のダイアログのスクショはパッケージ版で試したものである。
ここに書かれているオブジェクトの最大数は 2,162,688個であり、上述のMaxUObjects = 2 * 1024 * 1024
(=2,097,152)とは異なる。
オブジェクトはチャンク(1チャンク辺り 65,536個)で管理されており、実際のオブジェクト上限数はgc.MaxObjectsInGame
やgc.MaxObjectsInEditor
より大きくて最も近い数で管理されている。
InstallDir\Engine\Source\Runtime\CoreUObject\Public\UObject\UObjectArray.hの464行目、PreAllocate()
関数内で以下のように算出されている。
void PreAllocate(int32 InMaxElements)
{
// NumElementsPerChunkは 64 * 1024 (=65,536)
MaxChunks = InMaxElements / NumElementsPerChunk + 1;
MaxElements = MaxChunks * NumElementsPerChunk;
Objects = new FUObjectItem*[MaxChunks];
// 以下略...
}
要するに65,536の倍数に揃えられるということである。
※この計算に基づくと、エディタ版は gc.MaxObjectsInEditor
のデフォルト値 25,165,824から、実際の最大オブジェクト数は 25,231,360になる。(65,536の385倍)
では gc.MaxObjectsInGameはいくつに設定したらよいのか (※結論なし)
gc.MaxObjectsInEditor
は、BaseEngine.iniに初期値 25,165,824が与えられている。
一方、gc.MaxObjectsInGame
は、どの.iniファイルにも初期値が与えられておらず、UObjectBase.cppのUObjectBaseInit()
内に設定されたローカル変数の初期値 (2 * 1024 * 1024)が流用されているのみである。ここで、エディタ版とパッケージ版のオブジェクト上限数を並べてみると…
上限数 | |
---|---|
パッケージ版 | 2,097,152 |
エディタ版 | 25,165,824 |
縦に並べてみると、なんと1桁も違うことが分かる。
そうすると、果たしてパッケージ版(gc.MaxObjectsInGame
)を現状のまま、つまり値を設定せずに放置しておいてよいものか不安になってしまう。これでは、エディタでは十分動いていたものが、パッケージ版では動かないという現象が起きてしまうかもしれない。
とは言え、エディタ版の25,165,824をそのまま、パッケージ版にも流用(コピペ)するのも気が引ける。なぜなら、エディタ版ではエディットの都合上、非常に多くのオブジェクトを使うことができるようにする必要があるが、パッケージ版では「必要最低限」にしておきたいからだ。
問題提起だけしておいて結論を出せないで申し訳ないが、ここで言えることは以下のような感じだろうか…
- 2 * 1024 * 1024 (=2,097,152)のままでも十分パッケージ版が動く場合は、まあ触らぬ神に祟りなしでスルーしても良いだろう。
- パッケージ版で、オブジェクト数の上限を超えてしまう場合は:
- 安易にエディタ版の数値(25,165,824)をコピペして使うのだけはやめておきたい。その値はあくまでエディタでエディットするためのものである。
- プラットフォームのメモリ量に応じており、かつそのアプリに必要十分な値を、試行錯誤して見つけて設定する必要があるだろう。そのためには、様々なケースを想定したテストプレイで、実際のオブジェクト数の遷移をプロファイリングする必要があるだろう。