Struggling to understand the proper way to use Jungles...

I've recently released a DataField which aims to support quite a wide array of device layouts. After playing with different ways of defining the layout differences between screen sizes, I settled on a solution of using JSON resources - nice and elegant - then went to support the Forerunner 235 with it's 1.X SDK that doesn't support JSON resources. So I resorted to defining string resources, all of which are integers, and converting them to toInt() on initialization. I have about 7 different layout.xml files in resource folders containing my layout variables defined as strings, and whilst this technique absolutely does work, it is obviously not the proper way to describe a layout; it is (a) smelly because it is a perversion of the intended use of string resources, (b) causing build warnings about strings being defined multiple times for the same language and (c) probably not optimally efficient since it is performing an unnecessary type conversion and storing both the string keys and values in memory as well as the converted integers.

I figured I'd try creating several Layout.mc files instead and declare simple global variables in each, then get the the jungle to include the right one at build time. If that's what jungles are supposed to do, I can't figure out how to make it work. Depending on where I put the files I either get build errors because the variables are defined multiple times or not at all.

Anyway, rather than describing the solution I want, I'll describe the problem and ask if anyone can provide a memory-efficient solution that is better than using string resources. I have about 15 integer values that need to be different for each screen size. I need to retrieve those values once, at startup. I want the retrieval of those values to be as memory efficient as possible, and preferably, I'd like the place where they are defined to allow for either comments or descriptive variable names that make their intention clear without creating unwanted memory bloat.

