fegemo / cefet-games-meow-au

Um jogo simples, descomprometido e maroto para você descobrir sobre a secreta vida animal.
4 stars 17 forks source link

[heisenbug] Bug intermitente na exibição do menu principal #111

Closed andrebrait closed 6 years ago

andrebrait commented 6 years ago

Estive enfrentando um bug intermitente aqui quando inicio a aplicação. Ocorre em +- metade das execuções.

A exceção a seguir é lançada:

:desktop:runException in thread "LWJGL Application" com.badlogic.gdx.utils.GdxRuntimeException: Asset not loaded: menu/menu-background.png
    at com.badlogic.gdx.assets.AssetManager.get(AssetManager.java:148)
    at br.cefetmg.games.screens.MenuScreen.assetsLoaded(MenuScreen.java:101)
    at br.cefetmg.games.screens.BaseScreen.render(BaseScreen.java:197)
    at br.cefetmg.games.transition.FadeTransitionEffect.render(FadeTransitionEffect.java:36)
    at br.cefetmg.games.transition.TransitionScreen.render(TransitionScreen.java:71)
    at com.badlogic.gdx.Game.render(Game.java:46)
    at br.cefetmg.games.MeowAuGame.render(MeowAuGame.java:36)
    at com.badlogic.gdx.backends.lwjgl.LwjglApplication.mainLoop(LwjglApplication.java:225)
    at com.badlogic.gdx.backends.lwjgl.LwjglApplication$1.run(LwjglApplication.java:126)

Consegui arrumá-lo e estou testando a solução. Reportando aqui para constar.

fegemo commented 6 years ago

Legal, @andrebrait. Após a correção, você chegou a fazer testes extensivamente e não viu o bug ser reproduzido?

andrebrait commented 6 years ago

Testei extensivamente, e até vi ser reproduzido, mas com menos frequência. Estou analisando melhor ainda.

andrebrait commented 6 years ago

Reverti. Acabou que eu estava fazendo merda aqui e não vi.

andrebrait commented 6 years ago

Parece que se trata de um bug relacionado a uma condição de corrida com o objeto da classe AssetManager que usamos nos jogos.

andrebrait commented 6 years ago

Parece que havia uma condição de corrida entre as Threads "AsynchExecutor-Thread" e "LWJGL Application"

Criei um Proxy para AssetManager e substituí ele em BaseScreen. O Proxy redireciona todas as chamadas de métodos públicos para o AssetManager. Assim consegui ver quando threads acessavam o objeto.

Não entendi exatamente qual o biziu, mas acredito que colocando uma trava sobre o objeto no render resolveu o bug. Estou tentando reproduzir há um bom tempo sem sucesso com a trava.

EDIT: Aconteceu de novo. Vou ver o que consigo achar

Objeto acessado na Thread AsynchExecutor-Thread
Objeto acessado na Thread AsynchExecutor-Thread
Objeto acessado na Thread LWJGL Application
Objeto acessado na Thread LWJGL Application
Objeto acessado na Thread LWJGL Application
Objeto acessado na Thread LWJGL Application
Objeto acessado na Thread LWJGL Application
Objeto acessado na Thread LWJGL Application
Objeto acessado na Thread LWJGL Application
Objeto acessado na Thread LWJGL Application
Exception in thread "LWJGL Application" com.badlogic.gdx.utils.GdxRuntimeException: Asset not loaded: menu/menu-background.png
    at com.badlogic.gdx.assets.AssetManager.get(AssetManager.java:148)
    at br.cefetmg.games.screens.BaseScreen$AssetManagerProxy.get(BaseScreen.java:76)
    at br.cefetmg.games.screens.MenuScreen.assetsLoaded(MenuScreen.java:107)
    at br.cefetmg.games.screens.BaseScreen.render(BaseScreen.java:373)
    at br.cefetmg.games.transition.FadeTransitionEffect.render(FadeTransitionEffect.java:36)
    at br.cefetmg.games.transition.TransitionScreen.render(TransitionScreen.java:71)
    at com.badlogic.gdx.Game.render(Game.java:46)
    at br.cefetmg.games.MeowAuGame.render(MeowAuGame.java:36)
    at com.badlogic.gdx.backends.lwjgl.LwjglApplication.mainLoop(LwjglApplication.java:225)
    at com.badlogic.gdx.backends.lwjgl.LwjglApplication$1.run(LwjglApplication.java:126)
andrebrait commented 6 years ago

Acredito ter desvendado a treta agora!

Encontrei um "guru" de LibGDX comentando sobre esse problema

If you set a screen as the current screen before your assets are loaded, then it's normal. either you load the assets in the Render event of your screen, or you load your assets in the main Render event and, only once finished, you display your screen

Daí tentei descobrir e... eureka!!

Isto ocorre com MenuScreen, RankingScreen e PlayingGameScreen porque elas têm o load em seu método show(), ou seja, elas implementam diretamente o método appear(), enquanto a PlayingGameScreen utiliza um dos GameSequencer para gerenciar que jogo irá carregar, já carregando todos os assets previamente (os jogos apenas recebem o BaseScreen criado). Assim, na PlayingGameScreen (na hora de jogar) este erro nunca ocorre, enquanto que nela em outras situações e nestas outras telas ocorre este problema.

Como o método show() é chamado assim que a tela é colocada como a principal no método setScreen da classe Game, este erro ocorre de forma intermitente!

Testei passar o método appear() ao fim do construtor do BaseScreen e... sucesso! Ele não deve ser chamado dentro do show(), mas antes. Mas esse ( o construtor) não é um lugar muito feliz para um carinha desses estar. Assim, segui o conselho do camarada ali e incluí um passo no render() do BaseScreen (com um boolean para controlar a entrada nessa seção) para tentar carregar os assets, ou seja, chamar o appear().

Já estou há 30 minutos startando a aplicação e alternando entre as telas de Ranking e o Menu (que eram as mais afetadas) e não consegui mais reproduzir o Heisenbug.

Ufa! Melhor integrar essa mudança no seu projeto base no futuro.

andrebrait commented 6 years ago

Corrigido no #117