Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Javascript Support #304

Closed
wants to merge 23 commits into from
Closed

Adds Javascript Support #304

wants to merge 23 commits into from

Conversation

arcanis
Copy link
Contributor

@arcanis arcanis commented Dec 26, 2016

  • As mentioned in the title, this PR adds Javascript support to Yoga. Two different builds are included in this PR thanks to nbind, which conveniently allow to target both Node.js' native addons and browser environments via asmjs with approximately the same codebase. That should solve JavaScript variant planned? #215.

  • All tests successfully pass on both codepaths. You can run yarn test:all inside the javascript directory to test it.

  • Because of a bug in nbind, the following PR needs to be merged and a new version released before this one can be safely merged as well.

  • I had to use double types instead of float in the C++ bindings because of an Emscripten bug where symbols aren't correctly exported when using floats.

  • There's some tweaks to do before this PR is 100% ready to merge, but I wanted to have your opinion first. What do you think of this?


To do:

  • Ensure that Travis run
  • Add missing functions
  • Check measurement functions
  • Recursive frees
  • Make a decision about the memory management

@emilsjolander
Copy link
Contributor

#299 Also adds javascript support via emscrimten and is nearly ready to be merged. Could you collaborate with @mikearmstrong001 on adding anything that might be missing from that PR?

@arcanis
Copy link
Contributor Author

arcanis commented Dec 26, 2016

