Jump to content

iOS8 Memory Issues/Two caches?


kenray
 Share

Recommended Posts

The app that we are trying to turn into a standalone app with Cordova/PhoneGap (or Cocoon) is still crashing on iOS 8 on older devices (original iPad Mini and iPad 2's for example) under Phaser 2.2.1 and we're trying to determine why. Regardless of the memory reduction actions we've taken (audiosprites instead of multiple audio files, .m4a instead of .mp3, clearing the Phaser cache before going to the next screen, etc.), and implementing suggestions from the forum (specifying CANVAS instead of AUTO to avoid iOS's poor implementation of WebGL) we still get a crash. 
 
We have run this app dozens of times, starting with a cleared cache and no other apps open on the iPad Mini we're testing with. We're using the "Activity Monitor" instrument in Instruments (version 6.2) to see what's going on with memory. We've noticed that our app remains pretty consistent through its use, with Real Memory starting out around 11MB and actually going DOWN as low as 6.5MB; Virtual Memory operating within the range of 589 - 595MB and staying about the same throughout, and CPU % averaging around 8%, and only going as high as 21% during playback. 
 
However we noticed that there are two processes running at the same time named "com.apple.WebKit", and one of them remains kind of consistent (averaging around 7 MB Real/570 MB Virtual/0.5% CPU), but the OTHER starts out at 53 MB Real/767 MB Virtual/73% CPU, and although the CPU% stays about the same, the Real/Virtual memory continually climbs until it reached 204 MB Real/918 MB Virtual and then it crashed. FYI, the app performs beautifully, but does slow down just before it crashes (usually on the screen just before the one it crashes on), the crash usually occurs as we transition from one screen to another, and we end up frozen on a white screen (it doesn't kick us back to the iOS Home Screen, which is interesting).
 
Based on what we're seeing, it looks like images (and probably other assets like audio) that we have purged from Phaser's cache with game.cache.removeImage(<key>) are being cached by the web browser engine (which I'll call the "WebKit Engine" for clarity) because if we return to a page that has been viewed, even though we've cleared out the Phaser cache, the page redraws in about 1/2 the time it originally took. This would also support what we see in the "com.apple.WebKit" process as it would make sense that it gets larger and larger as we go screen to screen because it is caching everything.
 
We're really in a bind here, as we need to be able to deploy to this platform and it appears as though the Phaser part of everything is fine; so my questions are:

 

1) Are there actually two different caches - Phaser's and the WebKit Engine?

 
2) If so, is there a way to prevent the WebKit Engine from caching assets we load/use in Phaser?
 
3) If the WebKit Engine is caching assets, is there a way to delete items from the WebKit Engine's cache? 
 
     - I've read about methods to pseudo-delete items from a browser cache by setting the 'src' of an image to a single pixel bitmap - that relies on the DOM, though, but is there some form of this kind of implementation that can be done from Phaser? (Assuming it would help, that is)
 
     - One idea that came up was possible forcing a full restart of the app when we detect a memory issue (if that's even possible), but with instructions to jump to the next screen it was supposed to go to after the app restarts. I'm not even sure if this can be done, or if it would affect this issue, but I figured I'd mention it anyway.
 
4) Do you see another reason why the WebKit Engine process would continually increase over the running of our app?
 
5) Is there a better way to look at what's going on memory-wise with our app other than Instruments?
 
Thanks for any help you can provide...
Link to comment
Share on other sites

How does it run when not being viewed through Cordova? Do you see a difference between a webview in Safari and a webview saved to the home screen? 

 

You're going to be hard pressed to clear webkits cache manually. However that shouldn't be the issue. If that was the case Safari would be crashing every 15 minutes because you looked at a bunch of images. Generally a cached item is in memory until it makes sense to only reference it from a local cache file, and then its removed once that fills up or a specified time has passed. My bet is that you're experiencing some sort of memory leak thats not allowing the removal of images from memory. Make sure you're not referencing the images you want to remove any where else. They don't actually get "deleted" until the Garbage Collector removes them, and it will only get rid of items that are no longer being referenced by code... even the code hidden far away in a deep dark corner.

Link to comment
Share on other sites

The app runs all the way through (at least once) when running just in Mobile Safari, but it will eventually crash. The WebKit Engine still grows and grows, but I'm guessing that since it is running "raw" in Mobile Safari that the Cordova app wrapper is using some more memory so the available amount is less and so it crashes sooner.

 

