phina.jsに限った話じゃないのですが、phina.jsを使っている時に遭遇した問題なのでphina.jsのアドベントカレンダーとして書いてみたりします。
#前置き
phina.jsで、結構大きめの画像を大量に扱うアプリケーションを作っていたのですが、iOS12のsafariでテストしていた時に、
"Total canvas memory use exceeds the maximum limit(288MB)"
という警告が画像の読み込み時に発生する様になりました。
この警告が発生してからgetContext('2d')を実行すると必ずnullが返り、それ以降canvasからcontextの取得が出来なくなってしまいます。
288MBの部分は機種によっては255MBとかになるみたいです(iPhone Xは288MB)
翻訳すると、「総キャンバスメモリ使用量が上限を超えています」との事。
検索してみると、stackoverflowで外人さんが同じ現象で困っておりました。
親切な事にテスト用コードまで付けてくれています。
以下のコードがそれです(一部変更してます)
let ctxs = []
let counter = 0
// create a 1MB image
const createImage = () => {
const size = 512
const canvas = document.createElement('canvas')
canvas.height = size
canvas.width = size
const ctx = canvas.getContext('2d')
ctx.strokeRect(0, 0, size, size)
return canvas
}
const createImages = n => {
// create n * 1MB images
for(let i = 0; i < n; i++ ){
const canvas = createImage()
ctxs.push(canvas)
}
console.log(`done for ${ctxs.length} MB`)
}
512x512で1MBなんですなー。初めて知りました。
このコードをsafariのデバッグコンソールに貼り付けてテストが出来ます。
試しに、createImages(288)を実行して、その後に、createImages(1)をやると・・・
###調べてみると
iOS11以前は、2151MBまで確保が出来た様です(手持ちのiPhone7(iOS11)調べ)
それがiOS12からは288MB。
もう少し調べてみると、WebKitのリポジトリに記述があり、iOS12からcanvasの最大使用量がRAMサイズの4分の1に縮小された模様です。
https://github.com/WebKit/webkit/commit/5d5b478917c685e50d1032ccf761ca53fc8f1b74#diff-b411cd4839e4bbc17b00570536abfa8f
余計な事しやがって
そもそもiOS11までは2分の1じゃない気がするんですが。
最初はsafariのバグじゃないかと思って放置今後のアップデートで直らないかなーと思ってたのですが、望みが薄そうなので、このメモリ量でやりくりするしかありません。
とりあえず、今まで使ってはそのままにしていたcanvasを解放してメモリを節約していきましょう。
###で、解放ってどうやんのよ
ブラウザではメモリの解放を手動で出来ないので、delete命令等でオブジェクトの参照を切って、あとは神の見えざる手(GC)に頼るしかありません。
不要になったcanvasをdelete canvasで削除してみます。
const deleteImages = () => {
for( let i = 0; i < ctxs.length; i++ ){
delete ctxs[i];
}
ctxs = [];
}
###これで大丈夫・・・
GCが動くと言ったな。アレは嘘だ。
deleteしても、nullを代入しても、参照は切れているのに、canvasの総メモリとしてはカウントされたままの模様です。
どうしようかと思って苦し紛れに試したのが以下の方法です。
const deleteImages = () => {
for( let i = 0; i < ctxs.length; i++ ){
ctxs[i].width = 0;
ctxs[i].height = 0;
delete ctxs[i];
}
ctxs = [];
}
deleteの前に、canvasのwidthとheightを0にしただけ。
これでいいのかよ、と思う様な解決方法。
恐らく、canvasだけに限った話ならdeleteも要らないんじゃないかと。
###解決はしましたが
とりあえずの方法なので、これが正しく安全な方法とは思えません。
stackoverflowで困ってた外人さんにもお伝えしたところ、この方法で問題の解決が出来た模様で、まぁ間違ってはいないみたいです。
なにか知ってる親切なお方がいましたら、コメントで教えてくれると非常にありがたいです。