TiForward/discuss

The Titanium Mobile API written in JS

Opened this issue · 35 comments

Issue #1 outlines the desire to expose all native APIs to JS. If we were to use that ability to write the entire Ti API set, then the community of JS developers could more easily contribute. Right now if there is an issue on the "native" side of things, there is no way for a JS dev to fix it.

yuchi commented

I feel the urge to explicit that it’s not related solely to the UI side of Titanium.

We originally talked about building all APIs on top of a low-level native-to-JS API, but I felt that was a bad idea. All you're doing is taking what we have today in Obj-C/Java/C#/etc and rewriting it in Hyperloop's not-quite-pure JavaScript syntax. Now we need to be experts in both JavaScript and Obj-C/Java/C#/etc.

I was also concerned about the burden on the JavaScript engine running so much JS code. Large amounts of JS code takes time to load, parse, and execute and eats up lots of memory. Crafting extremely efficient JS is not easy and probably not very readable.

I felt it was far better to build a highly portable library in pure C++. At the heart of this library is HAL/JavaScriptCore and a plugin system. Then create native C++ plugins for filesystem, networking, database, xml parsing, buffers, streams, debugging, crypto, zlib, and module support.

Each platform (Android, iOS, WIndows) could have specific plugin interfaces for Obj-C/Java/C#/etc modules. Module authors have their choice of writing modules in C++, JavaScript, or whatever the native platform's language is.

With this in place, we could build all other APIs in a platform specific plugins (UI, calendar, gestures, accelerometer, contacts, etc) or in cross platform JS plugins (analytics, facebook, arrow).

Maybe I'm reading this wrong, but having to code each API plugins using C++ would have the same problems that we have with Kroll right now. By that I mean that in order to access every property and member, they would have to be implemented in HAL.

Thoughts?

On 10 Apr 2015, at 08:48, Chris Barber notifications@github.com wrote:

We originally talked about building all APIs on top of a low-level native-to-JS API, but I felt that was a bad idea. All you're doing is taking what we have today in Obj-C/Java/C#/etc and rewriting it in Hyperloop's not-quite-pure JavaScript syntax. Now we need to be experts in both JavaScript and Obj-C/Java/C#/etc.

I was also concerned about the burden on the JavaScript engine running so much JS code. Large amounts of JS code takes time to load, parse, and execute and eats up lots of memory. Crafting extremely efficient JS is not easy and probably not very readable.

I felt it was far better to build a highly portable library in pure C++. At the heart of this library is HAL/JavaScriptCore and a plugin system. Then create native C++ plugins for filesystem, networking, database, xml parsing, buffers, streams, debugging, crypto, zlib, and module support.

Each platform (Android, iOS, WIndows) could have specific plugin interfaces for Obj-C/Java/C#/etc modules. Module authors have their choice of writing modules in C++, JavaScript, or whatever the native platform's language is.

With this in place, we could build all other APIs in a platform specific plugins (UI, calendar, gestures, accelerometer, contacts, etc) or in cross platform JS plugins (analytics, facebook, arrow).


Reply to this email directly or view it on GitHub.

@TheBrousse So what are the problems we're having with Kroll right now? What's the bottleneck? The JS-to-native bridge? Don't think so. HAL proves the JS-to-native bridge to be pretty snappy. I believe the major issue with Kroll is the thread synchronization between the UI thread and the JS thread. Because you have object state in both native land proxies and in the JavaScript engine, you have to do all sorts of crazy synchronization locks and this KILLS performance.

HAL reduces this complexity. With HAL/JavaScriptCore running on a background thread, then we can create a lock-free queue and pass messages between the threads. This drastically speeds things up while keeping the architecture flexible, modular, and loosely coupled.

The main problem with Kroll right now is not so much that it is a bottleneck. The real issue here is that there is no true 1 to 1 mapping between methods and properties of the native objects. Which means that every time a new property is made available from the native API. It must be implemented in "the layer".

You see this from a pure engineering pout of view (performance, threads, etc.). But what I am talking here is to bring everything that is currently in native to the JS front.

On 10 Apr 2015, at 09:18, Chris Barber notifications@github.com wrote:

@TheBrousse So what are the problems we're having with Kroll right now? What's the bottleneck? The JS-to-native bridge? Don't think so. HAL proves the JS-to-native bridge to be pretty snappy. I believe the major issue with Kroll is the thread synchronization between the UI thread and the JS thread. Because you have object state in both native land proxies and in the JavaScript engine, you have to do all sorts of crazy synchronization locks and this KILLS performance.