As to referencing - isn't that a responsibility of Phaser's manipulation of the images? Meaning... if I load and add an image with:

var MyImage = game.load.image('image1','/path/to/image1');var MySprite = game.add.sprite(100,100,'image1');

and then remove the image from the cache later with:

game.cache.removeImage('image1');

Does the fact that MySprite and MyImage both have references relating to the image prevent the Garbage Collector from getting rid of it? 

Link to comment
Share on other sites

I'm really thinking something is holding a reference to these image objects. It could be as simple as setting MyImage = null; before removing the image from the cache, but it could be more than that. 

 

You can dereference these variables by setting them to null. Phaser has no idea what variables you may have created that reference the images/sounds/json files it loads in for you. 

Link to comment
Share on other sites

I will check my code and see - I may be nulling out the references *after* I remove the assets, so I can swap them and see if it makes a difference. But what I really don't understand is how the Phaser app process running on iOS8 remains stable memory-wise but the WebKit engine process grows and grows... what's the relationship between the two? I'm not sure if nulling out variables in Phaser will do anything to the memory used by the WebKit engine process, which appears to the be the culprit. 

 

I mean that since our app's process in Instruments/Activity Monitor ("FlywithArnold") is remaining stable, if we can only make the WebKit engine process ("com.apple.WebKit") remain stable then the crashes would go away.

Link to comment
Share on other sites

I've only messed with Cordova a little bit. I actually use Ejecta for all my stuff which avoids the webview completely. You may want to check with some Cordova devs and see whats up. I do remember an issue existing when 8 first came out that caused all sorts of issues, but I believe that got sorted out with a later iOS update. 

Link to comment
Share on other sites

It's not actually the images (we think) that's the issue, although it may be a contributor... audio loads and is uncached after each screen as well. And 'yes', there's enough images and audio (even in optimized form) where we can't preload everything and run the app without a crash (unfortunately).

Link to comment
Share on other sites

OK, after doing more testing, we finally see that it doesn't matter if it's audio or images. I put together a very simple example that you can use to see what's going on:

 

If you go to http://www.interactplayer.com/memorytest/index.html, it loads up the state "MainState" and loads and shows a 1024x1024 coin image - click it and it destroys the cache and reloads the same state, but with no assets. Click again, and it reloads the state with a flipped version of the coin (a different image file). If you continue clicking, you'll see it flips between the coin and the flipped version, with black screens in between. 

 

