Thank you everyone - the issue has been solved.
Thank you everyone - the issue has been solved.
IMHO requestUpdate doesn't mean that the request will be fulfilled or that it will be fulfilled immediately. It just tells the system there's a change in the display buffer to be sent to the display "when…
requestUpdate doesn't mean that the request will be fulfilled or that it will be fulfilled immediately. It just tells the system there's a change in the display buffer to be sent to the display…
Users notice. It uses more battery
Yeah I think the 2 main things that users complain about when it comes to CIQ watchface is perceived additional battery drain and perceived sluggishness…
requestUpdate doesn't mean that the request will be fulfilled or that it will be fulfilled immediately. It just tells the system there's a change in the display buffer to be sent to the display "when the system feels apropriate"
"requestUpdate...just tells the system there's a change in the display buffer to be sent to the display"
I wouldn't characterize it that way, unless you were speaking purely metaphorically.
requestUpdate() literally asks the system call to onUpdate() for the current view, and onUpdate() can make several changes to the display / dc.
It's not like the app literally "updates the display buffer" (before calling requestUpdate()) then atomically requests to send that update to the display (using requestUpdate()). The updating of the screen actually comes after requestUpdate().
So in plain language: "requestUpdate() tells the system the app wants to update the current view." Not that there's (already) a change to be sent to the display.
Is there any reason why this would freeze on a real device?
It looks like your spritePhase() function is calling a new array of 16 bitmaps from a resource every time it's called. If this is hitting the file system to load each bitmap (and I think it is), that's a huge bottleneck. (File system / storage access is very slow.)
It also appears to be completely unnecessary - you don't want to load all 16 bitmaps over and over again at each step of the animation.
The reason you don't see this problem on the simulator is because the simulator doesn't simulate the slow storage of the device (in the same way it doesn't simulate the slow CPU of the device.)
I kinda get why you're doing it this way - you have two phases and you don't want to load all the resources for both phases at once.
Here's what I would suggest - just load the required bitmaps once per phase. Use two class members to store the bitmap arrays for each goal - in the code for each phase, make sure the bitmap array for the other array is not loaded.
e.g.
var spriteArray = null; var goalArray = null; function spritePhase(seconds, minutes) { goalArray = null; if (spriteArray == null) { //sprite array of 16 bitmaps spriteArray = [ (new WatchUi.Bitmap({ :rezId => Rez.Drawables.sprite1, :locX => venus2X, :locY => venus2Y })), //... ]; } if (minutes % 2 == 0) { return spriteArray[seconds % 2]; } else { return spriteArray[seconds % 16]; } } function goalPhase(seconds, minutes) { spriteArray = null; if (goalArray == null) { goalArray = [ // load bitmaps ] } //... }
That's pretty spot on, when I look at a more detailed analysis of the memory usage- the simulation shows 14kb but it also says it spikes at 40 kb at times and that 14kb is just it's average. So it must be bottle necking. It doesnt crash- but it freezes the watch on a real device. The longer the array is- the more memory used. I agree maybe I should break the array into smaller pieces in separate functions.
Oh dang- that makes a lot of sense on how it's working so sporadically. The simulation is smooth and not showing this. I think I might just use the normal animation code instead of trying to write a loop.
I agree maybe I should break the array into smaller pieces in separate functions.
That's not really what I suggested tho. I just suggested not loading all the bitmaps every time you call spritePhase() or goalPhase(). Just load them once (as needed), as in the example code I posted above.
I think that one change should fix the immediate problem you are seeing. Not saying your app wouldn't benefit from more refactoring, just that I think that one change is the minimum change you need to get over your current problem.
The longer the array is- the more memory used.
Yeah, but the real problem is that the process of loading the bitmaps from the file system is really slow. I think that whenever you call new WatchUi.Bitmap({:rezId => Rez.Drawables.sprite1,...}), that's hitting the file system.
So you're loading 16 bitmaps from the file system every 200-400 ms. Given how slow Garmin device storage is (ever try copying stuff to and from the watch using the USB cable?), it's not surprising this would freeze your app.
It's actually better to load all those bitmaps as little as possible, and just keep them in memory as long as you need them (assuming you have enough memory -- looks like you do.) Also note that on newer devices - with CIQ >= 4 - resources like bitmaps and fonts are loaded into the shared graphics pool - which means your app memory would be largely unaffected by loading a bunch of graphical resources. Either way, ofc you shouldn't keep resources around longer than you need them. (But you shouldn't load them over and over again either.)
That's interesting to consider. Thank you for your input.
I think the issue actually has to do with requestUpdate being sporadic as Flocsy suggested. I am going to play around with Franco Trimboli's "Nyan Cat" github example and the Monkey Motion example as I try to make it work with how I organize my code.
I think the issue actually has to do with requestUpdate being sporadic as Flocsy suggested.
Well, you could find out by trying the change I suggested it (which again, was the minimum change I could think of which would solve the problem if my theory was correct.) My prediction is that the suggested code should only see a freeze on a real device twice: once at the start of each "phase". (I can think of ways to get around that, too, but again, just wanted to offer the minimum possible change.)
You could also profile your existing code by putting System.println() statements before and after the existing code that creates the array of bitmaps. Each println() could print the value of System.getTimer(), so you'd have an idea of how many milliseconds the bitmap array creation takes on a real device. Or you could use the built-in profiler - you could put the bitmap loading code in a separate function so you could isolate the time for it (tbf spritePhase() doesn't have a lot of additional code besides loading the bitmaps, so you could leave it as-is.)
I will admit my theory doesn't really explain why it would only freeze some of the time, unless those happen to be times that the watch is also accessing storage in the background or something.
But you will see from various forum discussions (and the docs) that loading resources in onUpdate() is *not* recommended, as it's very slow. Even if it's not *the* problem, it's *a* problem. (I happen to think it's *the* problem tho. But I could be wrong, as I don't have a copy of your project to try out on my device to recreate the problem, and I haven't tried to recreate it independently, since I don't know the size of your bitmaps, what kind of device you're using, etc.)
https://developer.garmin.com/connect-iq/core-topics/resources/
Loading a resource can be an expensive operation, so do not load resources when handling screen updates.
So the only question in mind is: does new WatchUi.Bitmap({:rezId => Rez.Drawables.sprite1,...}) load the specified resource? I think it has to. How else would it work? (But this is one of those cases where the API docs could be better.)
https://forums.garmin.com/developer/connect-iq/f/discussion/2574/how-to-load-resources-effectively
In the case of the built-in Ui.Bitmap, it is documented to take a resource id, and it presumably calls loadResource() for you.
^ this guy works for Garmin now, although he didn't at the time he made that post.
TL;DR the fact that your code load resources during every single onUpdate() jumped out at me and seems to be an obvious issue. It's also something that you could change easily, with the code I gave you as a starting point. At the very least you could confirm or deny my guess.
Anyway, it's up to you. Good luck!
I've been reading a lot on it after your advice, and I agree that I need to totally reorganize and use a"Lazy loading" approach as you suggested.
It's kind if funny becuase I guess the users don't care much or even notice- but I do wish it was more efficient and less bloated. I'm also learning how expensive it is having a ton of user settings and enums. I was blaming the .pngs the whole time- but it might actually be that.
Cool but the meat and potatoes of my advice is to avoid loading the bitmaps on every single update (which is where I believe the performance issue lies.). The "lazy" part is only there bc you have two sets of bitmaps (one for the goal phase and one for sprite phase), and it's not strictly necessary to have both sets loaded at once.
If you only had one set of bitmaps, it would be fine to load them once at app startup. In the actual case where you do have 2 sets of bitmaps, if the bitmaps are small enough, you could probably get away with loading both sets once at app startup. It might actually be better that way, to avoid a possible freeze every other minute when you switch phases.