HAL reduces this complexity. With HAL/JavaScriptCore running on a background thread, then we can create a lock-free queue and pass messages between the threads. This drastically speeds things up while keeping the architecture flexible, modular, and loosely coupled.


Reply to this email directly or view it on GitHub.

I think nativescript has proven that this (direct exposure of all native APIs to JS) can in fact be done without overloading the js vm and without creating ugly/un-maintainable js code

For the record I am not an advocate of typescript. Flow could be used for a similar effect with no need to learn a new language

@cb1kenobi Thank you SO MUCH for this information, hearing what has been going on with HAL and Hyperloop delays behind the scenes all this time is extremely helpful!
I am curious on your thoughts with regards to how/why NativeScript was able to do this (even if overall nativescript is not "there" yet).

@TheBrousse I would argue that it is both. As is on iOS parallax scrolling without a native module is next to impossible due to bridge delays

I would also argue that one of the biggest reasons for the almost non-existant community support of the Ti.Current SDK thus far is that you have a community of JS devs, and a native core. So the only recourse for a JS dev when there is an issue is to file a ticket and make a bunch of noise to hope Appc fixes it quickly.
With a JS SDK JS devs can help fix and improve the core more easily.
And to your point @cb1kenobi that Hyperloop's not-quite-pure JavaScript syntax. Now we need to be experts in both JavaScript and Obj-C/Java/C#/etc. I think this could be improved, again ala NativeScript's module syntax to where this would be far less of an issue.

The 1:1 native mappings is nothing more than a compile-time optimization and a runtime reflection. I'm skeptical about the runtime performance of reflection in Java and C# as well as exposing all that to the JS engine. Exposing a million classes, functions, and constants to the JS engine makes for a crazy amount of scope resolution.

I'm a JavaScript purist, so I'd prefer ES6 over Typescript. You're still going to need to know native paradigms and how to use them in Hyperloop. I built the Windows Hybrid platform which is basically Titanium Mobile Web in a native web view and it uses reflection. The API isn't as good as Hyperloop, but it does show how challenging it is to work with from JavaScript. Check out some examples here: https://wiki.appcelerator.org/display/guides2/Windows+Phone+8+Module+Development+Guide.

Have you looked at how nativescript does this? It's rather simplistically brilliant

Also. I 100% agree on js purest.

@cb1kenobi can you explain your thoughts about "Exposing a million classes, functions, and constants to the JS engine makes for a crazy amount of scope resolution." a little more. This to me only seems like it would be an issue if you require every API into global scope

@mattapperson I'm just thinking that if the JS engine needs to resolve a variable which is actually a native class, then it would first need to scan all scopes to the outer most scope in JavaScript land, then before throwing an exception it would need to scan to see if it's a native class. I'm sure this could be optimized, but it's still having to do a lot of work to find whatever it is you're trying to invoke.

@cb1kenobi I will say it again, you talk about this as a pure engineering problem (bytes, memory, mhz). But what I'm worried about more than anything is platform adoption. The Titanium community is essentially made of Javascript developers. While some of them don't want to have deep knowledge of the underlying platform (they can thank TitaniumKit for this). There are times where where you need to extend beyond what is offered by the Titanium platform (For Ti.current, we create modules).

Let me ask you this, let's say I find a native solution to a problem I have online (StackOverflow for example). Would I be able to implement this same solution using only Javascript? Or would I have to implement it using some esoteric HAL syntax?

