お世話にあっております。
TickableGameObjectに関して表題のクラッシュが起きるのでご連絡です。
問題としましては、FTickableGameObjectを継承したクラスを別スレッドで生成したときに、FTickableGameObject::TickObjects()とタイミングが重なり合ったときにクラッシュが発生します。
原因はvtable初期化前にFTickableGameObject::TickObjects()が回ってきてしまい、FTickableGameObjectBaseのTick()及びGetStatId()の純粋仮想関数が呼び出されクラッシュします。
テストではsleepを挟んで強制的に発生するようにFMyTickable1クラスを使用していますが、FTickableGameObjectを直接継承したクラスでも発生していました。
FTickableGameObjectはスレッドセーフ化されているクラスという認識で間違っていませんでしょうか?それであればこの問題の修正を将来的で構いませんのでお願いしたいです。
またこの問題を修正、調査するときに気がついた点などを共有しておきます。
- GameThreadからnewするようにすれば起きませんが、AsyncLoadingやNiagaraなどゲーム側のコードでも気をつけないと別スレッドから発生する可能性があります
- サードパーティ製のプラグインでも気軽に使われているので今更GameThread固定化のルールは難しそうでした
- FTickableGameObjectBase::Tick()及びGetStatId()の純粋仮想関数をやめることでクラッシュ自体は起きなくなりますが、vtableが初期化されるタイミングによっては別の問題が起きます
- vtable初期化前にIsTickable()が呼び出されFTickableGameObjectBaseのtrueが使用されますが、Tickを呼び出すタイミングでvtableが初期化されていて継承したクラス側のTickが呼び出される場合があります、このときIsTickable()の判定が無視されているので実際にはTickしてほしくないものでもTickされてしまうという問題が起きます
- FMyTickable2をその状態のコードにしてあります
- 結局FTickableGameObject::TickObjects()内で使用されたIsTickable()は信用できないので、FMyTickable2::Tick()内でもIsTickable()を再判定する必要性が出てきます
- 結局はFTickableGameObject()にある、QueueTickableObjectForAdd()をnewするオブジェクトのコンストラクタで呼び出すようなルールに変更するのが一番良いのですが、FTickableGameObjectは既にいろいろな箇所で使用されているので断念しました
- ということで直近のプロジェクトでは、純粋仮想関数化をやめ、問題の出るクラスのみTick()内でIsTickable()を再判定するという対応でしのぎました
以上になります、よろしくお願いいたします。