Odd behavior of data in datafield

I'm seeing odd behavior in a data field. My data field is on screen one. It shows total ascent, among other fields. On the watch during an activity, when I go from screen 1 to screen 2 then back to screen 1, the ascent display changes rapidly for a second or so to different numbers, large and small, then after a second it changes to a single incorrect value for another second, then it settles down to the correct value.

In the compute function, I have:
totalAscent = info.totalAscent;

and in the onUpdate function I have:
totalAscent = (totalAscent == null ? 0 : totalAscent)*metersConversionFactor;


I found that if I move the null check assignment from onUpdate to compute, everything works fine.

Is there a race condition or something that causes an issue when I assign a value to totalAscent in the onCompute function?? I've seen a similar issue with elapsed distance where for the first second it is correct, then it goes to 0, then it goes back to being correct. Have others seen this type of behavior? I am really flummoxed! How does the displayed value of total ascent change rapidly? It's as if onUpdate is being called repeatedly instead of once per second, and total ascent somehow has different values each time.

This is on a 735xt with firmware 3.20 using the current SDK.
  • The approach I use for DF's, is that onUpdate() only displays the data, and the data to display or calculate is all handled in compute().

    compute() gets called if your DF is hidden (it's not on the current screen), but onUpdate() wont be called.

    There might be a number of onUpdate() calls when you first switch to the screen with your DF (I've never checked), and it would be getting the data each time, and therefore, the flicker.
  • 956

    The approach I use for DF's, is that onUpdate() only displays the data, and the data to display or calculate is all handled in compute().

    compute() gets called if your DF is hidden (it's not on the current screen), but onUpdate() wont be called.

    My thinking was to only put the minimum of calculations in compute(), since it is called even when the field is not displayed.
    There might be a number of onUpdate() calls when you first switch to the screen with your DF (I've never checked), and it would be getting the data each time, and therefore, the flicker.

    Even if onUpdate() is called multiple times when switching back to the DF, which I agree it may be, that doesn't explain the changing value of the totalAscent variable, unless it is not yet properly initialized for some reason.

    Seems like a bug.
  • Even if onUpdate() is called multiple times when switching back to the DF, which I agree it may be, that doesn't explain the changing value of the totalAscent variable, unless it is not yet properly initialized for some reason.


    I'm fairly certain that the issue is that onUpdate() is being called multiple times between calls to compute(). Imagine the following simplified testcase...

    // assume metersConversionFactor = 3.28084 (meters to feet conversion)

    // this happens in compute()
    var totalAscent = 100.0; // info.totalAscent was 100.0m

    // this simulates what happens when you switch data pages. the system
    // calls onUpdate() ~15 times in a row to properly render the data page
    // sliding into view.
    totalAscent = totalAscent * 3.28084; // 328.084
    totalAscent = totalAscent * 3.28084; // 1076.39111056
    totalAscent = totalAscent * 3.28084; // 3531.46701116967
    totalAscent = totalAscent * 3.28084; // 11586.1782289259
    totalAscent = totalAscent * 3.28084; // 38012.39698058925
    totalAscent = totalAscent * 3.28084; // 124712.5925097964
    totalAscent = totalAscent * 3.28084; // 409162.0620098406
    totalAscent = totalAscent * 3.28084; // 1342395.259524365
    totalAscent = totalAscent * 3.28084; // 4404184.063257919
    totalAscent = totalAscent * 3.28084; // 14449423.24209911
    totalAscent = totalAscent * 3.28084; // 47406245.74960845
    totalAscent = totalAscent * 3.28084; // 155532307.3051454
    totalAscent = totalAscent * 3.28084; // 510276615.0990132
    totalAscent = totalAscent * 3.28084; // 1674135929.881446
    totalAscent = totalAscent * 3.28084; // 5492572124.192244

    // now that the animation has finished, compute() is called after a
    // delay for ~1 second.
    totalAscent = 100.1; // info.totalAscent was 100.1m

    // and now things are back to normal.


    Depending on the number of times that onUpdate() is called for the animation and the value of metersConversionFactor, you could easily overflow and see a final value smaller than the original. e.g., If you are converting the totalAscent value using totalAscent.format("%d"), it would overflow at 4294967295.

    I'm pretty confident that this is your problem. The solution is pretty simple. Don't write the result of the conversion back to totalAscent in onUpdate(). Another thing you could do would be to initialize totalAscent to 0 (either in in initialize() or by assigning an initial value at the declaration site). This would avoid the need for the null check entirely.

    var totalAscent;

    function initialize() {
    DataField.initialize();

    // ...snip...

    totalAscent = 0;

    // ...snip...
    }

    function compute(info) {
    // ...snip...

    totalAscent = info.totalAscent;

    // ...snip...
    }

    function onUpdate(dc) {
    // ...snip...

    var convertedTotalAscent = totalAscent * metersConversionFactor;

    // ...snip...
    }


    Travis
  • Assuming Jim and I are right, then yes, there is a bug... in your code. You have to write your code to properly handle the case where compute() is called many times between calls to onUpdate() (the case where the data field is not on screen) and the case where onUpdate() is called many times between calls to compute() (the case where the data page is being animated).

    Travis
  • Thanks, very helpful!! Embarrassingly obvious when you realize that onUpdate() is being called multiple times to animate the return to the data field screen. That never occurred to me. I tested, it is being called 5 or so times.

    Travis, I need the null check in compute() because I'm checking the value of info.totalAscent for null.
  • Like I said, save off/calculate in compute() and do nothing more than display the data on onUpdate(). It's the safest way, and what you saw could be different on other watches. (how many times onUpdate() is called, etc)