You asked about the problem we have with Kroll right now. While it "can" be speed on some occasions; the main issue is that it segments the community into two distinct categories. The ones who can create modules (because they know ObjC or Java) and the ones who can't (second class citizens?). And I personality think that is we don't go full JS, we will be in the same situation in the future (those who know HAL syntax and those who don't).

yuchi commented

(@TheBrousse, I updated your comment because the formatting was all messed up, could you check if everything is ok with it?)

@yuchi thanks

@cb1kenobi I think the scenario is. For those of us building apps for a living. This is a must have critical feature. We simply must find a solution, and it must be easy to maintain.
So please don't take our comments as ignoring you, it's just that this feature is critical to get to. Nativescript has it, xamarin has it. We too must have it.

@cb1kenobi it seems to me that if native APIs are all their own modules, that are required in. There would be little scanning / scope overhead. At least it would be brought down to a better level that would still leave it much faster then Ti.current.

@cb1kenobi if we really wanted to be able to allow for the most optimized / fastest Ti apps... Perhaps in the runtime, the ability to include a metabase based native access is a flag you can turn off and only allow native / hyperloop modules to access native to avoide the perf hit?

yuchi commented

Please let me recap the discussion, with an opinionated summary of positions.

Recap

  • @mattapperson and @TheBrousse are pointing that the current state of the things repels developer away because:
    • something (e.g. Parallax scrolling) is not doable on JS-land, you need to go to native-land, and
    • going native-land is very pricey.
  • @cb1kenobi thinks that doing everything in JS-land could be very challenging on the perf side, and that choosing a language over another wouldn’t solve the fact that the native SDKs’ semantics should be known to the developer to be used.
  • @mattapperson points that the JS-on-the-front/Native-on-the-inside fragmentation doesn’t help the community to contribute back.
Also discussed
  • Kroll is not the bottleneck, sync APIs are. (@cb1kenobi, and I second this) Related to #4.
  • Nativescript got resolving of dependencies right (@mattapperson)

@TheBrousse I get what you're saying. You guys want the programming API to be correct and I want the supporting lower level to be correct. We gotta nail both of them. Whether or not you'll be able to access native APIs in pure JS is not something I know. I'm probably not the guy who's gonna be building the next version of Titanium.

Based on early Hyperloop prototypes, there was proprietary APIs to handle pointers, data type coercion, etc. Do we still need to do this? Are there better ways? I have no clue. I have not been involved the design of any Hyperloop prototypes or HAL.

Internally at Appc, I have been very boisterous about the future of the Titanium API. I strongly believe that we need to adopt ES6 and Node.js compatible APIs.

Getting back to the original title of this issue, I believe we should be able to access native APIs via JS. I don't believe that all Titanium APIs should be written in JS. This could lead to poor performance. For example, I think basic APIs such as filesystem be should not be 100% JS and call fopen() and fread() just because Hyperloop can do it. Native function such as UI, calendar, contacts, etc I'm fine with these being implemented in as much JS as possible.

These are just my personal comments and do not reflect the future of Appcelerator, Titanium, Hyperloop, or HAL.

yuchi commented

I strongly believe that we need to adopt ES6 and Node.js compatible APIs.

I cannot second this enough. But it’s just my personal POV.

@cb1kenobi personaly I am not at all opposed to some APIs in JS and some "core" ones in native. This seems reasonable. Ones like files in particular tend to be stable anyway so this does not bother me at all. Any others you would nominate for Native vs JS?

With HAL I think we have the best of both worlds here. By example, here's an implementation of Analytics that is written in pure JS.

here's the Titanium Kit native bootstrap that exposes a couple of native methods

https://github.com/appcelerator/titanium_mobile_windows/blob/723834d11ce8a9acdfc55c74afeb8abe5b774693/Source/TitaniumKit/src/Analytics.cpp

here's the pure JavaScript version

https://github.com/appcelerator/titanium_mobile_windows/blob/da818efe5c6e6dc0d972f3f6028375bfdfaf25ba/Source/TitaniumKit/src/analytics.js

What's nice is that you can (relatively) easily mix and match between native and JS.

One thing we're working on and hope to share very soon as a prototype is our ability to generate HAL code so that you could write more code in pure JS instead of Native code (but still with the ability to drop back to native if necessary). This is an option we're trying to vet technically right now that combines the earlier Hyperloop metabase + HAL to generate the ahead of time native code. I'm hoping we can share something in the next week or so (we're working on getting it working) as a concept.

@jhaynie this is great to hear! Thanks for sharing! If you guys are open to it, I am sure the community would be happy to help vet this and contribute some brain power. But ether way its great to have some insight!

@jhaynie would this allow for 100% of native API access, and then Titanium SDK would be a mix? or would some APIs not be able to be bound in this way?

Yeah, I think for me the best of both worlds if the developer decides.

So what I've been advocating is an architecture that allows both. Why both?

Well, let's use a simple example.

UIView in iOS. We can generate 100% of that from the metabase into both pure JS code and HAL code. And likely, that would be as performant if not more performant than if you as a developer hand wrote the code (and without the bugs). No problem.

Now, let's go to Open GL. Certainly, we can do the same. However, my guess is (anecdotal), there might be a set of more common routines or implementation where it makes sense to write that in Native (ala pure HAL C++).

So, HAL supports both. What we need is the "assembler" at package time that simply is smart about mixing the two at package/compile time.