If you run this in Safari on the Mac desktop, or compile it as a Cocoon app and run it on iOS 8, if you look at usage through Instruments (or the equivyou will see the memory continue to rise until (at least on iOS 8) you crash, even though we are destroying the cache before each restart of the state.

 

Hopefully this will help get this problem tracked down - because otherwise any Phaser apps that show "screens" of content (like a presentation app, or wireframe demo, or RPG, etc.) that include audio or images will eventually crash on iOS 8!

Link to comment
Share on other sites

Weird - Works on my Mac in the same Chrome version (along with Safari) - the code is not that complex. I *did* notice that I had a few variables that aren't being used from previous tests that I cleaned up, and the game size was defined as 2048x2048 with 2048x2048 images (which froze Safari), so I changed the images to 1024x1024 but forgot to change the game size to match. 

 

Give it another try and let me know if you still crash. 

Link to comment
Share on other sites

OK, after some additional testing, the memory goes up but then remains stable - but the problem is that even though I'm purging the Phaser cache, the WebKit browser cache is still retaining the images being used. What we're looking for is that when going to a blank page that it drops back down to where it would be if no images were cached... I'm going to modify the test to load 5 images so it's easier to see the memory going up and not coming back down.

 

EDIT: I modified the test - the com.apple.WebKit process rises 30MB over the 5 different images and never goes back down.

 

http://www.interactplayer.com/memorytest/index.html

Link to comment
Share on other sites

According to the Phaser documentation its clearing the PIXI cache by default when you clear the Phaser Cache.

/**    * Removes an image from the cache and optionally from the Pixi.BaseTextureCache as well.    *    * @method Phaser.Cache#removeImage    * @param {string} key - Key of the asset you want to remove.    * @param {boolean} [removeFromPixi=true] - Should this image also be removed from the Pixi BaseTextureCache?    */    removeImage: function (key, removeFromPixi) {        if (typeof removeFromPixi === 'undefined') { removeFromPixi = true; }        delete this._images[key];        if (removeFromPixi)        {            PIXI.BaseTextureCache[key].destroy();        }    },

Though I guess you can make entirely sure thats happening by calling your removes:

game.cache.removeImage('image1',true);

Link to comment
Share on other sites

OK, I also figured out how to do that with PIXI.Texture.removeTextureFromCache(myRef).destroy(true); but the browser (it appears) is still hanging onto the images that are loaded even if Pixi/Phaser are not. I am trying to get to the point where if I test the memory at each blank screen I will have the same amount of memory used (or really close to the same amount).

 

I'm not calling 'removeImage', I'm using 'game.cache.destroy()' - is that an issue?

Link to comment
Share on other sites

I ran your test and clearly nothing is ever removed from an actual memory cache. I've never for the life of me heard of a way to remove something from a browser cache through javascript. Perhaps someone like Rich will chime in and say I'm wrong. 

 

I've implemented something that one might consider a 'cache', but it's simply designed to prevent a new Image() object from being created for an image with the same source. Otherwise creating two sprites with the same image source would fill up memory with duplicate images. My bet is that Phaser simply removes the image from the listing (not from the browser cache) so it can be replaced or recreated. 

Link to comment
Share on other sites

Your going to love this:

/** * Remove a texture from the global PIXI.TextureCache. * * @static * @method removeTextureFromCache * @param id {String} The id of the texture to be removed * @return {Texture} The texture that was removed */PIXI.Texture.removeTextureFromCache = function(id){    var texture = PIXI.TextureCache[id];    delete PIXI.TextureCache[id];    delete PIXI.BaseTextureCache[id];    return texture;};

I think this confirms my thoughts in the previous message.

 

var texture = PIXI.TextureCache[id]; // this saves a reference to the image we are planning to 'remove' from the cache.

delete PIXI.TextureCache[id]; // this removes it from the actual PIXI.TextureCache object.

delete PIXI.BaseTextureCache[id]; // this removes it from the PIXI.BaseTextureCache as well;

return texture; // this returns the very object we deleted in the cache... which means it still exists. 

 

So it's not doing anything fancy, just hoping that if it dereferences the object and you don't store the returned texture in a variable someplace that the GC will eventually kick in and clear it out.

 

My best bet is that you're running into the memory limits of the device (either because the GC isn't kicking in, or it can't kick in because the data is referenced some place). Those calls aren't actually removing anything from the actual device memory cache.

 

Your best bet is to make 100% sure that these images aren't being referenced/stored because the GC will never kick for them. Otherwise I'm afraid your running into the memory limits of the device before the GC removes them in most instances. I've run into this on older devices with images much smaller than this so I wouldn't be surprised. It can be a tough one to track down.

Link to comment
Share on other sites

Remember everything in canvas or webgl is hardware accelerated. So you have two places that could be killing your app. Try hardware accelerating multiple images on a large website and watch it crash and burn. You have to be smart about what your doing on the web. For things like this native is better.

Link to comment
Share on other sites

Thanks for your posts!

 

When you say "native is better", what do you mean? I was originally (several weeks ago) using Phaser.AUTO and was told that doing that under iOS 8 uses WebGL... so I changed it to Phaser.CANVAS (which helped a lot!) but you're saying both Canvas and WebGL are hardware accelerated... any suggestions?

Link to comment
Share on other sites

The biggest problem you're going to have is with control. You can't stop the device from hardware accelerating (you wouldn't want to anyways), so you have to live within those restraints. 

 

Native is better in this situation because your main problem seems to be with memory usage. Which is something you have no real control of in javascript. You're at the mercy of the garbage collector which may or may not kick in when you need it to. You could try something like Ejecta or CacoonJS and see if they work better, but honestly I think the size and number of images you need to use is simply too much. 

Link to comment
Share on other sites

  • 1 month later...

wow, this was a really nice post, documents very well some of the problems I'm having too, thanks for that!

 

I've clashed with mobile Safari's odd optimizations-that-crash-it before, using impact, and, it seems, now using phaser too :/ 

 

Kenray, did you ever find a way to work around this problem?

Link to comment
Share on other sites

  • 2 months later...

EDIT:

 

WKWebview plugin indeed save memory, but your app might crash faster.

 

Still looking for some solution on memory management.

 

EDIT2:

 

the best approach so far seems to be add the shutdown hook on state and inside make removeImage calls to game.cache. Just as was told earlier on this thread...

 

maybe on iOS 8.4 our problems be gone with the new webview.

Link to comment
Share on other sites

 Share

  • Recently Browsing   0 members

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