アプリ置き場

アプリ置き場

http://www.moreread.net/

Xamarin + CocosSharp を使ってみたい ⑦

AndroidGC発生しまくりでカクカク問題
 
nursery-sizeをでっかく指定しておけばとりあえずGCを我慢してくれるようだけど。。。いろいろパラメータ指定したら、なんかときどき実行中にエラーでて落ちるように。
MONO_GC_PARAMS=bridge-implementation=tarjan,nursery-size=128m,soft-heap-limit=512m
 
The Xamarin.Android garbage collector can be configured by setting the MONO_GC_PARAMS environment variable. Environment variables may be set with a Build action of AndroidEnvironment.
The MONO_GC_PARAMS environment variable is a comma-separated list of the following parameters:
  • nursery-size = size : Sets the size of the nursery. The size is specified in bytes and must be a power of two. The suffixes k , m and g can be used to specify kilo-, mega- and gigabytes, respectively. The nursery is the first generation (of two). A larger nursery will usually speed up the program but will obviously use more memory. The default nursery size 512 kb.
  • soft-heap-limit = size : The target maximum managed memory consumption for the app. When memory use is below the specified value, the GC is optimized for execution time (fewer collections). Above this limit, the GC is optimized for memory usage (more collections).
  • evacuation-threshold = threshold : Sets the evacuation threshold in percent. The value must be an integer in the range 0 to 100. The default is 66. If the sweep phase of the collection finds that the occupancy of a specific heap block type is less than this percentage, it will do a copying collection for that block type in the next major collection, thereby restoring occupancy to close to 100 percent. A value of 0 turns evacuation off.
  • bridge-implementation = bridge implementation : This will set the GC Bridge option to help address GC performance issues. There are three possible values: old , new , tarjan.
  • bridge-require-precise-merge: The Tarjan bridge contains an optimization which may, on rare occasions, cause an object to be collected one GC after it first becomes garbage. Including this option disables that optimization, making GCs more predictable but potentially slower.
For example, to configure the GC to have a heap size limit of 128MB, add a new file to your Project with a Build action of AndroidEnvironment with the contents:
MONO_GC_PARAMS=soft-heap-limit=128m
 
.Androidプロジェクトにテキストファイルを追加して、プロパティウィンドウからタイプを
AndroidEnvironment
にすればよいらしい。要調整。
 

音が鳴らなくなる問題①
 
iOSバイスでしばらく動作テストしてると、必ず音が鳴らなくなって、そのうちフリーズ。ホワッツ
うそやろ……1024回で打ち止めって、しかも2年以上前から直ってないの?
たしかに1024回で「Failed to generate OpenAL data buffer:」と吐き出して、それ以上の再生が不可能になる。何かリークしてるのか?再生途中でストップをかけた分は回収されていて、寿命が延びてる気がする。
CCAudioEngine.SharedEngine.End();
とかしてみたが、特に意味はなかった。
 
自前でAudioエンジンのインスタンスを作ってみる。
CCAudioEngine engine =new CCAudioEngine();
 
~略~
 
engine.End();
engine.Dispose();
engine = new CCAudioEngine();
1024回をカウントして作り直してみるが効果なし。
が、なんということでしょう。CCAudioEngineを作り直したあとにGCかけると復活する。
System.GC.Collect();
 
AudioEngine作り直す度にBGMが止まったらうんこすぎるので、AudioEngineをBGM用とSE用を分けて2個にしてみる。おし、いけた。
余裕のあるタイミングでときどき初期化しとくほうがいいかなー。自前でインスタンス作ったから、PauseとResumeの伝達も自前になるんよね、たぶん。はぁ……。
 

音が鳴らなくなる問題②
 
iOSバイスでBGMが鳴らなくなってフリーズするのは別件だった。Audioは地雷原か……。
MP3をループで再生したところ、終了時点でおかしくなった。ループせず、以降のBGMの再生できず。午後のこ~だをひっぱりだしてきて、MP3→WAV→MP3と再変換してみたが直らず。データが悪いわけではなく、やはり何か問題を抱えてそうだ。
いくつか試した感じだとビットレートなどは関係なく、短めのBGMをループさせると、ちょくちょく再生終了時に死ぬ。誰も使ってないのかよ。
しょうがないので自前でループ機構を作る。終了を検知できないから、曲の再生時間を覚えておいて、フレームカウントから経過時間算出だよ!
いまのところちゃんと動いてる気がする。スリープ、レジュームすると狂いそうだなぁ。
 

固有実装
 
いざとなったらごりごり書くのか?書くしかないのか?
  • OnPlatform (XF 1.0 ~)
  • DependecyService (XF 1.0 ~)
  • Plugins for Xamarin
  • Custom Renderer (XF 1.0 ~)
  • Effects (XF 2.1 ~)
  • Native Embedding (XF 2.2 ~)
 

ジョイパッド対応
 
手元のGPD XDでデバッグしやすいようにパッド対応しようと思った。
あっさりできてうれぴ。Xamarinとはあまり関係ないけど。
どこにマッピングするのか試行錯誤がしやすいように割り当てを辞書に入れた。
// MainActivity.cs
public override bool OnKeyDown([GeneratedEnum] Keycode keyCode, KeyEvent e)
{
    if (dicPadmap.ContainsKey(keyCode))
    {
        Pad.getInstance().dwButtons |= dicPadmap[keyCode];//Padは独自クラス
        return true;
    }
    return base.OnKeyDown(keyCode, e);
}
 
public override bool OnKeyUp([GeneratedEnum] Keycode keyCode, KeyEvent e)
{
    if (dicPadmap.ContainsKey(keyCode))
    {
        Pad.getInstance().dwButtons&= ~dicPadmap[keyCode];
        return true;
    }
    return base.OnKeyUp(keyCode, e);
}
 
あとAndroidでステータスバーなしのフルスクリーン。Activityでセット。
Window.AddFlags(WindowManagerFlags.Fullscreen);
 

FPS計測
 
CocosSharpって簡単にFPSとる方法ってあるんだろうか?
よくわからないから自前で実装してみる。
どういう構造になっているかいまだによく理解してないが、VisitRendererとやらが実際に描画されたときに呼ばれるようだ。ここでカウントすればよさげ。
 
protected override void VisitRenderer(ref CCAffineTransform worldTransform)
{
    base.VisitRenderer(ref worldTransform);
 
    frameCounter.nextFrame();
}
 

最適化
 
GCの発生頻度を抑えるべくAndroidEnvironmentに加えて、CCNode系のクラスの生成/破棄をフレームごとに行うのをやめた。さすがに無理があったようだ。
そしたらGPD XD(2015年のミドルクラスくらい?)でも60FPSでるようになったのでこれで十分かな?