The other thing we can do with HAL isn't just a full module (as you see from the example above), but mixing only certain methods.

I'd like ultimately to build some tools that would tell us where we have hot spots and we could then use that to drive potential re-implementation in native or creating higher level APIs. We can't do a full compiler re-compile hot spot design simply because on all platforms we can do code generation on the fly -- but we could still get good information from real apps and use that to tune based on analytics.

My thought here is that a mix of both is good, but at the same time... a 1 to 1 mapping of the APIs is easier to document and easier for native devs to pick it up and it feel "right".
Less need for education, and less "this is the way you do things in Titanium" lets more people use and contribute to Ti, thus ultimately a more empowered and strong community.
There is of course a balance between performance, core code maintainability, and this that must be found.

Yes, we want a 1:1 mapping i think either way. I agree with that.

Just to chime in since I'm prototyping some of this for Windows on HAL:

NativeScript's approach is intriguing for a few reasons, but is not a general solution for all of our platforms necessarily. What they do is add a GetProperty callback hook on Global and then dynamically lookup "properties" which are actually references to native APIs/types by searching the metadata pre-generated and then using FFI/JNI and reflection to basically wrap that native type/function/property.
Now for iOS and Android we have reflection capabilities and can do this dynamic late-resolution. The interesting thing to me is how that holds up in terms of performance and also if they've solved all of the language mismatches for some types/structures. In any case, it is an approach we could research and likely implement on those platforms. Ideally we'd use JavaScriptCore so we didn't have to port HAL to V8, but either way it's feasible. You guys have also noted some of the other issues that raises about using 3rd party libraries.

On Windows, we're using C++ because that is the only supported Windows Runtime component language for the Phone. If we could do C#, then again we could try reflection. Maybe there's a way to have the end-user app in C# consume our C++ components and somehow also have it handle the reflection bit from the GetProperty callback. I'm not entirely sure how to achieve that right now. Another harder avenue is to use some super C virtual table hacking and RoActivateInstance to achieve something like reflection. That sort of hacking is above my knowledge, but does appear to be how the CLR engine handles reflection with COM. It also seems like quite a difficult route to try and get right across various architectures.

An approach I am playing with right now is to generate native C++ wrapper types from the metadata using our HAL layer - similar to how our Titanium API proxies are written (though if you combined the TitaniumKit and windows subclasses together). Then we could basically walk the user's code to see what native types they required and bundle up/compile against our wrapper library: https://github.com/sgtcoolguy/titanium_mobile_windows/tree/native/Tools/Scripts/metadata_stubber

@sgtcoolguy great insight! One thing I have noted in my tests is that perf does take a hit here on NativeScript. In particular because in JS it is common to do things like:

if(something) {
    // ...
}

This causes a bridge crossing if something is false.

A similar idea we had was more along the lines of:

var native = require('native');
var native.call('native method or something', function(returnedValue) { // ... })

Nothing official or thought out at all in that really in terms of API... but just a way to call native from JS without having to bind ALL the APIs that native platforms have, while still getting access to them all.
This will also make it easier to document an API for runtime access vs compile time ala hyperloop.

Again, not 100% thought out, but that is the direction my brain goes.

@mattapperson The NativeScript approach basically just builds up the loaded APIs as you go. They don't actually have to worry about bloating the app disk size in this way. I don't think they ever cross the bridge until you run the code that references the new type/property, but perhaps they actually do on the load of the script rather than at execution time?

The plan for the Windows research spike is to pre-generate wrappers (headers and impl) for the full API set once, and then in JS world we'd do something like:

var TextBox = require('Windows.UI.Xaml.Controls.TextBox');
var a_box = new TextBox();
a_box.Text = "Hello World!";
a_box.SelectedText = "Hello";

Then we can crawl the require statements in the JS, build up the list of native types required and cull the actually packaged set of wrappers down to those and hopefully the compiler/linker can take it from there in terms of knowing the dependencies between classes that we've baked into the generated wrappers. The "native" APIs would be exposed essentially as-is, but perhaps we could make some concessions to make them more javascript-y (i.e. downcased method/property names).

@sgtcoolguy I think that is a great plan, but just have one thought. The idea of crawling the code is great, and it's what Ti does now. But there are those times when in an app you call or require in an module dynamically... and this breaks then. So I would simply argue that there is the ability to turn off auto crawling and manually define all modules get pulled in, even those that are "core". And a dynamic crawling is just an option.

@sgtcoolguy What are the current technical hurdles to this approach?