-
I was hoping to understand abit more about what happens when you exit an app and return to the main screen and what role appID plays in functions like the one below, if any.
1. @param {boolean} isOn - True if the GPS should be on, false if not * @param {any} appID - A string with the app's name in, used to ensure one app can't turn off something another app is using * @returns {boolean} Is the GPS on? * @url http://www.espruino.com/Reference#l_Bangle_setGPSPower */ static setGPSPower(isOn: ShortBoolean, appID: string): boolean;
Is there an "onExit" type event you can subscribe to so can do things like saving app state before it closes?
If I don't set the app id is it auto injected?
How uptodate are the comments on functions like "setGPSPower"? I had assumed that once you exit an app to the main menu everything is reset to how it was before you opened the app. The comments kinda hint that multiple apps could be open at the same time. In what scenario would this happen? Widget?
-
Oh that's ok then. If nobody reads the code it can be as slow and
inefficient as you like.I think you know what I meant. The produced code doesn't matter as much as the thing producing it, which is dependent on which javascript version you want the compiled code in and what other compile settings you have.
-
It's more frustration at the TypeScript compiler
Typescript is a game charger for large scale javascript applications. Having type safe code saves so much time on testing and debugging. This is why its built in as standard now to the default react and angular boilerplates. Its also why python is introducing typing.ingenious ways to make JS code as large, incomprehensible and slow as
possibleIts not built for people to read the compiled code. In my setup its not just typescript. Webpack is doing the module splitting, minification etc. I also doubt I have it set up optimally.
So while you might be frustrated with Typescript, JavaScript isn't compatible on its own with making large scale stable applications. This is not just my opinion but an opinion held by the majority of leading tech companies using JavaScript.
..and just on why abstractions such as "isEmpty" are needed/good practice. If you have a large application with many developers working on it. What does it mean for an array to be empty? If its a preset length and each value is undefined is it empty? Or is it an array of undefined values. It doesn't matter which is true as long as its consistent across your app. The only way to do this is to create a single function everyone calls. This has other advantages. You can have unit test coverage so that if someone finds a faster way to determine if an array is empty that function can be rewritten and the unit tests remove any worry or time taken to test the new version. The person using isEmpty doesn't have to see the code or care how the code works, or what it means to be empty.
-
So it's 8x slower than just using an Array, and honestly I'm amazed
it's that good.At first I did just use an array but I need to limit the size otherwise the mem is very quickly consumed (think wanting to sample gps data, but only recent as you want to reduce noise). You are speed testing two different things. You are saying pushing to an array is quicker than pushing to an array and then pruning it. Which is obviously true. If you have a quicker way of pushing and then resizing that would be more constructive.
On Espruino as far as I can see you could just use Array directly, and
if you really need to limit the size of the queue you could override
.push to just call splice after it to limit the length.I am literally doing the same thing as splice after push (via shift). Originally I extended Array rather than having an internal array variable but the espruino compiler isn't releasing memory when splice or shift.
I mean, you look at isEmpty, it's calling this.any() which calls
_this.length which is a getter which calls this.internalArray.length. It's just a nightmare - it's like someone wrote the code specifically
to waste CPU cycles - even on Node.js it's going to suck
If I want to know if my array is empty I need to do a check in my code. I still need to call array.length. I could repeat this code every time I want to check, it wouldn't speed up the processing, or I can add an additional layer which can be individually unit tested and optimised further in future.Ultimately you might save some cpu time with large chunks of repeated code. It will limit you from creating good stable apps which are easy to maintain and extend. Most of the apps in the current app library are not extendable or maintainable. You need to be able to split the code into small segments to unit test. Otherwise you are taking a step back to pre object orientated test driven development.
-
-
I have narrowed down what is triggering the low memory (when run from RAM). I have the following class which is basically shifting the queue if push item and over max size:
export class Queue<T> { protected itemLimit:number; protected internalArray: Array<T>; constructor(itemLimit = 3) { this.itemLimit = itemLimit; this.internalArray = []; }
If I initiate 2 or more of these like this:
this.waypoints = new Queue<IWaypoint>(10); this.waterways = new Queue<ILocalisedFeature>(10);
...the memory bottoms out. Which is curious as even if I comment out the initialisation of the array it still bottoms out.
The actual non minified compiled typescript looks as follows:
/***/ './src/constructs/queue.ts': /***/ function (__unused_webpack_module, exports) { eval( '\r\nObject.defineProperty(exports, "__esModule", ({ value: true }));\r\nexports.Queue = void 0;\r\nvar Queue = /** @class */ (function () {\r\n // itemCount = 0;\r\n function Queue(itemLimit) {\r\n if (itemLimit === void 0) { itemLimit = 3; }\r\n var _this = this;\r\n this.lastN = function (count) {\r\n if (count === void 0) { count = 2; }\r\n var newAry = [];\r\n for (var i = _this.length - 1; i >= Math.max(0, _this.length - count); i--) {\r\n newAry.push(_this.internalArray[i]);\r\n }\r\n return newAry;\r\n };\r\n this.any = function () {\r\n return _this.length > 0;\r\n };\r\n this.lastEntry = function () {\r\n try {\r\n return _this.internalArray[_this.length - 1];\r\n }\r\n catch (err) {\r\n return null;\r\n }\r\n };\r\n this.firstEntry = function () {\r\n return _this.internalArray[0];\r\n };\r\n this.lastMinus = function (numberFromEnd) {\r\n if (numberFromEnd === void 0) { numberFromEnd = 0; }\r\n return _this.internalArray[_this.length - 1 - numberFromEnd];\r\n };\r\n this.clear = function () {\r\n _this.internalArray.length = 0;\r\n };\r\n this.asArray = function () {\r\n return _this.internalArray;\r\n };\r\n this.itemLimit = itemLimit;\r\n this.internalArray = [];\r\n }\r\n Object.defineProperty(Queue.prototype, "length", {\r\n get: function () {\r\n return this.internalArray.length;\r\n },\r\n enumerable: false,\r\n configurable: true\r\n });\r\n Queue.prototype.isEmpty = function () {\r\n return !this.any();\r\n };\r\n Queue.prototype.push = function () {\r\n var _a;\r\n var items = [];\r\n for (var _i = 0; _i < arguments.length; _i++) {\r\n items[_i] = arguments[_i];\r\n }\r\n var n = (_a = this.internalArray).push.apply(_a, items);\r\n if (this.itemLimit != null && this.itemLimit > 0) {\r\n this.internalArray.splice(0, this.length - this.itemLimit);\r\n }\r\n return this.length;\r\n };\r\n return Queue;\r\n}());\r\nexports.Queue = Queue;\r\n\n\n//# sourceURL=webpack://ck_nav/./src/constructs/queue.ts?', ); /***/ },
I am wondering if the get overrides (such as below)
which result in Class.prototype stuff is the cause but need to test more.
get length() { return this.internalArray.length; }
-
loadRoute is doing two things. Its reading the gpx file with Storage.read, and parsing additional json files with jsonRead. In both cases I am parsing the data into local constructs. The mem used by both these functions is correctly releasing the memory once outside the scope.
I actually think the memory which is released is memory bangle is using when loading up the main js file on app load.
-
-
The print mem function was defined within the class instance which was running for the entire length of the program so any variables local to that should stay locked in memory so this shouldn't be a factor.
In your example "this" could have change so they could print different results if printMem was different. Arrow functions were introduced to guarantee "this" stays the same. So
const app = new App(); app.setup() //"this" will be instance of the App app.printMem() // will be same as this.printMem inside setup
-
-
I agree that actually implementing it in practice would be difficult. I can actually do this with typescript using transformers by using custom decorators at the top of methods (eg @themed({themeOptions}. Which could inject lines at the top of method to set theme options and lines at the bottom to reset when compiling.
-
-
Thanks for the feature link. Shame no1 linked to it (and I wasn't able to find it) when I was asking about which version of javascript Espruino is based on. Maybe I was looking on bangle documentation only.
My code snippet is abit miss leading as its from a typescript file not a direct javascript file. I am compiling it down to es5 when building. I should probably check what typescript is doing with default params when the target output is es5.
The main concern is as Gordon says, not that it isn't supported, but that no error is fired.
-
I have noticed that if I initiate a menu, once returning to my normal screen render, text is offset slightly on the y axis. I assume this is due to the menu setting up some font settings.
Is theming always global?
If it is, has there been any discussion about making it scope specific? So if you have a function and you set the forecolour or font etc within that function, anything rendered to screen from that scope (the function and sub functions) uses that theming.
This seems like a sensible approach to me as as I build up my app and have "renderSomeThing" type methods, if I have scope specific colouring I either have to make sure every single render method sets the theme, which makes it hard to make compiler safe code. Or every method which changes something needs to call a theme reset at the end.
-
From your comments I will add my thoughts on what helped me, as I remember when I first started using source control it took a while to get my head around it.
- Git and Github are two separate things (I think you already know
this but pointing it out anyway). Git is the source control, it
doesn't know anything about github - but provides generic functions for handling remote repos. Github provides a place to remotely host your sources + loads of other tools - Use Github desktop or a third party tool to help with git. If you use an IDE they usually have plugins available. This will help with change merges (Problem 2).
- Having a graphical view of branches really helped me to understand the branch and merge process (see my screenshot from Webstorm).
- Having a merge tool where you can see your version, the other version and the resulting version side by side makes a huge difference. Git does have a built in merge tool, but its rubbish - but you can set it to use a third party one.
- Pull the latest master branch into your branch as often as you can. The further away from the latest version you have, the harder it will be to merge them later on.
When you are looking for a better process, merging branches is notoriously problematic. Merge tools can auto merge most stuff, like taking someone else's version of a file you haven't changed. However if you change a file and then try and merge another branch into yours but you have changed the same file as someone else, its hard for a merge tool to know which bits of which file you want to keep. If you make sure everytime you start working on the project you pull the latest and merge it into your branch the merge tool will be able to do alot more auto merging.
- Git and Github are two separate things (I think you already know
-
During some testing using the emulator and pushing the code to RAM I found the following oddity while logging the output from process.memory(), the same occurs if I run on the watch via RAM. If I push the code to a storage file and its run from there it works more as I would expect.
I have a test method inside an instance of an App class which runs for the duration of the program as follows:
setup = () => { this.route = RouteHelper.loadRoute('feature_test'); this.nav.setRoute(this.route); this.nav.setZoomLevel(5); this.nav.startRoute(); //print mem here // BangleMock.start(); };
printing out the memory at the comment part of the above script gives around 2000 free. Its the same if I do this outside the method after the block has finished.
If I add a timeout to print the memory usage after a few seconds:
setup = () => { ... setTimeout(this.printMem, 3000); };
Free memory jumps to around 9000.
That code block doesn't contain any async functions. Does anyone have some insight into why a big chunk of memory is being freed a few seconds after execution, rather than at the end of each block scope.
-
I have added a round function to my code libs to round numbers to a certain number of decimals. It looks roughly as follows;
const round = (numberToRound,decimalPlaces = 2) => (e => Math.round(numberToRound * e) / e)(Math.pow(10,decimalPlaces ));
expected outcome:
round(6800971.33999999985) = 6800971.34
This passes all local unit tests compiling in es5, es6 and esnext. It also works fine on a couple of online javascript compilers I tested (codepen, Typescript playground) - and its exact copy of code others have used for doing the same thing.
If I run this code on the bangle emulator or the bangle2 watch directly I get "NaN" returned.
-
It's easy to look at decisions in Espruino from the outside and pick holes in it. It's a reason the Performance page is a bit defensive
Yes I agree, I think constructive criticism is really important for improving and opens discussion. Bashing for the sake of bashing doesn't help anyone. Anytime I question something its with this aim of improving the product or my own understanding. Something I often fail to bring across in my wording.
Its really hard to not be defensive when you spend so much time making something so this is totally understandable. Especially when alot of comments have no positive intent and often lack context on why you made a certain decision (like with the hashmaps).
I would still implement assocs as hashmaps so they are future proofed for larger mem sizes. But I have no experience in creating compilers and I probably use assocs more as a dictionary than others might, coming from python and c#. You clearly have time constraints on this stuff as well and is a difference between perfect and good enough for moment.
-
I have now spent some time developing my hiking app on the platform so I have alot more understanding of how everything fits together. Its worth noting I bought a bangle 2 so any memory references are related to this rather than the first one. I will publish it to the app store once more complete.
See screenshot for how I structured the project. Everything is typescript and packed with webpack into a single app.js minified file. When working with limited mem this has the advantage that things like tests and mocks are not included in the prod build as well as any unused files or functions.
The app provides a vector drawn route relative to current location and bearing, direction needed to hit next waypoint (via arrow pointer), direction at next waypoint (relative to current bearing). Distances to next waypoint/end etc (Screenshot is from the emulator which is not showing the light coloured objects).
I took some ideas from the gipy project (which is the best attempt of oo I have seen) and nav compass. I read the gpx files directly from storage. I know gipy creates its own format, but I am not sure what calculations you need to make prior to running as vector maths is super quick on most processors. It also allows you to use gadget bridge to put a gpx file on the bangle without going though the app loader every time.
Observations:
Documentation is definitely a weak point especially with built in modules and which javascript spec the compiler is following (like 'let' and 'const' are implemented but regex named capture groups is not). Most of what I now know is from trial and error and declaration files.
I only hit memory issues if stored alot of information in long running variables - like entire location history.
Assuming the garbage collection is working as would expect I would definitely use the documentation to nudge people to use small modular functions over single large code chunks. Always using "let" over "var" so variables take up memory for the smallest time possible. Keep global variables to min. I think you can do this in the documentation and tutorials in a way which doesn't over complicate things for beginners and doesn't force it.
-
I'm on holiday at the moment so I don't have much time to read into your questions fully and answer in detail, but it feels like others have done quite a good job.
They have, you have a great community.
I just want to add that Bangle.js is very intentionally designed to
keep things simple, to cram as much as possible into what is quite a
constrained device.I think you have done an amazing job and anything which opens up programming to a wider audience is great.
Some of the differences with a desktop JS engine are documented here
but honestly the best bet is to have a play, do some benchmarking, and
figure out whether things really can really be improved by doing
things a different way.Yeh I am surprised assoc arrays are implemented via linked lists and not hashmaps. I have a few performance questions, but maybe best in a private message or separate thread...and like you said if I play about with it and do some benchmarking I will have better idea.
-
I don't see anything better that offers the simplicity, flexibility and extensibility while still having low memory requirements. Also the asynchronous nature of js make things easier than using languages like micropython or lua.
Not sure I agree with the async part, I think the introduction of async await layer to wrap promises helps, but promises are a fairly hard concept to get head around if new to programming (and the code gets very messy if still use promise.then syntax), something which is apparent in user app code when timeouts are being used instead of async functions, and I suspect are causing some of freezing issues I have seen briefly seen in the forum)...but I am a python fanboy so I will defend it against all reason.
-
But it shows that on a platform this constrained there won't be a way
to make every code nice and tidy, sometimes getting to the edges of
available resources means ugly code or hacks to even get it to work at
all.At this stage I am probably underestimating the memory constraints of the system. I write evolutionary algorithms (type of AI) for scheduling software for my day job. Some functions get called millions of times during the evolutionary process. Sometimes having to sacrifice nice code for speed so I understand why you would.
Given the hardware constraints I am starting to see why javascript might not be the best language for watch development.
I did stumble upon a project in the forums where someone had been trying to run reactjs apps in bangle. It doesn't look like the project code has been updated in 4 years. Did they basically give up at some point?
-
Instead I want to control when to power on GPS or not. When reading
the position continously I just want to power on GPS on start and
never disable it again (until the app closes).If it really takes up to 15mins to get a fix its not really usable for much anyway. I assume its quicker via the android integration app. If it is draining the power quickly you kinda do want some sort of intelligent power off. Like if the person hasn't moved in a while getting a continuous fix doesn't make sense. Either way you could supply options to the manager when you initiate it.
If the use case is to
get a position every hour or so the myGpsManager.getFix() (as promise
I assume?) might be ok, but if needed can be written a less then 5
minutes, no need to import a big library with maybe 100 functions that
are unused.This is why you use tree shaking and don't import the entire library. The idea that because a function takes 5mins to write you shouldn't split it into a helper library for reuse makes little sense. If you are designing good code most functions shouldn't take a long time to write, something which becomes very apparent when writing unit tests. Having helper libraries allows you to have an initial implementation covered by unit tests which can be updated in future, with all projects using that lib getting the benefits of the update.
-
Besides the performance and RAM reasons for keeping the code "flat" there should also be taken into consideration that there are users who are technical novices just starting out to code using the espruino environment. Too much rules and focus on "best practice" could maybe deter those from even trying. There are however some apps already using typescript, so I do not see a problem in trying to get better code quality for some apps while keeping in mind the resources (especially in Bangle.js 1) and the beginners just starting out.
Some of this depends on if you using typescript/webpack etc to turn your files into production code. It also depends on which version of javascript as it is shifting slowly towards only functions being used from a file being imported without needing the likes of webpack to do this.
Even without any of that, there shouldn't be any difference in RAM usage between code split into multiple files and shoved in a single file.
Thanks for the response. What situation would you want to turn on the gps and have something else turn it off? This is why I wondered if app id was legacy and was always being injected anyway. It means a widget could turn the gps off for my app without me knowing (not sure why they would...but they could).
I also feel like widgets should be restricted to showing information and not changing any.
If a boot app is managing power and says "no sorry you can't have gps on at moment" it can't switch it off until my app exits?