If anyone can point me in the right direction I'd be very grateful.

  • One other thing to consider is that that fr235 only allows 16k for a DF, while other devices, 28k or more

  • I want the retrieval of those values to be as memory efficient as possible, and preferably, I'd like the place where they are defined to allow for either comments or descriptive variable names that make their intention clear without creating unwanted memory bloat.

    I don't know whether or not this counts as the "proper" way to use jungles, but I had similar issues and resolved them with something like the below. The example is catering for monochrome devices, but the same principle applies to basically anything, so I have used it for catering for CIQ1 vs CIQ2,3 etc.

    In Monkey.jungle:

    ################
    ## SOURCE FILE CHANGES
    ## monochrome TEST
    # Edge 130 supports only 2 colour
    # Garmin Swim™ 2 and Forerunner® 45 only support 8 colour, but treat as full colour
    # And treat everything else as full colour.
    rectangle-230x303.excludeAnnotations=$(rectangle-230x303.excludeAnnotations);notMonochrome
    
    rectangle-148x205.excludeAnnotations = $(rectangle-148x205.excludeAnnotations);monochrome
    rectangle-200x265.excludeAnnotations = $(rectangle-200x265.excludeAnnotations);monochrome
    rectangle-205x148.excludeAnnotations = $(rectangle-205x148.excludeAnnotations);monochrome
    #rectangle-230x303	
    rectangle-240x400.excludeAnnotations = $(rectangle-240x400.excludeAnnotations);monochrome
    rectangle-246x322.excludeAnnotations = $(rectangle-246x322.excludeAnnotations);monochrome
    rectangle-282x470.excludeAnnotations = $(rectangle-282x470.excludeAnnotations);monochrome
    
    round.excludeAnnotations = $(round.excludeAnnotations);monochrome	
    semiround.excludeAnnotations = $(semiround.excludeAnnotations);monochrome

    In .mc files:

    (:monochrome)		hidden var isMono = true;
    (:monochrome)		
    function colList() {
    	var bg = getBackgroundColor(), fg = Graphics.COLOR_WHITE - bg;
    	return [bg,fg,fg,fg,fg];
    }
    (:notMonochrome)	hidden var isMono = false;
    (:notMonochrome)	
    function colList() {
    	var bg = getBackgroundColor(), fg = Graphics.COLOR_WHITE - bg;
    	return [bg,fg,Graphics.COLOR_RED,Graphics.COLOR_GREEN,Graphics.COLOR_BLUE];
    }

  • Something to consider is not really care about each layout on a device, but do some general things like looking at height/width, character height and obscurity flags.

    If you've not looked at obscurity flags, in Eclipse, under file>new>Connect IQ Project and have it generate a complex DF.  You can also search this forum as there are a bunch of threads about using them, etc.

  • I'd seen a few example of this and ruled out using excludeAnnotations on the basis that it seemed I'd have to have a big long list of them on each of my layout definitions. In other words, each layout would need to exclude every other layout except itself. If the library had includeAnnotations that'd be perfect, but as far as I can tell they don't exist and that's why a lot of the examples of their use seem to be binary feature flags as in your example. Still, maintainability aside this may be preferable to what I have now - I'll give it some thought - thanks Slight smile

  • Exclude is by device, not by layout. 

    But it can (as per colours) simplify things for devices. For example, the difference between simple square layout Edge and... everything else...

  • Thanks Jim. I did actually experiment a little with measuring rendered strings and flowing my layout from them and I still do a little of that in my codebase, but I have a relatively large amount of data rendered quite precisely to fit on each screen and it felt like I was somewhat over-complicating the codebase (and potentially spending more memory allocations) in order to derive measurements that could be readily known for a given screen shape.

    I know the Forerunner 230 etc. are limited to 16KB and the newer models have more. Believe me, I know. That limit is even more exasperating when you realise that the blank DataField template uses something like 9K before you've written a line of code. I'm currently at 14KB flat on the Forerunner 235 because I've been doing everything possible to keep memory allocations to the bare minimum - I'm trying to keep a little headroom so that (a) I don't fly too close to the sun and (b) I have scope to introduce features without rewriting the whole codebase to eek out a few more bytes - better apply ruthless efficiency as I go.

    In an ideal world I'd just give the older 16KB devices the finger, but three people I know own the Forerunner 235 and whilst I have released the thing publicly, I want my friends and family to be able to use it too. I wrote one a couple of years back that I never released and even then I remember being surprised that the limit was quite that low. Even 32KB is many orders of magnitude easier to stay within - if we could only have 128KB as a minimum then we might even be able to write maintainable object-oriented code! I bet all those Swift developers writing for their Apple Watches are laughing at us, but we'll have the last laugh when they have to charge their watch every night and ours are still good for a run 8 days later.

  • I managed to get this working how I wanted now. I must have been doing something funny with my jungle file before because the end solution was very close to what I started with:

    Folder structure:

    source
    |--MyView.mc
    |--Layout.mc
    source-round-240x240
    |--Layout.mc
    source-semiround-218x180
    |--Layout.mc
    monkey.jungle
    

    monkey.jungle:

    project.manifest = manifest.xml
    base.sourcePath = source
    base.excludeAnnotations = base
    
    # Configure source paths based on screen shape
    round-240x240.sourcePath = $(base.sourcePath);source-round-240x240
    semiround-215x180.sourcePath = $(base.sourcePath);source-semiround-215x180
    
    # Configure exclusion annotations based on screen shape
    round-240x240.excludeAnnotations = $(base.excludeAnnotations);base
    semiround-215x180.excludeAnnotations = $(base.excludeAnnotations);base

    source/Layout.mc:

    // Used as the default if not overridden by a device family
    (:base) var lines = [30, 70, 145, 203];
    (:base) var heartRateIconX = 3;
    ... etc

    source-round-240x240/Layout.mc:

    var lines = [30, 70, 145, 203];
    var heartRateIconX = 3;
    ... etc

    source-round-218x185/Layout.mc:

    var lines = [28, 62, 132, 160];
    var heartRateIconX = 20;
    ... etc
    

    source/MyView.mc:

    for (var x = 0; x < lines.size(); x++) {
      dc.drawLine(0, lines[x], dc.getWidth(), lines[x]);
    }
    ... etc

    All told this change dropped my memory usage by about 1.2KB - not bad at all considering that's more or less doubled the amount of free space I have to play with. Now at a very comfortable 12.8KB resting memory usage for the running DataField, plenty of room to grow from there and what feels like a cleaner, more efficient solution that doesn't throw build warnings Slight smile

  • Nicely done!

    One small thing:

    round-240x240.excludeAnnotations = $(base.sourcePath);base

    $(base.sourcePath) there quotes the source path of base!

    Methinks you may have meant:

    round-240x240.excludeAnnotations = $(round-240x240.excludeAnnotations);base

    EG: Add "base" to the list of whatever was there before.

  • hello,

    I see you define layout variables in the Layout.mc files, for instance your variable "heartRateIconX".  I would like to do this same thing, so that it will make layouts easier, at least as far as portability to other watch designs go.   (I know about the existence of obscurity flags, however since different size watches require different placement, I think it is probably better memory-wise and execution-wise to write different layout files, rather than try to calculate the positioning on the fly.  I am currently calculating the positions on the fly, and it's complex, so I want to see how to do it using jungles.)

    MAIN QUESTION:  How ~programmatically~ does your view class retrieve the layout values in your Layout.mc files?  Do you include these values in a separate class, then instantiate the class in your view, then call member functions which return these values?  Do you just refer to them by the dot operator i.e, myLayoutClass.heartRateIconX when calling the drawText or other graphic functions?  Or do you not use module/class at all, but rather just define them in the files separately (not sure how this would work, I think some form of containment would probably be required, but I am not sure).

    ...

    And, for anyone else who wants to answer, are there any "best practices" to be aware of? 

    Thanks in advance for any feedback.

  • You're quite right - good catch!