LibGDX Mälu Haldamine¶
Mälu haldamine on oluline aspekt mängude arendamisel, kuna see mõjutab nii mängu jõudlust kui ka stabiilsust. Graafika, heliefektid ja muud ressursid võivad võtta palju mälu ning enamik neist ei ole hallatud Java prügikoguja (Garbage Collector) poolt. Selle asemel hallatakse neid otse süsteemitasandil, näiteks GPU või helidraiverite poolt. Seetõttu on oluline ressursside eluiga teadlikult hallata ja need õigel ajal vabastada.
"Disposable" Liides¶
LibGDX-is on paljud klassid märgistatud liidesega Disposable, mis tähendab, et neid tuleb
manuaalselt vabastada, kui need ei ole enam kasutusel.
Näited klassidest, mis kasutavad "Disposable" liidest:
Graafilised ressursid |
|
Graafika ja joonistamine |
|
Heli ja füüsika |
|
Mängu ressursside haldus |
|
Kuidas "Disposable" objekte õigesti vabastada¶
Kui loote Disposable objekti, tuleb see pärast kasutamist vabastada meetodiga .dispose().
LibGDX-i Screen liidesel on spetsiaalne meetod dispose(), kuhu tuleb kirjutada kõik
ressursside vabastamised. See meetod käivitatakse siis, kui ekraani enam ei vajata (näiteks
mängu sulgemisel).
Näide:
Texture texture = new Texture("menu_background.png");
...
texture.dispose(); // Tekstuuri vabastamine, kui see pole enam vajalik
Kui mängul on mitu ekraani (näiteks menüü ja mänguekraan), tuleb veenduda, et ühele ekraanile loodud ressursid vabastatakse, kui ekraan suletakse:
@Override
public void dispose() {
texture.dispose(); // Vabastame mälu
}
Objektide Taaskasutamine (Object Pooling)¶
Mälu optimeerimiseks võib kasutada "Object Pooling" tehnikat. See on kasulik eriti siis, kui samatüübilisi objekte tuleb luua ja hävitada korduvalt (nt kuuliobjektid "shooter" mängus). Selle asemel, et iga kord uus objekt luua, hoitakse neid mälus ja taaskasutatakse, kui objekt on vaba. Kui kõik objektid on juba kasutuses, luuakse alles uus.
Kuulide korduvkasutamine Object Pooling tehnikaga¶
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Pool;
public class Bullet implements Pool.Poolable {
public Vector2 position;
public Vector2 velocity;
public boolean alive;
/**
* Kuuli konstruktor. Initsialiseerib muutujad.
*/
public Bullet() {
this.position = new Vector2();
this.velocity = new Vector2();
this.alive = false;
}
/**
* Initsialiseerib kuuli. Kutsu seda meetodit pärast kuuli hankimist poolist.
*/
public void init(float posX, float posY, float speedX, float speedY) {
position.set(posX, posY);
velocity.set(speedX, speedY);
alive = true;
}
/**
* Tagasikutsumise meetod, mida kutsutakse automaatselt Pool.free() poolt.
* Peab lähtestama kõik kuuli olulised väljad.
*/
@Override
public void reset() {
position.set(0, 0);
velocity.set(0, 0);
alive = false;
}
/**
* Meetod, mida kutsutakse igal kaadril – uuendab kuuli liikumist.
*/
public void update(float delta) {
// Uuendame asukohta: uus_positsioon = vana + (kiirus * aeg)
// mulAdd on LibGDX optimeeritud meetod vektorite liitmiseks
position.mulAdd(velocity, delta);
// Kui kuul on ekraanilt väljas, märgime ta "surnuks"
if (isOutOfScreen()) alive = false;
}
}
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Pool;
public class World {
// Massiiv aktiivsete kuulide hoidmiseks
private final Array<Bullet> activeBullets = new Array<Bullet>();
// Kuulide pool (objektide hoidla)
private final Pool<Bullet> bulletPool = new Pool<Bullet>() {
@Override
protected Bullet newObject() {
return new Bullet();
}
};
public void update(float delta) {
// Kui soovid luua uue kuuli:
Bullet item = bulletPool.obtain();
// Initsialiseerime kuuli asukohaga (2, 2) ja kiirusega (100, 100)
item.init(2, 2, 100, 100);
activeBullets.add(item);
// Kontrollime aktiivseid kuule ja vabastame "surnud" kuulid.
// Käime massiivi tagurpidi läbi, et eemaldamine ei rikuks indekseid.
int len = activeBullets.size;
for (int i = len; --i >= 0;) {
item = activeBullets.get(i);
if (!item.alive) {
activeBullets.removeIndex(i);
bulletPool.free(item);
}
}
}
}
See lähenemine vähendab prügikogumise koormust ja parandab jõudlust, eriti kui objekte luuakse
tihti. obtain() hangib objekti poolist, free() vabastab objekti tagasi taaskasutamiseks,
et vältida pidevat uute objektide loomist ja mälu kulutamist.
AssetManager – Ressursside Keskne Haldamine¶
Kui mäng kasutab palju ressursse (tekstuure, fonte, helisid), võib olla kasulik kasutada
AssetManager klassi, mis aitab neid efektiivselt hallata.
AssetManager kasutamine¶
AssetManager assetManager = new AssetManager();
// Laeme ressursid
assetManager.load("player.png", Texture.class);
assetManager.load("background_music.mp3", Music.class);
assetManager.finishLoading();
// Kasutame ressursse
Texture playerTexture = assetManager.get("player.png", Texture.class);
Music backgroundMusic = assetManager.get("background_music.mp3", Music.class);
// Vabastame kõik ressursid mängu lõpetamisel
assetManager.dispose();
AssetManager on kasulik, kuna see:
Laeb ressursse taustal (asünkroonselt), vältides mängu külmumist laadimise ajal.
Väldib sama ressursi korduvat laadimist – kui sama fail on juba laetud, tagastatakse olemasolev eksemplar.
Haldab automaatselt
Disposableobjekte, vabastades needdispose()kutsumisel.
Loe AssetManager-i kohta lähemalt siit:
GameDevDoc: Asset Manager
VisualVM – Mälulekete ja Jõudluse Diagnostika¶
VisualVM on Java rakendus, mis aitab tuvastada mälulekkeid ja jälgida mängu jõudlust reaalajas.
VisualVM-i kasutamine:¶
Käivita oma mäng ja ava VisualVM. Allalaadimislink: https://visualvm.github.io/
Leia protsess, mis vastab sinu mängule.
Vasakul on näha lokaalselt töötavaid programme. Antud juhul on näha, et mänguserver töötab lokaalselt.
Kasuta Monitor vaadet, et jälgida:
CPU kasutust
Mälu kasutust
Objektide arvu
Kui mälu kasutus kasvab pidevalt ja ei vähene, võib see viidata mälulekkele.
Kasuta Perform GC nuppu, et sundida prügikogumist ja vaadata, kas mälu vabaneb korralikult.
VisualVM on tõhus tööriist mälulekete avastamiseks – see aitab tuvastada olukordi, kus programm ei vabasta mälu. Selline olukord võib tekkida ka objektidega, mida haldab Garbage Collector automaatselt.
Mida Garbage Collector automaatselt haldab:
Java Garbage Collector vastutab objekti mälu vabastamise eest, kui seda enam ei kasutata.
See hõlmab selliseid objekte nagu ArrayList, HashMap ja String, mis asuvad heap
mälus ning mida haldab GC.
// Kuni muutuja 'list' viitab sellele nimekirjale, hoiab GC seda mälus.
// Kui 'list' viide kaob (nt list = null või meetod lõppeb), vabastab GC mälu.
ArrayList<String> list = new ArrayList<>();
list.add("Test");
list = null;
Sama loogika kehtib ka teiste tüüpide puhul:
HashMap: Mälu vabaneb, kui eemaldate kaardist kirjed või kui
HashMapobjektile enam ei viidata.String: Teksti all olev mälu vabastatakse, kui ühegi muutuja kaudu pole sellele sõnele enam võimalik ligi pääseda.
Miks see oluline on?
Näiteks koodis, kus on mõni katkine lõpmatu tsükkel, mis pidevalt teeb ja saadab pakette, võib tekkida olukord, et pakette tehakse kiiremini kui prügikoguja suudab vanade pakettide mälu vabastada – ning mingil hetkel jookseb mäng kokku näiliselt ilma ühegi veateateta.
Korduvad VisualVM-i probleemid ja lahendused:
Kõige tavalisem probleem on see, et VisualVM vajab JDK asukoha parameetrit käivitamiseks. Selle lahendamiseks mine terminalis VisualVM kausta ja käivita järgmiste parameetritega:
--jdkhome "C:\path\to\your\jdk" --userdir "C:\path\to\user\directory"
VisualVM on vahend töötavate Java rakenduste kohta info saamiseks. Selle abil saab jälgida rakenduse jõudlust ja tuvastada erinevaid probleeme, sealhulgas mälulekkeid.