2017年8月28日月曜日

千年戦争アイギス・御城プロジェクト:REの動作を改善するユーザースクリプト

DMM社提供の「千年戦争アイギス」及び「御城プロジェクト:RE」は基本となる構成が同じせいか、経験上ブラウザクラッシュ(主にChrome)の傾向も似通っている.

クラッシュ時のエラーコンソールを確認するに, 主たる原因はcanvasグラフィックを描く際のメモリ浪費にあると当たりをつけ, この部分を修正するユーザースクリプトを作ってみた.

なお, これで本当に問題が解決するかどうかは全く不明なため, 本スクリプトを利用する際は全て自己責任にてお願いします. 現状困っていないのであれば無理に導入する必要はありません.

(今日の今日作った突貫工事品だし, さほど検証もしていないし. 効果があるといいなあ. 何度もクラッシュするのは精神的にきついので)

→実際に改善効果があるっぽいので, 困っている方は是非試してみて下さい.




[使い方]


通常のユーザースクリプトの導入と一緒.
  • Web Extensionsをサポートする環境(FireFox, Chrome, Opera等)
    Tampermonkeyアドオンを導入して下記コードを新規ユーザースクリプトとして登録
  • レガシーFireFox(※動くかどうか確認していないが多分動くと思う)
    GreaseMonkeyアドオンを導入して下記コードをユーザースクリプトとして登録
導入するとカンバス周りのAPIの中身を書き換えて, ゲーム本体に手を入れることなくパフォーマンスが改善します.

    
    // ==UserScript==
    // @name        aigis_oshiro_light
    // @namespace   defghi1977
    // @description 千年戦争アイギス/御城プロジェクト:REの動作を保護します
    // @include     http://assets.shiropro-re.net/html/Oshiro.html
    // @include     http://assets.millennium-war.net/*
    // @version     0.1
    // @grant       none
    // ==/UserScript==
    
    //createImageDataメソッドの呼び出し過ぎによるメモリ浪費を抑え
    //予期せぬクラッシュの頻度を下げます
    'use strict';
    {
     const proto = CanvasRenderingContext2D.prototype;
     const o_createImageData = proto.createImageData;
     const initialSize = 2048 * 2048 * 4;//16MB
     let sharedBuffer = new ArrayBuffer(initialSize);
     proto.createImageData = function(x, y){
      const requiredSize = x * y * 4;
      if(requiredSize > sharedBuffer.length){
       sharedBuffer = new ArrayBuffer(sharedBuffer.length * 2);
      }
      const array = new Uint8ClampedArray(sharedBuffer, 0, requiredSize);
      array.fill(0);
      const id = new ImageData(array, x, y);
      return id;
     };
    }
    
    


    [原因(仮説)]


    本ゲームはゲーム内のテキスト(およびドット絵)を描画する際にゲームサーバーから転送された(文字)画像を利用している.

    この画像は何らかの形式で圧縮されており, これを展開した後にcanvas要素に描画することとなるが, その際に生成するImageDataオブジェクトの数がべらぼうに多い.

    具体的には1操作につき多くのImageDataオブジェクトが生成されるが, その都度数百KB〜十数MBのメモリを消費しており, 現状全て使い捨てである.

    そのため, ゲーム本編よりもむしろイベント会話中の方がメモリの浪費が激しく, ブラウザが不安定となり得, これは経験的なゲームクラッシュのタイミングとも一致している.

    ・追記
    なお, 得られた画像は内部で高コストなcanvas要素としてキャッシュされており, これらも徐々にメモリを逼迫していく(メモリリーク). そのため,  ゲームを進行していくとどこかのタイミングでメモリが確保できなくなりクラッシュする(しやすくなる)のである.


    [対策]


    根本的にフレームワーク側の「お大尽」なメモリ管理に起因する問題であるため, ユーザースクリプト側での対策はかなり限定される. (フレームワーク内部のメモリリークを改善することは出来ない)

    そのため対症療法ではあるが, ImageDataオブジェクトが単一のメモリ(ArrayBuffer)を共有するように機能を改めた. (createImageDataメソッドとputImageDataメソッドの呼び出し順を検証するに,ゲーム内部でのImageDataオブジェクトの再利用が存在しないことから)

    こうすることでメモリの確保・開放のサイクル(ガベージコレクトの頻度)が抑えられ, 結果アプリの安定度が増す.

    この考え方は他のブラウザゲームにも適用可能かもしれないので憶えておくと良いだろう.

    ・追記(2017/09/03)
    Chromeではハードウェアアクセラレーションが有効な場合にImageDataオブジェクトを介したグラフィック操作が高コストとなる. そのため, 当該ゲームにおける推奨動作環境として「ハードウェアアクセラレーションをOFF」とする必要があるようだ.

    ・追記(2017/09/14)
    Chromeに導入して一週間程経ったが,体感では大幅にクラッシュの頻度が減った(というかクラッシュしなくなった). 時折ゲームが停止しているように見えることがあるものの, おそらくメモリのガベージコレクトによるものであり, このタイミングでのクラッシュが無くなっただけでも大きな成果と言えよう.

    0 件のコメント:

    コメントを投稿