Search code examples
javaintellij-idealibgdxgame-development

What memory leaks/corruption can cause a crash using libgdx?


I am coding a choose your own adventure game where each game state is a different screen class. You create an instance of the class (that will be displayed) when you click the corresponding button on the screen before (think Henry Stickman games but much simpler). I'm not sure how else to do it; this is my first real game.

Here are the two classes I have so far:

public class MarketScreen implements Screen {

    final Heist game;

    private final Texture backgroundImage;
    private final Music backgroundMusic;

    private Stage stage;
    private Table table;
    private TextButton button1;
    private TextButton button2;
    private TextArea text;

    private OrthographicCamera camera;

    public MarketScreen(final Heist game) {
        this.game = game;

        //load images (64x64 pixels)
        backgroundImage = new Texture("background.jpeg");

        //load sound effects and music
        backgroundMusic = Gdx.audio.newMusic(Gdx.files.internal("ironLung.mp3"));
        backgroundMusic.setLooping(true);

        //create camera and spriteBatch
        camera = new OrthographicCamera();
        camera.setToOrtho(false, 800, 480);

        //stage setup
        stage = new Stage(new ScreenViewport());
        Gdx.input.setInputProcessor(stage);

        //table setup
        table = new Table();
        table.setFillParent(true);
        stage.addActor(table);
        table.pad(50);
        table.padTop(100);

        //button and text box setup
        String dialogue = "You turn around from the market stall to the sound of your name at a shout. It's your long-time friend and " +
                " fellow bounty hunter, Wesdru. There's an urgency to her mannerisms that raises your heart rate immediately, " +
                "your hand flying to your sword pommel at your hip. 'It's Lorena. Dras has her.' Keegin Dras, this lawless town's " +
                "most feared crime boss. 'What,' you intone breathlessly. 'She doesn't stoop to meddle with cowboys!' Wesdru grabs " +
                "your shoulders roughly and makes brutal eye contact. 'I don't know why. But I do know we have to go NOW. You and I " +
                "both know how cruel Dras can be.'\n\n\n\nYou only have enough time to buy one thing from the market. What do you take?";


        text = new TextArea(dialogue, Heist.skin);
        table.add(text).grow().colspan(2);

        table.row();

        button1 = new TextButton("Modded Grenade", Heist.skin);
        table.add(button1).width(200).expand();

        button2 = new TextButton("Lockpick", Heist.skin);
        table.add(button2).width(100).expand();

        button1.addListener(new ChangeListener() {
            @Override
            public void changed(ChangeEvent event, Actor actor) {
                game.hasGrenade = true;
                game.setScreen(new OutsideScreen(game));
                dispose();
            }
        });

        button2.addListener(new ChangeListener() {
            @Override
            public void changed(ChangeEvent event, Actor actor) {
                game.hasLockpick = true;
                game.setScreen(new OutsideScreen(game));
                dispose();
            }
        });
    }

    @Override
    public void show() { //called when screen is shown.
        backgroundMusic.play();
    }

    @Override
    public void render(float delta) {
        ScreenUtils.clear(1, 0, 0, 1);

        game.batch.begin();
        game.batch.draw(backgroundImage, 0, 0);
        game.batch.end();
        
        stage.draw();
    }

    @Override
    public void resize(int width, int height) {

    }

    @Override
    public void pause() {

    }

    @Override
    public void resume() {

    }

    @Override
    public void hide() {
        Gdx.input.setInputProcessor(null);
    }

    @Override
    public void dispose() {
        game.batch.dispose();
        backgroundImage.dispose();
        backgroundMusic.dispose();
        stage.dispose();

    }
}

public class OutsideScreen implements Screen {

    final Heist game;

    private final Texture backgroundImage;
    private final Music backgroundMusic;

    private TextButton button1;
    private TextButton button2;
    private TextButton button3;
    private TextArea text;

    private Stage stage;
    private Table table;

    private OrthographicCamera camera;