Aww I completely missed it, sorry @mikearmstrong001 :(

Looking at it, I'm not sure anything is missing per-se, but there's two major differences between our PRs:

  • WIP: JavaScript port via emscripten #299 doesn't export a native Node.js module. I think it's the main difference, and maybe the most difficult one to implement since it requires either a lot of glue code, or a dedicated build chain. That's why I chose nbind, which nicely abstract the interfacing of both Node.js addons and asmjs modules under the same umbrella.

  • This PR uses an OOP API which might be a bit more idiomatic than free functions, at least in Javascript. That's kind of a philosophical choice, so there's really nothing to "fix" on WIP: JavaScript port via emscripten #299.

"Fixing" both of these issues to match the behaviour in this PR would probably result in a PR very similar to mine, so I guess you'll have to choose which one of those two approaches you prefer.

@emilsjolander
Copy link
Contributor

#299 doesn't export a native Node.js module. I think it's the main difference, and maybe the most difficult one to implement since it requires either a lot of glue code, or a dedicated build chain. That's why I chose nbind, which nicely abstract the interfacing of both Node.js addons and asmjs modules under the same umbrella.

Sounds cool, I don't know anything about this. @mattpodwysocki was looking at building node.js bindings. Wonder if he has any comments.

This PR uses an OOP API which might be a bit more idiomatic than free functions, at least in Javascript. That's kind of a philosophical choice, so there's really nothing to "fix" on #299.

If you read the comments in the other pull request you can see that this is the way we were going with it 👍

"Fixing" both of these issues to match the behaviour in mine would probably end up in a PR very similar to mine, so I guess you'll have to choose which one of those two approaches you prefer.

Agreed, we will most likely end up just merging one of these. Would love some input from people who know more than me about this. cc: @mikearmstrong001 @vjeux

@vjeux
Copy link
Contributor

vjeux commented Dec 26, 2016

I'm not really familiar with the c -> js options.

The only thing I know is that it is very annoying when a node dependency has a native module as any time you npm install it it takes forever to actually build it. But, on the other hand, it's probably unfortunate to lose the performance when you can actually run C code.

Anyway, I don't have much to add here.

@emilsjolander
Copy link
Contributor

@arcanis I'll await comments @mikearmstrong001 on using nbind before conducting a code review.

@mikearmstrong001
Copy link

As @emilsjolander and I discussed: I actually have moved to and arrived at the same interface through a different process so the lack of a native implementation is the difference.

We should go with the solution that solves the most problems with the minimum effort! which seems to be this PR, as long as it works for day to day maintenance, handles running on a browser as is not ill performant I'm happy.

I'll check my requirements and get back very soon. Nice work @arcanis I've not used or looked at nbind before.

@emilsjolander
Copy link
Contributor

@mikearmstrong001 awesome! Would be super helpful if you can help check that this meets all the requirements you have.

@arcanis Can you start by making this PR run the JS tests in travis?

@arcanis
Copy link
Contributor Author

arcanis commented Dec 26, 2016

@mikearmstrong001 great ! thanks !

@emilsjolander sure - native tests should be easy enough to run, asmjs not that much (the main issue I had when I tried this task previously is that building the asmjs version requires emscripten, which in turn requires to build llvm, which is too heavy for Travis CI). Would that work for you?

I'm currently working on implementing measureFunc and it should be mostly done. However, it seems there is no automatic measurement test for non-cpp languages, is that correct?

@emilsjolander
Copy link
Contributor

@arcanis I would like us to run the javascript tests in CI. In #299 the output of emscripten was checked in. Can you do similar here? Or can we pre-build an emscripten binary for travis?

I'm currently working on implementing measureFunc and it should be mostly done. However, it seems there is no automatic measurement test for non-cpp languages, is that correct?

Not sure what you mean, could you explain?

@mikearmstrong001
Copy link

mikearmstrong001 commented Dec 26, 2016

measureFun: @arcanis that is correct, a reasonable base to work with is the Benchmark or a variant of the cpp YGMeasureTest

@arcanis
Copy link
Contributor Author

arcanis commented Dec 26, 2016

@arcanis I would like us to run the javascript tests in CI. In #299 the output of emscripten was checked in. Can you do similar here? Or can we pre-build an emscripten binary for travis?

Hm, I think you're right, commiting the pre-built asmjs file to the repository might be good enough. Only issue I see is that it will not correctly detect modifications if a contributor forget to rebuild it before pushing.

@mikearmstrong001 Good idea, I'll use your benchmark !

@emilsjolander
Copy link
Contributor

@arcanis Yeah that is the downside. Can you look into getting a pre-build emscripten binary running on travis. That would be awesome.

@mikearmstrong001
Copy link

mikearmstrong001 commented Dec 26, 2016

@arcanis how are you handling the free functions? they seem to be missing from the generated tests, we should confirm these work as expected. My use case will certainly be hitting these.

I'd also recommend YogaNodeTest in c# this is the final test I was working through. I can help if you could send me something I could run as asm.js

@arcanis
Copy link
Contributor Author

arcanis commented Dec 26, 2016

@mikearmstrong001 Which functions do you specifically need? I've currently exported setExperimentalFeatureEnabled and isExperimentalFeatureEnabled (here), and exporting the others shouldn't take too long.

@mikearmstrong001
Copy link

The reason I was asking about free is that I need to ensure the the YGNodeRef m_node is correctly deallocated. I'm not familiar on how nbind will handle destructors within JS based code.

Having something in the PR I can run in a browser would be very helpful.

These will need to be exposed and there may be others I've missed.

WIN_EXPORT void YGNodeFree(const YGNodeRef node);
WIN_EXPORT void YGNodeFreeRecursive(const YGNodeRef node);
WIN_EXPORT void YGNodeReset(const YGNodeRef node);
WIN_EXPORT void YGNodeMarkDirty(const YGNodeRef node);
WIN_EXPORT bool YGNodeIsDirty(const YGNodeRef node);
WIN_EXPORT bool YGNodeCanUseCachedMeasurement(const YGMeasureMode widthMode,
WIN_EXPORT void YGNodeCopyStyle(const YGNodeRef dstNode, const YGNodeRef srcNode);
YG_NODE_STYLE_PROPERTY(float, AspectRatio, aspectRatio);
WIN_EXPORT void YGNodeStyleSetFlex(const YGNodeRef node, const float flex);
YG_NODE_PROPERTY(bool, HasNewLayout, hasNewLayout);

By looking at the handwritten test functions eg YGStyleTest these would be actively hit.

@arcanis
Copy link
Contributor Author

arcanis commented Dec 26, 2016

I'll check this. According to charto/nbind#34, destructors should be automatically called by the garbage collector in native modules, and by calling the node.free() function inside browsers.

Having something in the PR I can run in a browser would be very helpful.

If you have emcc in your PATH, you should be able to run yarn build:asmjs and use the module with bundlers such as webpack (you can use a symlink from the node_modules/yoga-layout to the javascript directory to make it easier).

@arcanis arcanis force-pushed the emscripten branch 4 times, most recently from 1da7d81 to 7664635 Compare December 26, 2016 14:41
@mikearmstrong001
Copy link

@emilsjolander and @arcanis for easy of adoption I think prebuilt JS library should be included. Building a JS solution via emcc is going to be a chore for anyone just looking to use the lib.

Can we run tests on a travis generated build and also against the prebuilt solution which we should actively encourage to be unloaded as part of a PR. I'm sure we could look at automating the generation.

@arcanis
Copy link
Contributor Author

arcanis commented Dec 26, 2016

The option I went for with https://github.com/manaflair/text-layout was to ship the asmjs file but only on npm, not github, simply by adding a .npmignore with different rules than the .gitignore (the asmjs file is ignored by git, but not by npm), so that emscripten is only required when running yarn publish.

@arcanis arcanis force-pushed the emscripten branch 5 times, most recently from df4e70f to 78a1e6f Compare December 26, 2016 16:55
@arcanis
Copy link
Contributor Author

arcanis commented Dec 30, 2016

@emilsjolander @mikearmstrong001 I've added a npm run bench script that gives some insight about the perfs of the node module vs the asmjs one, renamed a few other scripts to make everything consistent, rebased the branch on top of master, and added some documentation.

I haven't changed the gist links yet, since I wasn't sure if you wanted to do it yourself or not (also, have you considered using something like gist-it to directly print the relevant source code in the documentation? that would avoid the need to manually update the gist when something changes).

I think this PR is close to be ready, do you see something else? 😊

@emilsjolander
Copy link
Contributor

@arcanis This looks good to go from my end. @mikearmstrong001 what do you think?

Regarding using gists, I did it mostly because I like the styling of them and did not want to spend time replicating it 👍 I would rather have the code in the docs file :) I can update them and update the main landing page as well once this is landed.

@mikearmstrong001
Copy link

@arcanis Thanks for the doc entries! I wasn't expecting you to take that on as well, really appreciated. I too would like to pull docs straight from code, it works well for React VR :)

I think your approach and justification for Node is good and agree that the error message is somewhat obtuse at
Browser:

Yoga.bundle.js:269 Uncaught TypeError: Cannot read property 'apply' of undefined
    at new Bound (Yoga.bundle.js:269)
   ...

Node

Error: Wrong number of arguments
    at evalmachine.<anonymous>:15:14
    at YGBENCHMARK (/Users/mikea/git/yoga-arc/javascript/tests/run-bench.js:29:21)

But given docs on how to create nodes already exist, this will be fine.

I made changes to your bench test and those are

  1. allow the JS to warm up
  2. run the function several times and average the result

We should apply these to make measurements more valid

Before Change:

Stack with flex
  - node: 23ms (100%)
  - browser: 181ms (786.96%)

Align stretch in undefined axis
  - node: 50ms (100%)
  - browser: 193ms (386%)

Nested flex
  - node: 53ms (100%)
  - browser: 77ms (145.28%)

Huge nested layout
  - node: 18ms (100%)
  - browser: 86ms (477.78%)

After change:

Stack with flex
  - node: 19.4ms (100%)
  - browser: 33.8ms (174.23%)

Align stretch in undefined axis
  - node: 47.4ms (145.4%)
  - browser: 32.6ms (100%)

Nested flex
  - node: 38ms (125.41%)
  - browser: 30.3ms (100%)

Huge nested layout
  - node: 15ms (100%)
  - browser: 27.5ms (183.33%)
                let testEntry = testResults.get(name);

                if (testEntry === undefined)
                    testResults.set(name, testEntry = new Map());

                let WARMUP_IT = 3;
                for (let i=0; i<WARMUP_IT; i++) {
                    fn();
                }

                let BENCHMARK_IT = 10;
                let start = Date.now();
                for (let i=0; i<BENCHMARK_IT; i++) {
                    fn();
                }
                let end = Date.now();

                testEntry.set(type, (end - start) / BENCHMARK_IT);

Great stuff!

@emilsjolander
Copy link
Contributor

Thanks! This is an awesome addition. I'll import the PR now and start the merge process

@facebook-github-bot
Copy link
Contributor

@emilsjolander has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.


}

patch(lib.Node.prototype, `free`, function () {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For future maintainers: perhaps some comments on why these functions are patched.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just added some comments

@arcanis
Copy link
Contributor Author

arcanis commented Dec 30, 2016

Oh right ! Just pushed your changes. Funny how the asmjs build is noticeably faster than the native one on some tests ... maybe some optim flags will need to be tweaked.

@emilsjolander
Copy link
Contributor

@arcanis Just noticed that the added files are missing our open source license header. Could you please make sure all the added files have it?

@mikearmstrong001
Copy link

mikearmstrong001 commented Dec 30, 2016

Funny how the asmjs build is noticeably faster than the native one on some tests ... maybe some optim flags will need to be tweaked.

Yeah it is interesting, although we definitely want to make sure we are not measuring the JIT :)

I'll run a test against a simple emscripten compile of the code so we can get an understanding of the nbind overhead.

@emilsjolander please note that the benchmark numbers have been changed from the original c version so they shouldn't be compared directly.

@arcanis Again, thanks for your work!

@arcanis
Copy link
Contributor Author

arcanis commented Dec 30, 2016

@emilsjolander Yup, here they are !

@facebook-github-bot
Copy link
Contributor

@emilsjolander has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants