• I'm waiting for my BangleJS 2 to arrive by mail. In the meantime, I already had a look at a bunch of documentation and started building my first app with the emulator.

    My first attempt was trying to reimplement the pebble clock but using the layout library.

    However, my code seems to be very inefficient, even though I'm lacking still a bunch of features of the original implementation but the emulator is already running out of memory for the BangleJS 1. It wouldn't matter in my case since I have the BangleJS 2. But if something that small is already causing issues I should understand how to build apps more efficiently.

    >
     ____                 _
    |  __|___ ___ ___ _ _|_|___ ___
    |  __|_ -| . |  _| | | |   | . |
    |____|___|  _|_| |___|_|_|_|___|
             |_| espruino.com
     2v12 (c) 2021 G.Williams
    >
    >Execution Interrupted
    in function called from line 1 col 44
    c.type=='btn'&&b.push(c),c.c&&c.c.forEac­h(a)
                            in function called from line 1 col 44
    c.type=='btn'&&b.push(c),c.c&&c.c.forEac­h(a)
                                               in function "a" called from line 1 col 286
    ...(c),c.c&&c.c.forEach(a)}a(c),b.length­&&(this.physBtns=0,this...
                    in function "Layout" called from line 27 col 4
      });
       ^
    in function "createLayout" called from line 1 col 29
    const layout = createLayout();
                                ^
    New interpreter error: LOW_MEMORY,MEMORY
    

    The original version of the app seems to run fine in the emulator for BangleJS 1 + 2.

    I have attached the full code to this post, it's too long to post here due to the fonts. This is what it currently looks like:

    The font files seem to be pretty big, but they are also part of the original app, so I would exclude this as the main problem for now.

    If you have some pointers that can improve the efficiency, simplify the rendering/use layout for the calendar icon (function drawCalendar), or how to better push down the font for the time (id time-top-padding), then I'm happy to hear about it.


    1 Attachment

  • The problem you are facing is that you are uploading your code to RAM and not somewhere to flash storage, which means all the code is stored in RAM as a string, which takes up a lot of space, considering the font. You can see the difference yourself by logging the memory used at the beginning of the file and saving to file vs. RAM (using console.log(process.memory())).

    I suspect the non-Layout version didn't run out of memory with the code in RAM because while Layout is great, it does use a lot of RAM to compute the (initial?) sizes of everything.

  • @g_lander thanks for your hint. It was indeed uploading straight to RAM, changed the IDE now to upload to flash.

    Original implementation BJS1 uploaded to RAM

    before render { "free": 2449, "usage": 51, "total": 2500, "history": 9, "gc": 0, "gctime": 0, "blocksize": 13 }
    after render { "free": 1059, "usage": 1441, "total": 2500, "history": 243, "gc": 0, "gctime": 2, "blocksize": 13 }
    after render { "free": 967, "usage": 1533, "total": 2500, "history": 249, "gc": 0, "gctime": 3, "blocksize": 13 }
    

    Original implementation BJS2 uploaded to RAM

    before render { "free": 11950, "usage": 50, "total": 12000, "history": 8, "gc": 0, "gctime": 2, "blocksize": 15 }
    after render { "free": 10708, "usage": 1292, "total": 12000, "history": 204, "gc": 0, "gctime": 9, "blocksize": 15 }
    after render { "free": 10688, "usage": 1312, "total": 12000, "history": 210, "gc": 13, "gctime": 12, "blocksize": 15 }
    

    My implementation BJS1 uploaded to RAM

    before render { "free": 1841, "usage": 659, "total": 2500, "history": 9, "gc": 0, "gctime": 3, "blocksize": 13 }
    New interpreter error: LOW_MEMORY,MEMORY
    

    My implementation BJS2 uploaded to RAM

    before render { "free": 11409, "usage": 591, "total": 12000, "history": 8, "gc": 0, "gctime": 11, "blocksize": 15 }
    after render { "free": 9377, "usage": 2623, "total": 12000, "history": 90, "gc": 333, "gctime": 10, "blocksize": 15 }
    after render { "free": 9394, "usage": 2606, "total": 12000, "history": 96, "gc": 186, "gctime": 12, "blocksize": 15 }
    

    My implementation exceeds the maximum memory for BJS1 while running on BJS2 and the before render call in both implementations indicates a higher memory usage on BJS1 in general.

    After figuring out the issue with the RAM I also dropped the huge fonts and rely on the available 6x8 font now. I also started nesting the calendar elements, so I don't have to have the extra function rendering over my layout, but can simply update the day of the month with layout['day-of-month'].label = date.getDate();

    console.log('before render', process.memory());
    
    const WIDTH = g.getWidth();
    const HEIGHT = g.getHeight();
    const FG_COLOR = 0x0000;
    const HL_COLOR = 0xFFFF;
    const BG_COLOR = 0xFFE0;
    const THICKNESS = 4;
    
    function getShoeImageSource() {
      return require("heatshrink").decompress(atob("o­FAwkEogA/AH4A/AH4A/AH4A/AE8AAAoeXoAfeDQU­BmcyD7A+Dh///8QD649CiAfaHwUvD4sEHy0DDYIf­EICg+Cn4fHICY+DD4nxcgojOHwgfEIAYfRCIQaDD­4ZAFD5r7DH4//kAfRCIZ/GAAnwD5p9DX44fTHgYS­Bf4ofVDAQEBl4fFUAgfOXoQzBgIfFBAIfPP4RAEA­oYAB+cRiK/SG4h/WIBAfXIA7CBAAswD55AHn6fUI­BMCD65AHl4gCmcziAfQQJqfQQJpiDgk0IDXxQLRA­EECaBM+QgRYRYgUIA0CD4ggSQJiDCiAKBICszAAs­wD55AHABKBVD7BAFABIqBD5pAFABPxD55AOD6BAD­iIAJQAyxLABwf/gaAPAH4A/AH4ARA=="));
    }
    
    function createLayout() {
      const Layout = require("Layout");
    
      return new Layout({
        type: "v",
        col: FG_COLOR,
        bgCol: BG_COLOR,
        c: [
          {type: "h", c: [
            {width: WIDTH/2, height: HEIGHT/2, id: "top-left", type: "v", c: [
              {height: THICKNESS*3},
              {type: "h", c: [
                {bgCol: FG_COLOR, height: THICKNESS, width: THICKNESS, id: "left-hook"},
                {width: THICKNESS*3},
                {bgCol: FG_COLOR, height: THICKNESS, width: THICKNESS, id: "right-hook"},
              ]},
              {bgCol: FG_COLOR, height: 50-THICKNESS*2, width: 50-THICKNESS*2, type: "v", c: [
                {height: THICKNESS},
                {bgCol: HL_COLOR, type: "h", c: [
                  {height: 50-THICKNESS*4, width: 50-THICKNESS*4, id: "day-of-month", type: "txt", font: "6x8:2", label: "1"}
                ]},
                {height: THICKNESS},
              ]},
              {height: THICKNESS},
              {id: "day", type: "txt", font: "6x8:2", label: "MON"}
            ]},
            {width: WIDTH/2, height: HEIGHT/2, id: "top-right", type: "v", c: [
              {type: "img", src: getShoeImageSource},
              {id: "steps", type: "txt", font: "6x8:2", label: "000"}
            ]}
          ]},
          {height: HEIGHT/2, type: "v", c: [
            {bgCol: FG_COLOR, fillx: 1, height: 5, id: "top-separator"},
            {bgCol: HL_COLOR, fillx: 1, height: 60, id: "time", type: "txt", font: "6x8:5", label: "00:00"},
            {bgCol: FG_COLOR, fillx: 1, height: 5, id: "bottom-separator"}
          ]}
        ]
      }, {lazy:true});
    }
    
    function draw() {
      const locale = require("locale");
      const date = new Date();
    
      layout.time.label = locale.time(date, 1).replace(/ +/, 0);
      layout.steps.label = Bangle.getHealthStatus("day").steps;
      layout.day.label = locale.dow(date, 1).toUpperCase();
      layout['day-of-month'].label = date.getDate();
    
      layout.render();
      console.log('after render', process.memory());
      //layout.debug();
    }
    
    g.clear();
    
    const layout = createLayout();
    
    setInterval(draw, 15000);
    
    draw();
    
    Bangle.setUI("clock");
    

    My updated implementation BJS1 uploaded to RAM

    before render { "free": 1841, "usage": 659, "total": 2500, "history": 9, "gc": 0, "gctime": 0, "blocksize": 13 }
    after render { "free": 976, "usage": 1524, "total": 2500, "history": 155, "gc": 355, "gctime": 1, "blocksize": 13 }
    after render { "free": 954, "usage": 1546, "total": 2500, "history": 161, "gc": 175, "gctime": 1, "blocksize": 13 }
    

    My updated implementation BJS2 uploaded to RAM

    before render { "free": 11409, "usage": 591, "total": 12000, "history": 8, "gc": 0, "gctime": 11, "blocksize": 15 }
    after render { "free": 10532, "usage": 1468, "total": 12000, "history": 130, "gc": 333, "gctime": 17, "blocksize": 15 }
    after render { "free": 10549, "usage": 1451, "total": 12000, "history": 136, "gc": 186, "gctime": 14, "blocksize": 15 }
    

    My updated implementation BJS1 uploaded to Flash

    before render { "free": 2348, "usage": 152, "total": 2500, "history": 0, "gc": 0, "gctime": 0, "blocksize": 13 }
    after render { "free": 1653, "usage": 847, "total": 2500, "history": 0, "gc": 205, "gctime": 2, "blocksize": 13 }
    after render { "free": 1638, "usage": 862, "total": 2500, "history": 0, "gc": 89, "gctime": 1, "blocksize": 13 }
    

    My updated implementation BJS2 uploaded to Flash

    before render { "free": 11849, "usage": 151, "total": 12000, "history": 0, "gc": 0, "gctime": 1, "blocksize": 15 }
    after render { "free": 11129, "usage": 871, "total": 12000, "history": 0, "gc": 205, "gctime": 13, "blocksize": 15 }
    after render { "free": 11143, "usage": 857, "total": 12000, "history": 0, "gc": 113, "gctime": 12, "blocksize": 15 }
    

    Difference between rendering on BJS1+2:

    While it seems to be sufficient to specify col: FG_COLOR on the root element of the layout for BJS2, it requires BJS1 to explicitly specify it per type: "txt" element to make it work.

  • I'm watching with interest.

  • the part with

    return require("heatshrink").decompress(atob("o­FAwkEogA/AH4A/AH4A/AH4A/AE8AAAoeXoAfeDQU­BmcyD7A+Dh///8QD649CiAfaHwUvD4sEHy0DDYIf­EICg+Cn4fHICY+DD4nxcgojOHwgfEIAYfRCIQaDD­4ZAFD5r7DH4//kAfRCIZ/GAAnwD5p9DX44fTHgYS­Bf4ofVDAQEBl4fFUAgfOXoQzBgIfFBAIfPP4RAEA­oYAB+cRiK/SG4h/WIBAfXIA7CBAAswD55AHn6fUI­BMCD65AHl4gCmcziAfQQJqfQQJpiDgk0IDXxQLRA­EECaBM+QgRYRYgUIA0CD4ggSQJiDCiAKBICszAAs­wD55AHABKBVD7BAFABIqBD5pAFABPxD55AOD6BAD­iIAJQAyxLABwf/gaAPAH4A/AH4ARA=="));
    

    could be rewritten to decompress to file in storage once at startup or even be already uploaded in decompressed state as binary file. Storage.read doesn't take extra RAM and returns pointer to the storage data directly. With bangle 1 and 2 this data is read on demand from SPI flash. Bangle 2 also has about 500KB of internal directly mapped flash available as drive c:. Not sure whether this feature is already enabled in stable firmware but if you do Storage.write("c:filename",buffer) it should end in internal flash. When reading specifying the drive letter is optional, should be found both as c:filename and also filename. This internal flash is directly visible for CPU like RAM so speed should be more or less the same like having data in RAM.

  • What you're doing there with the getShoeImageSource function looks good to me.

    In terms of the different rendering, could it be that the default 'theme' is different on Bangle.js 1 and 2? 1 uses white on black, 2 uses black on white, but they are configurable.

    The layout library is a tricky one - it does use a lot more memory/time than just manually specifying sizes, but then it is a lot more flexible. I'd love to get the memory usage down, but it's hard as it does have to store the layout information for each UI element.

  • What you're doing there with the getShoeImageSource function looks good to me.

    Well, just measured and calling the getShoeImageSource takes 15ms

    >var d=Date();getShoeImageSource();d=Date().m­s-d.ms;
    =15.47241210937
    >var d=Date();getShoeImageSource();d=Date().m­s-d.ms;
    =15.65551757812
    

    writing to file and then drawing it from file vs from getShoeImageSource

    var s=require("Storage")
    s.write("yyshoeimage",getShoeImageSource­())
    >var d=Date();g.drawImage(s.read("yyshoeimage­"),20,20);d=Date().ms-d.ms;
    =12.05444335937
    >var d=Date();g.drawImage(getShoeImageSource(­),20,20);d=Date().ms-d.ms;
    =26.51977539062
    

    whether 15ms of CPU time is worth optimizing (for power, when e.g. redrawing watchface periodically) is of course debatable, if drawn every second it can make a difference.

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Building first app, but code seems to be very inefficient

Posted by Avatar for fewieden @fewieden

Actions