    public OutsideScreen(final Heist game) {
        this.game = game;

        //load images (64x64 pixels)
        backgroundImage = new Texture("state2.png");

        //load sound effects and music
        backgroundMusic = Gdx.audio.newMusic(Gdx.files.internal("Doom City.mp3"));
        backgroundMusic.setLooping(true);

        //create camera and spriteBatch
        camera = new OrthographicCamera();
        camera.setToOrtho(false, 800, 480);


        //stage setup
        stage = new Stage(new ScreenViewport());
        Gdx.input.setInputProcessor(stage);

        //table setup
        table = new Table();
        table.setFillParent(true);
        stage.addActor(table);
        table.pad(50);
        table.padTop(100);

        //button and text box setup

        String dialogue = "Hello";

        text = new TextArea(dialogue, Heist.skin);
        table.add(text).grow().colspan(3);

        table.row();

        button1 = new TextButton("Storm them", Heist.skin);
        table.add(button1).width(150).expand();

        button2 = new TextButton("Sweet talk them", Heist.skin);
        table.add(button2).width(150).expand();

        button3 = new TextButton("Climb the building", Heist.skin);
        table.add(button3).width(175).expand();

        //STORM THEM
        button1.addListener(new ChangeListener() {
            @Override
            public void changed(ChangeEvent event, Actor actor) {
                game.hasGrenade = true;
//                game.setScreen(new NEXTSCREEN(game));
//                dispose();
            }
        });

        //SWEET TALK THEM
        button2.addListener(new ChangeListener() {
            @Override
            public void changed(ChangeEvent event, Actor actor) {
                game.hasLockpick = true;
//                game.setScreen(new NEXTSCREEN(game));
//                dispose();
            }
        });

        //CLIMB THE BUILDING
        button3.addListener(new ChangeListener() {
            @Override
            public void changed(ChangeEvent event, Actor actor) {
                game.hasLockpick = false;
                game.hasGrenade = false;
            }
        });
    }

    @Override
    public void show() {
        backgroundMusic.play();
    }

    @Override
    public void render(float delta) {
        ScreenUtils.clear(1, 0, 0, 1);

        game.batch.begin();
        game.batch.draw(backgroundImage, 0, 0);
        game.batch.end();

        stage.draw();
    }

    @Override
    public void resize(int width, int height) {

    }

    @Override
    public void pause() {

    }

    @Override
    public void resume() {

    }

    @Override
    public void hide() {
        Gdx.input.setInputProcessor(null);
    }

    @Override
    public void dispose() {
        backgroundImage.dispose();
        backgroundMusic.dispose();
        stage.dispose();
    }
}

Every time I click a button in an instance of MarketScreen, the next screen crashes. Sometimes it lasts about 15 seconds, and then runs for a few more without the background image (simply displaying the default screenfill in the render method) and then crashes, but it always crashes. The error message always starts with

A fatal error has been detected by the Java Runtime Environment:
SIGSEGV (0xb) at pc=0x000000010d7fa85c, pid=22079, tid=23555

which I believe describes a segmentation fault which often results from memory leaks.

What could be the problem? Why does it crash at different speeds?

I originally had the TextButton, Stage, Table, and other objects as game attributes and simply changed the text and arrangement of them in each screen, but I thought maybe Stage was written to be class specific, so I refactored to have each screen class create its own objects. The game usually crashes slower now, but it still crashes.

Edit: Since I wanted the screens to be adaptable based on booleans that tracked the game's progress, I didn't create all the screens ahead of time. Instead, I ended up putting the code that creates the buttons and creates the listeners in show() and called dispose() from hide() (since hide is the only thing called automatically when you change screens) and that seemed to work! There was definitely a memory leak :P


Solution

  • I think you created pasta.

    Error lines:

    game.setScreen(new OutsideScreen(game));
    

    Description: You create new class each time you click with 2nd thread. Then in 1st (render) thread you draw. Racing happens between 2 threads and God knows what happens next.

    Fix: call this in create() method (init)

    OutsideScreen os = new OutsideScreen(game);
    NEXTSCREEN ns = new NEXTSCREEN(game);
    
    //to swap
    
    game.setScreen(ns);
    game.setScreen(os);
    

    Remove dispose() when you click button.

    To dispose call it inside class >>public class MyGdxGame extends ApplicationAdapter<< inside dispose().