menu MAX_SIZE = 16 in Sim but not device?

The docs indicates a limit in menu of 16 options, but I have accidentally discovered that I'm populating a menu with 18 items on devices (vahr and f5), although the sim truncates the list at 16.

I know it's very bad policy to develop using a loophole in the tools, but this would be a godsend to me, so I'm wondering if it's actually a loophole or an indication that the limit is about to be raised?

  • I'd stick with a max of 16.


    Yes, you're preaching to the converted, but I'm looking to the future.
    Does the limit apply in menu2? I haven't explored that option.
    [EDIT]
    Can that, menu2 is 3.0.0 and I need to be compatible with VA-HR on 2.4.9 :mad:
  • If you use the Dynamic Menu class, you can have most of the functionality of Menu2 on all devices (regardless of CIQ version).

    https://github.com/flowstatedev/Dmenu
    (Use ^ this fork instead of the original, as it has more bug fixes.)

    The disadvantage is you'll have to rewrite your code slightly as it doesn't/can't inherit from the Menu class. Of course there's the increased memory load due to the additional code, as well.

    Notes:
    - Animation is disabled by default, because CIQ animation is broken in FR230, 235, 630, and 920XT. You can re-enable by setting the ANIM_TIME constant to non-zero (look for the existing references)

    - The look and feel is pretty close to the native settings menu of modern round watches (e.g. 935 and Fenix 5), except that all 3 visible items are the same height, instead of the currently selected (middle) item being slightly bigger. This does mean that it won't look native on older devices, since older devices use a white on black theme for menus, while newer devices use black on white.

    - It has full support for touchscreens, including the usual swipe/tap gestures for scrolling through menus

    - Some things aren't perfect on modern 240x240 watches, like the arrows are too small (can't think of anything else at this point.) But the code is available so that can be fixed. (It was originally developed for F3, a 218x218 watch).

    - Unlike Menu2, text doesn't wrap/shrink if it's too long, but you can add that feature by subclassing the draw() method for menu items

    If you want to see it in action on a real watch without building anything or writing any code:
    https://apps.garmin.com/en-US/apps/3...8-f45584b0a117

    (Install the app on a pre-CIQ3 watch, as the app uses Menu2 for CIQ3 devices)
    Here's a screenshot from the project sample app (not the app I linked from the store). [IMG2=JSON]{"data-align":"none","data-size":"full","src":"https:\/\/raw.githubusercontent.com\/flowstatedev\/Dmenu\/master\/misc\/dmenu_f3.png"}[/IMG2]
  • If you use the Dynamic Menu class, you can have most of the functionality of Menu2 on all devices (regardless of CIQ version).


    That looks really nice.
    Do you know how memory hungry it is, and do you think it will handle say 100 items?
  • RaceQs it's hard for me to answer either question definitively. I don't see any reason it can't handle 100 items as long as you have enough free memory.

    I will say that I didn't originally create it and I can see many opportunities for code optimization if you choose to modify the code. Simple example: replace enum references with hard-coded constants (which is evil but saves tiny amounts of memory.) However, if you want lots of menu items then you are probably more interested in optimizing the run-time object memory consumed by each menu item as opposed to optimizing tiny pieces of code that are not multiplied by the number of items.

    I used it in a full app/widget that had a decent amount of breathing room for code (unlike all my data field apps) and a fixed number of menu items (about 10).

    My suggestion is to modify the sample app to automatically create 1, 10, and 100 items, and compare the memory usage. For an idea of how much code space it uses, you can try to compare the code size of the sample app to a modified version of the app that doesn't have the DMenu class (so it'll basically have almost no code).

    If it doesn't fit in your app, then you can explore reducing memory usage in your own app or optimizing the DMenu class itself.

    As I've mentioned elsewhere, one huge way to optimize memory usage is to replace a class with a "context array" and static functions. i.e. All your member variables go in the context array, and each static function takes the context as a parameter. When you need a new "object instance", you simply initialize a new context array.

    This produces really ugly code, but it saves a significant amount of memory, as classes have a lot of overhead.

    So one way to optimize DMenu would be to leave the DMenu as a class but replace the DMenuItem class with a context array + helper functions.

    This is especially relevant in your case because you want to have 100 items.

    However, I would also consider the UX implications of having more than 20 or so items in a list, especially on a watch where scrolling is slow/difficult/annoying. In that case you may wish to organize your items into multiple submenus of no more than about 20 items each.

    In that case, you could probably do the same thing with plain old CIQ Menu (with a limit of 16).
  • Great reply, thanks.
    As you have probably gathered, I'm very conscious of memory so would like to explore your concept of "context array" and static functions. Could you point me to the posts you "mentioned elsewhere".
  • RaceQs oh I pretty much covered it just now >_>. I only recommend it as a last resort, because you lose compile-time code validation for accessing "member vars", subclassing, data hiding, modularity, reusability, readability, and maintainability. But I used the technique successfully to cram more features into some data fields for CIQ1/CIQ2 devices.

    Example

    Before:
    // super basic class that adds and multiplies numbers, while storing the last result, for some reason
    class BasicClass {
    var a, b, lastResult;
    function initialize(_a, _b) {
    a = _a;
    b = _b;
    }

    function add() {
    lastResult = a + b;
    return lastResult;
    }
    function multiply() {
    lastResult = a * b;
    return lastResult;
    }
    }

    function test() {
    var basic = new BasicClass(5, 10);

    basic.add();
    System.println("lastResult = " + basic.lastResult); // prints 15, I hope

    basic.multiply();
    System.println("lastResult = " + basic.lastResult); // prints 50 :fingerscrossed:
    }



    After:
    (this is just awful code, but it sure saves a lot of memory compared to the nice code, especially with multiple objects)
    // ======================
    // START Basic pseudo-class

    // basic context array:
    // [lastResult, a, b]
    function basic_initialize(_a, _b) {
    return [null, _a, _b]; // return context
    }

    function basic_add(context) {
    context[0] = context[1] + context[2];
    return context[0];
    }

    function basic_multiply(context) {
    context[0] = context[1] * context[2];
    return context[0];
    }

    // END Basic pseudo-class
    // ======================

    function test() {
    var basic_context = basic_initialize(5, 10);

    basic_add(basic_context);
    System.println("lastResult = " + basic_context[0]);

    basic_multiply(basic_context);
    System.println("lastResult = " + basic_context[0]);
    }



    EDIT: I will say that if you choose to code like this, make sure to add lots of comments explaining what all the code does....
  • Ah, I see. I don't have that many cases for that kind of palaver and just use call to functions in my top level class to do the repetitive stuff.
    Thanks.
    I played with your Dmenu sample code and saw that each new Alarm consumed around 1.3 Kb.

    I hope to get an answer from the Garmin camp as to the future of the MAX_SIZE const.
  • RaceQs actually this technique can be applied to any utility class (*), no matter how simple or complex, to save memory, especially if you have multiple objects (like multiple menu items). It’s a generic technique for transforming a class into some static functions, to get rid of the overhead of classes. Don’t be misled by the example I showed with a useless class — the point was just to show the technique on a generic class with a couple of member variables and functions.

    The real question is, should you use it?

    (* Obviously it can’t be applied to something that has to be a class so external code like the Garmin firmware/CIQ-specific code can use it. So it’s out of the question for any view class, such as DMenu itself. But DMenuItem doesn’t need to be a class if I’m not mistaken, since external code won’t interact with it directly.)

    It's def not for everyone and it is a last resort, but if you applied that technique to the DMenuItems (for instance), I think you would find that not only would the code be smaller, but every new DMenuItem would be much smaller.

    I had a pretty simple class that calculated rolling average (e.g. over 30 seconds), and managed to save 100s of bytes (for a use case of 6 instances), by applying this technique. 6 arrays versus 6 objects makes a noticeable difference.

    There's quite a bit of overhead for instance objects vs. arrays.

    Anyway, I'm sure there's plenty of more conventional ways to save memory in your app that you can explore first, like changing dictionaries to arrays, etc.
  • I hope to get an answer from the Garmin camp as to the future of the MAX_SIZE const.

    I'd need to test this, but looking at the code I cannot see how this is possible given how the menu is defined. I'd expect every menu item after 16 to be ignored and there should not be device-specific behavior either.