[ 編集このページを編集する ]

トップ > MAP作成 > トリガーエディタ > Jass Script > メモリリーク

メモリリークのないMAP作りを行う方法。

Jass NewGenの導入が前提です。導入せずとも利用できる内容も一部ありますが、あったほうが断然有利です。



メモリリークとは?

メモリリーク/memory leak
利用可能なメモリ容量が、徐々に減っていく現象。

ゲームを続けていくと、だんだんと処理が重くなるようなMAPはありませんか?それはPCのメモリ不足が原因です。World Editorでは、一部の変数をのぞいて、変数等は正しく破棄しないと、処理が終了してもPC上のメモリに残り続け、メモリ容量を圧迫->メモリリークを起こします。

メモリ不足は、ユニット数が異常に多いとき、スペルエフェクト等が激しすぎる時などに、一時的にみられる場合もあります。これはメモリリークとは関係なく、それらが消え去れば症状も改善されます。

メモリリークがあるMAPの場合、プレイを続けていくにつれて徐々に処理が重くなってしまい、ゲームの快適性を大きく損ねます。

メモリリークを防ぐには

call Destroy〜 を必ず呼ぶ

real, integer, boolean以外は、使い終わったら set ○○ = null を徹底する

〜BJ な、Blizzard.j で定義されている関数は、安全が確認されているもの以外使わない。

具体的な対策

handle型変数のnull化

handle型の変数はすべて、利用後に null を代入することで、メモリからデータを消去する必要がある。handle型の変数は、変数型 のページにあるように、integer, real, boolean 型以外の全ての変数。

具体的には、以下のようになる。

    function Sample takes player whichPlayer returns nothing
        local item whichItem = GetManipulatedItem()
        local real whichReal = GetRandomReal(0.00,1.11)
        local string whichString = "SampleString"
        local unit whichUnit = GetManipulatingUnit()

        //ここに何かしら処理がある
        //処理終了。以下、handle型変数のnull化

        // item 型とunit型は、ともにwidget型の派生。
        //widget型は、handle型の派生であるから、null化する必要がある。
        // player 型も handle 型の派生だが、
        //takes 〜 で渡された数字は、null化してはいけない。
        //※引数をnull化すると、関数をcallしたときに設定した変数がnull化されてしまう。
        set whichItem = null
        set i = null
    endfunction

オブジェクトの破棄

以下のオブジェクトは、利用後にそれぞれ専用の関数で破棄しなければならない。また、これは必ずnull化の前に行うこと。

boolexpr
call DestroyBoolExpr(whichBoolExpr)
conditionfunc
call DestroyCondition(whichConditionFunc)
defeatcondition
call DestroyDefeatCondition(whichDefeatCondition)
effect
call DestroyEffect(whichEffect)
filterfunc
call DestroyFilter(whichFilterFunc)
fogmodifier
call DestroyFogModifier(whichFogModifier)
force
call DestroyForce(whichForce)
group
call DestroyGroup(whichGroup)
image
call DestroyImage(whichImage)
itempool
call DestroyItemPool(whichItemPool)
leaderboard
call DestroyLeaderboard(whichLeaderboard)
lightning
call DestroyLightning(whichLightning)
multiboard
call DestroyMultiboard(whichMultiboard)
quest
call DestroyQuest(whichQuest)
texttag
call DestroyTextTag(whichTextTag)
timer
call DestroyTimer(whichTimer)
timerdialog
call DestroyTimerDialog(whichTimerDialog)
trigger
call DestroyTrigger(whichTrigger)
ubersplat
call DestroyUbersplat(whichUbersplat)
unitpool
call DestroyUnitPool(whichUnitPool)
gamecache
call FlushGameCache(whichGameCache)
unit
call RemoveUnit(whichUnit)
item
call RemoveItem(whichItem)
location
call RemoveLocation(whichLocation)
rect
call RemoveRect(whichRect)
region
call RemoveRegion(whichRegion)
weathereffect
call RemoveWeatherEffect(whichWeatherEffect)

製作のヒント

handle型変数の戻り値を返す

以上のことを理解できたら、以下の関数はメモリリークが発生してしまうことがわかるだろう。

    //メモリリークを起こす例
    function CreateUnitLocust takes player whichPlayer returns unit
        local unit newUnit = CreateUnit(whichPlayer,'hpea',0,0,0)

        //作成したユニットを、Locustに設定する
        call UnitAddAbility(newUnit,'Aloc')
    
        return newUnit
    endfunction

どこでメモリリークを起こすかわかるだろうか。そう、ローカル変数 newUnit がnull化されていない点だ。ではどうすればよいか。まず、ダメな回答例をあげる。

    //メモリリークを起こさない例
    function CreateUnitLocust takes player whichPlayer returns unit
        
        //Blizzard.j で定義されているグローバル変数、bj_lastCreatedUnit を使う
        set bj_lastCreatedUnit = CreateUnit(whichPlayer,'hpea',0,0,0)

        //作成したユニットを、Locustに設定する
        call UnitAddAbility(bj_lastCreatedUnit,'Aloc')
    
        return bj_lastCreatedUnit
    endfunction

最初からローカル変数を定義しなければよいのだが、これは例としてあまりに不適切なウンコ回答なのでスルーしてもらいたい。誤作動の元になりうる。*1上の例は無視しよう。

では、どうすればよいか。以下が正式な回答だ。

    //handle型変数を、Integerに変換
    function H2I takes handle h returns integer
        return h
        return 0
    endfunction
 
    //メモリリークを起こさない例
    function CreateUnitLocust takes player whichPlayer returns unit
        local unit newUnit          = CreateUnit(whichPlayer,'hpea',0,0,0)
        //ユニットのhandleを、Integer型で格納する。
        local integer newUnitHandle = H2I(newUnit)

        //作成したユニットを、Locustに設定する
        call UnitAddAbility(newUnit,'Aloc')
    
        //メモリリーク対策。newUnit変数を開放
        set newUnit = null

        // integer型を返す操作になるが、
        //integer型とhandle型は等価であるからエラーは発生しない。
        // また、この関数の戻り値は unit型に設定されているので、
        //戻り値のintegerは、この関数の呼び出し元では
        //handle型->widget型->unit型として処理される。
        return newUnitHandle
        // 形式上はユニット型を返さないとコンパイルエラーが起こるので、
        //ここではreturn null を末尾に記入。"ただし、実際には実行されない。"
        return null
    endfunction

まず、integer型でユニットのhandleを格納し、その後にユニットを格納していた変数を開放。最後に、ユニット型変数のかわりに、handleと同じ値を持つinteger型変数を返す。このinteger値は、関数の呼び出し元では本来の戻り値であるはずのunit型変数(=widget=handle)と解釈されるため、正常に動作する。また最終行で、コンパイラのエラーを回避するために null を返す。

ここで利用されている H2I 関数は、GameCacheなどあらゆるシステムで非常に便利。GameCacheの項目でライブラリを公開するので、利用するといいだろう。

handleとは?
ポインタのようなもので、あらゆるオブジェクトが格納されているメモリ上の場所を示す整数(integer)。integer, real, boolean以外のすべては固有のhandleを持つ。詳しくは、変数型の項で。

*1 グローバル変数を使うと、もしこのfunctionが実行に時間のかかるものであれば、二重に呼び出されたときなどに bj_lastCreatedUnit が書き換えられてしまうこともあるからだ