Jump to content

Error creating tile sprite after destroying and creating new Phaser.Game instance


jbg
 Share

Recommended Posts

If you create a Phaser.Game instance, destroy it, and then subsequently create a new game instance game.add.tileSprite() will fail.  This appears to affect Chrome but not Firefox.

 

The error appears to occur due to the special __default image not existing for the new instance.  Phaser.TileSprite() assumes that game.cache.getImage('__default', true) will always return non-null, which turns out not to be a valid assumption in the situation I'm describing.

 

What's the correct way to approach this?  Manually creating a new cache for the new instance (e.g. by game.cache = new  Phaser.Cache(game) or something similar) doesn't resolve the issue.

 

Trivial case to illustrate the problem below.  Clicking the button creates a Phaser.Game instance if there isn't already one, or destroys one if it exists.  On Chrome the first game instance will be created with no problems, and the world tiled with a blue sprite.  Subsequent instances will fail with the error "Cannot read property base of null" (on line 60273 of the uncompressed 2.8.1 phaser.js).  On Firefox no error occurs.

 

There's one line commented out in the example below.  It creates a non-tiled sprite, which does not produce the error (and demonstrates that everything else is working as expected).

 

<!doctype html>
<html>
<head>
<meta charset="UTF-8"/>
<title>test</title>
<script src="http://cdnjs.cloudflare.com/ajax/libs/phaser-ce/2.8.1/phaser.js"></script>
</head>
<body>
<button id="button">Click</button>
<div id="canvas"></div>
<script>
window.onload = function() {
        var d, n, game;
        var imgData = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAABmJLR0QA/wD/AACNv0geAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AYIFzY3uKzt4gAAAJlJREFUSMftldsKACEIRDX8/1+efViIyM2s7UKQRG+ljp2JiUAzI9DkWJsAYIAnJmDGncH+Icd9VPAFbXsC0UMu4ZYO34+kaJIBLp3vQL1Nog6zyp+pUb5Hxjpow/1O/AVGcZqKuCQfmMDmQIMiTe+kers+mH/67/q8qI+S4Cwz/eyMPrREwelosTkbNC2A/Gd1Kcnaac63igdzJl46JOj1wQAAAABJRU5ErkJggg==';

        d = document.getElementById('button');
        n = document.getElementById('canvas');

        function preload() {
                game.load.image('foo', imgData);
        }
        function create() {
                var sp;

                sp = game.add.tileSprite(0, 0, game.world.width,
                        game.world.height, 'foo');
                //sp = game.add.sprite(0, 0, 'foo');
        }
        function update() {
        }
        function handler() {
                if(game) {
                        game.destroy();
                        game = null;
                } else {
                        game = new Phaser.Game({
                                width:          800,
                                height:         600,
                                renderer:       Phaser.AUTO,
                                parent:         n,
                                enableDebug:    false,
                                state:          {
                                        preload:        preload,
                                        create:         create,
                                        update:         update
                                },
                        });
                }
        }
        d.onclick = handler;
};
</script>
</body>
</html>

 

Link to comment
Share on other sites

You can try, in preload:

this.cache.checkKey(Phaser.Cache.IMAGE, '__default') || this.load.image('__default', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgAQMAAABJtOi3AAAAA1BMVEX///+nxBvIAAAAAXRSTlMAQObYZgAAABVJREFUeF7NwIEAAAAAgKD9qdeocAMAoAABm3DkcAAAAABJRU5ErkJggg==');

But that gives me another error from a missing baseTexture.source.

Link to comment
Share on other sites

I think it's just a bug.  There underlying problem is that Phaser.Cache.addDefaultImage() creates an image and adds it to the cache via the image's onload() method.  This creates a race condition where if the new instance's create() method is called (and it does anything relying on the special __default cache entry, like a tile sprite) before the image's onload() is completed you get the error we're seeing.

 

An simple way to illustrate the problem (and a crude hack to work around it) is to wrap the create() method in a zero-length setTimeout():

        function create() {
                setTimeout(function() { crudeHack(); }, 0);
        }
        function crudeHack() {
                var sp;

                sp = game.add.tileSprite(0, 0, game.world.width,
                        game.world.height, 'foo');
                //sp = game.add.sprite(0, 0, 'foo');
        }

 

...makes the problem go away.  Or at least hides it.

Link to comment
Share on other sites

The problem is that Phaser loads the default textures asynchronously (a fix for #138) but doesn't delay booting until they're complete.

For your TileSprite, you should try overwriting the TileSprite constructor to use Phaser.Cache.DEFAULT instead of '__default'.

 

Link to comment
Share on other sites

Monkeypatching TileSprite works for the toy example but I'd be concerned about getting surprised by other manifestations of the same issue later on.  Particularly since, being a race condition, when it shows up it won't necessarily be consistent or reproducible.

 

For my purposes wrapping the game's create() method in setTimeout(fn, 0) works well and has the advantage that it'll prevent any other surprises that might be hidden behind the cache behaviour.  I really think it's just a bug---either Phaser.Game shouldn't boot until all async startup tasks are completed, or builtin methods that rely on specific stuff being in the cache (like Phaser.TileSprite) should check for and handle the exception themselves.

Link to comment
Share on other sites

It's definitely a bug but I'm trying to find workarounds for you.

If you definitely need to wait for the default image, use a boot state:

update () {
  if (this.cache.checkKey(Phaser.Cache.IMAGE, '__default')) {
    this.state.start('main');
  }
}

With setTimeout you're just going to have to wait for the sprite in update.

Link to comment
Share on other sites

Cool.  That's more or less what I'd put together.  I'm already having to wait for other asynchronously-loaded assets to be ready---the real code I'm working on (as opposed to the toy example I posted above) involves using Phaser in a transparent canvas for UI elements over a scene rendered via Three.js in another canvas.

 

I had been hoping it was just some lifecycle method or config option I was missing or something like that.  But knowing what the underlying problem is it turns out I already had most of a reliable workaround already coded.

Link to comment
Share on other sites

 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...