How to deal with low-memory devices

Hi,

On my quest to support more and more devices I have taken on the FR745 this evening. It seems that on the FR745 there is less memory available for glances than on other glance devices I tested so far (Epix Gen 2, Venu 3S, FR265). My app makes web requests to a third party server, and the response is relatively large, too large for the FR745 to process it in the glance. So I need to implement the glance differently and only show static information.

How could I implement such different behavior. Is there a way to check in the code how much memory is available? Or have code execute only for certain device types? Only way I could think of right now is to have a property in the device-specific resource folder, that tells me I should do something different. Is that the way, or is there something better?

Regards, Robert

  • You could definitely check available memory at runtime but I then your compiled runtime will have code for low-mem or higher memory devies, and thus limit the low-mem devices even more.

    Instead, I would look to doing the checks at compile time. Read up on using the build configuration to be able to have code specific to a device. I have a relatively complicated build config for several of my apps so as to support most devices within a single code base.

  • Yes, I use jungles for stuff like this.  In this case, I'd consider using a different .mc file for those devices with less memory for glance view.

    In watch faces, I use this same thing for watch faces on devices with less memory for the app over all, like the Instict2 devices.

    I do the same thing in apps for devices with and without on-board maps.

    In the compiler.json file for a device, you can see the limits for that device.  Here's that part for the 745:


        "appTypes": [
            {
                "memoryLimit": 524288,
                "type": "audioContentProvider"
            },
            {
                "memoryLimit": 65536,
                "type": "background"
            },
            {
                "memoryLimit": 131072,
                "type": "datafield"
            },
            {
                "memoryLimit": 32768,
                "type": "glance"
            },
            {
                "memoryLimit": 1310720,
                "type": "watchApp"
            },
            {
                "memoryLimit": 98304,
                "type": "watchFace"
            },
            {
                "memoryLimit": 1048576,
                "type": "widget"
            }
        ],

  • As the other comments pointed out, the best approach is to use jungles to exclude code at compile time. Any run time solution will incur the cost of keeping around dead code which won't be executed for certain devices.

    Note that exclusions are only possible on a per-symbol basis, which means that you can have two versions of the same function, but if there's any common code in the two versions, you will either have to duplicate it or extract it to a separate helper function (which ofc wastes slightly more code.)

    In the past I've used a cheesy line-by-line homemade conditional compilation system (using #ifdef a la C or C#) with Eclipse, but I didn't bother to automate it for VS Code.

    [https://forums.garmin.com/developer/connect-iq/f/discussion/7733/solution-for-line-by-line-mc-conditional-compilation]

  • Thanks, you guys are great, jungles are the way to go!

    However in my case, it seems they help me only to keep the low memory code out of the high memory code but not vice versa.

    At least with the FR745 the memory for the app is plenty and I do want to keep it as it is. Only for the glance, I'd like to use a tinier version. Unfortunately most of my code is shared between glance and app, so I also have to include it in the fr745 source path:

    # By default we use the "normal" glance
    base.sourcePath = source;source-glance
    
    # Only for fr745 we use the "tiny" glance
    fr745.sourcePath = source;source-tinyglance

    And as long as I do not want to maintain duplicated code files, the common code will still carry the (:glance) annotation, so it will also be included in the FR745 glance.

    Or is there a way to somehow modify the (:glance) annotation to make it device specific?

  • I'm assuming that it's not practical or desirable to do something like this:

    base.sourcePath = source;source-common-big;source-glance
    fr745.sourcePath = source;source-common-tiny;source-tinyglance

    If that's the case, you could use exclude annotations for stuff that's 745 vs non-745

    common_excludes = $(base.excludeAnnotations)

    base.excludeAnnotations = $(common_excludes);non_fr745
    fr745.excludeAnnotations = $(common_excludes);fr745

    In your source:

    (:glance) function someGlanceFn() { ... } // glance (all devices)
    (:glance :fr745) function someGlanceFn2() { ... } // glance and fr745 only
    (:glance :non_fr745) function someGlanceFn2() { ... } // glance and non_fr745 only

    Ofc this becomes a huge pain if you have to handle multiple groups of devices, instead of just two.

  • What probably works is something like this in your jungle:

    fr745.excludeAnnotations=exc1

    And then
    (:glance :exc1)
    I can't recall the last time i use multiple annotations though, so try it yourself.
    Another thing to note, is while you are thinking 32k for a glace on a 745, the CIQ VM grabs about 4k of that, leaving about 28k.  Here's a screen shot of my WU widget in the sim for the 745 showing the glance.  Notice the numbers on the bottom line.




    Another thing is if you move to the glance loop, that times out after a minute or two on a real device and returns to the watch face, so your glance may not be something you watch for a long time.  You don't see this in the sim.  Same when you open a widget to full screen
    And something else - liveUpdate - there's no way to check this in the API, but for example, with the fr745, that's true, but false for the Fenix6.  See https://developer.garmin.com/connect-iq/core-topics/glances/ and Live UI Updates and Background UI Updates,
    What this gets down to, is you may not want to try to do too much in a glance.
    I know you've used my WU widget, but there is actually more going on there than you first see.  It's not just a glance and a full screen view.  There's also a background service on devices that support them that updates the data on a regular basic (default is every 15 minutes), and on devices with complications, publishes things that can then be used in a watch face.  On devices with enough memory, when you go to full screen, the 3rd page is a forecast that's obtained with a different request than current data.  Two different calls instead of one big one.  The way this is written, is the code to make the calls is actually in the background service (:background), but then can be used in the glance and full screen view.
  • Technically it works with excludedAnnotiations as you describe, but in my case it does not save me much memory, because I need most of the code also for the FR745 when it launches the widget. I would need an annotation that works like :glance, an annotation that marks all stuff I need for the FR745 (or other glance watches with limited memory) in glance mode. As far as I can see from the documentation there is no way to annotate like that.

    Anyway, I found now an implementation that I am happy with. I simplified the view (no bitmaps) and do the web request in a background task. For background tasks there are 64KB and that seems to be enough to process the JSON response.

  • I'd use this csv: https://github.com/flocsy/garmin-dev-tools/blob/main/csv/device2memory-glance.csv or even better this: https://github.com/flocsy/garmin-dev-tools/blob/main/csv/memory2devices-glance.csv to see in which devices you have 32k and in which devices you have 64k for glances.

    Then I'd use https://github.com/flocsy/garmin-dev-tools/blob/main/monkey-generator/monkey-generator.py to generate either excludeAnnotations and/or sourcePaths for each device in the jungle file.

    However the good thing is that there are only 2 groups of devices, and I think it's safe to assume that newer devices will have at least 64k, so you could do this manually:

    base.sourcePath=source;source-64k

    for each device with only 32k:

    descentg1.sourcePath=source;source-32k

    All the devices with 64k and future devices will automatically inherit the base.sourcePath, so you won't need to edit this any more (hopefully, unless a new device will have less than 64k for glance)

  • Anyway, I found now an implementation that I am happy with. I simplified the view (no bitmaps) and do the web request in a background task.

    That happiness did not last very long. I have a user request for Fenix 6 support and it seems that the Fenix 6 is even more demanding than the FR745. It has the same 32KB for glances, but only 32KB for background apps, compared to the 64KB of the FR745.

    With 32KB it seems only very simple web requests are possible. My background app implementation is very simple, there is not much to optimize there. Very simple web requests (with ~ 20 fields in the JSON response) seem to work, but my more complex one with 100+ fields does not. Trying to figure out a way now to get a simpler response from the server.

    Is there a way to perform the "View Memory" in the simulator on the background task?

  • All you can do is do a println with System.getSystemStats() and usedMemory/totalMemory/freeMemory, 

    32kb (really 28kb of app memory) for a background service isn't that uncommon for older devices.